diff --git a/lib/python3.12/site-packages/asttokens/__init__.py b/lib/python3.12/site-packages/asttokens/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b717d9aea96ea5d4cd88bedcc6b108d96331c38
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/__init__.py
@@ -0,0 +1,24 @@
+# Copyright 2016 Grist Labs, Inc.
+#
+# 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
+#
+# https://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.
+
+"""
+This module enhances the Python AST tree with token and source code information, sufficent to
+detect the source text of each AST node. This is helpful for tools that make source code
+transformations.
+"""
+
+from .line_numbers import LineNumbers
+from .asttokens import ASTText, ASTTokens, supports_tokenless
+
+__all__ = ['ASTText', 'ASTTokens', 'LineNumbers', 'supports_tokenless']
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..738e5cb3396b2770c180077955a5816d8b4700ef
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/astroid_compat.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/astroid_compat.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..40f7ec011315634dc14acb7c4b6ed2325a763453
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/astroid_compat.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/asttokens.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/asttokens.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..567674eac2a961a0c8894d6f4efe61f6ae602041
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/asttokens.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/line_numbers.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/line_numbers.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1d6c6aa583035ebc9feb8bf8595146b2a4dd6f9f
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/line_numbers.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/mark_tokens.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/mark_tokens.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f0b78c773f21f961541077441609e5127ae211b3
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/mark_tokens.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/util.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/util.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9208ac90b49353eedfe4a52fd55c2be54a6a0681
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/util.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/__pycache__/version.cpython-312.pyc b/lib/python3.12/site-packages/asttokens/__pycache__/version.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4c5021186c834f87af756cb1cdd0b4acf8d8e3c1
Binary files /dev/null and b/lib/python3.12/site-packages/asttokens/__pycache__/version.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/asttokens/astroid_compat.py b/lib/python3.12/site-packages/asttokens/astroid_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..9af3e17e028d5718e9a34da08d4bc3c6757c0596
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/astroid_compat.py
@@ -0,0 +1,18 @@
+try:
+ from astroid import nodes as astroid_node_classes
+
+ # astroid_node_classes should be whichever module has the NodeNG class
+ from astroid.nodes import NodeNG
+ from astroid.nodes import BaseContainer
+except Exception:
+ try:
+ from astroid import node_classes as astroid_node_classes
+ from astroid.node_classes import NodeNG
+ from astroid.node_classes import _BaseContainer as BaseContainer
+ except Exception: # pragma: no cover
+ astroid_node_classes = None
+ NodeNG = None
+ BaseContainer = None
+
+
+__all__ = ["astroid_node_classes", "NodeNG", "BaseContainer"]
diff --git a/lib/python3.12/site-packages/asttokens/asttokens.py b/lib/python3.12/site-packages/asttokens/asttokens.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cbc5aad29624b3a4548013010752f365f3ee210
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/asttokens.py
@@ -0,0 +1,450 @@
+# Copyright 2016 Grist Labs, Inc.
+#
+# 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
+#
+# https://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 abc
+import ast
+import bisect
+import sys
+import token
+from ast import Module
+from typing import Iterable, Iterator, List, Optional, Tuple, Any, cast, TYPE_CHECKING
+
+from .line_numbers import LineNumbers
+from .util import (
+ Token, match_token, is_non_coding_token, patched_generate_tokens, last_stmt,
+ annotate_fstring_nodes, generate_tokens, is_module, is_stmt
+)
+
+if TYPE_CHECKING: # pragma: no cover
+ from .util import AstNode, TokenInfo
+
+
+class ASTTextBase(metaclass=abc.ABCMeta):
+ def __init__(self, source_text: str, filename: str) -> None:
+ self._filename = filename
+
+ # Decode source after parsing to let Python 2 handle coding declarations.
+ # (If the encoding was not utf-8 compatible, then even if it parses correctly,
+ # we'll fail with a unicode error here.)
+ source_text = str(source_text)
+
+ self._text = source_text
+ self._line_numbers = LineNumbers(source_text)
+
+ @abc.abstractmethod
+ def get_text_positions(self, node, padded):
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
+ """
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
+ returns ``(1, 0)`` for both.
+
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
+ This means that if ``padded`` is True, the start position will be adjusted to include
+ leading whitespace if ``node`` is a multiline statement.
+ """
+ raise NotImplementedError # pragma: no cover
+
+ def get_text_range(self, node, padded=True):
+ # type: (AstNode, bool) -> Tuple[int, int]
+ """
+ Returns the (startpos, endpos) positions in source text corresponding to the given node.
+ Returns (0, 0) for nodes (like `Load`) that don't correspond to any particular text.
+
+ See ``get_text_positions()`` for details on the ``padded`` argument.
+ """
+ start, end = self.get_text_positions(node, padded)
+ return (
+ self._line_numbers.line_to_offset(*start),
+ self._line_numbers.line_to_offset(*end),
+ )
+
+ def get_text(self, node, padded=True):
+ # type: (AstNode, bool) -> str
+ """
+ Returns the text corresponding to the given node.
+ Returns '' for nodes (like `Load`) that don't correspond to any particular text.
+
+ See ``get_text_positions()`` for details on the ``padded`` argument.
+ """
+ start, end = self.get_text_range(node, padded)
+ return self._text[start: end]
+
+
+class ASTTokens(ASTTextBase):
+ """
+ ASTTokens maintains the text of Python code in several forms: as a string, as line numbers, and
+ as tokens, and is used to mark and access token and position information.
+
+ ``source_text`` must be a unicode or UTF8-encoded string. If you pass in UTF8 bytes, remember
+ that all offsets you'll get are to the unicode text, which is available as the ``.text``
+ property.
+
+ If ``parse`` is set, the ``source_text`` will be parsed with ``ast.parse()``, and the resulting
+ tree marked with token info and made available as the ``.tree`` property.
+
+ If ``tree`` is given, it will be marked and made available as the ``.tree`` property. In
+ addition to the trees produced by the ``ast`` module, ASTTokens will also mark trees produced
+ using ``astroid`` library .
+
+ If only ``source_text`` is given, you may use ``.mark_tokens(tree)`` to mark the nodes of an AST
+ tree created separately.
+ """
+
+ def __init__(self, source_text, parse=False, tree=None, filename='', tokens=None):
+ # type: (Any, bool, Optional[Module], str, Optional[Iterable[TokenInfo]]) -> None
+ super(ASTTokens, self).__init__(source_text, filename)
+
+ self._tree = ast.parse(source_text, filename) if parse else tree
+
+ # Tokenize the code.
+ if tokens is None:
+ tokens = generate_tokens(self._text)
+ self._tokens = list(self._translate_tokens(tokens))
+
+ # Extract the start positions of all tokens, so that we can quickly map positions to tokens.
+ self._token_offsets = [tok.startpos for tok in self._tokens]
+
+ if self._tree:
+ self.mark_tokens(self._tree)
+
+ def mark_tokens(self, root_node):
+ # type: (Module) -> None
+ """
+ Given the root of the AST or Astroid tree produced from source_text, visits all nodes marking
+ them with token and position information by adding ``.first_token`` and
+ ``.last_token`` attributes. This is done automatically in the constructor when ``parse`` or
+ ``tree`` arguments are set, but may be used manually with a separate AST or Astroid tree.
+ """
+ # The hard work of this class is done by MarkTokens
+ from .mark_tokens import MarkTokens # to avoid import loops
+ MarkTokens(self).visit_tree(root_node)
+
+ def _translate_tokens(self, original_tokens):
+ # type: (Iterable[TokenInfo]) -> Iterator[Token]
+ """
+ Translates the given standard library tokens into our own representation.
+ """
+ for index, tok in enumerate(patched_generate_tokens(original_tokens)):
+ tok_type, tok_str, start, end, line = tok
+ yield Token(tok_type, tok_str, start, end, line, index,
+ self._line_numbers.line_to_offset(start[0], start[1]),
+ self._line_numbers.line_to_offset(end[0], end[1]))
+
+ @property
+ def text(self):
+ # type: () -> str
+ """The source code passed into the constructor."""
+ return self._text
+
+ @property
+ def tokens(self):
+ # type: () -> List[Token]
+ """The list of tokens corresponding to the source code from the constructor."""
+ return self._tokens
+
+ @property
+ def tree(self):
+ # type: () -> Optional[Module]
+ """The root of the AST tree passed into the constructor or parsed from the source code."""
+ return self._tree
+
+ @property
+ def filename(self):
+ # type: () -> str
+ """The filename that was parsed"""
+ return self._filename
+
+ def get_token_from_offset(self, offset):
+ # type: (int) -> Token
+ """
+ Returns the token containing the given character offset (0-based position in source text),
+ or the preceeding token if the position is between tokens.
+ """
+ return self._tokens[bisect.bisect(self._token_offsets, offset) - 1]
+
+ def get_token(self, lineno, col_offset):
+ # type: (int, int) -> Token
+ """
+ Returns the token containing the given (lineno, col_offset) position, or the preceeding token
+ if the position is between tokens.
+ """
+ # TODO: add test for multibyte unicode. We need to translate offsets from ast module (which
+ # are in utf8) to offsets into the unicode text. tokenize module seems to use unicode offsets
+ # but isn't explicit.
+ return self.get_token_from_offset(self._line_numbers.line_to_offset(lineno, col_offset))
+
+ def get_token_from_utf8(self, lineno, col_offset):
+ # type: (int, int) -> Token
+ """
+ Same as get_token(), but interprets col_offset as a UTF8 offset, which is what `ast` uses.
+ """
+ return self.get_token(lineno, self._line_numbers.from_utf8_col(lineno, col_offset))
+
+ def next_token(self, tok, include_extra=False):
+ # type: (Token, bool) -> Token
+ """
+ Returns the next token after the given one. If include_extra is True, includes non-coding
+ tokens from the tokenize module, such as NL and COMMENT.
+ """
+ i = tok.index + 1
+ if not include_extra:
+ while is_non_coding_token(self._tokens[i].type):
+ i += 1
+ return self._tokens[i]
+
+ def prev_token(self, tok, include_extra=False):
+ # type: (Token, bool) -> Token
+ """
+ Returns the previous token before the given one. If include_extra is True, includes non-coding
+ tokens from the tokenize module, such as NL and COMMENT.
+ """
+ i = tok.index - 1
+ if not include_extra:
+ while is_non_coding_token(self._tokens[i].type):
+ i -= 1
+ return self._tokens[i]
+
+ def find_token(self, start_token, tok_type, tok_str=None, reverse=False):
+ # type: (Token, int, Optional[str], bool) -> Token
+ """
+ Looks for the first token, starting at start_token, that matches tok_type and, if given, the
+ token string. Searches backwards if reverse is True. Returns ENDMARKER token if not found (you
+ can check it with `token.ISEOF(t.type)`).
+ """
+ t = start_token
+ advance = self.prev_token if reverse else self.next_token
+ while not match_token(t, tok_type, tok_str) and not token.ISEOF(t.type):
+ t = advance(t, include_extra=True)
+ return t
+
+ def token_range(self,
+ first_token, # type: Token
+ last_token, # type: Token
+ include_extra=False, # type: bool
+ ):
+ # type: (...) -> Iterator[Token]
+ """
+ Yields all tokens in order from first_token through and including last_token. If
+ include_extra is True, includes non-coding tokens such as tokenize.NL and .COMMENT.
+ """
+ for i in range(first_token.index, last_token.index + 1):
+ if include_extra or not is_non_coding_token(self._tokens[i].type):
+ yield self._tokens[i]
+
+ def get_tokens(self, node, include_extra=False):
+ # type: (AstNode, bool) -> Iterator[Token]
+ """
+ Yields all tokens making up the given node. If include_extra is True, includes non-coding
+ tokens such as tokenize.NL and .COMMENT.
+ """
+ return self.token_range(node.first_token, node.last_token, include_extra=include_extra)
+
+ def get_text_positions(self, node, padded):
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
+ """
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
+ returns ``(1, 0)`` for both.
+
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
+ This means that if ``padded`` is True, the start position will be adjusted to include
+ leading whitespace if ``node`` is a multiline statement.
+ """
+ if not hasattr(node, 'first_token'):
+ return (1, 0), (1, 0)
+
+ start = node.first_token.start
+ end = node.last_token.end
+ if padded and any(match_token(t, token.NEWLINE) for t in self.get_tokens(node)):
+ # Set col_offset to 0 to include leading indentation for multiline statements.
+ start = (start[0], 0)
+
+ return start, end
+
+
+class ASTText(ASTTextBase):
+ """
+ Supports the same ``get_text*`` methods as ``ASTTokens``,
+ but uses the AST to determine the text positions instead of tokens.
+ This is faster than ``ASTTokens`` as it requires less setup work.
+
+ It also (sometimes) supports nodes inside f-strings, which ``ASTTokens`` doesn't.
+
+ Some node types and/or Python versions are not supported.
+ In these cases the ``get_text*`` methods will fall back to using ``ASTTokens``
+ which incurs the usual setup cost the first time.
+ If you want to avoid this, check ``supports_tokenless(node)`` before calling ``get_text*`` methods.
+ """
+ def __init__(self, source_text, tree=None, filename=''):
+ # type: (Any, Optional[Module], str) -> None
+ super(ASTText, self).__init__(source_text, filename)
+
+ self._tree = tree
+ if self._tree is not None:
+ annotate_fstring_nodes(self._tree)
+
+ self._asttokens = None # type: Optional[ASTTokens]
+
+ @property
+ def tree(self):
+ # type: () -> Module
+ if self._tree is None:
+ self._tree = ast.parse(self._text, self._filename)
+ annotate_fstring_nodes(self._tree)
+ return self._tree
+
+ @property
+ def asttokens(self):
+ # type: () -> ASTTokens
+ if self._asttokens is None:
+ self._asttokens = ASTTokens(
+ self._text,
+ tree=self.tree,
+ filename=self._filename,
+ )
+ return self._asttokens
+
+ def _get_text_positions_tokenless(self, node, padded):
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
+ """
+ Version of ``get_text_positions()`` that doesn't use tokens.
+ """
+ if is_module(node):
+ # Modules don't have position info, so just return the range of the whole text.
+ # The token-using method does something different, but its behavior seems weird and inconsistent.
+ # For example, in a file with only comments, it only returns the first line.
+ # It's hard to imagine a case when this matters.
+ return (1, 0), self._line_numbers.offset_to_line(len(self._text))
+
+ if getattr(node, 'lineno', None) is None:
+ return (1, 0), (1, 0)
+
+ assert node # tell mypy that node is not None, which we allowed up to here for compatibility
+
+ decorators = getattr(node, 'decorator_list', [])
+ if not decorators:
+ # Astroid uses node.decorators.nodes instead of node.decorator_list.
+ decorators_node = getattr(node, 'decorators', None)
+ decorators = getattr(decorators_node, 'nodes', [])
+ if decorators:
+ # Function/Class definition nodes are marked by AST as starting at def/class,
+ # not the first decorator. This doesn't match the token-using behavior,
+ # or inspect.getsource(), and just seems weird.
+ start_node = decorators[0]
+ else:
+ start_node = node
+
+ start_lineno = start_node.lineno
+ end_node = last_stmt(node)
+
+ # Include leading indentation for multiline statements.
+ # This doesn't mean simple statements that happen to be on multiple lines,
+ # but compound statements where inner indentation matters.
+ # So we don't just compare node.lineno and node.end_lineno,
+ # we check for a contained statement starting on a different line.
+ if padded and (
+ start_lineno != end_node.lineno
+ or (
+ # Astroid docstrings aren't treated as separate statements.
+ # So to handle function/class definitions with a docstring but no other body,
+ # we just check that the node is a statement with a docstring
+ # and spanning multiple lines in the simple, literal sense.
+ start_lineno != node.end_lineno
+ and getattr(node, "doc_node", None)
+ and is_stmt(node)
+ )
+ ):
+ start_col_offset = 0
+ else:
+ start_col_offset = self._line_numbers.from_utf8_col(start_lineno, start_node.col_offset)
+
+ start = (start_lineno, start_col_offset)
+
+ # To match the token-using behaviour, we exclude trailing semicolons and comments.
+ # This means that for blocks containing multiple statements, we have to use the last one
+ # instead of the actual node for end_lineno and end_col_offset.
+ end_lineno = cast(int, end_node.end_lineno)
+ end_col_offset = cast(int, end_node.end_col_offset)
+ end_col_offset = self._line_numbers.from_utf8_col(end_lineno, end_col_offset)
+ end = (end_lineno, end_col_offset)
+
+ return start, end
+
+ def get_text_positions(self, node, padded):
+ # type: (AstNode, bool) -> Tuple[Tuple[int, int], Tuple[int, int]]
+ """
+ Returns two ``(lineno, col_offset)`` tuples for the start and end of the given node.
+ If the positions can't be determined, or the nodes don't correspond to any particular text,
+ returns ``(1, 0)`` for both.
+
+ ``padded`` corresponds to the ``padded`` argument to ``ast.get_source_segment()``.
+ This means that if ``padded`` is True, the start position will be adjusted to include
+ leading whitespace if ``node`` is a multiline statement.
+ """
+ if getattr(node, "_broken_positions", None):
+ # This node was marked in util.annotate_fstring_nodes as having untrustworthy lineno/col_offset.
+ return (1, 0), (1, 0)
+
+ if supports_tokenless(node):
+ return self._get_text_positions_tokenless(node, padded)
+
+ return self.asttokens.get_text_positions(node, padded)
+
+
+# Node types that _get_text_positions_tokenless doesn't support.
+# These initial values are missing lineno.
+_unsupported_tokenless_types = ("arguments", "Arguments", "withitem") # type: Tuple[str, ...]
+if sys.version_info[:2] == (3, 8):
+ # _get_text_positions_tokenless works incorrectly for these types due to bugs in Python 3.8.
+ _unsupported_tokenless_types += ("arg", "Starred")
+ # no lineno in 3.8
+ _unsupported_tokenless_types += ("Slice", "ExtSlice", "Index", "keyword")
+
+
+def supports_tokenless(node=None):
+ # type: (Any) -> bool
+ """
+ Returns True if the Python version and the node (if given) are supported by
+ the ``get_text*`` methods of ``ASTText`` without falling back to ``ASTTokens``.
+ See ``ASTText`` for why this matters.
+
+ The following cases are not supported:
+
+ - PyPy
+ - ``ast.arguments`` / ``astroid.Arguments``
+ - ``ast.withitem``
+ - ``astroid.Comprehension``
+ - ``astroid.AssignName`` inside ``astroid.Arguments`` or ``astroid.ExceptHandler``
+ - The following nodes in Python 3.8 only:
+ - ``ast.arg``
+ - ``ast.Starred``
+ - ``ast.Slice``
+ - ``ast.ExtSlice``
+ - ``ast.Index``
+ - ``ast.keyword``
+ """
+ return (
+ type(node).__name__ not in _unsupported_tokenless_types
+ and not (
+ # astroid nodes
+ not isinstance(node, ast.AST) and node is not None and (
+ (
+ type(node).__name__ == "AssignName"
+ and type(node.parent).__name__ in ("Arguments", "ExceptHandler")
+ )
+ )
+ )
+ and 'pypy' not in sys.version.lower()
+ )
diff --git a/lib/python3.12/site-packages/asttokens/line_numbers.py b/lib/python3.12/site-packages/asttokens/line_numbers.py
new file mode 100644
index 0000000000000000000000000000000000000000..1441a53b7872cd2c491a0d39c28c7a4d16294d92
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/line_numbers.py
@@ -0,0 +1,74 @@
+# Copyright 2016 Grist Labs, Inc.
+#
+# 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
+#
+# https://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 bisect
+import re
+from typing import Dict, List, Tuple
+
+_line_start_re = re.compile(r'^', re.M)
+
+class LineNumbers:
+ """
+ Class to convert between character offsets in a text string, and pairs (line, column) of 1-based
+ line and 0-based column numbers, as used by tokens and AST nodes.
+
+ This class expects unicode for input and stores positions in unicode. But it supports
+ translating to and from utf8 offsets, which are used by ast parsing.
+ """
+ def __init__(self, text):
+ # type: (str) -> None
+ # A list of character offsets of each line's first character.
+ self._line_offsets = [m.start(0) for m in _line_start_re.finditer(text)]
+ self._text = text
+ self._text_len = len(text)
+ self._utf8_offset_cache = {} # type: Dict[int, List[int]] # maps line num to list of char offset for each byte in line
+
+ def from_utf8_col(self, line, utf8_column):
+ # type: (int, int) -> int
+ """
+ Given a 1-based line number and 0-based utf8 column, returns a 0-based unicode column.
+ """
+ offsets = self._utf8_offset_cache.get(line)
+ if offsets is None:
+ end_offset = self._line_offsets[line] if line < len(self._line_offsets) else self._text_len
+ line_text = self._text[self._line_offsets[line - 1] : end_offset]
+
+ offsets = [i for i,c in enumerate(line_text) for byte in c.encode('utf8')]
+ offsets.append(len(line_text))
+ self._utf8_offset_cache[line] = offsets
+
+ return offsets[max(0, min(len(offsets)-1, utf8_column))]
+
+ def line_to_offset(self, line, column):
+ # type: (int, int) -> int
+ """
+ Converts 1-based line number and 0-based column to 0-based character offset into text.
+ """
+ line -= 1
+ if line >= len(self._line_offsets):
+ return self._text_len
+ elif line < 0:
+ return 0
+ else:
+ return min(self._line_offsets[line] + max(0, column), self._text_len)
+
+ def offset_to_line(self, offset):
+ # type: (int) -> Tuple[int, int]
+ """
+ Converts 0-based character offset to pair (line, col) of 1-based line and 0-based column
+ numbers.
+ """
+ offset = max(0, min(self._text_len, offset))
+ line_index = bisect.bisect_right(self._line_offsets, offset) - 1
+ return (line_index + 1, offset - self._line_offsets[line_index])
diff --git a/lib/python3.12/site-packages/asttokens/mark_tokens.py b/lib/python3.12/site-packages/asttokens/mark_tokens.py
new file mode 100644
index 0000000000000000000000000000000000000000..62d38f8871b3bdf1a96daf61f15bd91ad52865e5
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/mark_tokens.py
@@ -0,0 +1,469 @@
+# Copyright 2016 Grist Labs, Inc.
+#
+# 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
+#
+# https://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 ast
+import numbers
+import sys
+import token
+from ast import Module
+from typing import Callable, List, Union, cast, Optional, Tuple, TYPE_CHECKING
+
+from . import util
+from .asttokens import ASTTokens
+from .astroid_compat import astroid_node_classes as nc, BaseContainer as AstroidBaseContainer
+
+if TYPE_CHECKING:
+ from .util import AstNode
+
+
+# Mapping of matching braces. To find a token here, look up token[:2].
+_matching_pairs_left = {
+ (token.OP, '('): (token.OP, ')'),
+ (token.OP, '['): (token.OP, ']'),
+ (token.OP, '{'): (token.OP, '}'),
+}
+
+_matching_pairs_right = {
+ (token.OP, ')'): (token.OP, '('),
+ (token.OP, ']'): (token.OP, '['),
+ (token.OP, '}'): (token.OP, '{'),
+}
+
+
+class MarkTokens:
+ """
+ Helper that visits all nodes in the AST tree and assigns .first_token and .last_token attributes
+ to each of them. This is the heart of the token-marking logic.
+ """
+ def __init__(self, code):
+ # type: (ASTTokens) -> None
+ self._code = code
+ self._methods = util.NodeMethods()
+ self._iter_children = None # type: Optional[Callable]
+
+ def visit_tree(self, node):
+ # type: (Module) -> None
+ self._iter_children = util.iter_children_func(node)
+ util.visit_tree(node, self._visit_before_children, self._visit_after_children)
+
+ def _visit_before_children(self, node, parent_token):
+ # type: (AstNode, Optional[util.Token]) -> Tuple[Optional[util.Token], Optional[util.Token]]
+ col = getattr(node, 'col_offset', None)
+ token = self._code.get_token_from_utf8(node.lineno, col) if col is not None else None
+
+ if not token and util.is_module(node):
+ # We'll assume that a Module node starts at the start of the source code.
+ token = self._code.get_token(1, 0)
+
+ # Use our own token, or our parent's if we don't have one, to pass to child calls as
+ # parent_token argument. The second value becomes the token argument of _visit_after_children.
+ return (token or parent_token, token)
+
+ def _visit_after_children(self, node, parent_token, token):
+ # type: (AstNode, Optional[util.Token], Optional[util.Token]) -> None
+ # This processes the node generically first, after all children have been processed.
+
+ # Get the first and last tokens that belong to children. Note how this doesn't assume that we
+ # iterate through children in order that corresponds to occurrence in source code. This
+ # assumption can fail (e.g. with return annotations).
+ first = token
+ last = None
+ for child in cast(Callable, self._iter_children)(node):
+ # astroid slices have especially wrong positions, we don't want them to corrupt their parents.
+ if util.is_empty_astroid_slice(child):
+ continue
+ if not first or child.first_token.index < first.index:
+ first = child.first_token
+ if not last or child.last_token.index > last.index:
+ last = child.last_token
+
+ # If we don't have a first token from _visit_before_children, and there were no children, then
+ # use the parent's token as the first token.
+ first = first or parent_token
+
+ # If no children, set last token to the first one.
+ last = last or first
+
+ # Statements continue to before NEWLINE. This helps cover a few different cases at once.
+ if util.is_stmt(node):
+ last = self._find_last_in_stmt(cast(util.Token, last))
+
+ # Capture any unmatched brackets.
+ first, last = self._expand_to_matching_pairs(cast(util.Token, first), cast(util.Token, last), node)
+
+ # Give a chance to node-specific methods to adjust.
+ nfirst, nlast = self._methods.get(self, node.__class__)(node, first, last)
+
+ if (nfirst, nlast) != (first, last):
+ # If anything changed, expand again to capture any unmatched brackets.
+ nfirst, nlast = self._expand_to_matching_pairs(nfirst, nlast, node)
+
+ node.first_token = nfirst
+ node.last_token = nlast
+
+ def _find_last_in_stmt(self, start_token):
+ # type: (util.Token) -> util.Token
+ t = start_token
+ while (not util.match_token(t, token.NEWLINE) and
+ not util.match_token(t, token.OP, ';') and
+ not token.ISEOF(t.type)):
+ t = self._code.next_token(t, include_extra=True)
+ return self._code.prev_token(t)
+
+ def _expand_to_matching_pairs(self, first_token, last_token, node):
+ # type: (util.Token, util.Token, AstNode) -> Tuple[util.Token, util.Token]
+ """
+ Scan tokens in [first_token, last_token] range that are between node's children, and for any
+ unmatched brackets, adjust first/last tokens to include the closing pair.
+ """
+ # We look for opening parens/braces among non-child tokens (i.e. tokens between our actual
+ # child nodes). If we find any closing ones, we match them to the opens.
+ to_match_right = [] # type: List[Tuple[int, str]]
+ to_match_left = []
+ for tok in self._code.token_range(first_token, last_token):
+ tok_info = tok[:2]
+ if to_match_right and tok_info == to_match_right[-1]:
+ to_match_right.pop()
+ elif tok_info in _matching_pairs_left:
+ to_match_right.append(_matching_pairs_left[tok_info])
+ elif tok_info in _matching_pairs_right:
+ to_match_left.append(_matching_pairs_right[tok_info])
+
+ # Once done, extend `last_token` to match any unclosed parens/braces.
+ for match in reversed(to_match_right):
+ last = self._code.next_token(last_token)
+ # Allow for trailing commas or colons (allowed in subscripts) before the closing delimiter
+ while any(util.match_token(last, token.OP, x) for x in (',', ':')):
+ last = self._code.next_token(last)
+ # Now check for the actual closing delimiter.
+ if util.match_token(last, *match):
+ last_token = last
+
+ # And extend `first_token` to match any unclosed opening parens/braces.
+ for match in to_match_left:
+ first = self._code.prev_token(first_token)
+ if util.match_token(first, *match):
+ first_token = first
+
+ return (first_token, last_token)
+
+ #----------------------------------------------------------------------
+ # Node visitors. Each takes a preliminary first and last tokens, and returns the adjusted pair
+ # that will actually be assigned.
+
+ def visit_default(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # pylint: disable=no-self-use
+ # By default, we don't need to adjust the token we computed earlier.
+ return (first_token, last_token)
+
+ def handle_comp(self, open_brace, node, first_token, last_token):
+ # type: (str, AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # For list/set/dict comprehensions, we only get the token of the first child, so adjust it to
+ # include the opening brace (the closing brace will be matched automatically).
+ before = self._code.prev_token(first_token)
+ util.expect_token(before, token.OP, open_brace)
+ return (before, last_token)
+
+ def visit_comprehension(self,
+ node, # type: AstNode
+ first_token, # type: util.Token
+ last_token, # type: util.Token
+ ):
+ # type: (...) -> Tuple[util.Token, util.Token]
+ # The 'comprehension' node starts with 'for' but we only get first child; we search backwards
+ # to find the 'for' keyword.
+ first = self._code.find_token(first_token, token.NAME, 'for', reverse=True)
+ return (first, last_token)
+
+ def visit_if(self, node, first_token, last_token):
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ while first_token.string not in ('if', 'elif'):
+ first_token = self._code.prev_token(first_token)
+ return first_token, last_token
+
+ def handle_attr(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # Attribute node has ".attr" (2 tokens) after the last child.
+ dot = self._code.find_token(last_token, token.OP, '.')
+ name = self._code.next_token(dot)
+ util.expect_token(name, token.NAME)
+ return (first_token, name)
+
+ visit_attribute = handle_attr
+ visit_assignattr = handle_attr
+ visit_delattr = handle_attr
+
+ def handle_def(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # With astroid, nodes that start with a doc-string can have an empty body, in which case we
+ # need to adjust the last token to include the doc string.
+ if not node.body and (getattr(node, 'doc_node', None) or getattr(node, 'doc', None)): # type: ignore[union-attr]
+ last_token = self._code.find_token(last_token, token.STRING)
+
+ # Include @ from decorator
+ if first_token.index > 0:
+ prev = self._code.prev_token(first_token)
+ if util.match_token(prev, token.OP, '@'):
+ first_token = prev
+ return (first_token, last_token)
+
+ visit_classdef = handle_def
+ visit_functiondef = handle_def
+
+ def handle_following_brackets(self, node, last_token, opening_bracket):
+ # type: (AstNode, util.Token, str) -> util.Token
+ # This is for calls and subscripts, which have a pair of brackets
+ # at the end which may contain no nodes, e.g. foo() or bar[:].
+ # We look for the opening bracket and then let the matching pair be found automatically
+ # Remember that last_token is at the end of all children,
+ # so we are not worried about encountering a bracket that belongs to a child.
+ first_child = next(cast(Callable, self._iter_children)(node))
+ call_start = self._code.find_token(first_child.last_token, token.OP, opening_bracket)
+ if call_start.index > last_token.index:
+ last_token = call_start
+ return last_token
+
+ def visit_call(self, node, first_token, last_token):
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ last_token = self.handle_following_brackets(node, last_token, '(')
+
+ # Handling a python bug with decorators with empty parens, e.g.
+ # @deco()
+ # def ...
+ if util.match_token(first_token, token.OP, '@'):
+ first_token = self._code.next_token(first_token)
+ return (first_token, last_token)
+
+ def visit_matchclass(self, node, first_token, last_token):
+ # type: (util.Token, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ last_token = self.handle_following_brackets(node, last_token, '(')
+ return (first_token, last_token)
+
+ def visit_subscript(self,
+ node, # type: AstNode
+ first_token, # type: util.Token
+ last_token, # type: util.Token
+ ):
+ # type: (...) -> Tuple[util.Token, util.Token]
+ last_token = self.handle_following_brackets(node, last_token, '[')
+ return (first_token, last_token)
+
+ def visit_slice(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # consume `:` tokens to the left and right. In Python 3.9, Slice nodes are
+ # given a col_offset, (and end_col_offset), so this will always start inside
+ # the slice, even if it is the empty slice. However, in 3.8 and below, this
+ # will only expand to the full slice if the slice contains a node with a
+ # col_offset. So x[:] will only get the correct tokens in 3.9, but x[1:] and
+ # x[:1] will even on earlier versions of Python.
+ while True:
+ prev = self._code.prev_token(first_token)
+ if prev.string != ':':
+ break
+ first_token = prev
+ while True:
+ next_ = self._code.next_token(last_token)
+ if next_.string != ':':
+ break
+ last_token = next_
+ return (first_token, last_token)
+
+ def handle_bare_tuple(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # A bare tuple doesn't include parens; if there is a trailing comma, make it part of the tuple.
+ maybe_comma = self._code.next_token(last_token)
+ if util.match_token(maybe_comma, token.OP, ','):
+ last_token = maybe_comma
+ return (first_token, last_token)
+
+ # In Python3.8 parsed tuples include parentheses when present.
+ def handle_tuple_nonempty(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
+ # It's a bare tuple if the first token belongs to the first child. The first child may
+ # include extraneous parentheses (which don't create new nodes), so account for those too.
+ child = node.elts[0]
+ if TYPE_CHECKING:
+ child = cast(AstNode, child)
+ child_first, child_last = self._gobble_parens(child.first_token, child.last_token, True)
+ if first_token == child_first:
+ return self.handle_bare_tuple(node, first_token, last_token)
+ return (first_token, last_token)
+
+ def visit_tuple(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ assert isinstance(node, ast.Tuple) or isinstance(node, AstroidBaseContainer)
+ if not node.elts:
+ # An empty tuple is just "()", and we need no further info.
+ return (first_token, last_token)
+ return self.handle_tuple_nonempty(node, first_token, last_token)
+
+ def _gobble_parens(self, first_token, last_token, include_all=False):
+ # type: (util.Token, util.Token, bool) -> Tuple[util.Token, util.Token]
+ # Expands a range of tokens to include one or all pairs of surrounding parentheses, and
+ # returns (first, last) tokens that include these parens.
+ while first_token.index > 0:
+ prev = self._code.prev_token(first_token)
+ next = self._code.next_token(last_token)
+ if util.match_token(prev, token.OP, '(') and util.match_token(next, token.OP, ')'):
+ first_token, last_token = prev, next
+ if include_all:
+ continue
+ break
+ return (first_token, last_token)
+
+ def visit_str(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ return self.handle_str(first_token, last_token)
+
+ def visit_joinedstr(self,
+ node, # type: AstNode
+ first_token, # type: util.Token
+ last_token, # type: util.Token
+ ):
+ # type: (...) -> Tuple[util.Token, util.Token]
+ if sys.version_info < (3, 12):
+ # Older versions don't tokenize the contents of f-strings
+ return self.handle_str(first_token, last_token)
+
+ last = first_token
+ while True:
+ if util.match_token(last, getattr(token, "FSTRING_START")):
+ # Python 3.12+ has tokens for the start (e.g. `f"`) and end (`"`)
+ # of the f-string. We can't just look for the next FSTRING_END
+ # because f-strings can be nested, e.g. f"{f'{x}'}", so we need
+ # to treat this like matching balanced parentheses.
+ count = 1
+ while count > 0:
+ last = self._code.next_token(last)
+ # mypy complains about token.FSTRING_START and token.FSTRING_END.
+ if util.match_token(last, getattr(token, "FSTRING_START")):
+ count += 1
+ elif util.match_token(last, getattr(token, "FSTRING_END")):
+ count -= 1
+ last_token = last
+ last = self._code.next_token(last_token)
+ elif util.match_token(last, token.STRING):
+ # Similar to handle_str, we also need to handle adjacent strings.
+ last_token = last
+ last = self._code.next_token(last_token)
+ else:
+ break
+ return (first_token, last_token)
+
+ def visit_bytes(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ return self.handle_str(first_token, last_token)
+
+ def handle_str(self, first_token, last_token):
+ # type: (util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # Multiple adjacent STRING tokens form a single string.
+ last = self._code.next_token(last_token)
+ while util.match_token(last, token.STRING):
+ last_token = last
+ last = self._code.next_token(last_token)
+ return (first_token, last_token)
+
+ def handle_num(self,
+ node, # type: AstNode
+ value, # type: Union[complex, int, numbers.Number]
+ first_token, # type: util.Token
+ last_token, # type: util.Token
+ ):
+ # type: (...) -> Tuple[util.Token, util.Token]
+ # A constant like '-1' gets turned into two tokens; this will skip the '-'.
+ while util.match_token(last_token, token.OP):
+ last_token = self._code.next_token(last_token)
+
+ if isinstance(value, complex):
+ # A complex number like -2j cannot be compared directly to 0
+ # A complex number like 1-2j is expressed as a binary operation
+ # so we don't need to worry about it
+ value = value.imag
+
+ # This makes sure that the - is included
+ if value < 0 and first_token.type == token.NUMBER: # type: ignore[operator]
+ first_token = self._code.prev_token(first_token)
+ return (first_token, last_token)
+
+ def visit_num(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ n = node.n # type: ignore[union-attr] # ast.Num has been removed in python 3.14
+ assert isinstance(n, (complex, int, numbers.Number))
+ return self.handle_num(node, n, first_token, last_token)
+
+ def visit_const(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ assert isinstance(node, ast.Constant) or isinstance(node, nc.Const)
+ if isinstance(node.value, numbers.Number):
+ return self.handle_num(node, node.value, first_token, last_token)
+ elif isinstance(node.value, (str, bytes)):
+ return self.visit_str(node, first_token, last_token)
+ return (first_token, last_token)
+
+ visit_constant = visit_const
+
+ def visit_keyword(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # Until python 3.9 (https://bugs.python.org/issue40141),
+ # ast.keyword nodes didn't have line info. Astroid has lineno None.
+ assert isinstance(node, ast.keyword) or isinstance(node, nc.Keyword)
+ if node.arg is not None and getattr(node, 'lineno', None) is None:
+ equals = self._code.find_token(first_token, token.OP, '=', reverse=True)
+ name = self._code.prev_token(equals)
+ util.expect_token(name, token.NAME, node.arg)
+ first_token = name
+ return (first_token, last_token)
+
+ def visit_starred(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # Astroid has 'Starred' nodes (for "foo(*bar)" type args), but they need to be adjusted.
+ if not util.match_token(first_token, token.OP, '*'):
+ star = self._code.prev_token(first_token)
+ if util.match_token(star, token.OP, '*'):
+ first_token = star
+ return (first_token, last_token)
+
+ def visit_assignname(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ # Astroid may turn 'except' clause into AssignName, but we need to adjust it.
+ if util.match_token(first_token, token.NAME, 'except'):
+ colon = self._code.find_token(last_token, token.OP, ':')
+ first_token = last_token = self._code.prev_token(colon)
+ return (first_token, last_token)
+
+ # Async nodes should typically start with the word 'async'
+ # but Python < 3.7 doesn't put the col_offset there
+ # AsyncFunctionDef is slightly different because it might have
+ # decorators before that, which visit_functiondef handles
+ def handle_async(self, node, first_token, last_token):
+ # type: (AstNode, util.Token, util.Token) -> Tuple[util.Token, util.Token]
+ if not first_token.string == 'async':
+ first_token = self._code.prev_token(first_token)
+ return (first_token, last_token)
+
+ visit_asyncfor = handle_async
+ visit_asyncwith = handle_async
+
+ def visit_asyncfunctiondef(self,
+ node, # type: AstNode
+ first_token, # type: util.Token
+ last_token, # type: util.Token
+ ):
+ # type: (...) -> Tuple[util.Token, util.Token]
+ if util.match_token(first_token, token.NAME, 'def'):
+ # Include the 'async' token
+ first_token = self._code.prev_token(first_token)
+ return self.visit_functiondef(node, first_token, last_token)
diff --git a/lib/python3.12/site-packages/asttokens/py.typed b/lib/python3.12/site-packages/asttokens/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/asttokens/version.py b/lib/python3.12/site-packages/asttokens/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..055276878107052a2bd2810e5a0b07182ef1cd58
--- /dev/null
+++ b/lib/python3.12/site-packages/asttokens/version.py
@@ -0,0 +1 @@
+__version__ = "3.0.1"
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER b/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA b/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..8a2f639061cc4a203f7109d8335d28076442c61d
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA
@@ -0,0 +1,202 @@
+Metadata-Version: 2.4
+Name: h11
+Version: 0.16.0
+Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
+Home-page: https://github.com/python-hyper/h11
+Author: Nathaniel J. Smith
+Author-email: njs@pobox.com
+License: MIT
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+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: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: System :: Networking
+Requires-Python: >=3.8
+License-File: LICENSE.txt
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: requires-python
+Dynamic: summary
+
+h11
+===
+
+.. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master
+ :target: https://travis-ci.org/python-hyper/h11
+ :alt: Automated test status
+
+.. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/python-hyper/h11
+ :alt: Test coverage
+
+.. image:: https://readthedocs.org/projects/h11/badge/?version=latest
+ :target: http://h11.readthedocs.io/en/latest/?badge=latest
+ :alt: Documentation Status
+
+This is a little HTTP/1.1 library written from scratch in Python,
+heavily inspired by `hyper-h2 `_.
+
+It's a "bring-your-own-I/O" library; h11 contains no IO code
+whatsoever. This means you can hook h11 up to your favorite network
+API, and that could be anything you want: synchronous, threaded,
+asynchronous, or your own implementation of `RFC 6214
+`_ -- h11 won't judge you.
+(Compare this to the current state of the art, where every time a `new
+network API `_ comes along then someone
+gets to start over reimplementing the entire HTTP protocol from
+scratch.) Cory Benfield made an `excellent blog post describing the
+benefits of this approach
+`_, or if you like video
+then here's his `PyCon 2016 talk on the same theme
+`_.
+
+This also means that h11 is not immediately useful out of the box:
+it's a toolkit for building programs that speak HTTP, not something
+that could directly replace ``requests`` or ``twisted.web`` or
+whatever. But h11 makes it much easier to implement something like
+``requests`` or ``twisted.web``.
+
+At a high level, working with h11 goes like this:
+
+1) First, create an ``h11.Connection`` object to track the state of a
+ single HTTP/1.1 connection.
+
+2) When you read data off the network, pass it to
+ ``conn.receive_data(...)``; you'll get back a list of objects
+ representing high-level HTTP "events".
+
+3) When you want to send a high-level HTTP event, create the
+ corresponding "event" object and pass it to ``conn.send(...)``;
+ this will give you back some bytes that you can then push out
+ through the network.
+
+For example, a client might instantiate and then send a
+``h11.Request`` object, then zero or more ``h11.Data`` objects for the
+request body (e.g., if this is a POST), and then a
+``h11.EndOfMessage`` to indicate the end of the message. Then the
+server would then send back a ``h11.Response``, some ``h11.Data``, and
+its own ``h11.EndOfMessage``. If either side violates the protocol,
+you'll get a ``h11.ProtocolError`` exception.
+
+h11 is suitable for implementing both servers and clients, and has a
+pleasantly symmetric API: the events you send as a client are exactly
+the ones that you receive as a server and vice-versa.
+
+`Here's an example of a tiny HTTP client
+`_
+
+It also has `a fine manual `_.
+
+FAQ
+---
+
+*Whyyyyy?*
+
+I wanted to play with HTTP in `Curio
+`__ and `Trio
+`__, which at the time didn't have any
+HTTP libraries. So I thought, no big deal, Python has, like, a dozen
+different implementations of HTTP, surely I can find one that's
+reusable. I didn't find one, but I did find Cory's call-to-arms
+blog-post. So I figured, well, fine, if I have to implement HTTP from
+scratch, at least I can make sure no-one *else* has to ever again.
+
+*Should I use it?*
+
+Maybe. You should be aware that it's a very young project. But, it's
+feature complete and has an exhaustive test-suite and complete docs,
+so the next step is for people to try using it and see how it goes
+:-). If you do then please let us know -- if nothing else we'll want
+to talk to you before making any incompatible changes!
+
+*What are the features/limitations?*
+
+Roughly speaking, it's trying to be a robust, complete, and non-hacky
+implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230:
+HTTP/1.1 Message Syntax and Routing
+`_. That is, it mostly focuses on
+implementing HTTP at the level of taking bytes on and off the wire,
+and the headers related to that, and tries to be anal about spec
+conformance. It doesn't know about higher-level concerns like URL
+routing, conditional GETs, cross-origin cookie policies, or content
+negotiation. But it does know how to take care of framing,
+cross-version differences in keep-alive handling, and the "obsolete
+line folding" rule, so you can focus your energies on the hard /
+interesting parts for your application, and it tries to support the
+full specification in the sense that any useful HTTP/1.1 conformant
+application should be able to use h11.
+
+It's pure Python, and has no dependencies outside of the standard
+library.
+
+It has a test suite with 100.0% coverage for both statements and
+branches.
+
+Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3.
+The last Python 2-compatible version was h11 0.11.x.
+(Originally it had a Cython wrapper for `http-parser
+`_ and a beautiful nested state
+machine implemented with ``yield from`` to postprocess the output. But
+I had to take these out -- the new *parser* needs fewer lines-of-code
+than the old *parser wrapper*, is written in pure Python, uses no
+exotic language syntax, and has more features. It's sad, really; that
+old state machine was really slick. I just need a few sentences here
+to mourn that.)
+
+I don't know how fast it is. I haven't benchmarked or profiled it yet,
+so it's probably got a few pointless hot spots, and I've been trying
+to err on the side of simplicity and robustness instead of
+micro-optimization. But at the architectural level I tried hard to
+avoid fundamentally bad decisions, e.g., I believe that all the
+parsing algorithms remain linear-time even in the face of pathological
+input like slowloris, and there are no byte-by-byte loops. (I also
+believe that it maintains bounded memory usage in the face of
+arbitrary/pathological input.)
+
+The whole library is ~800 lines-of-code. You can read and understand
+the whole thing in less than an hour. Most of the energy invested in
+this so far has been spent on trying to keep things simple by
+minimizing special-cases and ad hoc state manipulation; even though it
+is now quite small and simple, I'm still annoyed that I haven't
+figured out how to make it even smaller and simpler. (Unfortunately,
+HTTP does not lend itself to simplicity.)
+
+The API is ~feature complete and I don't expect the general outlines
+to change much, but you can't judge an API's ergonomics until you
+actually document and use it, so I'd expect some changes in the
+details.
+
+*How do I try it?*
+
+.. code-block:: sh
+
+ $ pip install h11
+ $ git clone git@github.com:python-hyper/h11
+ $ cd h11/examples
+ $ python basic-client.py
+
+and go from there.
+
+*License?*
+
+MIT
+
+*Code of conduct?*
+
+Contributors are requested to follow our `code of conduct
+`_ in
+all project spaces.
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD b/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..a8f8e63f529ce81b7d7c970ea791147d9a732175
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD
@@ -0,0 +1,29 @@
+h11-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348
+h11-0.16.0.dist-info/RECORD,,
+h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
+h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124
+h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4
+h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507
+h11/__pycache__/__init__.cpython-312.pyc,,
+h11/__pycache__/_abnf.cpython-312.pyc,,
+h11/__pycache__/_connection.cpython-312.pyc,,
+h11/__pycache__/_events.cpython-312.pyc,,
+h11/__pycache__/_headers.cpython-312.pyc,,
+h11/__pycache__/_readers.cpython-312.pyc,,
+h11/__pycache__/_receivebuffer.cpython-312.pyc,,
+h11/__pycache__/_state.cpython-312.pyc,,
+h11/__pycache__/_util.cpython-312.pyc,,
+h11/__pycache__/_version.cpython-312.pyc,,
+h11/__pycache__/_writers.cpython-312.pyc,,
+h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815
+h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863
+h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792
+h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412
+h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590
+h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252
+h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231
+h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888
+h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686
+h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081
+h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL b/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..1eb3c49d99559863120cfb8433fc8738fba43ba9
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (78.1.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt b/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8f080eae848f759c9173bfc0c79506357ebe5090
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Nathaniel J. Smith and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt b/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0d24def711344ec6f4da2108f7d5c9261eb35f8b
--- /dev/null
+++ b/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+h11
diff --git a/lib/python3.12/site-packages/httpcore/__init__.py b/lib/python3.12/site-packages/httpcore/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a92dc4a440bdf6f259ec1083c89c817eb7b631b
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/__init__.py
@@ -0,0 +1,141 @@
+from ._api import request, stream
+from ._async import (
+ AsyncConnectionInterface,
+ AsyncConnectionPool,
+ AsyncHTTP2Connection,
+ AsyncHTTP11Connection,
+ AsyncHTTPConnection,
+ AsyncHTTPProxy,
+ AsyncSOCKSProxy,
+)
+from ._backends.base import (
+ SOCKET_OPTION,
+ AsyncNetworkBackend,
+ AsyncNetworkStream,
+ NetworkBackend,
+ NetworkStream,
+)
+from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
+from ._backends.sync import SyncBackend
+from ._exceptions import (
+ ConnectError,
+ ConnectionNotAvailable,
+ ConnectTimeout,
+ LocalProtocolError,
+ NetworkError,
+ PoolTimeout,
+ ProtocolError,
+ ProxyError,
+ ReadError,
+ ReadTimeout,
+ RemoteProtocolError,
+ TimeoutException,
+ UnsupportedProtocol,
+ WriteError,
+ WriteTimeout,
+)
+from ._models import URL, Origin, Proxy, Request, Response
+from ._ssl import default_ssl_context
+from ._sync import (
+ ConnectionInterface,
+ ConnectionPool,
+ HTTP2Connection,
+ HTTP11Connection,
+ HTTPConnection,
+ HTTPProxy,
+ SOCKSProxy,
+)
+
+# The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed.
+try:
+ from ._backends.anyio import AnyIOBackend
+except ImportError: # pragma: nocover
+
+ class AnyIOBackend: # type: ignore
+ def __init__(self, *args, **kwargs): # type: ignore
+ msg = (
+ "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed."
+ )
+ raise RuntimeError(msg)
+
+
+# The 'httpcore.TrioBackend' class is conditional on 'trio' being installed.
+try:
+ from ._backends.trio import TrioBackend
+except ImportError: # pragma: nocover
+
+ class TrioBackend: # type: ignore
+ def __init__(self, *args, **kwargs): # type: ignore
+ msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed."
+ raise RuntimeError(msg)
+
+
+__all__ = [
+ # top-level requests
+ "request",
+ "stream",
+ # models
+ "Origin",
+ "URL",
+ "Request",
+ "Response",
+ "Proxy",
+ # async
+ "AsyncHTTPConnection",
+ "AsyncConnectionPool",
+ "AsyncHTTPProxy",
+ "AsyncHTTP11Connection",
+ "AsyncHTTP2Connection",
+ "AsyncConnectionInterface",
+ "AsyncSOCKSProxy",
+ # sync
+ "HTTPConnection",
+ "ConnectionPool",
+ "HTTPProxy",
+ "HTTP11Connection",
+ "HTTP2Connection",
+ "ConnectionInterface",
+ "SOCKSProxy",
+ # network backends, implementations
+ "SyncBackend",
+ "AnyIOBackend",
+ "TrioBackend",
+ # network backends, mock implementations
+ "AsyncMockBackend",
+ "AsyncMockStream",
+ "MockBackend",
+ "MockStream",
+ # network backends, interface
+ "AsyncNetworkStream",
+ "AsyncNetworkBackend",
+ "NetworkStream",
+ "NetworkBackend",
+ # util
+ "default_ssl_context",
+ "SOCKET_OPTION",
+ # exceptions
+ "ConnectionNotAvailable",
+ "ProxyError",
+ "ProtocolError",
+ "LocalProtocolError",
+ "RemoteProtocolError",
+ "UnsupportedProtocol",
+ "TimeoutException",
+ "PoolTimeout",
+ "ConnectTimeout",
+ "ReadTimeout",
+ "WriteTimeout",
+ "NetworkError",
+ "ConnectError",
+ "ReadError",
+ "WriteError",
+]
+
+__version__ = "1.0.9"
+
+
+__locals = locals()
+for __name in __all__:
+ # Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14
+ if not __name.startswith(("__", "SOCKET_OPTION")):
+ setattr(__locals[__name], "__module__", "httpcore") # noqa
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d7e3c4a244b02fe5d57fd97dba9e294700f6ac30
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..077a42217f8bec55a7f25d3f9340abddb2b7bb7c
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dcf74e036c4a3e7d78e40721bd8c87b1334ec0df
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fe01485c8785381fc7c3335000cb35cceb6f66da
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f05f73573b592b491f4f87b2a5c69784a93ae080
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..78271a29891856a61462ce5edbfa81e8e79638d0
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..377b01002ea54af31e4dfda92f080cf90150bdf0
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4ff901f66664d69d4441f1fb1f0c6acf791b94ab
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_api.py b/lib/python3.12/site-packages/httpcore/_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..38b961d10de88bebc98c758d0d1f14af1e7c0370
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_api.py
@@ -0,0 +1,94 @@
+from __future__ import annotations
+
+import contextlib
+import typing
+
+from ._models import URL, Extensions, HeaderTypes, Response
+from ._sync.connection_pool import ConnectionPool
+
+
+def request(
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.Iterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+) -> Response:
+ """
+ Sends an HTTP request, returning the response.
+
+ ```
+ response = httpcore.request("GET", "https://www.example.com/")
+ ```
+
+ Arguments:
+ method: The HTTP method for the request. Typically one of `"GET"`,
+ `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
+ url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
+ or as str/bytes.
+ headers: The HTTP request headers. Either as a dictionary of str/bytes,
+ or as a list of two-tuples of str/bytes.
+ content: The content of the request body. Either as bytes,
+ or as a bytes iterator.
+ extensions: A dictionary of optional extra information included on the request.
+ Possible keys include `"timeout"`.
+
+ Returns:
+ An instance of `httpcore.Response`.
+ """
+ with ConnectionPool() as pool:
+ return pool.request(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ )
+
+
+@contextlib.contextmanager
+def stream(
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.Iterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+) -> typing.Iterator[Response]:
+ """
+ Sends an HTTP request, returning the response within a content manager.
+
+ ```
+ with httpcore.stream("GET", "https://www.example.com/") as response:
+ ...
+ ```
+
+ When using the `stream()` function, the body of the response will not be
+ automatically read. If you want to access the response body you should
+ either use `content = response.read()`, or `for chunk in response.iter_content()`.
+
+ Arguments:
+ method: The HTTP method for the request. Typically one of `"GET"`,
+ `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
+ url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
+ or as str/bytes.
+ headers: The HTTP request headers. Either as a dictionary of str/bytes,
+ or as a list of two-tuples of str/bytes.
+ content: The content of the request body. Either as bytes,
+ or as a bytes iterator.
+ extensions: A dictionary of optional extra information included on the request.
+ Possible keys include `"timeout"`.
+
+ Returns:
+ An instance of `httpcore.Response`.
+ """
+ with ConnectionPool() as pool:
+ with pool.stream(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ ) as response:
+ yield response
diff --git a/lib/python3.12/site-packages/httpcore/_async/__init__.py b/lib/python3.12/site-packages/httpcore/_async/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..88dc7f01e132933728cbcf45c88ce82e85ddf65f
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/__init__.py
@@ -0,0 +1,39 @@
+from .connection import AsyncHTTPConnection
+from .connection_pool import AsyncConnectionPool
+from .http11 import AsyncHTTP11Connection
+from .http_proxy import AsyncHTTPProxy
+from .interfaces import AsyncConnectionInterface
+
+try:
+ from .http2 import AsyncHTTP2Connection
+except ImportError: # pragma: nocover
+
+ class AsyncHTTP2Connection: # type: ignore
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
+ raise RuntimeError(
+ "Attempted to use http2 support, but the `h2` package is not "
+ "installed. Use 'pip install httpcore[http2]'."
+ )
+
+
+try:
+ from .socks_proxy import AsyncSOCKSProxy
+except ImportError: # pragma: nocover
+
+ class AsyncSOCKSProxy: # type: ignore
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
+ raise RuntimeError(
+ "Attempted to use SOCKS support, but the `socksio` package is not "
+ "installed. Use 'pip install httpcore[socks]'."
+ )
+
+
+__all__ = [
+ "AsyncHTTPConnection",
+ "AsyncConnectionPool",
+ "AsyncHTTPProxy",
+ "AsyncHTTP11Connection",
+ "AsyncHTTP2Connection",
+ "AsyncConnectionInterface",
+ "AsyncSOCKSProxy",
+]
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..afec1a55405e4411b12af31b3e2cfdc1f2c5fa82
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9273f1da10f22bbb224ac4d7705bc583111bf8f1
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4ad0285888d8a18d2ff7b96becc36e16b930abd3
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..13a1671613665057a900c207062cbc705e964d00
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2d2b01abbbae9dee322fdc1f814250c0d066c388
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..30bf549aaac9f65f3a731ef3be578957cd96d6ef
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..32a3320ddf452edc2a9e970e7d17dfc3a8dfb748
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3f48869ca38468f0036c69ffdad4bf427bd7f842
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_async/connection.py b/lib/python3.12/site-packages/httpcore/_async/connection.py
new file mode 100644
index 0000000000000000000000000000000000000000..b42581dff8aabf4c2ef80ffda26296e1b368d693
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/connection.py
@@ -0,0 +1,222 @@
+from __future__ import annotations
+
+import itertools
+import logging
+import ssl
+import types
+import typing
+
+from .._backends.auto import AutoBackend
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
+from .._exceptions import ConnectError, ConnectTimeout
+from .._models import Origin, Request, Response
+from .._ssl import default_ssl_context
+from .._synchronization import AsyncLock
+from .._trace import Trace
+from .http11 import AsyncHTTP11Connection
+from .interfaces import AsyncConnectionInterface
+
+RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc.
+
+
+logger = logging.getLogger("httpcore.connection")
+
+
+def exponential_backoff(factor: float) -> typing.Iterator[float]:
+ """
+ Generate a geometric sequence that has a ratio of 2 and starts with 0.
+
+ For example:
+ - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
+ - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
+ """
+ yield 0
+ for n in itertools.count():
+ yield factor * 2**n
+
+
+class AsyncHTTPConnection(AsyncConnectionInterface):
+ def __init__(
+ self,
+ origin: Origin,
+ ssl_context: ssl.SSLContext | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: AsyncNetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ self._origin = origin
+ self._ssl_context = ssl_context
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._retries = retries
+ self._local_address = local_address
+ self._uds = uds
+
+ self._network_backend: AsyncNetworkBackend = (
+ AutoBackend() if network_backend is None else network_backend
+ )
+ self._connection: AsyncConnectionInterface | None = None
+ self._connect_failed: bool = False
+ self._request_lock = AsyncLock()
+ self._socket_options = socket_options
+
+ async def handle_async_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
+ )
+
+ try:
+ async with self._request_lock:
+ if self._connection is None:
+ stream = await self._connect(request)
+
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+ if http2_negotiated or (self._http2 and not self._http1):
+ from .http2 import AsyncHTTP2Connection
+
+ self._connection = AsyncHTTP2Connection(
+ origin=self._origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = AsyncHTTP11Connection(
+ origin=self._origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ except BaseException as exc:
+ self._connect_failed = True
+ raise exc
+
+ return await self._connection.handle_async_request(request)
+
+ async def _connect(self, request: Request) -> AsyncNetworkStream:
+ timeouts = request.extensions.get("timeout", {})
+ sni_hostname = request.extensions.get("sni_hostname", None)
+ timeout = timeouts.get("connect", None)
+
+ retries_left = self._retries
+ delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)
+
+ while True:
+ try:
+ if self._uds is None:
+ kwargs = {
+ "host": self._origin.host.decode("ascii"),
+ "port": self._origin.port,
+ "local_address": self._local_address,
+ "timeout": timeout,
+ "socket_options": self._socket_options,
+ }
+ async with Trace("connect_tcp", logger, request, kwargs) as trace:
+ stream = await self._network_backend.connect_tcp(**kwargs)
+ trace.return_value = stream
+ else:
+ kwargs = {
+ "path": self._uds,
+ "timeout": timeout,
+ "socket_options": self._socket_options,
+ }
+ async with Trace(
+ "connect_unix_socket", logger, request, kwargs
+ ) as trace:
+ stream = await self._network_backend.connect_unix_socket(
+ **kwargs
+ )
+ trace.return_value = stream
+
+ if self._origin.scheme in (b"https", b"wss"):
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": sni_hostname
+ or self._origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ async with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = await stream.start_tls(**kwargs)
+ trace.return_value = stream
+ return stream
+ except (ConnectError, ConnectTimeout):
+ if retries_left <= 0:
+ raise
+ retries_left -= 1
+ delay = next(delays)
+ async with Trace("retry", logger, request, kwargs) as trace:
+ await self._network_backend.sleep(delay)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ async def aclose(self) -> None:
+ if self._connection is not None:
+ async with Trace("close", logger, None, {}):
+ await self._connection.aclose()
+
+ def is_available(self) -> bool:
+ if self._connection is None:
+ # If HTTP/2 support is enabled, and the resulting connection could
+ # end up as HTTP/2 then we should indicate the connection as being
+ # available to service multiple requests.
+ return (
+ self._http2
+ and (self._origin.scheme == b"https" or not self._http1)
+ and not self._connect_failed
+ )
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.is_closed()
+
+ def info(self) -> str:
+ if self._connection is None:
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
+ return self._connection.info()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ async def __aenter__(self) -> AsyncHTTPConnection:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ await self.aclose()
diff --git a/lib/python3.12/site-packages/httpcore/_async/connection_pool.py b/lib/python3.12/site-packages/httpcore/_async/connection_pool.py
new file mode 100644
index 0000000000000000000000000000000000000000..96e973d0ce223f6bed9be9e6a6a2f3c01622c611
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/connection_pool.py
@@ -0,0 +1,420 @@
+from __future__ import annotations
+
+import ssl
+import sys
+import types
+import typing
+
+from .._backends.auto import AutoBackend
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
+from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
+from .._models import Origin, Proxy, Request, Response
+from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock
+from .connection import AsyncHTTPConnection
+from .interfaces import AsyncConnectionInterface, AsyncRequestInterface
+
+
+class AsyncPoolRequest:
+ def __init__(self, request: Request) -> None:
+ self.request = request
+ self.connection: AsyncConnectionInterface | None = None
+ self._connection_acquired = AsyncEvent()
+
+ def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None:
+ self.connection = connection
+ self._connection_acquired.set()
+
+ def clear_connection(self) -> None:
+ self.connection = None
+ self._connection_acquired = AsyncEvent()
+
+ async def wait_for_connection(
+ self, timeout: float | None = None
+ ) -> AsyncConnectionInterface:
+ if self.connection is None:
+ await self._connection_acquired.wait(timeout=timeout)
+ assert self.connection is not None
+ return self.connection
+
+ def is_queued(self) -> bool:
+ return self.connection is None
+
+
+class AsyncConnectionPool(AsyncRequestInterface):
+ """
+ A connection pool for making HTTP requests.
+ """
+
+ def __init__(
+ self,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy: Proxy | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: AsyncNetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish a
+ connection.
+ local_address: Local address to connect from. Can also be used to connect
+ using a particular address family. Using `local_address="0.0.0.0"`
+ will connect using an `AF_INET` address (IPv4), while using
+ `local_address="::"` will connect using an `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ socket_options: Socket options that have to be included
+ in the TCP socket when the connection was established.
+ """
+ self._ssl_context = ssl_context
+ self._proxy = proxy
+ self._max_connections = (
+ sys.maxsize if max_connections is None else max_connections
+ )
+ self._max_keepalive_connections = (
+ sys.maxsize
+ if max_keepalive_connections is None
+ else max_keepalive_connections
+ )
+ self._max_keepalive_connections = min(
+ self._max_connections, self._max_keepalive_connections
+ )
+
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._retries = retries
+ self._local_address = local_address
+ self._uds = uds
+
+ self._network_backend = (
+ AutoBackend() if network_backend is None else network_backend
+ )
+ self._socket_options = socket_options
+
+ # The mutable state on a connection pool is the queue of incoming requests,
+ # and the set of connections that are servicing those requests.
+ self._connections: list[AsyncConnectionInterface] = []
+ self._requests: list[AsyncPoolRequest] = []
+
+ # We only mutate the state of the connection pool within an 'optional_thread_lock'
+ # context. This holds a threading lock unless we're running in async mode,
+ # in which case it is a no-op.
+ self._optional_thread_lock = AsyncThreadLock()
+
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
+ if self._proxy is not None:
+ if self._proxy.url.scheme in (b"socks5", b"socks5h"):
+ from .socks_proxy import AsyncSocks5Connection
+
+ return AsyncSocks5Connection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_auth=self._proxy.auth,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+ elif origin.scheme == b"http":
+ from .http_proxy import AsyncForwardHTTPConnection
+
+ return AsyncForwardHTTPConnection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_headers=self._proxy.headers,
+ proxy_ssl_context=self._proxy.ssl_context,
+ remote_origin=origin,
+ keepalive_expiry=self._keepalive_expiry,
+ network_backend=self._network_backend,
+ )
+ from .http_proxy import AsyncTunnelHTTPConnection
+
+ return AsyncTunnelHTTPConnection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_headers=self._proxy.headers,
+ proxy_ssl_context=self._proxy.ssl_context,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+ return AsyncHTTPConnection(
+ origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ retries=self._retries,
+ local_address=self._local_address,
+ uds=self._uds,
+ network_backend=self._network_backend,
+ socket_options=self._socket_options,
+ )
+
+ @property
+ def connections(self) -> list[AsyncConnectionInterface]:
+ """
+ Return a list of the connections currently in the pool.
+
+ For example:
+
+ ```python
+ >>> pool.connections
+ [
+ ,
+ ,
+ ,
+ ]
+ ```
+ """
+ return list(self._connections)
+
+ async def handle_async_request(self, request: Request) -> Response:
+ """
+ Send an HTTP request, and return an HTTP response.
+
+ This is the core implementation that is called into by `.request()` or `.stream()`.
+ """
+ scheme = request.url.scheme.decode()
+ if scheme == "":
+ raise UnsupportedProtocol(
+ "Request URL is missing an 'http://' or 'https://' protocol."
+ )
+ if scheme not in ("http", "https", "ws", "wss"):
+ raise UnsupportedProtocol(
+ f"Request URL has an unsupported protocol '{scheme}://'."
+ )
+
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("pool", None)
+
+ with self._optional_thread_lock:
+ # Add the incoming request to our request queue.
+ pool_request = AsyncPoolRequest(request)
+ self._requests.append(pool_request)
+
+ try:
+ while True:
+ with self._optional_thread_lock:
+ # Assign incoming requests to available connections,
+ # closing or creating new connections as required.
+ closing = self._assign_requests_to_connections()
+ await self._close_connections(closing)
+
+ # Wait until this request has an assigned connection.
+ connection = await pool_request.wait_for_connection(timeout=timeout)
+
+ try:
+ # Send the request on the assigned connection.
+ response = await connection.handle_async_request(
+ pool_request.request
+ )
+ except ConnectionNotAvailable:
+ # In some cases a connection may initially be available to
+ # handle a request, but then become unavailable.
+ #
+ # In this case we clear the connection and try again.
+ pool_request.clear_connection()
+ else:
+ break # pragma: nocover
+
+ except BaseException as exc:
+ with self._optional_thread_lock:
+ # For any exception or cancellation we remove the request from
+ # the queue, and then re-assign requests to connections.
+ self._requests.remove(pool_request)
+ closing = self._assign_requests_to_connections()
+
+ await self._close_connections(closing)
+ raise exc from None
+
+ # Return the response. Note that in this case we still have to manage
+ # the point at which the response is closed.
+ assert isinstance(response.stream, typing.AsyncIterable)
+ return Response(
+ status=response.status,
+ headers=response.headers,
+ content=PoolByteStream(
+ stream=response.stream, pool_request=pool_request, pool=self
+ ),
+ extensions=response.extensions,
+ )
+
+ def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]:
+ """
+ Manage the state of the connection pool, assigning incoming
+ requests to connections as available.
+
+ Called whenever a new request is added or removed from the pool.
+
+ Any closing connections are returned, allowing the I/O for closing
+ those connections to be handled seperately.
+ """
+ closing_connections = []
+
+ # First we handle cleaning up any connections that are closed,
+ # have expired their keep-alive, or surplus idle connections.
+ for connection in list(self._connections):
+ if connection.is_closed():
+ # log: "removing closed connection"
+ self._connections.remove(connection)
+ elif connection.has_expired():
+ # log: "closing expired connection"
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+ elif (
+ connection.is_idle()
+ and len([connection.is_idle() for connection in self._connections])
+ > self._max_keepalive_connections
+ ):
+ # log: "closing idle connection"
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+
+ # Assign queued requests to connections.
+ queued_requests = [request for request in self._requests if request.is_queued()]
+ for pool_request in queued_requests:
+ origin = pool_request.request.url.origin
+ available_connections = [
+ connection
+ for connection in self._connections
+ if connection.can_handle_request(origin) and connection.is_available()
+ ]
+ idle_connections = [
+ connection for connection in self._connections if connection.is_idle()
+ ]
+
+ # There are three cases for how we may be able to handle the request:
+ #
+ # 1. There is an existing connection that can handle the request.
+ # 2. We can create a new connection to handle the request.
+ # 3. We can close an idle connection and then create a new connection
+ # to handle the request.
+ if available_connections:
+ # log: "reusing existing connection"
+ connection = available_connections[0]
+ pool_request.assign_to_connection(connection)
+ elif len(self._connections) < self._max_connections:
+ # log: "creating new connection"
+ connection = self.create_connection(origin)
+ self._connections.append(connection)
+ pool_request.assign_to_connection(connection)
+ elif idle_connections:
+ # log: "closing idle connection"
+ connection = idle_connections[0]
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+ # log: "creating new connection"
+ connection = self.create_connection(origin)
+ self._connections.append(connection)
+ pool_request.assign_to_connection(connection)
+
+ return closing_connections
+
+ async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None:
+ # Close connections which have been removed from the pool.
+ with AsyncShieldCancellation():
+ for connection in closing:
+ await connection.aclose()
+
+ async def aclose(self) -> None:
+ # Explicitly close the connection pool.
+ # Clears all existing requests and connections.
+ with self._optional_thread_lock:
+ closing_connections = list(self._connections)
+ self._connections = []
+ await self._close_connections(closing_connections)
+
+ async def __aenter__(self) -> AsyncConnectionPool:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ await self.aclose()
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ with self._optional_thread_lock:
+ request_is_queued = [request.is_queued() for request in self._requests]
+ connection_is_idle = [
+ connection.is_idle() for connection in self._connections
+ ]
+
+ num_active_requests = request_is_queued.count(False)
+ num_queued_requests = request_is_queued.count(True)
+ num_active_connections = connection_is_idle.count(False)
+ num_idle_connections = connection_is_idle.count(True)
+
+ requests_info = (
+ f"Requests: {num_active_requests} active, {num_queued_requests} queued"
+ )
+ connection_info = (
+ f"Connections: {num_active_connections} active, {num_idle_connections} idle"
+ )
+
+ return f"<{class_name} [{requests_info} | {connection_info}]>"
+
+
+class PoolByteStream:
+ def __init__(
+ self,
+ stream: typing.AsyncIterable[bytes],
+ pool_request: AsyncPoolRequest,
+ pool: AsyncConnectionPool,
+ ) -> None:
+ self._stream = stream
+ self._pool_request = pool_request
+ self._pool = pool
+ self._closed = False
+
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ try:
+ async for part in self._stream:
+ yield part
+ except BaseException as exc:
+ await self.aclose()
+ raise exc from None
+
+ async def aclose(self) -> None:
+ if not self._closed:
+ self._closed = True
+ with AsyncShieldCancellation():
+ if hasattr(self._stream, "aclose"):
+ await self._stream.aclose()
+
+ with self._pool._optional_thread_lock:
+ self._pool._requests.remove(self._pool_request)
+ closing = self._pool._assign_requests_to_connections()
+
+ await self._pool._close_connections(closing)
diff --git a/lib/python3.12/site-packages/httpcore/_async/http11.py b/lib/python3.12/site-packages/httpcore/_async/http11.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6d6d709852b137a862cfe2b3af42dc790fa705d
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/http11.py
@@ -0,0 +1,379 @@
+from __future__ import annotations
+
+import enum
+import logging
+import ssl
+import time
+import types
+import typing
+
+import h11
+
+from .._backends.base import AsyncNetworkStream
+from .._exceptions import (
+ ConnectionNotAvailable,
+ LocalProtocolError,
+ RemoteProtocolError,
+ WriteError,
+ map_exceptions,
+)
+from .._models import Origin, Request, Response
+from .._synchronization import AsyncLock, AsyncShieldCancellation
+from .._trace import Trace
+from .interfaces import AsyncConnectionInterface
+
+logger = logging.getLogger("httpcore.http11")
+
+
+# A subset of `h11.Event` types supported by `_send_event`
+H11SendEvent = typing.Union[
+ h11.Request,
+ h11.Data,
+ h11.EndOfMessage,
+]
+
+
+class HTTPConnectionState(enum.IntEnum):
+ NEW = 0
+ ACTIVE = 1
+ IDLE = 2
+ CLOSED = 3
+
+
+class AsyncHTTP11Connection(AsyncConnectionInterface):
+ READ_NUM_BYTES = 64 * 1024
+ MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024
+
+ def __init__(
+ self,
+ origin: Origin,
+ stream: AsyncNetworkStream,
+ keepalive_expiry: float | None = None,
+ ) -> None:
+ self._origin = origin
+ self._network_stream = stream
+ self._keepalive_expiry: float | None = keepalive_expiry
+ self._expire_at: float | None = None
+ self._state = HTTPConnectionState.NEW
+ self._state_lock = AsyncLock()
+ self._request_count = 0
+ self._h11_state = h11.Connection(
+ our_role=h11.CLIENT,
+ max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
+ )
+
+ async def handle_async_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection "
+ f"to {self._origin}"
+ )
+
+ async with self._state_lock:
+ if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
+ self._request_count += 1
+ self._state = HTTPConnectionState.ACTIVE
+ self._expire_at = None
+ else:
+ raise ConnectionNotAvailable()
+
+ try:
+ kwargs = {"request": request}
+ try:
+ async with Trace(
+ "send_request_headers", logger, request, kwargs
+ ) as trace:
+ await self._send_request_headers(**kwargs)
+ async with Trace("send_request_body", logger, request, kwargs) as trace:
+ await self._send_request_body(**kwargs)
+ except WriteError:
+ # If we get a write error while we're writing the request,
+ # then we supress this error and move on to attempting to
+ # read the response. Servers can sometimes close the request
+ # pre-emptively and then respond with a well formed HTTP
+ # error response.
+ pass
+
+ async with Trace(
+ "receive_response_headers", logger, request, kwargs
+ ) as trace:
+ (
+ http_version,
+ status,
+ reason_phrase,
+ headers,
+ trailing_data,
+ ) = await self._receive_response_headers(**kwargs)
+ trace.return_value = (
+ http_version,
+ status,
+ reason_phrase,
+ headers,
+ )
+
+ network_stream = self._network_stream
+
+ # CONNECT or Upgrade request
+ if (status == 101) or (
+ (request.method == b"CONNECT") and (200 <= status < 300)
+ ):
+ network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data)
+
+ return Response(
+ status=status,
+ headers=headers,
+ content=HTTP11ConnectionByteStream(self, request),
+ extensions={
+ "http_version": http_version,
+ "reason_phrase": reason_phrase,
+ "network_stream": network_stream,
+ },
+ )
+ except BaseException as exc:
+ with AsyncShieldCancellation():
+ async with Trace("response_closed", logger, request) as trace:
+ await self._response_closed()
+ raise exc
+
+ # Sending the request...
+
+ async def _send_request_headers(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
+ event = h11.Request(
+ method=request.method,
+ target=request.url.target,
+ headers=request.headers,
+ )
+ await self._send_event(event, timeout=timeout)
+
+ async def _send_request_body(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ assert isinstance(request.stream, typing.AsyncIterable)
+ async for chunk in request.stream:
+ event = h11.Data(data=chunk)
+ await self._send_event(event, timeout=timeout)
+
+ await self._send_event(h11.EndOfMessage(), timeout=timeout)
+
+ async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
+ bytes_to_send = self._h11_state.send(event)
+ if bytes_to_send is not None:
+ await self._network_stream.write(bytes_to_send, timeout=timeout)
+
+ # Receiving the response...
+
+ async def _receive_response_headers(
+ self, request: Request
+ ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ while True:
+ event = await self._receive_event(timeout=timeout)
+ if isinstance(event, h11.Response):
+ break
+ if (
+ isinstance(event, h11.InformationalResponse)
+ and event.status_code == 101
+ ):
+ break
+
+ http_version = b"HTTP/" + event.http_version
+
+ # h11 version 0.11+ supports a `raw_items` interface to get the
+ # raw header casing, rather than the enforced lowercase headers.
+ headers = event.headers.raw_items()
+
+ trailing_data, _ = self._h11_state.trailing_data
+
+ return http_version, event.status_code, event.reason, headers, trailing_data
+
+ async def _receive_response_body(
+ self, request: Request
+ ) -> typing.AsyncIterator[bytes]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ while True:
+ event = await self._receive_event(timeout=timeout)
+ if isinstance(event, h11.Data):
+ yield bytes(event.data)
+ elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
+ break
+
+ async def _receive_event(
+ self, timeout: float | None = None
+ ) -> h11.Event | type[h11.PAUSED]:
+ while True:
+ with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
+ event = self._h11_state.next_event()
+
+ if event is h11.NEED_DATA:
+ data = await self._network_stream.read(
+ self.READ_NUM_BYTES, timeout=timeout
+ )
+
+ # If we feed this case through h11 we'll raise an exception like:
+ #
+ # httpcore.RemoteProtocolError: can't handle event type
+ # ConnectionClosed when role=SERVER and state=SEND_RESPONSE
+ #
+ # Which is accurate, but not very informative from an end-user
+ # perspective. Instead we handle this case distinctly and treat
+ # it as a ConnectError.
+ if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
+ msg = "Server disconnected without sending a response."
+ raise RemoteProtocolError(msg)
+
+ self._h11_state.receive_data(data)
+ else:
+ # mypy fails to narrow the type in the above if statement above
+ return event # type: ignore[return-value]
+
+ async def _response_closed(self) -> None:
+ async with self._state_lock:
+ if (
+ self._h11_state.our_state is h11.DONE
+ and self._h11_state.their_state is h11.DONE
+ ):
+ self._state = HTTPConnectionState.IDLE
+ self._h11_state.start_next_cycle()
+ if self._keepalive_expiry is not None:
+ now = time.monotonic()
+ self._expire_at = now + self._keepalive_expiry
+ else:
+ await self.aclose()
+
+ # Once the connection is no longer required...
+
+ async def aclose(self) -> None:
+ # Note that this method unilaterally closes the connection, and does
+ # not have any kind of locking in place around it.
+ self._state = HTTPConnectionState.CLOSED
+ await self._network_stream.aclose()
+
+ # The AsyncConnectionInterface methods provide information about the state of
+ # the connection, allowing for a connection pooling implementation to
+ # determine when to reuse and when to close the connection...
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ def is_available(self) -> bool:
+ # Note that HTTP/1.1 connections in the "NEW" state are not treated as
+ # being "available". The control flow which created the connection will
+ # be able to send an outgoing request, but the connection will not be
+ # acquired from the connection pool for any other request.
+ return self._state == HTTPConnectionState.IDLE
+
+ def has_expired(self) -> bool:
+ now = time.monotonic()
+ keepalive_expired = self._expire_at is not None and now > self._expire_at
+
+ # If the HTTP connection is idle but the socket is readable, then the
+ # only valid state is that the socket is about to return b"", indicating
+ # a server-initiated disconnect.
+ server_disconnected = (
+ self._state == HTTPConnectionState.IDLE
+ and self._network_stream.get_extra_info("is_readable")
+ )
+
+ return keepalive_expired or server_disconnected
+
+ def is_idle(self) -> bool:
+ return self._state == HTTPConnectionState.IDLE
+
+ def is_closed(self) -> bool:
+ return self._state == HTTPConnectionState.CLOSED
+
+ def info(self) -> str:
+ origin = str(self._origin)
+ return (
+ f"{origin!r}, HTTP/1.1, {self._state.name}, "
+ f"Request Count: {self._request_count}"
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ origin = str(self._origin)
+ return (
+ f"<{class_name} [{origin!r}, {self._state.name}, "
+ f"Request Count: {self._request_count}]>"
+ )
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ async def __aenter__(self) -> AsyncHTTP11Connection:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ await self.aclose()
+
+
+class HTTP11ConnectionByteStream:
+ def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None:
+ self._connection = connection
+ self._request = request
+ self._closed = False
+
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ kwargs = {"request": self._request}
+ try:
+ async with Trace("receive_response_body", logger, self._request, kwargs):
+ async for chunk in self._connection._receive_response_body(**kwargs):
+ yield chunk
+ except BaseException as exc:
+ # If we get an exception while streaming the response,
+ # we want to close the response (and possibly the connection)
+ # before raising that exception.
+ with AsyncShieldCancellation():
+ await self.aclose()
+ raise exc
+
+ async def aclose(self) -> None:
+ if not self._closed:
+ self._closed = True
+ async with Trace("response_closed", logger, self._request):
+ await self._connection._response_closed()
+
+
+class AsyncHTTP11UpgradeStream(AsyncNetworkStream):
+ def __init__(self, stream: AsyncNetworkStream, leading_data: bytes) -> None:
+ self._stream = stream
+ self._leading_data = leading_data
+
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ if self._leading_data:
+ buffer = self._leading_data[:max_bytes]
+ self._leading_data = self._leading_data[max_bytes:]
+ return buffer
+ else:
+ return await self._stream.read(max_bytes, timeout)
+
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ await self._stream.write(buffer, timeout)
+
+ async def aclose(self) -> None:
+ await self._stream.aclose()
+
+ async def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> AsyncNetworkStream:
+ return await self._stream.start_tls(ssl_context, server_hostname, timeout)
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return self._stream.get_extra_info(info)
diff --git a/lib/python3.12/site-packages/httpcore/_async/http2.py b/lib/python3.12/site-packages/httpcore/_async/http2.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbd0beeb4da32d8c0175d412fa442eae8f837723
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/http2.py
@@ -0,0 +1,592 @@
+from __future__ import annotations
+
+import enum
+import logging
+import time
+import types
+import typing
+
+import h2.config
+import h2.connection
+import h2.events
+import h2.exceptions
+import h2.settings
+
+from .._backends.base import AsyncNetworkStream
+from .._exceptions import (
+ ConnectionNotAvailable,
+ LocalProtocolError,
+ RemoteProtocolError,
+)
+from .._models import Origin, Request, Response
+from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation
+from .._trace import Trace
+from .interfaces import AsyncConnectionInterface
+
+logger = logging.getLogger("httpcore.http2")
+
+
+def has_body_headers(request: Request) -> bool:
+ return any(
+ k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
+ for k, v in request.headers
+ )
+
+
+class HTTPConnectionState(enum.IntEnum):
+ ACTIVE = 1
+ IDLE = 2
+ CLOSED = 3
+
+
+class AsyncHTTP2Connection(AsyncConnectionInterface):
+ READ_NUM_BYTES = 64 * 1024
+ CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)
+
+ def __init__(
+ self,
+ origin: Origin,
+ stream: AsyncNetworkStream,
+ keepalive_expiry: float | None = None,
+ ):
+ self._origin = origin
+ self._network_stream = stream
+ self._keepalive_expiry: float | None = keepalive_expiry
+ self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
+ self._state = HTTPConnectionState.IDLE
+ self._expire_at: float | None = None
+ self._request_count = 0
+ self._init_lock = AsyncLock()
+ self._state_lock = AsyncLock()
+ self._read_lock = AsyncLock()
+ self._write_lock = AsyncLock()
+ self._sent_connection_init = False
+ self._used_all_stream_ids = False
+ self._connection_error = False
+
+ # Mapping from stream ID to response stream events.
+ self._events: dict[
+ int,
+ list[
+ h2.events.ResponseReceived
+ | h2.events.DataReceived
+ | h2.events.StreamEnded
+ | h2.events.StreamReset,
+ ],
+ ] = {}
+
+ # Connection terminated events are stored as state since
+ # we need to handle them for all streams.
+ self._connection_terminated: h2.events.ConnectionTerminated | None = None
+
+ self._read_exception: Exception | None = None
+ self._write_exception: Exception | None = None
+
+ async def handle_async_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ # This cannot occur in normal operation, since the connection pool
+ # will only send requests on connections that handle them.
+ # It's in place simply for resilience as a guard against incorrect
+ # usage, for anyone working directly with httpcore connections.
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection "
+ f"to {self._origin}"
+ )
+
+ async with self._state_lock:
+ if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
+ self._request_count += 1
+ self._expire_at = None
+ self._state = HTTPConnectionState.ACTIVE
+ else:
+ raise ConnectionNotAvailable()
+
+ async with self._init_lock:
+ if not self._sent_connection_init:
+ try:
+ sci_kwargs = {"request": request}
+ async with Trace(
+ "send_connection_init", logger, request, sci_kwargs
+ ):
+ await self._send_connection_init(**sci_kwargs)
+ except BaseException as exc:
+ with AsyncShieldCancellation():
+ await self.aclose()
+ raise exc
+
+ self._sent_connection_init = True
+
+ # Initially start with just 1 until the remote server provides
+ # its max_concurrent_streams value
+ self._max_streams = 1
+
+ local_settings_max_streams = (
+ self._h2_state.local_settings.max_concurrent_streams
+ )
+ self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams)
+
+ for _ in range(local_settings_max_streams - self._max_streams):
+ await self._max_streams_semaphore.acquire()
+
+ await self._max_streams_semaphore.acquire()
+
+ try:
+ stream_id = self._h2_state.get_next_available_stream_id()
+ self._events[stream_id] = []
+ except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover
+ self._used_all_stream_ids = True
+ self._request_count -= 1
+ raise ConnectionNotAvailable()
+
+ try:
+ kwargs = {"request": request, "stream_id": stream_id}
+ async with Trace("send_request_headers", logger, request, kwargs):
+ await self._send_request_headers(request=request, stream_id=stream_id)
+ async with Trace("send_request_body", logger, request, kwargs):
+ await self._send_request_body(request=request, stream_id=stream_id)
+ async with Trace(
+ "receive_response_headers", logger, request, kwargs
+ ) as trace:
+ status, headers = await self._receive_response(
+ request=request, stream_id=stream_id
+ )
+ trace.return_value = (status, headers)
+
+ return Response(
+ status=status,
+ headers=headers,
+ content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
+ extensions={
+ "http_version": b"HTTP/2",
+ "network_stream": self._network_stream,
+ "stream_id": stream_id,
+ },
+ )
+ except BaseException as exc: # noqa: PIE786
+ with AsyncShieldCancellation():
+ kwargs = {"stream_id": stream_id}
+ async with Trace("response_closed", logger, request, kwargs):
+ await self._response_closed(stream_id=stream_id)
+
+ if isinstance(exc, h2.exceptions.ProtocolError):
+ # One case where h2 can raise a protocol error is when a
+ # closed frame has been seen by the state machine.
+ #
+ # This happens when one stream is reading, and encounters
+ # a GOAWAY event. Other flows of control may then raise
+ # a protocol error at any point they interact with the 'h2_state'.
+ #
+ # In this case we'll have stored the event, and should raise
+ # it as a RemoteProtocolError.
+ if self._connection_terminated: # pragma: nocover
+ raise RemoteProtocolError(self._connection_terminated)
+ # If h2 raises a protocol error in some other state then we
+ # must somehow have made a protocol violation.
+ raise LocalProtocolError(exc) # pragma: nocover
+
+ raise exc
+
+ async def _send_connection_init(self, request: Request) -> None:
+ """
+ The HTTP/2 connection requires some initial setup before we can start
+ using individual request/response streams on it.
+ """
+ # Need to set these manually here instead of manipulating via
+ # __setitem__() otherwise the H2Connection will emit SettingsUpdate
+ # frames in addition to sending the undesired defaults.
+ self._h2_state.local_settings = h2.settings.Settings(
+ client=True,
+ initial_values={
+ # Disable PUSH_PROMISE frames from the server since we don't do anything
+ # with them for now. Maybe when we support caching?
+ h2.settings.SettingCodes.ENABLE_PUSH: 0,
+ # These two are taken from h2 for safe defaults
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
+ h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
+ },
+ )
+
+ # Some websites (*cough* Yahoo *cough*) balk at this setting being
+ # present in the initial handshake since it's not defined in the original
+ # RFC despite the RFC mandating ignoring settings you don't know about.
+ del self._h2_state.local_settings[
+ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
+ ]
+
+ self._h2_state.initiate_connection()
+ self._h2_state.increment_flow_control_window(2**24)
+ await self._write_outgoing_data(request)
+
+ # Sending the request...
+
+ async def _send_request_headers(self, request: Request, stream_id: int) -> None:
+ """
+ Send the request headers to a given stream ID.
+ """
+ end_stream = not has_body_headers(request)
+
+ # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
+ # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
+ # HTTP/1.1 style headers, and map them appropriately if we end up on
+ # an HTTP/2 connection.
+ authority = [v for k, v in request.headers if k.lower() == b"host"][0]
+
+ headers = [
+ (b":method", request.method),
+ (b":authority", authority),
+ (b":scheme", request.url.scheme),
+ (b":path", request.url.target),
+ ] + [
+ (k.lower(), v)
+ for k, v in request.headers
+ if k.lower()
+ not in (
+ b"host",
+ b"transfer-encoding",
+ )
+ ]
+
+ self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
+ self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
+ await self._write_outgoing_data(request)
+
+ async def _send_request_body(self, request: Request, stream_id: int) -> None:
+ """
+ Iterate over the request body sending it to a given stream ID.
+ """
+ if not has_body_headers(request):
+ return
+
+ assert isinstance(request.stream, typing.AsyncIterable)
+ async for data in request.stream:
+ await self._send_stream_data(request, stream_id, data)
+ await self._send_end_stream(request, stream_id)
+
+ async def _send_stream_data(
+ self, request: Request, stream_id: int, data: bytes
+ ) -> None:
+ """
+ Send a single chunk of data in one or more data frames.
+ """
+ while data:
+ max_flow = await self._wait_for_outgoing_flow(request, stream_id)
+ chunk_size = min(len(data), max_flow)
+ chunk, data = data[:chunk_size], data[chunk_size:]
+ self._h2_state.send_data(stream_id, chunk)
+ await self._write_outgoing_data(request)
+
+ async def _send_end_stream(self, request: Request, stream_id: int) -> None:
+ """
+ Send an empty data frame on on a given stream ID with the END_STREAM flag set.
+ """
+ self._h2_state.end_stream(stream_id)
+ await self._write_outgoing_data(request)
+
+ # Receiving the response...
+
+ async def _receive_response(
+ self, request: Request, stream_id: int
+ ) -> tuple[int, list[tuple[bytes, bytes]]]:
+ """
+ Return the response status code and headers for a given stream ID.
+ """
+ while True:
+ event = await self._receive_stream_event(request, stream_id)
+ if isinstance(event, h2.events.ResponseReceived):
+ break
+
+ status_code = 200
+ headers = []
+ assert event.headers is not None
+ for k, v in event.headers:
+ if k == b":status":
+ status_code = int(v.decode("ascii", errors="ignore"))
+ elif not k.startswith(b":"):
+ headers.append((k, v))
+
+ return (status_code, headers)
+
+ async def _receive_response_body(
+ self, request: Request, stream_id: int
+ ) -> typing.AsyncIterator[bytes]:
+ """
+ Iterator that returns the bytes of the response body for a given stream ID.
+ """
+ while True:
+ event = await self._receive_stream_event(request, stream_id)
+ if isinstance(event, h2.events.DataReceived):
+ assert event.flow_controlled_length is not None
+ assert event.data is not None
+ amount = event.flow_controlled_length
+ self._h2_state.acknowledge_received_data(amount, stream_id)
+ await self._write_outgoing_data(request)
+ yield event.data
+ elif isinstance(event, h2.events.StreamEnded):
+ break
+
+ async def _receive_stream_event(
+ self, request: Request, stream_id: int
+ ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
+ """
+ Return the next available event for a given stream ID.
+
+ Will read more data from the network if required.
+ """
+ while not self._events.get(stream_id):
+ await self._receive_events(request, stream_id)
+ event = self._events[stream_id].pop(0)
+ if isinstance(event, h2.events.StreamReset):
+ raise RemoteProtocolError(event)
+ return event
+
+ async def _receive_events(
+ self, request: Request, stream_id: int | None = None
+ ) -> None:
+ """
+ Read some data from the network until we see one or more events
+ for a given stream ID.
+ """
+ async with self._read_lock:
+ if self._connection_terminated is not None:
+ last_stream_id = self._connection_terminated.last_stream_id
+ if stream_id and last_stream_id and stream_id > last_stream_id:
+ self._request_count -= 1
+ raise ConnectionNotAvailable()
+ raise RemoteProtocolError(self._connection_terminated)
+
+ # This conditional is a bit icky. We don't want to block reading if we've
+ # actually got an event to return for a given stream. We need to do that
+ # check *within* the atomic read lock. Though it also need to be optional,
+ # because when we call it from `_wait_for_outgoing_flow` we *do* want to
+ # block until we've available flow control, event when we have events
+ # pending for the stream ID we're attempting to send on.
+ if stream_id is None or not self._events.get(stream_id):
+ events = await self._read_incoming_data(request)
+ for event in events:
+ if isinstance(event, h2.events.RemoteSettingsChanged):
+ async with Trace(
+ "receive_remote_settings", logger, request
+ ) as trace:
+ await self._receive_remote_settings_change(event)
+ trace.return_value = event
+
+ elif isinstance(
+ event,
+ (
+ h2.events.ResponseReceived,
+ h2.events.DataReceived,
+ h2.events.StreamEnded,
+ h2.events.StreamReset,
+ ),
+ ):
+ if event.stream_id in self._events:
+ self._events[event.stream_id].append(event)
+
+ elif isinstance(event, h2.events.ConnectionTerminated):
+ self._connection_terminated = event
+
+ await self._write_outgoing_data(request)
+
+ async def _receive_remote_settings_change(
+ self, event: h2.events.RemoteSettingsChanged
+ ) -> None:
+ max_concurrent_streams = event.changed_settings.get(
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
+ )
+ if max_concurrent_streams:
+ new_max_streams = min(
+ max_concurrent_streams.new_value,
+ self._h2_state.local_settings.max_concurrent_streams,
+ )
+ if new_max_streams and new_max_streams != self._max_streams:
+ while new_max_streams > self._max_streams:
+ await self._max_streams_semaphore.release()
+ self._max_streams += 1
+ while new_max_streams < self._max_streams:
+ await self._max_streams_semaphore.acquire()
+ self._max_streams -= 1
+
+ async def _response_closed(self, stream_id: int) -> None:
+ await self._max_streams_semaphore.release()
+ del self._events[stream_id]
+ async with self._state_lock:
+ if self._connection_terminated and not self._events:
+ await self.aclose()
+
+ elif self._state == HTTPConnectionState.ACTIVE and not self._events:
+ self._state = HTTPConnectionState.IDLE
+ if self._keepalive_expiry is not None:
+ now = time.monotonic()
+ self._expire_at = now + self._keepalive_expiry
+ if self._used_all_stream_ids: # pragma: nocover
+ await self.aclose()
+
+ async def aclose(self) -> None:
+ # Note that this method unilaterally closes the connection, and does
+ # not have any kind of locking in place around it.
+ self._h2_state.close_connection()
+ self._state = HTTPConnectionState.CLOSED
+ await self._network_stream.aclose()
+
+ # Wrappers around network read/write operations...
+
+ async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ if self._read_exception is not None:
+ raise self._read_exception # pragma: nocover
+
+ try:
+ data = await self._network_stream.read(self.READ_NUM_BYTES, timeout)
+ if data == b"":
+ raise RemoteProtocolError("Server disconnected")
+ except Exception as exc:
+ # If we get a network error we should:
+ #
+ # 1. Save the exception and just raise it immediately on any future reads.
+ # (For example, this means that a single read timeout or disconnect will
+ # immediately close all pending streams. Without requiring multiple
+ # sequential timeouts.)
+ # 2. Mark the connection as errored, so that we don't accept any other
+ # incoming requests.
+ self._read_exception = exc
+ self._connection_error = True
+ raise exc
+
+ events: list[h2.events.Event] = self._h2_state.receive_data(data)
+
+ return events
+
+ async def _write_outgoing_data(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ async with self._write_lock:
+ data_to_send = self._h2_state.data_to_send()
+
+ if self._write_exception is not None:
+ raise self._write_exception # pragma: nocover
+
+ try:
+ await self._network_stream.write(data_to_send, timeout)
+ except Exception as exc: # pragma: nocover
+ # If we get a network error we should:
+ #
+ # 1. Save the exception and just raise it immediately on any future write.
+ # (For example, this means that a single write timeout or disconnect will
+ # immediately close all pending streams. Without requiring multiple
+ # sequential timeouts.)
+ # 2. Mark the connection as errored, so that we don't accept any other
+ # incoming requests.
+ self._write_exception = exc
+ self._connection_error = True
+ raise exc
+
+ # Flow control...
+
+ async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
+ """
+ Returns the maximum allowable outgoing flow for a given stream.
+
+ If the allowable flow is zero, then waits on the network until
+ WindowUpdated frames have increased the flow rate.
+ https://tools.ietf.org/html/rfc7540#section-6.9
+ """
+ local_flow: int = self._h2_state.local_flow_control_window(stream_id)
+ max_frame_size: int = self._h2_state.max_outbound_frame_size
+ flow = min(local_flow, max_frame_size)
+ while flow == 0:
+ await self._receive_events(request)
+ local_flow = self._h2_state.local_flow_control_window(stream_id)
+ max_frame_size = self._h2_state.max_outbound_frame_size
+ flow = min(local_flow, max_frame_size)
+ return flow
+
+ # Interface for connection pooling...
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ def is_available(self) -> bool:
+ return (
+ self._state != HTTPConnectionState.CLOSED
+ and not self._connection_error
+ and not self._used_all_stream_ids
+ and not (
+ self._h2_state.state_machine.state
+ == h2.connection.ConnectionState.CLOSED
+ )
+ )
+
+ def has_expired(self) -> bool:
+ now = time.monotonic()
+ return self._expire_at is not None and now > self._expire_at
+
+ def is_idle(self) -> bool:
+ return self._state == HTTPConnectionState.IDLE
+
+ def is_closed(self) -> bool:
+ return self._state == HTTPConnectionState.CLOSED
+
+ def info(self) -> str:
+ origin = str(self._origin)
+ return (
+ f"{origin!r}, HTTP/2, {self._state.name}, "
+ f"Request Count: {self._request_count}"
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ origin = str(self._origin)
+ return (
+ f"<{class_name} [{origin!r}, {self._state.name}, "
+ f"Request Count: {self._request_count}]>"
+ )
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ async def __aenter__(self) -> AsyncHTTP2Connection:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ await self.aclose()
+
+
+class HTTP2ConnectionByteStream:
+ def __init__(
+ self, connection: AsyncHTTP2Connection, request: Request, stream_id: int
+ ) -> None:
+ self._connection = connection
+ self._request = request
+ self._stream_id = stream_id
+ self._closed = False
+
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ kwargs = {"request": self._request, "stream_id": self._stream_id}
+ try:
+ async with Trace("receive_response_body", logger, self._request, kwargs):
+ async for chunk in self._connection._receive_response_body(
+ request=self._request, stream_id=self._stream_id
+ ):
+ yield chunk
+ except BaseException as exc:
+ # If we get an exception while streaming the response,
+ # we want to close the response (and possibly the connection)
+ # before raising that exception.
+ with AsyncShieldCancellation():
+ await self.aclose()
+ raise exc
+
+ async def aclose(self) -> None:
+ if not self._closed:
+ self._closed = True
+ kwargs = {"stream_id": self._stream_id}
+ async with Trace("response_closed", logger, self._request, kwargs):
+ await self._connection._response_closed(stream_id=self._stream_id)
diff --git a/lib/python3.12/site-packages/httpcore/_async/http_proxy.py b/lib/python3.12/site-packages/httpcore/_async/http_proxy.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc9d92066e1680576846e46ccdf645a2b1dd5718
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/http_proxy.py
@@ -0,0 +1,367 @@
+from __future__ import annotations
+
+import base64
+import logging
+import ssl
+import typing
+
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
+from .._exceptions import ProxyError
+from .._models import (
+ URL,
+ Origin,
+ Request,
+ Response,
+ enforce_bytes,
+ enforce_headers,
+ enforce_url,
+)
+from .._ssl import default_ssl_context
+from .._synchronization import AsyncLock
+from .._trace import Trace
+from .connection import AsyncHTTPConnection
+from .connection_pool import AsyncConnectionPool
+from .http11 import AsyncHTTP11Connection
+from .interfaces import AsyncConnectionInterface
+
+ByteOrStr = typing.Union[bytes, str]
+HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
+HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
+
+
+logger = logging.getLogger("httpcore.proxy")
+
+
+def merge_headers(
+ default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+ override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+) -> list[tuple[bytes, bytes]]:
+ """
+ Append default_headers and override_headers, de-duplicating if a key exists
+ in both cases.
+ """
+ default_headers = [] if default_headers is None else list(default_headers)
+ override_headers = [] if override_headers is None else list(override_headers)
+ has_override = set(key.lower() for key, value in override_headers)
+ default_headers = [
+ (key, value)
+ for key, value in default_headers
+ if key.lower() not in has_override
+ ]
+ return default_headers + override_headers
+
+
+class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover
+ """
+ A connection pool that sends requests via an HTTP proxy.
+ """
+
+ def __init__(
+ self,
+ proxy_url: URL | bytes | str,
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: AsyncNetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ proxy_url: The URL to use when connecting to the proxy server.
+ For example `"http://127.0.0.1:8080/"`.
+ proxy_auth: Any proxy authentication as a two-tuple of
+ (username, password). May be either bytes or ascii-only str.
+ proxy_headers: Any HTTP headers to use for the proxy requests.
+ For example `{"Proxy-Authorization": "Basic :"}`.
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish
+ a connection.
+ local_address: Local address to connect from. Can also be used to
+ connect using a particular address family. Using
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
+ (IPv4), while using `local_address="::"` will connect using an
+ `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ """
+ super().__init__(
+ ssl_context=ssl_context,
+ max_connections=max_connections,
+ max_keepalive_connections=max_keepalive_connections,
+ keepalive_expiry=keepalive_expiry,
+ http1=http1,
+ http2=http2,
+ network_backend=network_backend,
+ retries=retries,
+ local_address=local_address,
+ uds=uds,
+ socket_options=socket_options,
+ )
+
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
+ if (
+ self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
+ ): # pragma: no cover
+ raise RuntimeError(
+ "The `proxy_ssl_context` argument is not allowed for the http scheme"
+ )
+
+ self._ssl_context = ssl_context
+ self._proxy_ssl_context = proxy_ssl_context
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ if proxy_auth is not None:
+ username = enforce_bytes(proxy_auth[0], name="proxy_auth")
+ password = enforce_bytes(proxy_auth[1], name="proxy_auth")
+ userpass = username + b":" + password
+ authorization = b"Basic " + base64.b64encode(userpass)
+ self._proxy_headers = [
+ (b"Proxy-Authorization", authorization)
+ ] + self._proxy_headers
+
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
+ if origin.scheme == b"http":
+ return AsyncForwardHTTPConnection(
+ proxy_origin=self._proxy_url.origin,
+ proxy_headers=self._proxy_headers,
+ remote_origin=origin,
+ keepalive_expiry=self._keepalive_expiry,
+ network_backend=self._network_backend,
+ proxy_ssl_context=self._proxy_ssl_context,
+ )
+ return AsyncTunnelHTTPConnection(
+ proxy_origin=self._proxy_url.origin,
+ proxy_headers=self._proxy_headers,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ proxy_ssl_context=self._proxy_ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+
+class AsyncForwardHTTPConnection(AsyncConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
+ keepalive_expiry: float | None = None,
+ network_backend: AsyncNetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ ) -> None:
+ self._connection = AsyncHTTPConnection(
+ origin=proxy_origin,
+ keepalive_expiry=keepalive_expiry,
+ network_backend=network_backend,
+ socket_options=socket_options,
+ ssl_context=proxy_ssl_context,
+ )
+ self._proxy_origin = proxy_origin
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ self._remote_origin = remote_origin
+
+ async def handle_async_request(self, request: Request) -> Response:
+ headers = merge_headers(self._proxy_headers, request.headers)
+ url = URL(
+ scheme=self._proxy_origin.scheme,
+ host=self._proxy_origin.host,
+ port=self._proxy_origin.port,
+ target=bytes(request.url),
+ )
+ proxy_request = Request(
+ method=request.method,
+ url=url,
+ headers=headers,
+ content=request.stream,
+ extensions=request.extensions,
+ )
+ return await self._connection.handle_async_request(proxy_request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ async def aclose(self) -> None:
+ await self._connection.aclose()
+
+ def info(self) -> str:
+ return self._connection.info()
+
+ def is_available(self) -> bool:
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ return self._connection.is_closed()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
+
+
+class AsyncTunnelHTTPConnection(AsyncConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ network_backend: AsyncNetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ self._connection: AsyncConnectionInterface = AsyncHTTPConnection(
+ origin=proxy_origin,
+ keepalive_expiry=keepalive_expiry,
+ network_backend=network_backend,
+ socket_options=socket_options,
+ ssl_context=proxy_ssl_context,
+ )
+ self._proxy_origin = proxy_origin
+ self._remote_origin = remote_origin
+ self._ssl_context = ssl_context
+ self._proxy_ssl_context = proxy_ssl_context
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._connect_lock = AsyncLock()
+ self._connected = False
+
+ async def handle_async_request(self, request: Request) -> Response:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("connect", None)
+
+ async with self._connect_lock:
+ if not self._connected:
+ target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)
+
+ connect_url = URL(
+ scheme=self._proxy_origin.scheme,
+ host=self._proxy_origin.host,
+ port=self._proxy_origin.port,
+ target=target,
+ )
+ connect_headers = merge_headers(
+ [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
+ )
+ connect_request = Request(
+ method=b"CONNECT",
+ url=connect_url,
+ headers=connect_headers,
+ extensions=request.extensions,
+ )
+ connect_response = await self._connection.handle_async_request(
+ connect_request
+ )
+
+ if connect_response.status < 200 or connect_response.status > 299:
+ reason_bytes = connect_response.extensions.get("reason_phrase", b"")
+ reason_str = reason_bytes.decode("ascii", errors="ignore")
+ msg = "%d %s" % (connect_response.status, reason_str)
+ await self._connection.aclose()
+ raise ProxyError(msg)
+
+ stream = connect_response.extensions["network_stream"]
+
+ # Upgrade the stream to SSL
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": self._remote_origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ async with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = await stream.start_tls(**kwargs)
+ trace.return_value = stream
+
+ # Determine if we should be using HTTP/1.1 or HTTP/2
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+
+ # Create the HTTP/1.1 or HTTP/2 connection
+ if http2_negotiated or (self._http2 and not self._http1):
+ from .http2 import AsyncHTTP2Connection
+
+ self._connection = AsyncHTTP2Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = AsyncHTTP11Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+
+ self._connected = True
+ return await self._connection.handle_async_request(request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ async def aclose(self) -> None:
+ await self._connection.aclose()
+
+ def info(self) -> str:
+ return self._connection.info()
+
+ def is_available(self) -> bool:
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ return self._connection.is_closed()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
diff --git a/lib/python3.12/site-packages/httpcore/_async/interfaces.py b/lib/python3.12/site-packages/httpcore/_async/interfaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..361583bede6b2b84088b38054d5d8116ef9f1597
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/interfaces.py
@@ -0,0 +1,137 @@
+from __future__ import annotations
+
+import contextlib
+import typing
+
+from .._models import (
+ URL,
+ Extensions,
+ HeaderTypes,
+ Origin,
+ Request,
+ Response,
+ enforce_bytes,
+ enforce_headers,
+ enforce_url,
+ include_request_headers,
+)
+
+
+class AsyncRequestInterface:
+ async def request(
+ self,
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+ ) -> Response:
+ # Strict type checking on our parameters.
+ method = enforce_bytes(method, name="method")
+ url = enforce_url(url, name="url")
+ headers = enforce_headers(headers, name="headers")
+
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
+ headers = include_request_headers(headers, url=url, content=content)
+
+ request = Request(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ )
+ response = await self.handle_async_request(request)
+ try:
+ await response.aread()
+ finally:
+ await response.aclose()
+ return response
+
+ @contextlib.asynccontextmanager
+ async def stream(
+ self,
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+ ) -> typing.AsyncIterator[Response]:
+ # Strict type checking on our parameters.
+ method = enforce_bytes(method, name="method")
+ url = enforce_url(url, name="url")
+ headers = enforce_headers(headers, name="headers")
+
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
+ headers = include_request_headers(headers, url=url, content=content)
+
+ request = Request(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ )
+ response = await self.handle_async_request(request)
+ try:
+ yield response
+ finally:
+ await response.aclose()
+
+ async def handle_async_request(self, request: Request) -> Response:
+ raise NotImplementedError() # pragma: nocover
+
+
+class AsyncConnectionInterface(AsyncRequestInterface):
+ async def aclose(self) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ def info(self) -> str:
+ raise NotImplementedError() # pragma: nocover
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ raise NotImplementedError() # pragma: nocover
+
+ def is_available(self) -> bool:
+ """
+ Return `True` if the connection is currently able to accept an
+ outgoing request.
+
+ An HTTP/1.1 connection will only be available if it is currently idle.
+
+ An HTTP/2 connection will be available so long as the stream ID space is
+ not yet exhausted, and the connection is not in an error state.
+
+ While the connection is being established we may not yet know if it is going
+ to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
+ treated as being available, but might ultimately raise `NewConnectionRequired`
+ required exceptions if multiple requests are attempted over a connection
+ that ends up being established as HTTP/1.1.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def has_expired(self) -> bool:
+ """
+ Return `True` if the connection is in a state where it should be closed.
+
+ This either means that the connection is idle and it has passed the
+ expiry time on its keep-alive, or that server has sent an EOF.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def is_idle(self) -> bool:
+ """
+ Return `True` if the connection is currently idle.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def is_closed(self) -> bool:
+ """
+ Return `True` if the connection has been closed.
+
+ Used when a response is closed to determine if the connection may be
+ returned to the connection pool or not.
+ """
+ raise NotImplementedError() # pragma: nocover
diff --git a/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py b/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py
new file mode 100644
index 0000000000000000000000000000000000000000..b363f55a0b071de6c5f377726be82dc2110e373c
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py
@@ -0,0 +1,341 @@
+from __future__ import annotations
+
+import logging
+import ssl
+
+import socksio
+
+from .._backends.auto import AutoBackend
+from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream
+from .._exceptions import ConnectionNotAvailable, ProxyError
+from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
+from .._ssl import default_ssl_context
+from .._synchronization import AsyncLock
+from .._trace import Trace
+from .connection_pool import AsyncConnectionPool
+from .http11 import AsyncHTTP11Connection
+from .interfaces import AsyncConnectionInterface
+
+logger = logging.getLogger("httpcore.socks")
+
+
+AUTH_METHODS = {
+ b"\x00": "NO AUTHENTICATION REQUIRED",
+ b"\x01": "GSSAPI",
+ b"\x02": "USERNAME/PASSWORD",
+ b"\xff": "NO ACCEPTABLE METHODS",
+}
+
+REPLY_CODES = {
+ b"\x00": "Succeeded",
+ b"\x01": "General SOCKS server failure",
+ b"\x02": "Connection not allowed by ruleset",
+ b"\x03": "Network unreachable",
+ b"\x04": "Host unreachable",
+ b"\x05": "Connection refused",
+ b"\x06": "TTL expired",
+ b"\x07": "Command not supported",
+ b"\x08": "Address type not supported",
+}
+
+
+async def _init_socks5_connection(
+ stream: AsyncNetworkStream,
+ *,
+ host: bytes,
+ port: int,
+ auth: tuple[bytes, bytes] | None = None,
+) -> None:
+ conn = socksio.socks5.SOCKS5Connection()
+
+ # Auth method request
+ auth_method = (
+ socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
+ if auth is None
+ else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
+ )
+ conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
+ outgoing_bytes = conn.data_to_send()
+ await stream.write(outgoing_bytes)
+
+ # Auth method response
+ incoming_bytes = await stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
+ if response.method != auth_method:
+ requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
+ responded = AUTH_METHODS.get(response.method, "UNKNOWN")
+ raise ProxyError(
+ f"Requested {requested} from proxy server, but got {responded}."
+ )
+
+ if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
+ # Username/password request
+ assert auth is not None
+ username, password = auth
+ conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
+ outgoing_bytes = conn.data_to_send()
+ await stream.write(outgoing_bytes)
+
+ # Username/password response
+ incoming_bytes = await stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
+ if not response.success:
+ raise ProxyError("Invalid username/password")
+
+ # Connect request
+ conn.send(
+ socksio.socks5.SOCKS5CommandRequest.from_address(
+ socksio.socks5.SOCKS5Command.CONNECT, (host, port)
+ )
+ )
+ outgoing_bytes = conn.data_to_send()
+ await stream.write(outgoing_bytes)
+
+ # Connect response
+ incoming_bytes = await stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5Reply)
+ if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
+ reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
+ raise ProxyError(f"Proxy Server could not connect: {reply_code}.")
+
+
+class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover
+ """
+ A connection pool that sends requests via an HTTP proxy.
+ """
+
+ def __init__(
+ self,
+ proxy_url: URL | bytes | str,
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ network_backend: AsyncNetworkBackend | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ proxy_url: The URL to use when connecting to the proxy server.
+ For example `"http://127.0.0.1:8080/"`.
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish
+ a connection.
+ local_address: Local address to connect from. Can also be used to
+ connect using a particular address family. Using
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
+ (IPv4), while using `local_address="::"` will connect using an
+ `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ """
+ super().__init__(
+ ssl_context=ssl_context,
+ max_connections=max_connections,
+ max_keepalive_connections=max_keepalive_connections,
+ keepalive_expiry=keepalive_expiry,
+ http1=http1,
+ http2=http2,
+ network_backend=network_backend,
+ retries=retries,
+ )
+ self._ssl_context = ssl_context
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
+ if proxy_auth is not None:
+ username, password = proxy_auth
+ username_bytes = enforce_bytes(username, name="proxy_auth")
+ password_bytes = enforce_bytes(password, name="proxy_auth")
+ self._proxy_auth: tuple[bytes, bytes] | None = (
+ username_bytes,
+ password_bytes,
+ )
+ else:
+ self._proxy_auth = None
+
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
+ return AsyncSocks5Connection(
+ proxy_origin=self._proxy_url.origin,
+ remote_origin=origin,
+ proxy_auth=self._proxy_auth,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+
+class AsyncSocks5Connection(AsyncConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ proxy_auth: tuple[bytes, bytes] | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ network_backend: AsyncNetworkBackend | None = None,
+ ) -> None:
+ self._proxy_origin = proxy_origin
+ self._remote_origin = remote_origin
+ self._proxy_auth = proxy_auth
+ self._ssl_context = ssl_context
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+
+ self._network_backend: AsyncNetworkBackend = (
+ AutoBackend() if network_backend is None else network_backend
+ )
+ self._connect_lock = AsyncLock()
+ self._connection: AsyncConnectionInterface | None = None
+ self._connect_failed = False
+
+ async def handle_async_request(self, request: Request) -> Response:
+ timeouts = request.extensions.get("timeout", {})
+ sni_hostname = request.extensions.get("sni_hostname", None)
+ timeout = timeouts.get("connect", None)
+
+ async with self._connect_lock:
+ if self._connection is None:
+ try:
+ # Connect to the proxy
+ kwargs = {
+ "host": self._proxy_origin.host.decode("ascii"),
+ "port": self._proxy_origin.port,
+ "timeout": timeout,
+ }
+ async with Trace("connect_tcp", logger, request, kwargs) as trace:
+ stream = await self._network_backend.connect_tcp(**kwargs)
+ trace.return_value = stream
+
+ # Connect to the remote host using socks5
+ kwargs = {
+ "stream": stream,
+ "host": self._remote_origin.host.decode("ascii"),
+ "port": self._remote_origin.port,
+ "auth": self._proxy_auth,
+ }
+ async with Trace(
+ "setup_socks5_connection", logger, request, kwargs
+ ) as trace:
+ await _init_socks5_connection(**kwargs)
+ trace.return_value = stream
+
+ # Upgrade the stream to SSL
+ if self._remote_origin.scheme == b"https":
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = (
+ ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ )
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": sni_hostname
+ or self._remote_origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ async with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = await stream.start_tls(**kwargs)
+ trace.return_value = stream
+
+ # Determine if we should be using HTTP/1.1 or HTTP/2
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+
+ # Create the HTTP/1.1 or HTTP/2 connection
+ if http2_negotiated or (
+ self._http2 and not self._http1
+ ): # pragma: nocover
+ from .http2 import AsyncHTTP2Connection
+
+ self._connection = AsyncHTTP2Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = AsyncHTTP11Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ except Exception as exc:
+ self._connect_failed = True
+ raise exc
+ elif not self._connection.is_available(): # pragma: nocover
+ raise ConnectionNotAvailable()
+
+ return await self._connection.handle_async_request(request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ async def aclose(self) -> None:
+ if self._connection is not None:
+ await self._connection.aclose()
+
+ def is_available(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ # If HTTP/2 support is enabled, and the resulting connection could
+ # end up as HTTP/2 then we should indicate the connection as being
+ # available to service multiple requests.
+ return (
+ self._http2
+ and (self._remote_origin.scheme == b"https" or not self._http1)
+ and not self._connect_failed
+ )
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.is_closed()
+
+ def info(self) -> str:
+ if self._connection is None: # pragma: nocover
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
+ return self._connection.info()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__init__.py b/lib/python3.12/site-packages/httpcore/_backends/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..281559c6e8da78758f31bc53db5438b2b2bc7b9b
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2a13c6f891643ca4ad93e810e02ab78802b15ea1
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e7425577b986cb1c1e58c1860c9f68da32304443
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..147e1e9cc5d1fd4b3c7eadc0c4457c46c46e540d
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bf9ee42e44f92fe137d4576d4a6c6bd73ddc1210
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bd06d7a1acd877109a07601a047ec02486c15306
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7d83bd98f4988331ddb929ff8f2e166f3099fd09
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_backends/anyio.py b/lib/python3.12/site-packages/httpcore/_backends/anyio.py
new file mode 100644
index 0000000000000000000000000000000000000000..a140095e1b8de022f321a41c0125e0e5febc0749
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/anyio.py
@@ -0,0 +1,146 @@
+from __future__ import annotations
+
+import ssl
+import typing
+
+import anyio
+
+from .._exceptions import (
+ ConnectError,
+ ConnectTimeout,
+ ReadError,
+ ReadTimeout,
+ WriteError,
+ WriteTimeout,
+ map_exceptions,
+)
+from .._utils import is_socket_readable
+from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
+
+
+class AnyIOStream(AsyncNetworkStream):
+ def __init__(self, stream: anyio.abc.ByteStream) -> None:
+ self._stream = stream
+
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ exc_map = {
+ TimeoutError: ReadTimeout,
+ anyio.BrokenResourceError: ReadError,
+ anyio.ClosedResourceError: ReadError,
+ anyio.EndOfStream: ReadError,
+ }
+ with map_exceptions(exc_map):
+ with anyio.fail_after(timeout):
+ try:
+ return await self._stream.receive(max_bytes=max_bytes)
+ except anyio.EndOfStream: # pragma: nocover
+ return b""
+
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ if not buffer:
+ return
+
+ exc_map = {
+ TimeoutError: WriteTimeout,
+ anyio.BrokenResourceError: WriteError,
+ anyio.ClosedResourceError: WriteError,
+ }
+ with map_exceptions(exc_map):
+ with anyio.fail_after(timeout):
+ await self._stream.send(item=buffer)
+
+ async def aclose(self) -> None:
+ await self._stream.aclose()
+
+ async def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> AsyncNetworkStream:
+ exc_map = {
+ TimeoutError: ConnectTimeout,
+ anyio.BrokenResourceError: ConnectError,
+ anyio.EndOfStream: ConnectError,
+ ssl.SSLError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ try:
+ with anyio.fail_after(timeout):
+ ssl_stream = await anyio.streams.tls.TLSStream.wrap(
+ self._stream,
+ ssl_context=ssl_context,
+ hostname=server_hostname,
+ standard_compatible=False,
+ server_side=False,
+ )
+ except Exception as exc: # pragma: nocover
+ await self.aclose()
+ raise exc
+ return AnyIOStream(ssl_stream)
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ if info == "ssl_object":
+ return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None)
+ if info == "client_addr":
+ return self._stream.extra(anyio.abc.SocketAttribute.local_address, None)
+ if info == "server_addr":
+ return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None)
+ if info == "socket":
+ return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
+ if info == "is_readable":
+ sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
+ return is_socket_readable(sock)
+ return None
+
+
+class AnyIOBackend(AsyncNetworkBackend):
+ async def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream: # pragma: nocover
+ if socket_options is None:
+ socket_options = []
+ exc_map = {
+ TimeoutError: ConnectTimeout,
+ OSError: ConnectError,
+ anyio.BrokenResourceError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ with anyio.fail_after(timeout):
+ stream: anyio.abc.ByteStream = await anyio.connect_tcp(
+ remote_host=host,
+ remote_port=port,
+ local_host=local_address,
+ )
+ # By default TCP sockets opened in `asyncio` include TCP_NODELAY.
+ for option in socket_options:
+ stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
+ return AnyIOStream(stream)
+
+ async def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream: # pragma: nocover
+ if socket_options is None:
+ socket_options = []
+ exc_map = {
+ TimeoutError: ConnectTimeout,
+ OSError: ConnectError,
+ anyio.BrokenResourceError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ with anyio.fail_after(timeout):
+ stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
+ for option in socket_options:
+ stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
+ return AnyIOStream(stream)
+
+ async def sleep(self, seconds: float) -> None:
+ await anyio.sleep(seconds) # pragma: nocover
diff --git a/lib/python3.12/site-packages/httpcore/_backends/auto.py b/lib/python3.12/site-packages/httpcore/_backends/auto.py
new file mode 100644
index 0000000000000000000000000000000000000000..49f0e698c97ad5623f376d8182675352e21c2c3c
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/auto.py
@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+import typing
+
+from .._synchronization import current_async_library
+from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
+
+
+class AutoBackend(AsyncNetworkBackend):
+ async def _init_backend(self) -> None:
+ if not (hasattr(self, "_backend")):
+ backend = current_async_library()
+ if backend == "trio":
+ from .trio import TrioBackend
+
+ self._backend: AsyncNetworkBackend = TrioBackend()
+ else:
+ from .anyio import AnyIOBackend
+
+ self._backend = AnyIOBackend()
+
+ async def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ await self._init_backend()
+ return await self._backend.connect_tcp(
+ host,
+ port,
+ timeout=timeout,
+ local_address=local_address,
+ socket_options=socket_options,
+ )
+
+ async def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream: # pragma: nocover
+ await self._init_backend()
+ return await self._backend.connect_unix_socket(
+ path, timeout=timeout, socket_options=socket_options
+ )
+
+ async def sleep(self, seconds: float) -> None: # pragma: nocover
+ await self._init_backend()
+ return await self._backend.sleep(seconds)
diff --git a/lib/python3.12/site-packages/httpcore/_backends/base.py b/lib/python3.12/site-packages/httpcore/_backends/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf55c8b10eb543872550be863206fe2f760d0d8d
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/base.py
@@ -0,0 +1,101 @@
+from __future__ import annotations
+
+import ssl
+import time
+import typing
+
+SOCKET_OPTION = typing.Union[
+ typing.Tuple[int, int, int],
+ typing.Tuple[int, int, typing.Union[bytes, bytearray]],
+ typing.Tuple[int, int, None, int],
+]
+
+
+class NetworkStream:
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ raise NotImplementedError() # pragma: nocover
+
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ def close(self) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> NetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return None # pragma: nocover
+
+
+class NetworkBackend:
+ def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ def sleep(self, seconds: float) -> None:
+ time.sleep(seconds) # pragma: nocover
+
+
+class AsyncNetworkStream:
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ raise NotImplementedError() # pragma: nocover
+
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ async def aclose(self) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ async def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> AsyncNetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return None # pragma: nocover
+
+
+class AsyncNetworkBackend:
+ async def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ async def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ raise NotImplementedError() # pragma: nocover
+
+ async def sleep(self, seconds: float) -> None:
+ raise NotImplementedError() # pragma: nocover
diff --git a/lib/python3.12/site-packages/httpcore/_backends/mock.py b/lib/python3.12/site-packages/httpcore/_backends/mock.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b6edca03d4d4b34f355fd53e49d4b4c699c972c
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/mock.py
@@ -0,0 +1,143 @@
+from __future__ import annotations
+
+import ssl
+import typing
+
+from .._exceptions import ReadError
+from .base import (
+ SOCKET_OPTION,
+ AsyncNetworkBackend,
+ AsyncNetworkStream,
+ NetworkBackend,
+ NetworkStream,
+)
+
+
+class MockSSLObject:
+ def __init__(self, http2: bool):
+ self._http2 = http2
+
+ def selected_alpn_protocol(self) -> str:
+ return "h2" if self._http2 else "http/1.1"
+
+
+class MockStream(NetworkStream):
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
+ self._buffer = buffer
+ self._http2 = http2
+ self._closed = False
+
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ if self._closed:
+ raise ReadError("Connection closed")
+ if not self._buffer:
+ return b""
+ return self._buffer.pop(0)
+
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ pass
+
+ def close(self) -> None:
+ self._closed = True
+
+ def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> NetworkStream:
+ return self
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return MockSSLObject(http2=self._http2) if info == "ssl_object" else None
+
+ def __repr__(self) -> str:
+ return ""
+
+
+class MockBackend(NetworkBackend):
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
+ self._buffer = buffer
+ self._http2 = http2
+
+ def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream:
+ return MockStream(list(self._buffer), http2=self._http2)
+
+ def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream:
+ return MockStream(list(self._buffer), http2=self._http2)
+
+ def sleep(self, seconds: float) -> None:
+ pass
+
+
+class AsyncMockStream(AsyncNetworkStream):
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
+ self._buffer = buffer
+ self._http2 = http2
+ self._closed = False
+
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ if self._closed:
+ raise ReadError("Connection closed")
+ if not self._buffer:
+ return b""
+ return self._buffer.pop(0)
+
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ pass
+
+ async def aclose(self) -> None:
+ self._closed = True
+
+ async def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> AsyncNetworkStream:
+ return self
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return MockSSLObject(http2=self._http2) if info == "ssl_object" else None
+
+ def __repr__(self) -> str:
+ return ""
+
+
+class AsyncMockBackend(AsyncNetworkBackend):
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
+ self._buffer = buffer
+ self._http2 = http2
+
+ async def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ return AsyncMockStream(list(self._buffer), http2=self._http2)
+
+ async def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ return AsyncMockStream(list(self._buffer), http2=self._http2)
+
+ async def sleep(self, seconds: float) -> None:
+ pass
diff --git a/lib/python3.12/site-packages/httpcore/_backends/sync.py b/lib/python3.12/site-packages/httpcore/_backends/sync.py
new file mode 100644
index 0000000000000000000000000000000000000000..4018a09c6fb1e0ef1b03ab8d84b13ebef4031f7c
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/sync.py
@@ -0,0 +1,241 @@
+from __future__ import annotations
+
+import functools
+import socket
+import ssl
+import sys
+import typing
+
+from .._exceptions import (
+ ConnectError,
+ ConnectTimeout,
+ ExceptionMapping,
+ ReadError,
+ ReadTimeout,
+ WriteError,
+ WriteTimeout,
+ map_exceptions,
+)
+from .._utils import is_socket_readable
+from .base import SOCKET_OPTION, NetworkBackend, NetworkStream
+
+
+class TLSinTLSStream(NetworkStream): # pragma: no cover
+ """
+ Because the standard `SSLContext.wrap_socket` method does
+ not work for `SSLSocket` objects, we need this class
+ to implement TLS stream using an underlying `SSLObject`
+ instance in order to support TLS on top of TLS.
+ """
+
+ # Defined in RFC 8449
+ TLS_RECORD_SIZE = 16384
+
+ def __init__(
+ self,
+ sock: socket.socket,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ):
+ self._sock = sock
+ self._incoming = ssl.MemoryBIO()
+ self._outgoing = ssl.MemoryBIO()
+
+ self.ssl_obj = ssl_context.wrap_bio(
+ incoming=self._incoming,
+ outgoing=self._outgoing,
+ server_hostname=server_hostname,
+ )
+
+ self._sock.settimeout(timeout)
+ self._perform_io(self.ssl_obj.do_handshake)
+
+ def _perform_io(
+ self,
+ func: typing.Callable[..., typing.Any],
+ ) -> typing.Any:
+ ret = None
+
+ while True:
+ errno = None
+ try:
+ ret = func()
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
+ errno = e.errno
+
+ self._sock.sendall(self._outgoing.read())
+
+ if errno == ssl.SSL_ERROR_WANT_READ:
+ buf = self._sock.recv(self.TLS_RECORD_SIZE)
+
+ if buf:
+ self._incoming.write(buf)
+ else:
+ self._incoming.write_eof()
+ if errno is None:
+ return ret
+
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
+ with map_exceptions(exc_map):
+ self._sock.settimeout(timeout)
+ return typing.cast(
+ bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes))
+ )
+
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
+ with map_exceptions(exc_map):
+ self._sock.settimeout(timeout)
+ while buffer:
+ nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer))
+ buffer = buffer[nsent:]
+
+ def close(self) -> None:
+ self._sock.close()
+
+ def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> NetworkStream:
+ raise NotImplementedError()
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ if info == "ssl_object":
+ return self.ssl_obj
+ if info == "client_addr":
+ return self._sock.getsockname()
+ if info == "server_addr":
+ return self._sock.getpeername()
+ if info == "socket":
+ return self._sock
+ if info == "is_readable":
+ return is_socket_readable(self._sock)
+ return None
+
+
+class SyncStream(NetworkStream):
+ def __init__(self, sock: socket.socket) -> None:
+ self._sock = sock
+
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
+ with map_exceptions(exc_map):
+ self._sock.settimeout(timeout)
+ return self._sock.recv(max_bytes)
+
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ if not buffer:
+ return
+
+ exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
+ with map_exceptions(exc_map):
+ while buffer:
+ self._sock.settimeout(timeout)
+ n = self._sock.send(buffer)
+ buffer = buffer[n:]
+
+ def close(self) -> None:
+ self._sock.close()
+
+ def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> NetworkStream:
+ exc_map: ExceptionMapping = {
+ socket.timeout: ConnectTimeout,
+ OSError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ try:
+ if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover
+ # If the underlying socket has already been upgraded
+ # to the TLS layer (i.e. is an instance of SSLSocket),
+ # we need some additional smarts to support TLS-in-TLS.
+ return TLSinTLSStream(
+ self._sock, ssl_context, server_hostname, timeout
+ )
+ else:
+ self._sock.settimeout(timeout)
+ sock = ssl_context.wrap_socket(
+ self._sock, server_hostname=server_hostname
+ )
+ except Exception as exc: # pragma: nocover
+ self.close()
+ raise exc
+ return SyncStream(sock)
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ if info == "ssl_object" and isinstance(self._sock, ssl.SSLSocket):
+ return self._sock._sslobj # type: ignore
+ if info == "client_addr":
+ return self._sock.getsockname()
+ if info == "server_addr":
+ return self._sock.getpeername()
+ if info == "socket":
+ return self._sock
+ if info == "is_readable":
+ return is_socket_readable(self._sock)
+ return None
+
+
+class SyncBackend(NetworkBackend):
+ def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream:
+ # Note that we automatically include `TCP_NODELAY`
+ # in addition to any other custom socket options.
+ if socket_options is None:
+ socket_options = [] # pragma: no cover
+ address = (host, port)
+ source_address = None if local_address is None else (local_address, 0)
+ exc_map: ExceptionMapping = {
+ socket.timeout: ConnectTimeout,
+ OSError: ConnectError,
+ }
+
+ with map_exceptions(exc_map):
+ sock = socket.create_connection(
+ address,
+ timeout,
+ source_address=source_address,
+ )
+ for option in socket_options:
+ sock.setsockopt(*option) # pragma: no cover
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ return SyncStream(sock)
+
+ def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> NetworkStream: # pragma: nocover
+ if sys.platform == "win32":
+ raise RuntimeError(
+ "Attempted to connect to a UNIX socket on a Windows system."
+ )
+ if socket_options is None:
+ socket_options = []
+
+ exc_map: ExceptionMapping = {
+ socket.timeout: ConnectTimeout,
+ OSError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ for option in socket_options:
+ sock.setsockopt(*option)
+ sock.settimeout(timeout)
+ sock.connect(path)
+ return SyncStream(sock)
diff --git a/lib/python3.12/site-packages/httpcore/_backends/trio.py b/lib/python3.12/site-packages/httpcore/_backends/trio.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f53f5f2a025e01e9949e2530bd9ca6928859251
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_backends/trio.py
@@ -0,0 +1,159 @@
+from __future__ import annotations
+
+import ssl
+import typing
+
+import trio
+
+from .._exceptions import (
+ ConnectError,
+ ConnectTimeout,
+ ExceptionMapping,
+ ReadError,
+ ReadTimeout,
+ WriteError,
+ WriteTimeout,
+ map_exceptions,
+)
+from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
+
+
+class TrioStream(AsyncNetworkStream):
+ def __init__(self, stream: trio.abc.Stream) -> None:
+ self._stream = stream
+
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ exc_map: ExceptionMapping = {
+ trio.TooSlowError: ReadTimeout,
+ trio.BrokenResourceError: ReadError,
+ trio.ClosedResourceError: ReadError,
+ }
+ with map_exceptions(exc_map):
+ with trio.fail_after(timeout_or_inf):
+ data: bytes = await self._stream.receive_some(max_bytes=max_bytes)
+ return data
+
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ if not buffer:
+ return
+
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ exc_map: ExceptionMapping = {
+ trio.TooSlowError: WriteTimeout,
+ trio.BrokenResourceError: WriteError,
+ trio.ClosedResourceError: WriteError,
+ }
+ with map_exceptions(exc_map):
+ with trio.fail_after(timeout_or_inf):
+ await self._stream.send_all(data=buffer)
+
+ async def aclose(self) -> None:
+ await self._stream.aclose()
+
+ async def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> AsyncNetworkStream:
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ exc_map: ExceptionMapping = {
+ trio.TooSlowError: ConnectTimeout,
+ trio.BrokenResourceError: ConnectError,
+ }
+ ssl_stream = trio.SSLStream(
+ self._stream,
+ ssl_context=ssl_context,
+ server_hostname=server_hostname,
+ https_compatible=True,
+ server_side=False,
+ )
+ with map_exceptions(exc_map):
+ try:
+ with trio.fail_after(timeout_or_inf):
+ await ssl_stream.do_handshake()
+ except Exception as exc: # pragma: nocover
+ await self.aclose()
+ raise exc
+ return TrioStream(ssl_stream)
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ if info == "ssl_object" and isinstance(self._stream, trio.SSLStream):
+ # Type checkers cannot see `_ssl_object` attribute because trio._ssl.SSLStream uses __getattr__/__setattr__.
+ # Tracked at https://github.com/python-trio/trio/issues/542
+ return self._stream._ssl_object # type: ignore[attr-defined]
+ if info == "client_addr":
+ return self._get_socket_stream().socket.getsockname()
+ if info == "server_addr":
+ return self._get_socket_stream().socket.getpeername()
+ if info == "socket":
+ stream = self._stream
+ while isinstance(stream, trio.SSLStream):
+ stream = stream.transport_stream
+ assert isinstance(stream, trio.SocketStream)
+ return stream.socket
+ if info == "is_readable":
+ socket = self.get_extra_info("socket")
+ return socket.is_readable()
+ return None
+
+ def _get_socket_stream(self) -> trio.SocketStream:
+ stream = self._stream
+ while isinstance(stream, trio.SSLStream):
+ stream = stream.transport_stream
+ assert isinstance(stream, trio.SocketStream)
+ return stream
+
+
+class TrioBackend(AsyncNetworkBackend):
+ async def connect_tcp(
+ self,
+ host: str,
+ port: int,
+ timeout: float | None = None,
+ local_address: str | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream:
+ # By default for TCP sockets, trio enables TCP_NODELAY.
+ # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream
+ if socket_options is None:
+ socket_options = [] # pragma: no cover
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ exc_map: ExceptionMapping = {
+ trio.TooSlowError: ConnectTimeout,
+ trio.BrokenResourceError: ConnectError,
+ OSError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ with trio.fail_after(timeout_or_inf):
+ stream: trio.abc.Stream = await trio.open_tcp_stream(
+ host=host, port=port, local_address=local_address
+ )
+ for option in socket_options:
+ stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
+ return TrioStream(stream)
+
+ async def connect_unix_socket(
+ self,
+ path: str,
+ timeout: float | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> AsyncNetworkStream: # pragma: nocover
+ if socket_options is None:
+ socket_options = []
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ exc_map: ExceptionMapping = {
+ trio.TooSlowError: ConnectTimeout,
+ trio.BrokenResourceError: ConnectError,
+ OSError: ConnectError,
+ }
+ with map_exceptions(exc_map):
+ with trio.fail_after(timeout_or_inf):
+ stream: trio.abc.Stream = await trio.open_unix_socket(path)
+ for option in socket_options:
+ stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
+ return TrioStream(stream)
+
+ async def sleep(self, seconds: float) -> None:
+ await trio.sleep(seconds) # pragma: nocover
diff --git a/lib/python3.12/site-packages/httpcore/_exceptions.py b/lib/python3.12/site-packages/httpcore/_exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc28d44f55bdc4b872951a74780469a3999d9ab4
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_exceptions.py
@@ -0,0 +1,81 @@
+import contextlib
+import typing
+
+ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]]
+
+
+@contextlib.contextmanager
+def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]:
+ try:
+ yield
+ except Exception as exc: # noqa: PIE786
+ for from_exc, to_exc in map.items():
+ if isinstance(exc, from_exc):
+ raise to_exc(exc) from exc
+ raise # pragma: nocover
+
+
+class ConnectionNotAvailable(Exception):
+ pass
+
+
+class ProxyError(Exception):
+ pass
+
+
+class UnsupportedProtocol(Exception):
+ pass
+
+
+class ProtocolError(Exception):
+ pass
+
+
+class RemoteProtocolError(ProtocolError):
+ pass
+
+
+class LocalProtocolError(ProtocolError):
+ pass
+
+
+# Timeout errors
+
+
+class TimeoutException(Exception):
+ pass
+
+
+class PoolTimeout(TimeoutException):
+ pass
+
+
+class ConnectTimeout(TimeoutException):
+ pass
+
+
+class ReadTimeout(TimeoutException):
+ pass
+
+
+class WriteTimeout(TimeoutException):
+ pass
+
+
+# Network errors
+
+
+class NetworkError(Exception):
+ pass
+
+
+class ConnectError(NetworkError):
+ pass
+
+
+class ReadError(NetworkError):
+ pass
+
+
+class WriteError(NetworkError):
+ pass
diff --git a/lib/python3.12/site-packages/httpcore/_models.py b/lib/python3.12/site-packages/httpcore/_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a65f13347d6621289a166d08123cbc8e1ad0157
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_models.py
@@ -0,0 +1,516 @@
+from __future__ import annotations
+
+import base64
+import ssl
+import typing
+import urllib.parse
+
+# Functions for typechecking...
+
+
+ByteOrStr = typing.Union[bytes, str]
+HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
+HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
+HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None]
+
+Extensions = typing.MutableMapping[str, typing.Any]
+
+
+def enforce_bytes(value: bytes | str, *, name: str) -> bytes:
+ """
+ Any arguments that are ultimately represented as bytes can be specified
+ either as bytes or as strings.
+
+ However we enforce that any string arguments must only contain characters in
+ the plain ASCII range. chr(0)...chr(127). If you need to use characters
+ outside that range then be precise, and use a byte-wise argument.
+ """
+ if isinstance(value, str):
+ try:
+ return value.encode("ascii")
+ except UnicodeEncodeError:
+ raise TypeError(f"{name} strings may not include unicode characters.")
+ elif isinstance(value, bytes):
+ return value
+
+ seen_type = type(value).__name__
+ raise TypeError(f"{name} must be bytes or str, but got {seen_type}.")
+
+
+def enforce_url(value: URL | bytes | str, *, name: str) -> URL:
+ """
+ Type check for URL parameters.
+ """
+ if isinstance(value, (bytes, str)):
+ return URL(value)
+ elif isinstance(value, URL):
+ return value
+
+ seen_type = type(value).__name__
+ raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.")
+
+
+def enforce_headers(
+ value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str
+) -> list[tuple[bytes, bytes]]:
+ """
+ Convienence function that ensure all items in request or response headers
+ are either bytes or strings in the plain ASCII range.
+ """
+ if value is None:
+ return []
+ elif isinstance(value, typing.Mapping):
+ return [
+ (
+ enforce_bytes(k, name="header name"),
+ enforce_bytes(v, name="header value"),
+ )
+ for k, v in value.items()
+ ]
+ elif isinstance(value, typing.Sequence):
+ return [
+ (
+ enforce_bytes(k, name="header name"),
+ enforce_bytes(v, name="header value"),
+ )
+ for k, v in value
+ ]
+
+ seen_type = type(value).__name__
+ raise TypeError(
+ f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}."
+ )
+
+
+def enforce_stream(
+ value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None,
+ *,
+ name: str,
+) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]:
+ if value is None:
+ return ByteStream(b"")
+ elif isinstance(value, bytes):
+ return ByteStream(value)
+ return value
+
+
+# * https://tools.ietf.org/html/rfc3986#section-3.2.3
+# * https://url.spec.whatwg.org/#url-miscellaneous
+# * https://url.spec.whatwg.org/#scheme-state
+DEFAULT_PORTS = {
+ b"ftp": 21,
+ b"http": 80,
+ b"https": 443,
+ b"ws": 80,
+ b"wss": 443,
+}
+
+
+def include_request_headers(
+ headers: list[tuple[bytes, bytes]],
+ *,
+ url: "URL",
+ content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes],
+) -> list[tuple[bytes, bytes]]:
+ headers_set = set(k.lower() for k, v in headers)
+
+ if b"host" not in headers_set:
+ default_port = DEFAULT_PORTS.get(url.scheme)
+ if url.port is None or url.port == default_port:
+ header_value = url.host
+ else:
+ header_value = b"%b:%d" % (url.host, url.port)
+ headers = [(b"Host", header_value)] + headers
+
+ if (
+ content is not None
+ and b"content-length" not in headers_set
+ and b"transfer-encoding" not in headers_set
+ ):
+ if isinstance(content, bytes):
+ content_length = str(len(content)).encode("ascii")
+ headers += [(b"Content-Length", content_length)]
+ else:
+ headers += [(b"Transfer-Encoding", b"chunked")] # pragma: nocover
+
+ return headers
+
+
+# Interfaces for byte streams...
+
+
+class ByteStream:
+ """
+ A container for non-streaming content, and that supports both sync and async
+ stream iteration.
+ """
+
+ def __init__(self, content: bytes) -> None:
+ self._content = content
+
+ def __iter__(self) -> typing.Iterator[bytes]:
+ yield self._content
+
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ yield self._content
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{len(self._content)} bytes]>"
+
+
+class Origin:
+ def __init__(self, scheme: bytes, host: bytes, port: int) -> None:
+ self.scheme = scheme
+ self.host = host
+ self.port = port
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return (
+ isinstance(other, Origin)
+ and self.scheme == other.scheme
+ and self.host == other.host
+ and self.port == other.port
+ )
+
+ def __str__(self) -> str:
+ scheme = self.scheme.decode("ascii")
+ host = self.host.decode("ascii")
+ port = str(self.port)
+ return f"{scheme}://{host}:{port}"
+
+
+class URL:
+ """
+ Represents the URL against which an HTTP request may be made.
+
+ The URL may either be specified as a plain string, for convienence:
+
+ ```python
+ url = httpcore.URL("https://www.example.com/")
+ ```
+
+ Or be constructed with explicitily pre-parsed components:
+
+ ```python
+ url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')
+ ```
+
+ Using this second more explicit style allows integrations that are using
+ `httpcore` to pass through URLs that have already been parsed in order to use
+ libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures
+ that URL parsing is treated identically at both the networking level and at any
+ higher layers of abstraction.
+
+ The four components are important here, as they allow the URL to be precisely
+ specified in a pre-parsed format. They also allow certain types of request to
+ be created that could not otherwise be expressed.
+
+ For example, an HTTP request to `http://www.example.com/` forwarded via a proxy
+ at `http://localhost:8080`...
+
+ ```python
+ # Constructs an HTTP request with a complete URL as the target:
+ # GET https://www.example.com/ HTTP/1.1
+ url = httpcore.URL(
+ scheme=b'http',
+ host=b'localhost',
+ port=8080,
+ target=b'https://www.example.com/'
+ )
+ request = httpcore.Request(
+ method="GET",
+ url=url
+ )
+ ```
+
+ Another example is constructing an `OPTIONS *` request...
+
+ ```python
+ # Constructs an 'OPTIONS *' HTTP request:
+ # OPTIONS * HTTP/1.1
+ url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')
+ request = httpcore.Request(method="OPTIONS", url=url)
+ ```
+
+ This kind of request is not possible to formulate with a URL string,
+ because the `/` delimiter is always used to demark the target from the
+ host/port portion of the URL.
+
+ For convenience, string-like arguments may be specified either as strings or
+ as bytes. However, once a request is being issue over-the-wire, the URL
+ components are always ultimately required to be a bytewise representation.
+
+ In order to avoid any ambiguity over character encodings, when strings are used
+ as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`.
+ If you require a bytewise representation that is outside this range you must
+ handle the character encoding directly, and pass a bytes instance.
+ """
+
+ def __init__(
+ self,
+ url: bytes | str = "",
+ *,
+ scheme: bytes | str = b"",
+ host: bytes | str = b"",
+ port: int | None = None,
+ target: bytes | str = b"",
+ ) -> None:
+ """
+ Parameters:
+ url: The complete URL as a string or bytes.
+ scheme: The URL scheme as a string or bytes.
+ Typically either `"http"` or `"https"`.
+ host: The URL host as a string or bytes. Such as `"www.example.com"`.
+ port: The port to connect to. Either an integer or `None`.
+ target: The target of the HTTP request. Such as `"/items?search=red"`.
+ """
+ if url:
+ parsed = urllib.parse.urlparse(enforce_bytes(url, name="url"))
+ self.scheme = parsed.scheme
+ self.host = parsed.hostname or b""
+ self.port = parsed.port
+ self.target = (parsed.path or b"/") + (
+ b"?" + parsed.query if parsed.query else b""
+ )
+ else:
+ self.scheme = enforce_bytes(scheme, name="scheme")
+ self.host = enforce_bytes(host, name="host")
+ self.port = port
+ self.target = enforce_bytes(target, name="target")
+
+ @property
+ def origin(self) -> Origin:
+ default_port = {
+ b"http": 80,
+ b"https": 443,
+ b"ws": 80,
+ b"wss": 443,
+ b"socks5": 1080,
+ b"socks5h": 1080,
+ }[self.scheme]
+ return Origin(
+ scheme=self.scheme, host=self.host, port=self.port or default_port
+ )
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return (
+ isinstance(other, URL)
+ and other.scheme == self.scheme
+ and other.host == self.host
+ and other.port == self.port
+ and other.target == self.target
+ )
+
+ def __bytes__(self) -> bytes:
+ if self.port is None:
+ return b"%b://%b%b" % (self.scheme, self.host, self.target)
+ return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target)
+
+ def __repr__(self) -> str:
+ return (
+ f"{self.__class__.__name__}(scheme={self.scheme!r}, "
+ f"host={self.host!r}, port={self.port!r}, target={self.target!r})"
+ )
+
+
+class Request:
+ """
+ An HTTP request.
+ """
+
+ def __init__(
+ self,
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes
+ | typing.Iterable[bytes]
+ | typing.AsyncIterable[bytes]
+ | None = None,
+ extensions: Extensions | None = None,
+ ) -> None:
+ """
+ Parameters:
+ method: The HTTP request method, either as a string or bytes.
+ For example: `GET`.
+ url: The request URL, either as a `URL` instance, or as a string or bytes.
+ For example: `"https://www.example.com".`
+ headers: The HTTP request headers.
+ content: The content of the request body.
+ extensions: A dictionary of optional extra information included on
+ the request. Possible keys include `"timeout"`, and `"trace"`.
+ """
+ self.method: bytes = enforce_bytes(method, name="method")
+ self.url: URL = enforce_url(url, name="url")
+ self.headers: list[tuple[bytes, bytes]] = enforce_headers(
+ headers, name="headers"
+ )
+ self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
+ enforce_stream(content, name="content")
+ )
+ self.extensions = {} if extensions is None else extensions
+
+ if "target" in self.extensions:
+ self.url = URL(
+ scheme=self.url.scheme,
+ host=self.url.host,
+ port=self.url.port,
+ target=self.extensions["target"],
+ )
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.method!r}]>"
+
+
+class Response:
+ """
+ An HTTP response.
+ """
+
+ def __init__(
+ self,
+ status: int,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes
+ | typing.Iterable[bytes]
+ | typing.AsyncIterable[bytes]
+ | None = None,
+ extensions: Extensions | None = None,
+ ) -> None:
+ """
+ Parameters:
+ status: The HTTP status code of the response. For example `200`.
+ headers: The HTTP response headers.
+ content: The content of the response body.
+ extensions: A dictionary of optional extra information included on
+ the responseself.Possible keys include `"http_version"`,
+ `"reason_phrase"`, and `"network_stream"`.
+ """
+ self.status: int = status
+ self.headers: list[tuple[bytes, bytes]] = enforce_headers(
+ headers, name="headers"
+ )
+ self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
+ enforce_stream(content, name="content")
+ )
+ self.extensions = {} if extensions is None else extensions
+
+ self._stream_consumed = False
+
+ @property
+ def content(self) -> bytes:
+ if not hasattr(self, "_content"):
+ if isinstance(self.stream, typing.Iterable):
+ raise RuntimeError(
+ "Attempted to access 'response.content' on a streaming response. "
+ "Call 'response.read()' first."
+ )
+ else:
+ raise RuntimeError(
+ "Attempted to access 'response.content' on a streaming response. "
+ "Call 'await response.aread()' first."
+ )
+ return self._content
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.status}]>"
+
+ # Sync interface...
+
+ def read(self) -> bytes:
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to read an asynchronous response using 'response.read()'. "
+ "You should use 'await response.aread()' instead."
+ )
+ if not hasattr(self, "_content"):
+ self._content = b"".join([part for part in self.iter_stream()])
+ return self._content
+
+ def iter_stream(self) -> typing.Iterator[bytes]:
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to stream an asynchronous response using 'for ... in "
+ "response.iter_stream()'. "
+ "You should use 'async for ... in response.aiter_stream()' instead."
+ )
+ if self._stream_consumed:
+ raise RuntimeError(
+ "Attempted to call 'for ... in response.iter_stream()' more than once."
+ )
+ self._stream_consumed = True
+ for chunk in self.stream:
+ yield chunk
+
+ def close(self) -> None:
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to close an asynchronous response using 'response.close()'. "
+ "You should use 'await response.aclose()' instead."
+ )
+ if hasattr(self.stream, "close"):
+ self.stream.close()
+
+ # Async interface...
+
+ async def aread(self) -> bytes:
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to read an synchronous response using "
+ "'await response.aread()'. "
+ "You should use 'response.read()' instead."
+ )
+ if not hasattr(self, "_content"):
+ self._content = b"".join([part async for part in self.aiter_stream()])
+ return self._content
+
+ async def aiter_stream(self) -> typing.AsyncIterator[bytes]:
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to stream an synchronous response using 'async for ... in "
+ "response.aiter_stream()'. "
+ "You should use 'for ... in response.iter_stream()' instead."
+ )
+ if self._stream_consumed:
+ raise RuntimeError(
+ "Attempted to call 'async for ... in response.aiter_stream()' "
+ "more than once."
+ )
+ self._stream_consumed = True
+ async for chunk in self.stream:
+ yield chunk
+
+ async def aclose(self) -> None:
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
+ raise RuntimeError(
+ "Attempted to close a synchronous response using "
+ "'await response.aclose()'. "
+ "You should use 'response.close()' instead."
+ )
+ if hasattr(self.stream, "aclose"):
+ await self.stream.aclose()
+
+
+class Proxy:
+ def __init__(
+ self,
+ url: URL | bytes | str,
+ auth: tuple[bytes | str, bytes | str] | None = None,
+ headers: HeadersAsMapping | HeadersAsSequence | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ ):
+ self.url = enforce_url(url, name="url")
+ self.headers = enforce_headers(headers, name="headers")
+ self.ssl_context = ssl_context
+
+ if auth is not None:
+ username = enforce_bytes(auth[0], name="auth")
+ password = enforce_bytes(auth[1], name="auth")
+ userpass = username + b":" + password
+ authorization = b"Basic " + base64.b64encode(userpass)
+ self.auth: tuple[bytes, bytes] | None = (username, password)
+ self.headers = [(b"Proxy-Authorization", authorization)] + self.headers
+ else:
+ self.auth = None
diff --git a/lib/python3.12/site-packages/httpcore/_ssl.py b/lib/python3.12/site-packages/httpcore/_ssl.py
new file mode 100644
index 0000000000000000000000000000000000000000..c99c5a67945b8a3a3544d481e979c791ab45fe23
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_ssl.py
@@ -0,0 +1,9 @@
+import ssl
+
+import certifi
+
+
+def default_ssl_context() -> ssl.SSLContext:
+ context = ssl.create_default_context()
+ context.load_verify_locations(certifi.where())
+ return context
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__init__.py b/lib/python3.12/site-packages/httpcore/_sync/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b476d76d9a7ff45de8d18ec22d33d6af2982f92e
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/__init__.py
@@ -0,0 +1,39 @@
+from .connection import HTTPConnection
+from .connection_pool import ConnectionPool
+from .http11 import HTTP11Connection
+from .http_proxy import HTTPProxy
+from .interfaces import ConnectionInterface
+
+try:
+ from .http2 import HTTP2Connection
+except ImportError: # pragma: nocover
+
+ class HTTP2Connection: # type: ignore
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
+ raise RuntimeError(
+ "Attempted to use http2 support, but the `h2` package is not "
+ "installed. Use 'pip install httpcore[http2]'."
+ )
+
+
+try:
+ from .socks_proxy import SOCKSProxy
+except ImportError: # pragma: nocover
+
+ class SOCKSProxy: # type: ignore
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
+ raise RuntimeError(
+ "Attempted to use SOCKS support, but the `socksio` package is not "
+ "installed. Use 'pip install httpcore[socks]'."
+ )
+
+
+__all__ = [
+ "HTTPConnection",
+ "ConnectionPool",
+ "HTTPProxy",
+ "HTTP11Connection",
+ "HTTP2Connection",
+ "ConnectionInterface",
+ "SOCKSProxy",
+]
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59dea0cef0c5fb28a2b0452469e317dd274eb58d
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..05f8126245f371a80a975042d6cf105ed2fb5573
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d141d6f3ce59f09b9c317bb888b6d2e04d1a2837
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6f6476b631281cec51b7ee5779c7288dcaf4b383
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e7df1e3043824ab5d706475ae16c3cb360ac12ea
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1803f04887cfea6333beebcec76a1dc715c9b18d
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..004c5c5661206c8712219b7d0e10bfa8e4fcb1d9
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7720ea9f2d58ae9d0677e385cb9be845cdafcfc1
Binary files /dev/null and b/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpcore/_sync/connection.py b/lib/python3.12/site-packages/httpcore/_sync/connection.py
new file mode 100644
index 0000000000000000000000000000000000000000..363f8be819d2576ea65365e625dd1596ea40429a
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/connection.py
@@ -0,0 +1,222 @@
+from __future__ import annotations
+
+import itertools
+import logging
+import ssl
+import types
+import typing
+
+from .._backends.sync import SyncBackend
+from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream
+from .._exceptions import ConnectError, ConnectTimeout
+from .._models import Origin, Request, Response
+from .._ssl import default_ssl_context
+from .._synchronization import Lock
+from .._trace import Trace
+from .http11 import HTTP11Connection
+from .interfaces import ConnectionInterface
+
+RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc.
+
+
+logger = logging.getLogger("httpcore.connection")
+
+
+def exponential_backoff(factor: float) -> typing.Iterator[float]:
+ """
+ Generate a geometric sequence that has a ratio of 2 and starts with 0.
+
+ For example:
+ - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
+ - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
+ """
+ yield 0
+ for n in itertools.count():
+ yield factor * 2**n
+
+
+class HTTPConnection(ConnectionInterface):
+ def __init__(
+ self,
+ origin: Origin,
+ ssl_context: ssl.SSLContext | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: NetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ self._origin = origin
+ self._ssl_context = ssl_context
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._retries = retries
+ self._local_address = local_address
+ self._uds = uds
+
+ self._network_backend: NetworkBackend = (
+ SyncBackend() if network_backend is None else network_backend
+ )
+ self._connection: ConnectionInterface | None = None
+ self._connect_failed: bool = False
+ self._request_lock = Lock()
+ self._socket_options = socket_options
+
+ def handle_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
+ )
+
+ try:
+ with self._request_lock:
+ if self._connection is None:
+ stream = self._connect(request)
+
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+ if http2_negotiated or (self._http2 and not self._http1):
+ from .http2 import HTTP2Connection
+
+ self._connection = HTTP2Connection(
+ origin=self._origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = HTTP11Connection(
+ origin=self._origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ except BaseException as exc:
+ self._connect_failed = True
+ raise exc
+
+ return self._connection.handle_request(request)
+
+ def _connect(self, request: Request) -> NetworkStream:
+ timeouts = request.extensions.get("timeout", {})
+ sni_hostname = request.extensions.get("sni_hostname", None)
+ timeout = timeouts.get("connect", None)
+
+ retries_left = self._retries
+ delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)
+
+ while True:
+ try:
+ if self._uds is None:
+ kwargs = {
+ "host": self._origin.host.decode("ascii"),
+ "port": self._origin.port,
+ "local_address": self._local_address,
+ "timeout": timeout,
+ "socket_options": self._socket_options,
+ }
+ with Trace("connect_tcp", logger, request, kwargs) as trace:
+ stream = self._network_backend.connect_tcp(**kwargs)
+ trace.return_value = stream
+ else:
+ kwargs = {
+ "path": self._uds,
+ "timeout": timeout,
+ "socket_options": self._socket_options,
+ }
+ with Trace(
+ "connect_unix_socket", logger, request, kwargs
+ ) as trace:
+ stream = self._network_backend.connect_unix_socket(
+ **kwargs
+ )
+ trace.return_value = stream
+
+ if self._origin.scheme in (b"https", b"wss"):
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": sni_hostname
+ or self._origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = stream.start_tls(**kwargs)
+ trace.return_value = stream
+ return stream
+ except (ConnectError, ConnectTimeout):
+ if retries_left <= 0:
+ raise
+ retries_left -= 1
+ delay = next(delays)
+ with Trace("retry", logger, request, kwargs) as trace:
+ self._network_backend.sleep(delay)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ def close(self) -> None:
+ if self._connection is not None:
+ with Trace("close", logger, None, {}):
+ self._connection.close()
+
+ def is_available(self) -> bool:
+ if self._connection is None:
+ # If HTTP/2 support is enabled, and the resulting connection could
+ # end up as HTTP/2 then we should indicate the connection as being
+ # available to service multiple requests.
+ return (
+ self._http2
+ and (self._origin.scheme == b"https" or not self._http1)
+ and not self._connect_failed
+ )
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ if self._connection is None:
+ return self._connect_failed
+ return self._connection.is_closed()
+
+ def info(self) -> str:
+ if self._connection is None:
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
+ return self._connection.info()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ def __enter__(self) -> HTTPConnection:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self.close()
diff --git a/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py b/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ccfa53e597a29ee387f9d16f3af4f695ac0d33a
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py
@@ -0,0 +1,420 @@
+from __future__ import annotations
+
+import ssl
+import sys
+import types
+import typing
+
+from .._backends.sync import SyncBackend
+from .._backends.base import SOCKET_OPTION, NetworkBackend
+from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
+from .._models import Origin, Proxy, Request, Response
+from .._synchronization import Event, ShieldCancellation, ThreadLock
+from .connection import HTTPConnection
+from .interfaces import ConnectionInterface, RequestInterface
+
+
+class PoolRequest:
+ def __init__(self, request: Request) -> None:
+ self.request = request
+ self.connection: ConnectionInterface | None = None
+ self._connection_acquired = Event()
+
+ def assign_to_connection(self, connection: ConnectionInterface | None) -> None:
+ self.connection = connection
+ self._connection_acquired.set()
+
+ def clear_connection(self) -> None:
+ self.connection = None
+ self._connection_acquired = Event()
+
+ def wait_for_connection(
+ self, timeout: float | None = None
+ ) -> ConnectionInterface:
+ if self.connection is None:
+ self._connection_acquired.wait(timeout=timeout)
+ assert self.connection is not None
+ return self.connection
+
+ def is_queued(self) -> bool:
+ return self.connection is None
+
+
+class ConnectionPool(RequestInterface):
+ """
+ A connection pool for making HTTP requests.
+ """
+
+ def __init__(
+ self,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy: Proxy | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: NetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish a
+ connection.
+ local_address: Local address to connect from. Can also be used to connect
+ using a particular address family. Using `local_address="0.0.0.0"`
+ will connect using an `AF_INET` address (IPv4), while using
+ `local_address="::"` will connect using an `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ socket_options: Socket options that have to be included
+ in the TCP socket when the connection was established.
+ """
+ self._ssl_context = ssl_context
+ self._proxy = proxy
+ self._max_connections = (
+ sys.maxsize if max_connections is None else max_connections
+ )
+ self._max_keepalive_connections = (
+ sys.maxsize
+ if max_keepalive_connections is None
+ else max_keepalive_connections
+ )
+ self._max_keepalive_connections = min(
+ self._max_connections, self._max_keepalive_connections
+ )
+
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._retries = retries
+ self._local_address = local_address
+ self._uds = uds
+
+ self._network_backend = (
+ SyncBackend() if network_backend is None else network_backend
+ )
+ self._socket_options = socket_options
+
+ # The mutable state on a connection pool is the queue of incoming requests,
+ # and the set of connections that are servicing those requests.
+ self._connections: list[ConnectionInterface] = []
+ self._requests: list[PoolRequest] = []
+
+ # We only mutate the state of the connection pool within an 'optional_thread_lock'
+ # context. This holds a threading lock unless we're running in async mode,
+ # in which case it is a no-op.
+ self._optional_thread_lock = ThreadLock()
+
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
+ if self._proxy is not None:
+ if self._proxy.url.scheme in (b"socks5", b"socks5h"):
+ from .socks_proxy import Socks5Connection
+
+ return Socks5Connection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_auth=self._proxy.auth,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+ elif origin.scheme == b"http":
+ from .http_proxy import ForwardHTTPConnection
+
+ return ForwardHTTPConnection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_headers=self._proxy.headers,
+ proxy_ssl_context=self._proxy.ssl_context,
+ remote_origin=origin,
+ keepalive_expiry=self._keepalive_expiry,
+ network_backend=self._network_backend,
+ )
+ from .http_proxy import TunnelHTTPConnection
+
+ return TunnelHTTPConnection(
+ proxy_origin=self._proxy.url.origin,
+ proxy_headers=self._proxy.headers,
+ proxy_ssl_context=self._proxy.ssl_context,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+ return HTTPConnection(
+ origin=origin,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ retries=self._retries,
+ local_address=self._local_address,
+ uds=self._uds,
+ network_backend=self._network_backend,
+ socket_options=self._socket_options,
+ )
+
+ @property
+ def connections(self) -> list[ConnectionInterface]:
+ """
+ Return a list of the connections currently in the pool.
+
+ For example:
+
+ ```python
+ >>> pool.connections
+ [
+ ,
+ ,
+ ,
+ ]
+ ```
+ """
+ return list(self._connections)
+
+ def handle_request(self, request: Request) -> Response:
+ """
+ Send an HTTP request, and return an HTTP response.
+
+ This is the core implementation that is called into by `.request()` or `.stream()`.
+ """
+ scheme = request.url.scheme.decode()
+ if scheme == "":
+ raise UnsupportedProtocol(
+ "Request URL is missing an 'http://' or 'https://' protocol."
+ )
+ if scheme not in ("http", "https", "ws", "wss"):
+ raise UnsupportedProtocol(
+ f"Request URL has an unsupported protocol '{scheme}://'."
+ )
+
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("pool", None)
+
+ with self._optional_thread_lock:
+ # Add the incoming request to our request queue.
+ pool_request = PoolRequest(request)
+ self._requests.append(pool_request)
+
+ try:
+ while True:
+ with self._optional_thread_lock:
+ # Assign incoming requests to available connections,
+ # closing or creating new connections as required.
+ closing = self._assign_requests_to_connections()
+ self._close_connections(closing)
+
+ # Wait until this request has an assigned connection.
+ connection = pool_request.wait_for_connection(timeout=timeout)
+
+ try:
+ # Send the request on the assigned connection.
+ response = connection.handle_request(
+ pool_request.request
+ )
+ except ConnectionNotAvailable:
+ # In some cases a connection may initially be available to
+ # handle a request, but then become unavailable.
+ #
+ # In this case we clear the connection and try again.
+ pool_request.clear_connection()
+ else:
+ break # pragma: nocover
+
+ except BaseException as exc:
+ with self._optional_thread_lock:
+ # For any exception or cancellation we remove the request from
+ # the queue, and then re-assign requests to connections.
+ self._requests.remove(pool_request)
+ closing = self._assign_requests_to_connections()
+
+ self._close_connections(closing)
+ raise exc from None
+
+ # Return the response. Note that in this case we still have to manage
+ # the point at which the response is closed.
+ assert isinstance(response.stream, typing.Iterable)
+ return Response(
+ status=response.status,
+ headers=response.headers,
+ content=PoolByteStream(
+ stream=response.stream, pool_request=pool_request, pool=self
+ ),
+ extensions=response.extensions,
+ )
+
+ def _assign_requests_to_connections(self) -> list[ConnectionInterface]:
+ """
+ Manage the state of the connection pool, assigning incoming
+ requests to connections as available.
+
+ Called whenever a new request is added or removed from the pool.
+
+ Any closing connections are returned, allowing the I/O for closing
+ those connections to be handled seperately.
+ """
+ closing_connections = []
+
+ # First we handle cleaning up any connections that are closed,
+ # have expired their keep-alive, or surplus idle connections.
+ for connection in list(self._connections):
+ if connection.is_closed():
+ # log: "removing closed connection"
+ self._connections.remove(connection)
+ elif connection.has_expired():
+ # log: "closing expired connection"
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+ elif (
+ connection.is_idle()
+ and len([connection.is_idle() for connection in self._connections])
+ > self._max_keepalive_connections
+ ):
+ # log: "closing idle connection"
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+
+ # Assign queued requests to connections.
+ queued_requests = [request for request in self._requests if request.is_queued()]
+ for pool_request in queued_requests:
+ origin = pool_request.request.url.origin
+ available_connections = [
+ connection
+ for connection in self._connections
+ if connection.can_handle_request(origin) and connection.is_available()
+ ]
+ idle_connections = [
+ connection for connection in self._connections if connection.is_idle()
+ ]
+
+ # There are three cases for how we may be able to handle the request:
+ #
+ # 1. There is an existing connection that can handle the request.
+ # 2. We can create a new connection to handle the request.
+ # 3. We can close an idle connection and then create a new connection
+ # to handle the request.
+ if available_connections:
+ # log: "reusing existing connection"
+ connection = available_connections[0]
+ pool_request.assign_to_connection(connection)
+ elif len(self._connections) < self._max_connections:
+ # log: "creating new connection"
+ connection = self.create_connection(origin)
+ self._connections.append(connection)
+ pool_request.assign_to_connection(connection)
+ elif idle_connections:
+ # log: "closing idle connection"
+ connection = idle_connections[0]
+ self._connections.remove(connection)
+ closing_connections.append(connection)
+ # log: "creating new connection"
+ connection = self.create_connection(origin)
+ self._connections.append(connection)
+ pool_request.assign_to_connection(connection)
+
+ return closing_connections
+
+ def _close_connections(self, closing: list[ConnectionInterface]) -> None:
+ # Close connections which have been removed from the pool.
+ with ShieldCancellation():
+ for connection in closing:
+ connection.close()
+
+ def close(self) -> None:
+ # Explicitly close the connection pool.
+ # Clears all existing requests and connections.
+ with self._optional_thread_lock:
+ closing_connections = list(self._connections)
+ self._connections = []
+ self._close_connections(closing_connections)
+
+ def __enter__(self) -> ConnectionPool:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self.close()
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ with self._optional_thread_lock:
+ request_is_queued = [request.is_queued() for request in self._requests]
+ connection_is_idle = [
+ connection.is_idle() for connection in self._connections
+ ]
+
+ num_active_requests = request_is_queued.count(False)
+ num_queued_requests = request_is_queued.count(True)
+ num_active_connections = connection_is_idle.count(False)
+ num_idle_connections = connection_is_idle.count(True)
+
+ requests_info = (
+ f"Requests: {num_active_requests} active, {num_queued_requests} queued"
+ )
+ connection_info = (
+ f"Connections: {num_active_connections} active, {num_idle_connections} idle"
+ )
+
+ return f"<{class_name} [{requests_info} | {connection_info}]>"
+
+
+class PoolByteStream:
+ def __init__(
+ self,
+ stream: typing.Iterable[bytes],
+ pool_request: PoolRequest,
+ pool: ConnectionPool,
+ ) -> None:
+ self._stream = stream
+ self._pool_request = pool_request
+ self._pool = pool
+ self._closed = False
+
+ def __iter__(self) -> typing.Iterator[bytes]:
+ try:
+ for part in self._stream:
+ yield part
+ except BaseException as exc:
+ self.close()
+ raise exc from None
+
+ def close(self) -> None:
+ if not self._closed:
+ self._closed = True
+ with ShieldCancellation():
+ if hasattr(self._stream, "close"):
+ self._stream.close()
+
+ with self._pool._optional_thread_lock:
+ self._pool._requests.remove(self._pool_request)
+ closing = self._pool._assign_requests_to_connections()
+
+ self._pool._close_connections(closing)
diff --git a/lib/python3.12/site-packages/httpcore/_sync/http11.py b/lib/python3.12/site-packages/httpcore/_sync/http11.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebd3a97480c720d418acb1285a7b75da19b62c8c
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/http11.py
@@ -0,0 +1,379 @@
+from __future__ import annotations
+
+import enum
+import logging
+import ssl
+import time
+import types
+import typing
+
+import h11
+
+from .._backends.base import NetworkStream
+from .._exceptions import (
+ ConnectionNotAvailable,
+ LocalProtocolError,
+ RemoteProtocolError,
+ WriteError,
+ map_exceptions,
+)
+from .._models import Origin, Request, Response
+from .._synchronization import Lock, ShieldCancellation
+from .._trace import Trace
+from .interfaces import ConnectionInterface
+
+logger = logging.getLogger("httpcore.http11")
+
+
+# A subset of `h11.Event` types supported by `_send_event`
+H11SendEvent = typing.Union[
+ h11.Request,
+ h11.Data,
+ h11.EndOfMessage,
+]
+
+
+class HTTPConnectionState(enum.IntEnum):
+ NEW = 0
+ ACTIVE = 1
+ IDLE = 2
+ CLOSED = 3
+
+
+class HTTP11Connection(ConnectionInterface):
+ READ_NUM_BYTES = 64 * 1024
+ MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024
+
+ def __init__(
+ self,
+ origin: Origin,
+ stream: NetworkStream,
+ keepalive_expiry: float | None = None,
+ ) -> None:
+ self._origin = origin
+ self._network_stream = stream
+ self._keepalive_expiry: float | None = keepalive_expiry
+ self._expire_at: float | None = None
+ self._state = HTTPConnectionState.NEW
+ self._state_lock = Lock()
+ self._request_count = 0
+ self._h11_state = h11.Connection(
+ our_role=h11.CLIENT,
+ max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
+ )
+
+ def handle_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection "
+ f"to {self._origin}"
+ )
+
+ with self._state_lock:
+ if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
+ self._request_count += 1
+ self._state = HTTPConnectionState.ACTIVE
+ self._expire_at = None
+ else:
+ raise ConnectionNotAvailable()
+
+ try:
+ kwargs = {"request": request}
+ try:
+ with Trace(
+ "send_request_headers", logger, request, kwargs
+ ) as trace:
+ self._send_request_headers(**kwargs)
+ with Trace("send_request_body", logger, request, kwargs) as trace:
+ self._send_request_body(**kwargs)
+ except WriteError:
+ # If we get a write error while we're writing the request,
+ # then we supress this error and move on to attempting to
+ # read the response. Servers can sometimes close the request
+ # pre-emptively and then respond with a well formed HTTP
+ # error response.
+ pass
+
+ with Trace(
+ "receive_response_headers", logger, request, kwargs
+ ) as trace:
+ (
+ http_version,
+ status,
+ reason_phrase,
+ headers,
+ trailing_data,
+ ) = self._receive_response_headers(**kwargs)
+ trace.return_value = (
+ http_version,
+ status,
+ reason_phrase,
+ headers,
+ )
+
+ network_stream = self._network_stream
+
+ # CONNECT or Upgrade request
+ if (status == 101) or (
+ (request.method == b"CONNECT") and (200 <= status < 300)
+ ):
+ network_stream = HTTP11UpgradeStream(network_stream, trailing_data)
+
+ return Response(
+ status=status,
+ headers=headers,
+ content=HTTP11ConnectionByteStream(self, request),
+ extensions={
+ "http_version": http_version,
+ "reason_phrase": reason_phrase,
+ "network_stream": network_stream,
+ },
+ )
+ except BaseException as exc:
+ with ShieldCancellation():
+ with Trace("response_closed", logger, request) as trace:
+ self._response_closed()
+ raise exc
+
+ # Sending the request...
+
+ def _send_request_headers(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
+ event = h11.Request(
+ method=request.method,
+ target=request.url.target,
+ headers=request.headers,
+ )
+ self._send_event(event, timeout=timeout)
+
+ def _send_request_body(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ assert isinstance(request.stream, typing.Iterable)
+ for chunk in request.stream:
+ event = h11.Data(data=chunk)
+ self._send_event(event, timeout=timeout)
+
+ self._send_event(h11.EndOfMessage(), timeout=timeout)
+
+ def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
+ bytes_to_send = self._h11_state.send(event)
+ if bytes_to_send is not None:
+ self._network_stream.write(bytes_to_send, timeout=timeout)
+
+ # Receiving the response...
+
+ def _receive_response_headers(
+ self, request: Request
+ ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ while True:
+ event = self._receive_event(timeout=timeout)
+ if isinstance(event, h11.Response):
+ break
+ if (
+ isinstance(event, h11.InformationalResponse)
+ and event.status_code == 101
+ ):
+ break
+
+ http_version = b"HTTP/" + event.http_version
+
+ # h11 version 0.11+ supports a `raw_items` interface to get the
+ # raw header casing, rather than the enforced lowercase headers.
+ headers = event.headers.raw_items()
+
+ trailing_data, _ = self._h11_state.trailing_data
+
+ return http_version, event.status_code, event.reason, headers, trailing_data
+
+ def _receive_response_body(
+ self, request: Request
+ ) -> typing.Iterator[bytes]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ while True:
+ event = self._receive_event(timeout=timeout)
+ if isinstance(event, h11.Data):
+ yield bytes(event.data)
+ elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
+ break
+
+ def _receive_event(
+ self, timeout: float | None = None
+ ) -> h11.Event | type[h11.PAUSED]:
+ while True:
+ with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
+ event = self._h11_state.next_event()
+
+ if event is h11.NEED_DATA:
+ data = self._network_stream.read(
+ self.READ_NUM_BYTES, timeout=timeout
+ )
+
+ # If we feed this case through h11 we'll raise an exception like:
+ #
+ # httpcore.RemoteProtocolError: can't handle event type
+ # ConnectionClosed when role=SERVER and state=SEND_RESPONSE
+ #
+ # Which is accurate, but not very informative from an end-user
+ # perspective. Instead we handle this case distinctly and treat
+ # it as a ConnectError.
+ if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
+ msg = "Server disconnected without sending a response."
+ raise RemoteProtocolError(msg)
+
+ self._h11_state.receive_data(data)
+ else:
+ # mypy fails to narrow the type in the above if statement above
+ return event # type: ignore[return-value]
+
+ def _response_closed(self) -> None:
+ with self._state_lock:
+ if (
+ self._h11_state.our_state is h11.DONE
+ and self._h11_state.their_state is h11.DONE
+ ):
+ self._state = HTTPConnectionState.IDLE
+ self._h11_state.start_next_cycle()
+ if self._keepalive_expiry is not None:
+ now = time.monotonic()
+ self._expire_at = now + self._keepalive_expiry
+ else:
+ self.close()
+
+ # Once the connection is no longer required...
+
+ def close(self) -> None:
+ # Note that this method unilaterally closes the connection, and does
+ # not have any kind of locking in place around it.
+ self._state = HTTPConnectionState.CLOSED
+ self._network_stream.close()
+
+ # The ConnectionInterface methods provide information about the state of
+ # the connection, allowing for a connection pooling implementation to
+ # determine when to reuse and when to close the connection...
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ def is_available(self) -> bool:
+ # Note that HTTP/1.1 connections in the "NEW" state are not treated as
+ # being "available". The control flow which created the connection will
+ # be able to send an outgoing request, but the connection will not be
+ # acquired from the connection pool for any other request.
+ return self._state == HTTPConnectionState.IDLE
+
+ def has_expired(self) -> bool:
+ now = time.monotonic()
+ keepalive_expired = self._expire_at is not None and now > self._expire_at
+
+ # If the HTTP connection is idle but the socket is readable, then the
+ # only valid state is that the socket is about to return b"", indicating
+ # a server-initiated disconnect.
+ server_disconnected = (
+ self._state == HTTPConnectionState.IDLE
+ and self._network_stream.get_extra_info("is_readable")
+ )
+
+ return keepalive_expired or server_disconnected
+
+ def is_idle(self) -> bool:
+ return self._state == HTTPConnectionState.IDLE
+
+ def is_closed(self) -> bool:
+ return self._state == HTTPConnectionState.CLOSED
+
+ def info(self) -> str:
+ origin = str(self._origin)
+ return (
+ f"{origin!r}, HTTP/1.1, {self._state.name}, "
+ f"Request Count: {self._request_count}"
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ origin = str(self._origin)
+ return (
+ f"<{class_name} [{origin!r}, {self._state.name}, "
+ f"Request Count: {self._request_count}]>"
+ )
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ def __enter__(self) -> HTTP11Connection:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self.close()
+
+
+class HTTP11ConnectionByteStream:
+ def __init__(self, connection: HTTP11Connection, request: Request) -> None:
+ self._connection = connection
+ self._request = request
+ self._closed = False
+
+ def __iter__(self) -> typing.Iterator[bytes]:
+ kwargs = {"request": self._request}
+ try:
+ with Trace("receive_response_body", logger, self._request, kwargs):
+ for chunk in self._connection._receive_response_body(**kwargs):
+ yield chunk
+ except BaseException as exc:
+ # If we get an exception while streaming the response,
+ # we want to close the response (and possibly the connection)
+ # before raising that exception.
+ with ShieldCancellation():
+ self.close()
+ raise exc
+
+ def close(self) -> None:
+ if not self._closed:
+ self._closed = True
+ with Trace("response_closed", logger, self._request):
+ self._connection._response_closed()
+
+
+class HTTP11UpgradeStream(NetworkStream):
+ def __init__(self, stream: NetworkStream, leading_data: bytes) -> None:
+ self._stream = stream
+ self._leading_data = leading_data
+
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
+ if self._leading_data:
+ buffer = self._leading_data[:max_bytes]
+ self._leading_data = self._leading_data[max_bytes:]
+ return buffer
+ else:
+ return self._stream.read(max_bytes, timeout)
+
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
+ self._stream.write(buffer, timeout)
+
+ def close(self) -> None:
+ self._stream.close()
+
+ def start_tls(
+ self,
+ ssl_context: ssl.SSLContext,
+ server_hostname: str | None = None,
+ timeout: float | None = None,
+ ) -> NetworkStream:
+ return self._stream.start_tls(ssl_context, server_hostname, timeout)
+
+ def get_extra_info(self, info: str) -> typing.Any:
+ return self._stream.get_extra_info(info)
diff --git a/lib/python3.12/site-packages/httpcore/_sync/http2.py b/lib/python3.12/site-packages/httpcore/_sync/http2.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddcc189001c50c37c6a03810dc21d955df919f10
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/http2.py
@@ -0,0 +1,592 @@
+from __future__ import annotations
+
+import enum
+import logging
+import time
+import types
+import typing
+
+import h2.config
+import h2.connection
+import h2.events
+import h2.exceptions
+import h2.settings
+
+from .._backends.base import NetworkStream
+from .._exceptions import (
+ ConnectionNotAvailable,
+ LocalProtocolError,
+ RemoteProtocolError,
+)
+from .._models import Origin, Request, Response
+from .._synchronization import Lock, Semaphore, ShieldCancellation
+from .._trace import Trace
+from .interfaces import ConnectionInterface
+
+logger = logging.getLogger("httpcore.http2")
+
+
+def has_body_headers(request: Request) -> bool:
+ return any(
+ k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
+ for k, v in request.headers
+ )
+
+
+class HTTPConnectionState(enum.IntEnum):
+ ACTIVE = 1
+ IDLE = 2
+ CLOSED = 3
+
+
+class HTTP2Connection(ConnectionInterface):
+ READ_NUM_BYTES = 64 * 1024
+ CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)
+
+ def __init__(
+ self,
+ origin: Origin,
+ stream: NetworkStream,
+ keepalive_expiry: float | None = None,
+ ):
+ self._origin = origin
+ self._network_stream = stream
+ self._keepalive_expiry: float | None = keepalive_expiry
+ self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
+ self._state = HTTPConnectionState.IDLE
+ self._expire_at: float | None = None
+ self._request_count = 0
+ self._init_lock = Lock()
+ self._state_lock = Lock()
+ self._read_lock = Lock()
+ self._write_lock = Lock()
+ self._sent_connection_init = False
+ self._used_all_stream_ids = False
+ self._connection_error = False
+
+ # Mapping from stream ID to response stream events.
+ self._events: dict[
+ int,
+ list[
+ h2.events.ResponseReceived
+ | h2.events.DataReceived
+ | h2.events.StreamEnded
+ | h2.events.StreamReset,
+ ],
+ ] = {}
+
+ # Connection terminated events are stored as state since
+ # we need to handle them for all streams.
+ self._connection_terminated: h2.events.ConnectionTerminated | None = None
+
+ self._read_exception: Exception | None = None
+ self._write_exception: Exception | None = None
+
+ def handle_request(self, request: Request) -> Response:
+ if not self.can_handle_request(request.url.origin):
+ # This cannot occur in normal operation, since the connection pool
+ # will only send requests on connections that handle them.
+ # It's in place simply for resilience as a guard against incorrect
+ # usage, for anyone working directly with httpcore connections.
+ raise RuntimeError(
+ f"Attempted to send request to {request.url.origin} on connection "
+ f"to {self._origin}"
+ )
+
+ with self._state_lock:
+ if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
+ self._request_count += 1
+ self._expire_at = None
+ self._state = HTTPConnectionState.ACTIVE
+ else:
+ raise ConnectionNotAvailable()
+
+ with self._init_lock:
+ if not self._sent_connection_init:
+ try:
+ sci_kwargs = {"request": request}
+ with Trace(
+ "send_connection_init", logger, request, sci_kwargs
+ ):
+ self._send_connection_init(**sci_kwargs)
+ except BaseException as exc:
+ with ShieldCancellation():
+ self.close()
+ raise exc
+
+ self._sent_connection_init = True
+
+ # Initially start with just 1 until the remote server provides
+ # its max_concurrent_streams value
+ self._max_streams = 1
+
+ local_settings_max_streams = (
+ self._h2_state.local_settings.max_concurrent_streams
+ )
+ self._max_streams_semaphore = Semaphore(local_settings_max_streams)
+
+ for _ in range(local_settings_max_streams - self._max_streams):
+ self._max_streams_semaphore.acquire()
+
+ self._max_streams_semaphore.acquire()
+
+ try:
+ stream_id = self._h2_state.get_next_available_stream_id()
+ self._events[stream_id] = []
+ except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover
+ self._used_all_stream_ids = True
+ self._request_count -= 1
+ raise ConnectionNotAvailable()
+
+ try:
+ kwargs = {"request": request, "stream_id": stream_id}
+ with Trace("send_request_headers", logger, request, kwargs):
+ self._send_request_headers(request=request, stream_id=stream_id)
+ with Trace("send_request_body", logger, request, kwargs):
+ self._send_request_body(request=request, stream_id=stream_id)
+ with Trace(
+ "receive_response_headers", logger, request, kwargs
+ ) as trace:
+ status, headers = self._receive_response(
+ request=request, stream_id=stream_id
+ )
+ trace.return_value = (status, headers)
+
+ return Response(
+ status=status,
+ headers=headers,
+ content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
+ extensions={
+ "http_version": b"HTTP/2",
+ "network_stream": self._network_stream,
+ "stream_id": stream_id,
+ },
+ )
+ except BaseException as exc: # noqa: PIE786
+ with ShieldCancellation():
+ kwargs = {"stream_id": stream_id}
+ with Trace("response_closed", logger, request, kwargs):
+ self._response_closed(stream_id=stream_id)
+
+ if isinstance(exc, h2.exceptions.ProtocolError):
+ # One case where h2 can raise a protocol error is when a
+ # closed frame has been seen by the state machine.
+ #
+ # This happens when one stream is reading, and encounters
+ # a GOAWAY event. Other flows of control may then raise
+ # a protocol error at any point they interact with the 'h2_state'.
+ #
+ # In this case we'll have stored the event, and should raise
+ # it as a RemoteProtocolError.
+ if self._connection_terminated: # pragma: nocover
+ raise RemoteProtocolError(self._connection_terminated)
+ # If h2 raises a protocol error in some other state then we
+ # must somehow have made a protocol violation.
+ raise LocalProtocolError(exc) # pragma: nocover
+
+ raise exc
+
+ def _send_connection_init(self, request: Request) -> None:
+ """
+ The HTTP/2 connection requires some initial setup before we can start
+ using individual request/response streams on it.
+ """
+ # Need to set these manually here instead of manipulating via
+ # __setitem__() otherwise the H2Connection will emit SettingsUpdate
+ # frames in addition to sending the undesired defaults.
+ self._h2_state.local_settings = h2.settings.Settings(
+ client=True,
+ initial_values={
+ # Disable PUSH_PROMISE frames from the server since we don't do anything
+ # with them for now. Maybe when we support caching?
+ h2.settings.SettingCodes.ENABLE_PUSH: 0,
+ # These two are taken from h2 for safe defaults
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
+ h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
+ },
+ )
+
+ # Some websites (*cough* Yahoo *cough*) balk at this setting being
+ # present in the initial handshake since it's not defined in the original
+ # RFC despite the RFC mandating ignoring settings you don't know about.
+ del self._h2_state.local_settings[
+ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
+ ]
+
+ self._h2_state.initiate_connection()
+ self._h2_state.increment_flow_control_window(2**24)
+ self._write_outgoing_data(request)
+
+ # Sending the request...
+
+ def _send_request_headers(self, request: Request, stream_id: int) -> None:
+ """
+ Send the request headers to a given stream ID.
+ """
+ end_stream = not has_body_headers(request)
+
+ # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
+ # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
+ # HTTP/1.1 style headers, and map them appropriately if we end up on
+ # an HTTP/2 connection.
+ authority = [v for k, v in request.headers if k.lower() == b"host"][0]
+
+ headers = [
+ (b":method", request.method),
+ (b":authority", authority),
+ (b":scheme", request.url.scheme),
+ (b":path", request.url.target),
+ ] + [
+ (k.lower(), v)
+ for k, v in request.headers
+ if k.lower()
+ not in (
+ b"host",
+ b"transfer-encoding",
+ )
+ ]
+
+ self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
+ self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
+ self._write_outgoing_data(request)
+
+ def _send_request_body(self, request: Request, stream_id: int) -> None:
+ """
+ Iterate over the request body sending it to a given stream ID.
+ """
+ if not has_body_headers(request):
+ return
+
+ assert isinstance(request.stream, typing.Iterable)
+ for data in request.stream:
+ self._send_stream_data(request, stream_id, data)
+ self._send_end_stream(request, stream_id)
+
+ def _send_stream_data(
+ self, request: Request, stream_id: int, data: bytes
+ ) -> None:
+ """
+ Send a single chunk of data in one or more data frames.
+ """
+ while data:
+ max_flow = self._wait_for_outgoing_flow(request, stream_id)
+ chunk_size = min(len(data), max_flow)
+ chunk, data = data[:chunk_size], data[chunk_size:]
+ self._h2_state.send_data(stream_id, chunk)
+ self._write_outgoing_data(request)
+
+ def _send_end_stream(self, request: Request, stream_id: int) -> None:
+ """
+ Send an empty data frame on on a given stream ID with the END_STREAM flag set.
+ """
+ self._h2_state.end_stream(stream_id)
+ self._write_outgoing_data(request)
+
+ # Receiving the response...
+
+ def _receive_response(
+ self, request: Request, stream_id: int
+ ) -> tuple[int, list[tuple[bytes, bytes]]]:
+ """
+ Return the response status code and headers for a given stream ID.
+ """
+ while True:
+ event = self._receive_stream_event(request, stream_id)
+ if isinstance(event, h2.events.ResponseReceived):
+ break
+
+ status_code = 200
+ headers = []
+ assert event.headers is not None
+ for k, v in event.headers:
+ if k == b":status":
+ status_code = int(v.decode("ascii", errors="ignore"))
+ elif not k.startswith(b":"):
+ headers.append((k, v))
+
+ return (status_code, headers)
+
+ def _receive_response_body(
+ self, request: Request, stream_id: int
+ ) -> typing.Iterator[bytes]:
+ """
+ Iterator that returns the bytes of the response body for a given stream ID.
+ """
+ while True:
+ event = self._receive_stream_event(request, stream_id)
+ if isinstance(event, h2.events.DataReceived):
+ assert event.flow_controlled_length is not None
+ assert event.data is not None
+ amount = event.flow_controlled_length
+ self._h2_state.acknowledge_received_data(amount, stream_id)
+ self._write_outgoing_data(request)
+ yield event.data
+ elif isinstance(event, h2.events.StreamEnded):
+ break
+
+ def _receive_stream_event(
+ self, request: Request, stream_id: int
+ ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
+ """
+ Return the next available event for a given stream ID.
+
+ Will read more data from the network if required.
+ """
+ while not self._events.get(stream_id):
+ self._receive_events(request, stream_id)
+ event = self._events[stream_id].pop(0)
+ if isinstance(event, h2.events.StreamReset):
+ raise RemoteProtocolError(event)
+ return event
+
+ def _receive_events(
+ self, request: Request, stream_id: int | None = None
+ ) -> None:
+ """
+ Read some data from the network until we see one or more events
+ for a given stream ID.
+ """
+ with self._read_lock:
+ if self._connection_terminated is not None:
+ last_stream_id = self._connection_terminated.last_stream_id
+ if stream_id and last_stream_id and stream_id > last_stream_id:
+ self._request_count -= 1
+ raise ConnectionNotAvailable()
+ raise RemoteProtocolError(self._connection_terminated)
+
+ # This conditional is a bit icky. We don't want to block reading if we've
+ # actually got an event to return for a given stream. We need to do that
+ # check *within* the atomic read lock. Though it also need to be optional,
+ # because when we call it from `_wait_for_outgoing_flow` we *do* want to
+ # block until we've available flow control, event when we have events
+ # pending for the stream ID we're attempting to send on.
+ if stream_id is None or not self._events.get(stream_id):
+ events = self._read_incoming_data(request)
+ for event in events:
+ if isinstance(event, h2.events.RemoteSettingsChanged):
+ with Trace(
+ "receive_remote_settings", logger, request
+ ) as trace:
+ self._receive_remote_settings_change(event)
+ trace.return_value = event
+
+ elif isinstance(
+ event,
+ (
+ h2.events.ResponseReceived,
+ h2.events.DataReceived,
+ h2.events.StreamEnded,
+ h2.events.StreamReset,
+ ),
+ ):
+ if event.stream_id in self._events:
+ self._events[event.stream_id].append(event)
+
+ elif isinstance(event, h2.events.ConnectionTerminated):
+ self._connection_terminated = event
+
+ self._write_outgoing_data(request)
+
+ def _receive_remote_settings_change(
+ self, event: h2.events.RemoteSettingsChanged
+ ) -> None:
+ max_concurrent_streams = event.changed_settings.get(
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
+ )
+ if max_concurrent_streams:
+ new_max_streams = min(
+ max_concurrent_streams.new_value,
+ self._h2_state.local_settings.max_concurrent_streams,
+ )
+ if new_max_streams and new_max_streams != self._max_streams:
+ while new_max_streams > self._max_streams:
+ self._max_streams_semaphore.release()
+ self._max_streams += 1
+ while new_max_streams < self._max_streams:
+ self._max_streams_semaphore.acquire()
+ self._max_streams -= 1
+
+ def _response_closed(self, stream_id: int) -> None:
+ self._max_streams_semaphore.release()
+ del self._events[stream_id]
+ with self._state_lock:
+ if self._connection_terminated and not self._events:
+ self.close()
+
+ elif self._state == HTTPConnectionState.ACTIVE and not self._events:
+ self._state = HTTPConnectionState.IDLE
+ if self._keepalive_expiry is not None:
+ now = time.monotonic()
+ self._expire_at = now + self._keepalive_expiry
+ if self._used_all_stream_ids: # pragma: nocover
+ self.close()
+
+ def close(self) -> None:
+ # Note that this method unilaterally closes the connection, and does
+ # not have any kind of locking in place around it.
+ self._h2_state.close_connection()
+ self._state = HTTPConnectionState.CLOSED
+ self._network_stream.close()
+
+ # Wrappers around network read/write operations...
+
+ def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("read", None)
+
+ if self._read_exception is not None:
+ raise self._read_exception # pragma: nocover
+
+ try:
+ data = self._network_stream.read(self.READ_NUM_BYTES, timeout)
+ if data == b"":
+ raise RemoteProtocolError("Server disconnected")
+ except Exception as exc:
+ # If we get a network error we should:
+ #
+ # 1. Save the exception and just raise it immediately on any future reads.
+ # (For example, this means that a single read timeout or disconnect will
+ # immediately close all pending streams. Without requiring multiple
+ # sequential timeouts.)
+ # 2. Mark the connection as errored, so that we don't accept any other
+ # incoming requests.
+ self._read_exception = exc
+ self._connection_error = True
+ raise exc
+
+ events: list[h2.events.Event] = self._h2_state.receive_data(data)
+
+ return events
+
+ def _write_outgoing_data(self, request: Request) -> None:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("write", None)
+
+ with self._write_lock:
+ data_to_send = self._h2_state.data_to_send()
+
+ if self._write_exception is not None:
+ raise self._write_exception # pragma: nocover
+
+ try:
+ self._network_stream.write(data_to_send, timeout)
+ except Exception as exc: # pragma: nocover
+ # If we get a network error we should:
+ #
+ # 1. Save the exception and just raise it immediately on any future write.
+ # (For example, this means that a single write timeout or disconnect will
+ # immediately close all pending streams. Without requiring multiple
+ # sequential timeouts.)
+ # 2. Mark the connection as errored, so that we don't accept any other
+ # incoming requests.
+ self._write_exception = exc
+ self._connection_error = True
+ raise exc
+
+ # Flow control...
+
+ def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
+ """
+ Returns the maximum allowable outgoing flow for a given stream.
+
+ If the allowable flow is zero, then waits on the network until
+ WindowUpdated frames have increased the flow rate.
+ https://tools.ietf.org/html/rfc7540#section-6.9
+ """
+ local_flow: int = self._h2_state.local_flow_control_window(stream_id)
+ max_frame_size: int = self._h2_state.max_outbound_frame_size
+ flow = min(local_flow, max_frame_size)
+ while flow == 0:
+ self._receive_events(request)
+ local_flow = self._h2_state.local_flow_control_window(stream_id)
+ max_frame_size = self._h2_state.max_outbound_frame_size
+ flow = min(local_flow, max_frame_size)
+ return flow
+
+ # Interface for connection pooling...
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._origin
+
+ def is_available(self) -> bool:
+ return (
+ self._state != HTTPConnectionState.CLOSED
+ and not self._connection_error
+ and not self._used_all_stream_ids
+ and not (
+ self._h2_state.state_machine.state
+ == h2.connection.ConnectionState.CLOSED
+ )
+ )
+
+ def has_expired(self) -> bool:
+ now = time.monotonic()
+ return self._expire_at is not None and now > self._expire_at
+
+ def is_idle(self) -> bool:
+ return self._state == HTTPConnectionState.IDLE
+
+ def is_closed(self) -> bool:
+ return self._state == HTTPConnectionState.CLOSED
+
+ def info(self) -> str:
+ origin = str(self._origin)
+ return (
+ f"{origin!r}, HTTP/2, {self._state.name}, "
+ f"Request Count: {self._request_count}"
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ origin = str(self._origin)
+ return (
+ f"<{class_name} [{origin!r}, {self._state.name}, "
+ f"Request Count: {self._request_count}]>"
+ )
+
+ # These context managers are not used in the standard flow, but are
+ # useful for testing or working with connection instances directly.
+
+ def __enter__(self) -> HTTP2Connection:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self.close()
+
+
+class HTTP2ConnectionByteStream:
+ def __init__(
+ self, connection: HTTP2Connection, request: Request, stream_id: int
+ ) -> None:
+ self._connection = connection
+ self._request = request
+ self._stream_id = stream_id
+ self._closed = False
+
+ def __iter__(self) -> typing.Iterator[bytes]:
+ kwargs = {"request": self._request, "stream_id": self._stream_id}
+ try:
+ with Trace("receive_response_body", logger, self._request, kwargs):
+ for chunk in self._connection._receive_response_body(
+ request=self._request, stream_id=self._stream_id
+ ):
+ yield chunk
+ except BaseException as exc:
+ # If we get an exception while streaming the response,
+ # we want to close the response (and possibly the connection)
+ # before raising that exception.
+ with ShieldCancellation():
+ self.close()
+ raise exc
+
+ def close(self) -> None:
+ if not self._closed:
+ self._closed = True
+ kwargs = {"stream_id": self._stream_id}
+ with Trace("response_closed", logger, self._request, kwargs):
+ self._connection._response_closed(stream_id=self._stream_id)
diff --git a/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py b/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecca88f7dc93b78f2aa26f16cf29d17a8a83ae27
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py
@@ -0,0 +1,367 @@
+from __future__ import annotations
+
+import base64
+import logging
+import ssl
+import typing
+
+from .._backends.base import SOCKET_OPTION, NetworkBackend
+from .._exceptions import ProxyError
+from .._models import (
+ URL,
+ Origin,
+ Request,
+ Response,
+ enforce_bytes,
+ enforce_headers,
+ enforce_url,
+)
+from .._ssl import default_ssl_context
+from .._synchronization import Lock
+from .._trace import Trace
+from .connection import HTTPConnection
+from .connection_pool import ConnectionPool
+from .http11 import HTTP11Connection
+from .interfaces import ConnectionInterface
+
+ByteOrStr = typing.Union[bytes, str]
+HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
+HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
+
+
+logger = logging.getLogger("httpcore.proxy")
+
+
+def merge_headers(
+ default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+ override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+) -> list[tuple[bytes, bytes]]:
+ """
+ Append default_headers and override_headers, de-duplicating if a key exists
+ in both cases.
+ """
+ default_headers = [] if default_headers is None else list(default_headers)
+ override_headers = [] if override_headers is None else list(override_headers)
+ has_override = set(key.lower() for key, value in override_headers)
+ default_headers = [
+ (key, value)
+ for key, value in default_headers
+ if key.lower() not in has_override
+ ]
+ return default_headers + override_headers
+
+
+class HTTPProxy(ConnectionPool): # pragma: nocover
+ """
+ A connection pool that sends requests via an HTTP proxy.
+ """
+
+ def __init__(
+ self,
+ proxy_url: URL | bytes | str,
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ local_address: str | None = None,
+ uds: str | None = None,
+ network_backend: NetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ proxy_url: The URL to use when connecting to the proxy server.
+ For example `"http://127.0.0.1:8080/"`.
+ proxy_auth: Any proxy authentication as a two-tuple of
+ (username, password). May be either bytes or ascii-only str.
+ proxy_headers: Any HTTP headers to use for the proxy requests.
+ For example `{"Proxy-Authorization": "Basic :"}`.
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish
+ a connection.
+ local_address: Local address to connect from. Can also be used to
+ connect using a particular address family. Using
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
+ (IPv4), while using `local_address="::"` will connect using an
+ `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ """
+ super().__init__(
+ ssl_context=ssl_context,
+ max_connections=max_connections,
+ max_keepalive_connections=max_keepalive_connections,
+ keepalive_expiry=keepalive_expiry,
+ http1=http1,
+ http2=http2,
+ network_backend=network_backend,
+ retries=retries,
+ local_address=local_address,
+ uds=uds,
+ socket_options=socket_options,
+ )
+
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
+ if (
+ self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
+ ): # pragma: no cover
+ raise RuntimeError(
+ "The `proxy_ssl_context` argument is not allowed for the http scheme"
+ )
+
+ self._ssl_context = ssl_context
+ self._proxy_ssl_context = proxy_ssl_context
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ if proxy_auth is not None:
+ username = enforce_bytes(proxy_auth[0], name="proxy_auth")
+ password = enforce_bytes(proxy_auth[1], name="proxy_auth")
+ userpass = username + b":" + password
+ authorization = b"Basic " + base64.b64encode(userpass)
+ self._proxy_headers = [
+ (b"Proxy-Authorization", authorization)
+ ] + self._proxy_headers
+
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
+ if origin.scheme == b"http":
+ return ForwardHTTPConnection(
+ proxy_origin=self._proxy_url.origin,
+ proxy_headers=self._proxy_headers,
+ remote_origin=origin,
+ keepalive_expiry=self._keepalive_expiry,
+ network_backend=self._network_backend,
+ proxy_ssl_context=self._proxy_ssl_context,
+ )
+ return TunnelHTTPConnection(
+ proxy_origin=self._proxy_url.origin,
+ proxy_headers=self._proxy_headers,
+ remote_origin=origin,
+ ssl_context=self._ssl_context,
+ proxy_ssl_context=self._proxy_ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+
+class ForwardHTTPConnection(ConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
+ keepalive_expiry: float | None = None,
+ network_backend: NetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ ) -> None:
+ self._connection = HTTPConnection(
+ origin=proxy_origin,
+ keepalive_expiry=keepalive_expiry,
+ network_backend=network_backend,
+ socket_options=socket_options,
+ ssl_context=proxy_ssl_context,
+ )
+ self._proxy_origin = proxy_origin
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ self._remote_origin = remote_origin
+
+ def handle_request(self, request: Request) -> Response:
+ headers = merge_headers(self._proxy_headers, request.headers)
+ url = URL(
+ scheme=self._proxy_origin.scheme,
+ host=self._proxy_origin.host,
+ port=self._proxy_origin.port,
+ target=bytes(request.url),
+ )
+ proxy_request = Request(
+ method=request.method,
+ url=url,
+ headers=headers,
+ content=request.stream,
+ extensions=request.extensions,
+ )
+ return self._connection.handle_request(proxy_request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ def close(self) -> None:
+ self._connection.close()
+
+ def info(self) -> str:
+ return self._connection.info()
+
+ def is_available(self) -> bool:
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ return self._connection.is_closed()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
+
+
+class TunnelHTTPConnection(ConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ ssl_context: ssl.SSLContext | None = None,
+ proxy_ssl_context: ssl.SSLContext | None = None,
+ proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ network_backend: NetworkBackend | None = None,
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
+ ) -> None:
+ self._connection: ConnectionInterface = HTTPConnection(
+ origin=proxy_origin,
+ keepalive_expiry=keepalive_expiry,
+ network_backend=network_backend,
+ socket_options=socket_options,
+ ssl_context=proxy_ssl_context,
+ )
+ self._proxy_origin = proxy_origin
+ self._remote_origin = remote_origin
+ self._ssl_context = ssl_context
+ self._proxy_ssl_context = proxy_ssl_context
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+ self._connect_lock = Lock()
+ self._connected = False
+
+ def handle_request(self, request: Request) -> Response:
+ timeouts = request.extensions.get("timeout", {})
+ timeout = timeouts.get("connect", None)
+
+ with self._connect_lock:
+ if not self._connected:
+ target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)
+
+ connect_url = URL(
+ scheme=self._proxy_origin.scheme,
+ host=self._proxy_origin.host,
+ port=self._proxy_origin.port,
+ target=target,
+ )
+ connect_headers = merge_headers(
+ [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
+ )
+ connect_request = Request(
+ method=b"CONNECT",
+ url=connect_url,
+ headers=connect_headers,
+ extensions=request.extensions,
+ )
+ connect_response = self._connection.handle_request(
+ connect_request
+ )
+
+ if connect_response.status < 200 or connect_response.status > 299:
+ reason_bytes = connect_response.extensions.get("reason_phrase", b"")
+ reason_str = reason_bytes.decode("ascii", errors="ignore")
+ msg = "%d %s" % (connect_response.status, reason_str)
+ self._connection.close()
+ raise ProxyError(msg)
+
+ stream = connect_response.extensions["network_stream"]
+
+ # Upgrade the stream to SSL
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": self._remote_origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = stream.start_tls(**kwargs)
+ trace.return_value = stream
+
+ # Determine if we should be using HTTP/1.1 or HTTP/2
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+
+ # Create the HTTP/1.1 or HTTP/2 connection
+ if http2_negotiated or (self._http2 and not self._http1):
+ from .http2 import HTTP2Connection
+
+ self._connection = HTTP2Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = HTTP11Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+
+ self._connected = True
+ return self._connection.handle_request(request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ def close(self) -> None:
+ self._connection.close()
+
+ def info(self) -> str:
+ return self._connection.info()
+
+ def is_available(self) -> bool:
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ return self._connection.is_closed()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
diff --git a/lib/python3.12/site-packages/httpcore/_sync/interfaces.py b/lib/python3.12/site-packages/httpcore/_sync/interfaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..e673d4cc1b1dd7e7ecdbde91fd6ada386c3de03f
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/interfaces.py
@@ -0,0 +1,137 @@
+from __future__ import annotations
+
+import contextlib
+import typing
+
+from .._models import (
+ URL,
+ Extensions,
+ HeaderTypes,
+ Origin,
+ Request,
+ Response,
+ enforce_bytes,
+ enforce_headers,
+ enforce_url,
+ include_request_headers,
+)
+
+
+class RequestInterface:
+ def request(
+ self,
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.Iterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+ ) -> Response:
+ # Strict type checking on our parameters.
+ method = enforce_bytes(method, name="method")
+ url = enforce_url(url, name="url")
+ headers = enforce_headers(headers, name="headers")
+
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
+ headers = include_request_headers(headers, url=url, content=content)
+
+ request = Request(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ )
+ response = self.handle_request(request)
+ try:
+ response.read()
+ finally:
+ response.close()
+ return response
+
+ @contextlib.contextmanager
+ def stream(
+ self,
+ method: bytes | str,
+ url: URL | bytes | str,
+ *,
+ headers: HeaderTypes = None,
+ content: bytes | typing.Iterator[bytes] | None = None,
+ extensions: Extensions | None = None,
+ ) -> typing.Iterator[Response]:
+ # Strict type checking on our parameters.
+ method = enforce_bytes(method, name="method")
+ url = enforce_url(url, name="url")
+ headers = enforce_headers(headers, name="headers")
+
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
+ headers = include_request_headers(headers, url=url, content=content)
+
+ request = Request(
+ method=method,
+ url=url,
+ headers=headers,
+ content=content,
+ extensions=extensions,
+ )
+ response = self.handle_request(request)
+ try:
+ yield response
+ finally:
+ response.close()
+
+ def handle_request(self, request: Request) -> Response:
+ raise NotImplementedError() # pragma: nocover
+
+
+class ConnectionInterface(RequestInterface):
+ def close(self) -> None:
+ raise NotImplementedError() # pragma: nocover
+
+ def info(self) -> str:
+ raise NotImplementedError() # pragma: nocover
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ raise NotImplementedError() # pragma: nocover
+
+ def is_available(self) -> bool:
+ """
+ Return `True` if the connection is currently able to accept an
+ outgoing request.
+
+ An HTTP/1.1 connection will only be available if it is currently idle.
+
+ An HTTP/2 connection will be available so long as the stream ID space is
+ not yet exhausted, and the connection is not in an error state.
+
+ While the connection is being established we may not yet know if it is going
+ to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
+ treated as being available, but might ultimately raise `NewConnectionRequired`
+ required exceptions if multiple requests are attempted over a connection
+ that ends up being established as HTTP/1.1.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def has_expired(self) -> bool:
+ """
+ Return `True` if the connection is in a state where it should be closed.
+
+ This either means that the connection is idle and it has passed the
+ expiry time on its keep-alive, or that server has sent an EOF.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def is_idle(self) -> bool:
+ """
+ Return `True` if the connection is currently idle.
+ """
+ raise NotImplementedError() # pragma: nocover
+
+ def is_closed(self) -> bool:
+ """
+ Return `True` if the connection has been closed.
+
+ Used when a response is closed to determine if the connection may be
+ returned to the connection pool or not.
+ """
+ raise NotImplementedError() # pragma: nocover
diff --git a/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py b/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ca96ddfb580b19413797f41e79f7abcecdd9d79
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py
@@ -0,0 +1,341 @@
+from __future__ import annotations
+
+import logging
+import ssl
+
+import socksio
+
+from .._backends.sync import SyncBackend
+from .._backends.base import NetworkBackend, NetworkStream
+from .._exceptions import ConnectionNotAvailable, ProxyError
+from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
+from .._ssl import default_ssl_context
+from .._synchronization import Lock
+from .._trace import Trace
+from .connection_pool import ConnectionPool
+from .http11 import HTTP11Connection
+from .interfaces import ConnectionInterface
+
+logger = logging.getLogger("httpcore.socks")
+
+
+AUTH_METHODS = {
+ b"\x00": "NO AUTHENTICATION REQUIRED",
+ b"\x01": "GSSAPI",
+ b"\x02": "USERNAME/PASSWORD",
+ b"\xff": "NO ACCEPTABLE METHODS",
+}
+
+REPLY_CODES = {
+ b"\x00": "Succeeded",
+ b"\x01": "General SOCKS server failure",
+ b"\x02": "Connection not allowed by ruleset",
+ b"\x03": "Network unreachable",
+ b"\x04": "Host unreachable",
+ b"\x05": "Connection refused",
+ b"\x06": "TTL expired",
+ b"\x07": "Command not supported",
+ b"\x08": "Address type not supported",
+}
+
+
+def _init_socks5_connection(
+ stream: NetworkStream,
+ *,
+ host: bytes,
+ port: int,
+ auth: tuple[bytes, bytes] | None = None,
+) -> None:
+ conn = socksio.socks5.SOCKS5Connection()
+
+ # Auth method request
+ auth_method = (
+ socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
+ if auth is None
+ else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
+ )
+ conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
+ outgoing_bytes = conn.data_to_send()
+ stream.write(outgoing_bytes)
+
+ # Auth method response
+ incoming_bytes = stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
+ if response.method != auth_method:
+ requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
+ responded = AUTH_METHODS.get(response.method, "UNKNOWN")
+ raise ProxyError(
+ f"Requested {requested} from proxy server, but got {responded}."
+ )
+
+ if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
+ # Username/password request
+ assert auth is not None
+ username, password = auth
+ conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
+ outgoing_bytes = conn.data_to_send()
+ stream.write(outgoing_bytes)
+
+ # Username/password response
+ incoming_bytes = stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
+ if not response.success:
+ raise ProxyError("Invalid username/password")
+
+ # Connect request
+ conn.send(
+ socksio.socks5.SOCKS5CommandRequest.from_address(
+ socksio.socks5.SOCKS5Command.CONNECT, (host, port)
+ )
+ )
+ outgoing_bytes = conn.data_to_send()
+ stream.write(outgoing_bytes)
+
+ # Connect response
+ incoming_bytes = stream.read(max_bytes=4096)
+ response = conn.receive_data(incoming_bytes)
+ assert isinstance(response, socksio.socks5.SOCKS5Reply)
+ if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
+ reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
+ raise ProxyError(f"Proxy Server could not connect: {reply_code}.")
+
+
+class SOCKSProxy(ConnectionPool): # pragma: nocover
+ """
+ A connection pool that sends requests via an HTTP proxy.
+ """
+
+ def __init__(
+ self,
+ proxy_url: URL | bytes | str,
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ max_connections: int | None = 10,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ retries: int = 0,
+ network_backend: NetworkBackend | None = None,
+ ) -> None:
+ """
+ A connection pool for making HTTP requests.
+
+ Parameters:
+ proxy_url: The URL to use when connecting to the proxy server.
+ For example `"http://127.0.0.1:8080/"`.
+ ssl_context: An SSL context to use for verifying connections.
+ If not specified, the default `httpcore.default_ssl_context()`
+ will be used.
+ max_connections: The maximum number of concurrent HTTP connections that
+ the pool should allow. Any attempt to send a request on a pool that
+ would exceed this amount will block until a connection is available.
+ max_keepalive_connections: The maximum number of idle HTTP connections
+ that will be maintained in the pool.
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
+ may be maintained for before being expired from the pool.
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
+ by the connection pool. Defaults to True.
+ http2: A boolean indicating if HTTP/2 requests should be supported by
+ the connection pool. Defaults to False.
+ retries: The maximum number of retries when trying to establish
+ a connection.
+ local_address: Local address to connect from. Can also be used to
+ connect using a particular address family. Using
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
+ (IPv4), while using `local_address="::"` will connect using an
+ `AF_INET6` address (IPv6).
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
+ network_backend: A backend instance to use for handling network I/O.
+ """
+ super().__init__(
+ ssl_context=ssl_context,
+ max_connections=max_connections,
+ max_keepalive_connections=max_keepalive_connections,
+ keepalive_expiry=keepalive_expiry,
+ http1=http1,
+ http2=http2,
+ network_backend=network_backend,
+ retries=retries,
+ )
+ self._ssl_context = ssl_context
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
+ if proxy_auth is not None:
+ username, password = proxy_auth
+ username_bytes = enforce_bytes(username, name="proxy_auth")
+ password_bytes = enforce_bytes(password, name="proxy_auth")
+ self._proxy_auth: tuple[bytes, bytes] | None = (
+ username_bytes,
+ password_bytes,
+ )
+ else:
+ self._proxy_auth = None
+
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
+ return Socks5Connection(
+ proxy_origin=self._proxy_url.origin,
+ remote_origin=origin,
+ proxy_auth=self._proxy_auth,
+ ssl_context=self._ssl_context,
+ keepalive_expiry=self._keepalive_expiry,
+ http1=self._http1,
+ http2=self._http2,
+ network_backend=self._network_backend,
+ )
+
+
+class Socks5Connection(ConnectionInterface):
+ def __init__(
+ self,
+ proxy_origin: Origin,
+ remote_origin: Origin,
+ proxy_auth: tuple[bytes, bytes] | None = None,
+ ssl_context: ssl.SSLContext | None = None,
+ keepalive_expiry: float | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ network_backend: NetworkBackend | None = None,
+ ) -> None:
+ self._proxy_origin = proxy_origin
+ self._remote_origin = remote_origin
+ self._proxy_auth = proxy_auth
+ self._ssl_context = ssl_context
+ self._keepalive_expiry = keepalive_expiry
+ self._http1 = http1
+ self._http2 = http2
+
+ self._network_backend: NetworkBackend = (
+ SyncBackend() if network_backend is None else network_backend
+ )
+ self._connect_lock = Lock()
+ self._connection: ConnectionInterface | None = None
+ self._connect_failed = False
+
+ def handle_request(self, request: Request) -> Response:
+ timeouts = request.extensions.get("timeout", {})
+ sni_hostname = request.extensions.get("sni_hostname", None)
+ timeout = timeouts.get("connect", None)
+
+ with self._connect_lock:
+ if self._connection is None:
+ try:
+ # Connect to the proxy
+ kwargs = {
+ "host": self._proxy_origin.host.decode("ascii"),
+ "port": self._proxy_origin.port,
+ "timeout": timeout,
+ }
+ with Trace("connect_tcp", logger, request, kwargs) as trace:
+ stream = self._network_backend.connect_tcp(**kwargs)
+ trace.return_value = stream
+
+ # Connect to the remote host using socks5
+ kwargs = {
+ "stream": stream,
+ "host": self._remote_origin.host.decode("ascii"),
+ "port": self._remote_origin.port,
+ "auth": self._proxy_auth,
+ }
+ with Trace(
+ "setup_socks5_connection", logger, request, kwargs
+ ) as trace:
+ _init_socks5_connection(**kwargs)
+ trace.return_value = stream
+
+ # Upgrade the stream to SSL
+ if self._remote_origin.scheme == b"https":
+ ssl_context = (
+ default_ssl_context()
+ if self._ssl_context is None
+ else self._ssl_context
+ )
+ alpn_protocols = (
+ ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
+ )
+ ssl_context.set_alpn_protocols(alpn_protocols)
+
+ kwargs = {
+ "ssl_context": ssl_context,
+ "server_hostname": sni_hostname
+ or self._remote_origin.host.decode("ascii"),
+ "timeout": timeout,
+ }
+ with Trace("start_tls", logger, request, kwargs) as trace:
+ stream = stream.start_tls(**kwargs)
+ trace.return_value = stream
+
+ # Determine if we should be using HTTP/1.1 or HTTP/2
+ ssl_object = stream.get_extra_info("ssl_object")
+ http2_negotiated = (
+ ssl_object is not None
+ and ssl_object.selected_alpn_protocol() == "h2"
+ )
+
+ # Create the HTTP/1.1 or HTTP/2 connection
+ if http2_negotiated or (
+ self._http2 and not self._http1
+ ): # pragma: nocover
+ from .http2 import HTTP2Connection
+
+ self._connection = HTTP2Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ else:
+ self._connection = HTTP11Connection(
+ origin=self._remote_origin,
+ stream=stream,
+ keepalive_expiry=self._keepalive_expiry,
+ )
+ except Exception as exc:
+ self._connect_failed = True
+ raise exc
+ elif not self._connection.is_available(): # pragma: nocover
+ raise ConnectionNotAvailable()
+
+ return self._connection.handle_request(request)
+
+ def can_handle_request(self, origin: Origin) -> bool:
+ return origin == self._remote_origin
+
+ def close(self) -> None:
+ if self._connection is not None:
+ self._connection.close()
+
+ def is_available(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ # If HTTP/2 support is enabled, and the resulting connection could
+ # end up as HTTP/2 then we should indicate the connection as being
+ # available to service multiple requests.
+ return (
+ self._http2
+ and (self._remote_origin.scheme == b"https" or not self._http1)
+ and not self._connect_failed
+ )
+ return self._connection.is_available()
+
+ def has_expired(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.has_expired()
+
+ def is_idle(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.is_idle()
+
+ def is_closed(self) -> bool:
+ if self._connection is None: # pragma: nocover
+ return self._connect_failed
+ return self._connection.is_closed()
+
+ def info(self) -> str:
+ if self._connection is None: # pragma: nocover
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
+ return self._connection.info()
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} [{self.info()}]>"
diff --git a/lib/python3.12/site-packages/httpcore/_synchronization.py b/lib/python3.12/site-packages/httpcore/_synchronization.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ecc9e9c363e2f16c4f934cf41cf871826d6a495
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_synchronization.py
@@ -0,0 +1,318 @@
+from __future__ import annotations
+
+import threading
+import types
+
+from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions
+
+# Our async synchronization primatives use either 'anyio' or 'trio' depending
+# on if they're running under asyncio or trio.
+
+try:
+ import trio
+except (ImportError, NotImplementedError): # pragma: nocover
+ trio = None # type: ignore
+
+try:
+ import anyio
+except ImportError: # pragma: nocover
+ anyio = None # type: ignore
+
+
+def current_async_library() -> str:
+ # Determine if we're running under trio or asyncio.
+ # See https://sniffio.readthedocs.io/en/latest/
+ try:
+ import sniffio
+ except ImportError: # pragma: nocover
+ environment = "asyncio"
+ else:
+ environment = sniffio.current_async_library()
+
+ if environment not in ("asyncio", "trio"): # pragma: nocover
+ raise RuntimeError("Running under an unsupported async environment.")
+
+ if environment == "asyncio" and anyio is None: # pragma: nocover
+ raise RuntimeError(
+ "Running with asyncio requires installation of 'httpcore[asyncio]'."
+ )
+
+ if environment == "trio" and trio is None: # pragma: nocover
+ raise RuntimeError(
+ "Running with trio requires installation of 'httpcore[trio]'."
+ )
+
+ return environment
+
+
+class AsyncLock:
+ """
+ This is a standard lock.
+
+ In the sync case `Lock` provides thread locking.
+ In the async case `AsyncLock` provides async locking.
+ """
+
+ def __init__(self) -> None:
+ self._backend = ""
+
+ def setup(self) -> None:
+ """
+ Detect if we're running under 'asyncio' or 'trio' and create
+ a lock with the correct implementation.
+ """
+ self._backend = current_async_library()
+ if self._backend == "trio":
+ self._trio_lock = trio.Lock()
+ elif self._backend == "asyncio":
+ self._anyio_lock = anyio.Lock()
+
+ async def __aenter__(self) -> AsyncLock:
+ if not self._backend:
+ self.setup()
+
+ if self._backend == "trio":
+ await self._trio_lock.acquire()
+ elif self._backend == "asyncio":
+ await self._anyio_lock.acquire()
+
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ if self._backend == "trio":
+ self._trio_lock.release()
+ elif self._backend == "asyncio":
+ self._anyio_lock.release()
+
+
+class AsyncThreadLock:
+ """
+ This is a threading-only lock for no-I/O contexts.
+
+ In the sync case `ThreadLock` provides thread locking.
+ In the async case `AsyncThreadLock` is a no-op.
+ """
+
+ def __enter__(self) -> AsyncThreadLock:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ pass
+
+
+class AsyncEvent:
+ def __init__(self) -> None:
+ self._backend = ""
+
+ def setup(self) -> None:
+ """
+ Detect if we're running under 'asyncio' or 'trio' and create
+ a lock with the correct implementation.
+ """
+ self._backend = current_async_library()
+ if self._backend == "trio":
+ self._trio_event = trio.Event()
+ elif self._backend == "asyncio":
+ self._anyio_event = anyio.Event()
+
+ def set(self) -> None:
+ if not self._backend:
+ self.setup()
+
+ if self._backend == "trio":
+ self._trio_event.set()
+ elif self._backend == "asyncio":
+ self._anyio_event.set()
+
+ async def wait(self, timeout: float | None = None) -> None:
+ if not self._backend:
+ self.setup()
+
+ if self._backend == "trio":
+ trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout}
+ timeout_or_inf = float("inf") if timeout is None else timeout
+ with map_exceptions(trio_exc_map):
+ with trio.fail_after(timeout_or_inf):
+ await self._trio_event.wait()
+ elif self._backend == "asyncio":
+ anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout}
+ with map_exceptions(anyio_exc_map):
+ with anyio.fail_after(timeout):
+ await self._anyio_event.wait()
+
+
+class AsyncSemaphore:
+ def __init__(self, bound: int) -> None:
+ self._bound = bound
+ self._backend = ""
+
+ def setup(self) -> None:
+ """
+ Detect if we're running under 'asyncio' or 'trio' and create
+ a semaphore with the correct implementation.
+ """
+ self._backend = current_async_library()
+ if self._backend == "trio":
+ self._trio_semaphore = trio.Semaphore(
+ initial_value=self._bound, max_value=self._bound
+ )
+ elif self._backend == "asyncio":
+ self._anyio_semaphore = anyio.Semaphore(
+ initial_value=self._bound, max_value=self._bound
+ )
+
+ async def acquire(self) -> None:
+ if not self._backend:
+ self.setup()
+
+ if self._backend == "trio":
+ await self._trio_semaphore.acquire()
+ elif self._backend == "asyncio":
+ await self._anyio_semaphore.acquire()
+
+ async def release(self) -> None:
+ if self._backend == "trio":
+ self._trio_semaphore.release()
+ elif self._backend == "asyncio":
+ self._anyio_semaphore.release()
+
+
+class AsyncShieldCancellation:
+ # For certain portions of our codebase where we're dealing with
+ # closing connections during exception handling we want to shield
+ # the operation from being cancelled.
+ #
+ # with AsyncShieldCancellation():
+ # ... # clean-up operations, shielded from cancellation.
+
+ def __init__(self) -> None:
+ """
+ Detect if we're running under 'asyncio' or 'trio' and create
+ a shielded scope with the correct implementation.
+ """
+ self._backend = current_async_library()
+
+ if self._backend == "trio":
+ self._trio_shield = trio.CancelScope(shield=True)
+ elif self._backend == "asyncio":
+ self._anyio_shield = anyio.CancelScope(shield=True)
+
+ def __enter__(self) -> AsyncShieldCancellation:
+ if self._backend == "trio":
+ self._trio_shield.__enter__()
+ elif self._backend == "asyncio":
+ self._anyio_shield.__enter__()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ if self._backend == "trio":
+ self._trio_shield.__exit__(exc_type, exc_value, traceback)
+ elif self._backend == "asyncio":
+ self._anyio_shield.__exit__(exc_type, exc_value, traceback)
+
+
+# Our thread-based synchronization primitives...
+
+
+class Lock:
+ """
+ This is a standard lock.
+
+ In the sync case `Lock` provides thread locking.
+ In the async case `AsyncLock` provides async locking.
+ """
+
+ def __init__(self) -> None:
+ self._lock = threading.Lock()
+
+ def __enter__(self) -> Lock:
+ self._lock.acquire()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self._lock.release()
+
+
+class ThreadLock:
+ """
+ This is a threading-only lock for no-I/O contexts.
+
+ In the sync case `ThreadLock` provides thread locking.
+ In the async case `AsyncThreadLock` is a no-op.
+ """
+
+ def __init__(self) -> None:
+ self._lock = threading.Lock()
+
+ def __enter__(self) -> ThreadLock:
+ self._lock.acquire()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ self._lock.release()
+
+
+class Event:
+ def __init__(self) -> None:
+ self._event = threading.Event()
+
+ def set(self) -> None:
+ self._event.set()
+
+ def wait(self, timeout: float | None = None) -> None:
+ if timeout == float("inf"): # pragma: no cover
+ timeout = None
+ if not self._event.wait(timeout=timeout):
+ raise PoolTimeout() # pragma: nocover
+
+
+class Semaphore:
+ def __init__(self, bound: int) -> None:
+ self._semaphore = threading.Semaphore(value=bound)
+
+ def acquire(self) -> None:
+ self._semaphore.acquire()
+
+ def release(self) -> None:
+ self._semaphore.release()
+
+
+class ShieldCancellation:
+ # Thread-synchronous codebases don't support cancellation semantics.
+ # We have this class because we need to mirror the async and sync
+ # cases within our package, but it's just a no-op.
+ def __enter__(self) -> ShieldCancellation:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ pass
diff --git a/lib/python3.12/site-packages/httpcore/_trace.py b/lib/python3.12/site-packages/httpcore/_trace.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f1cd7c47829ce17dbcf651ab56b4ffdce04a485
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_trace.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+
+import inspect
+import logging
+import types
+import typing
+
+from ._models import Request
+
+
+class Trace:
+ def __init__(
+ self,
+ name: str,
+ logger: logging.Logger,
+ request: Request | None = None,
+ kwargs: dict[str, typing.Any] | None = None,
+ ) -> None:
+ self.name = name
+ self.logger = logger
+ self.trace_extension = (
+ None if request is None else request.extensions.get("trace")
+ )
+ self.debug = self.logger.isEnabledFor(logging.DEBUG)
+ self.kwargs = kwargs or {}
+ self.return_value: typing.Any = None
+ self.should_trace = self.debug or self.trace_extension is not None
+ self.prefix = self.logger.name.split(".")[-1]
+
+ def trace(self, name: str, info: dict[str, typing.Any]) -> None:
+ if self.trace_extension is not None:
+ prefix_and_name = f"{self.prefix}.{name}"
+ ret = self.trace_extension(prefix_and_name, info)
+ if inspect.iscoroutine(ret): # pragma: no cover
+ raise TypeError(
+ "If you are using a synchronous interface, "
+ "the callback of the `trace` extension should "
+ "be a normal function instead of an asynchronous function."
+ )
+
+ if self.debug:
+ if not info or "return_value" in info and info["return_value"] is None:
+ message = name
+ else:
+ args = " ".join([f"{key}={value!r}" for key, value in info.items()])
+ message = f"{name} {args}"
+ self.logger.debug(message)
+
+ def __enter__(self) -> Trace:
+ if self.should_trace:
+ info = self.kwargs
+ self.trace(f"{self.name}.started", info)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ if self.should_trace:
+ if exc_value is None:
+ info = {"return_value": self.return_value}
+ self.trace(f"{self.name}.complete", info)
+ else:
+ info = {"exception": exc_value}
+ self.trace(f"{self.name}.failed", info)
+
+ async def atrace(self, name: str, info: dict[str, typing.Any]) -> None:
+ if self.trace_extension is not None:
+ prefix_and_name = f"{self.prefix}.{name}"
+ coro = self.trace_extension(prefix_and_name, info)
+ if not inspect.iscoroutine(coro): # pragma: no cover
+ raise TypeError(
+ "If you're using an asynchronous interface, "
+ "the callback of the `trace` extension should "
+ "be an asynchronous function rather than a normal function."
+ )
+ await coro
+
+ if self.debug:
+ if not info or "return_value" in info and info["return_value"] is None:
+ message = name
+ else:
+ args = " ".join([f"{key}={value!r}" for key, value in info.items()])
+ message = f"{name} {args}"
+ self.logger.debug(message)
+
+ async def __aenter__(self) -> Trace:
+ if self.should_trace:
+ info = self.kwargs
+ await self.atrace(f"{self.name}.started", info)
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: types.TracebackType | None = None,
+ ) -> None:
+ if self.should_trace:
+ if exc_value is None:
+ info = {"return_value": self.return_value}
+ await self.atrace(f"{self.name}.complete", info)
+ else:
+ info = {"exception": exc_value}
+ await self.atrace(f"{self.name}.failed", info)
diff --git a/lib/python3.12/site-packages/httpcore/_utils.py b/lib/python3.12/site-packages/httpcore/_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c44ff93cb2f572afc6e679308024b744b65c3b0a
--- /dev/null
+++ b/lib/python3.12/site-packages/httpcore/_utils.py
@@ -0,0 +1,37 @@
+from __future__ import annotations
+
+import select
+import socket
+import sys
+
+
+def is_socket_readable(sock: socket.socket | None) -> bool:
+ """
+ Return whether a socket, as identifed by its file descriptor, is readable.
+ "A socket is readable" means that the read buffer isn't empty, i.e. that calling
+ .recv() on it would immediately return some data.
+ """
+ # NOTE: we want check for readability without actually attempting to read, because
+ # we don't want to block forever if it's not readable.
+
+ # In the case that the socket no longer exists, or cannot return a file
+ # descriptor, we treat it as being readable, as if it the next read operation
+ # on it is ready to return the terminating `b""`.
+ sock_fd = None if sock is None else sock.fileno()
+ if sock_fd is None or sock_fd < 0: # pragma: nocover
+ return True
+
+ # The implementation below was stolen from:
+ # https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471-L478
+ # See also: https://github.com/encode/httpcore/pull/193#issuecomment-703129316
+
+ # Use select.select on Windows, and when poll is unavailable and select.poll
+ # everywhere else. (E.g. When eventlet is in use. See #327)
+ if (
+ sys.platform == "win32" or getattr(select, "poll", None) is None
+ ): # pragma: nocover
+ rready, _, _ = select.select([sock_fd], [], [], 0)
+ return bool(rready)
+ p = select.poll()
+ p.register(sock_fd, select.POLLIN)
+ return bool(p.poll(0))
diff --git a/lib/python3.12/site-packages/httpcore/py.typed b/lib/python3.12/site-packages/httpcore/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/INSTALLER b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/LICENSE b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..b96dcb0480a0b0be0727976e5202a1e7b23edc3f
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+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/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/METADATA b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..89017efbd21c66a707b946cd6d06e0d7d253d301
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/METADATA
@@ -0,0 +1,110 @@
+Metadata-Version: 2.1
+Name: hydra-core
+Version: 1.3.2
+Summary: A framework for elegantly configuring complex applications
+Home-page: https://github.com/facebookresearch/hydra
+Author: Omry Yadan
+Author-email: omry@fb.com
+License: MIT
+Keywords: command-line configuration yaml tab-completion
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: Microsoft :: Windows
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Requires-Dist: omegaconf (<2.4,>=2.2)
+Requires-Dist: antlr4-python3-runtime (==4.9.*)
+Requires-Dist: packaging
+Requires-Dist: importlib-resources ; python_version < "3.9"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A framework for elegantly configuring complex applications.
+
+
+ Check the website for more information,
+ or click the thumbnail below for a one-minute video introduction to Hydra.
+
+
+
+
+
+
+
+
+----------------------
+
+
+### Releases
+
+#### Stable
+
+**Hydra 1.3** is the stable version of Hydra.
+- [Documentation](https://hydra.cc/docs/1.3/intro/)
+- Installation : `pip install hydra-core --upgrade`
+
+See the [NEWS.md](NEWS.md) file for a summary of recent changes to Hydra.
+
+### License
+Hydra is licensed under [MIT License](LICENSE).
+
+## Hydra Ecosystem
+
+#### Check out these third-party libraries that build on Hydra's functionality:
+* [hydra-zen](https://github.com/mit-ll-responsible-ai/hydra-zen): Pythonic utilities for working with Hydra. Dynamic config generation capabilities, enhanced config store features, a Python API for launching Hydra jobs, and more.
+* [lightning-hydra-template](https://github.com/ashleve/lightning-hydra-template): user-friendly template combining Hydra with [Pytorch-Lightning](https://github.com/Lightning-AI/lightning) for ML experimentation.
+* [hydra-torch](https://github.com/pytorch/hydra-torch): [configen](https://github.com/facebookresearch/hydra/tree/main/tools/configen)-generated configuration classes enabling type-safe PyTorch configuration for Hydra apps.
+* NVIDIA's DeepLearningExamples repository contains a Hydra Launcher plugin, the [distributed_launcher](https://github.com/NVIDIA/DeepLearningExamples/tree/9c34e35c218514b8607d7cf381d8a982a01175e9/Tools/PyTorch/TimeSeriesPredictionPlatform/distributed_launcher), which makes use of the pytorch [distributed.launch](https://pytorch.org/docs/stable/distributed.html#launch-utility) API.
+
+#### Ask questions in Github Discussions or StackOverflow (Use the tag #fb-hydra or #omegaconf):
+* [Github Discussions](https://github.com/facebookresearch/hydra/discussions)
+* [StackOverflow](https://stackexchange.com/filters/391828/hydra-questions)
+* [Twitter](https://twitter.com/Hydra_Framework)
+
+Check out the Meta AI [blog post](https://ai.facebook.com/blog/reengineering-facebook-ais-deep-learning-platforms-for-interoperability/) to learn about how Hydra fits into Meta's efforts to reengineer deep learning platforms for interoperability.
+
+### Citing Hydra
+If you use Hydra in your research please use the following BibTeX entry:
+```BibTeX
+@Misc{Yadan2019Hydra,
+ author = {Omry Yadan},
+ title = {Hydra - A framework for elegantly configuring complex applications},
+ howpublished = {Github},
+ year = {2019},
+ url = {https://github.com/facebookresearch/hydra}
+}
+```
+
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/RECORD b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..265bfb62113a44b31fb072100c3d425022541948
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/RECORD
@@ -0,0 +1,229 @@
+hydra/__init__.py,sha256=RfXj7duXieCLncBhvAP-dK_yiBX7AP1c5OT1ZJ4mzqQ,586
+hydra/__pycache__/__init__.cpython-312.pyc,,
+hydra/__pycache__/compose.cpython-312.pyc,,
+hydra/__pycache__/errors.cpython-312.pyc,,
+hydra/__pycache__/initialize.cpython-312.pyc,,
+hydra/__pycache__/main.cpython-312.pyc,,
+hydra/__pycache__/types.cpython-312.pyc,,
+hydra/__pycache__/utils.cpython-312.pyc,,
+hydra/__pycache__/version.cpython-312.pyc,,
+hydra/_internal/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/_internal/__pycache__/__init__.cpython-312.pyc,,
+hydra/_internal/__pycache__/callbacks.cpython-312.pyc,,
+hydra/_internal/__pycache__/config_loader_impl.cpython-312.pyc,,
+hydra/_internal/__pycache__/config_repository.cpython-312.pyc,,
+hydra/_internal/__pycache__/config_search_path_impl.cpython-312.pyc,,
+hydra/_internal/__pycache__/defaults_list.cpython-312.pyc,,
+hydra/_internal/__pycache__/deprecation_warning.cpython-312.pyc,,
+hydra/_internal/__pycache__/hydra.cpython-312.pyc,,
+hydra/_internal/__pycache__/sources_registry.cpython-312.pyc,,
+hydra/_internal/__pycache__/utils.cpython-312.pyc,,
+hydra/_internal/callbacks.py,sha256=8oihvw_Qgjl462wvC-51IymMlDw92vJ326WUhnIflH8,2310
+hydra/_internal/config_loader_impl.py,sha256=pfp4-GhTZUmQj33IvdqbllTEUDAd41mobB0aO7S4Aik,23925
+hydra/_internal/config_repository.py,sha256=xFZxs-OrN9EWT9gInHO-2RWIqv2VD02J74utekteEsU,13541
+hydra/_internal/config_search_path_impl.py,sha256=EZNyyd4bupjj6BZkRcG6Sf_lHrY6B-rZwS9Ggy1_7LU,3565
+hydra/_internal/core_plugins/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/_internal/core_plugins/__pycache__/__init__.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/bash_completion.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/basic_launcher.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/basic_sweeper.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/file_config_source.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/fish_completion.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/importlib_resources_config_source.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/structured_config_source.cpython-312.pyc,,
+hydra/_internal/core_plugins/__pycache__/zsh_completion.cpython-312.pyc,,
+hydra/_internal/core_plugins/bash_completion.py,sha256=Cat1tWgNyO2XCx2hmJ9ccT7igmAXnZd5tS0Kjd-yBuI,2957
+hydra/_internal/core_plugins/basic_launcher.py,sha256=pwa-Fkyguojr7EhL9rTM-JKSLASJ_VDwos4_W-OVux8,2755
+hydra/_internal/core_plugins/basic_sweeper.py,sha256=08K6gKtgb5XOStc4RgZ0Qi1w2eO4Q_DDIFmyQhgSigc,6580
+hydra/_internal/core_plugins/file_config_source.py,sha256=DXFmLksisy-lUK1FV8rkkww7C8CkSY3gymI6DVRtX3w,2317
+hydra/_internal/core_plugins/fish_completion.py,sha256=-koFBCRDZuUdqa_4sX6rNcHAo8e4VDLOyQmBtMHYpIM,2395
+hydra/_internal/core_plugins/importlib_resources_config_source.py,sha256=1hNPJ_Bx56qqaUhk9297iji5bOkdjUDOYL457GOmgyw,3686
+hydra/_internal/core_plugins/structured_config_source.py,sha256=Rfnn2JQEIrA5JWkT2WtpMLAl8xXVKVd_oaUduomippw,2348
+hydra/_internal/core_plugins/zsh_completion.py,sha256=QyJYgft0iFi5oCMA67b10N1v0DOzLypII5gPjIA-a5A,1564
+hydra/_internal/defaults_list.py,sha256=P8t0hgLaxeTKOyVJEZgdSdci3ojXhggl__aasNVEvF0,27080
+hydra/_internal/deprecation_warning.py,sha256=LnpqHkxTd-nCol4ZRsSsIWvmw3QmXW0640Q6AH76EmI,435
+hydra/_internal/grammar/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/_internal/grammar/__pycache__/__init__.cpython-312.pyc,,
+hydra/_internal/grammar/__pycache__/functions.cpython-312.pyc,,
+hydra/_internal/grammar/__pycache__/grammar_functions.cpython-312.pyc,,
+hydra/_internal/grammar/__pycache__/utils.cpython-312.pyc,,
+hydra/_internal/grammar/functions.py,sha256=qqH0PUgKCsTbucVB-HaJurm7kofhqKhXSYwMcD65hY0,2657
+hydra/_internal/grammar/grammar_functions.py,sha256=Tm41GXsVVRXr7t8PNAlvtuHPUTdYPXV32q04BfFdhOg,11797
+hydra/_internal/grammar/utils.py,sha256=yAjqmRXhub_TyUFLwRPWdIMqzvEBIgiX7mM-MnKaRlA,2133
+hydra/_internal/hydra.py,sha256=dLGZgMjrn-68mrF_yBcgoaCnZYbuHUWdr4Zwk4LDT4A,24147
+hydra/_internal/instantiate/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/_internal/instantiate/__pycache__/__init__.cpython-312.pyc,,
+hydra/_internal/instantiate/__pycache__/_instantiate2.cpython-312.pyc,,
+hydra/_internal/instantiate/_instantiate2.py,sha256=nk1NlHv-5M4uLfJunM_2sPIith5fTIIJb5Pl-irgv_M,14582
+hydra/_internal/sources_registry.py,sha256=a4rbFFdJjUWNJeqMqSq7XDI4gA4f4qcvZ40nsev3zro,1298
+hydra/_internal/utils.py,sha256=fXv__IqF6nHyO4IDfdYrAjgJw8yUb0_KUuwYt8AHRRs,22368
+hydra/compose.py,sha256=l1yF3TNFc3-7gzUYNO1DG6N2MO8hG5Yn0ibv22T8sAg,2105
+hydra/conf/__init__.py,sha256=SJas-v9d3FLB4MoY2_ZgixQH0z2hrTZCNcJ0SC4W5sA,5367
+hydra/conf/__pycache__/__init__.cpython-312.pyc,,
+hydra/conf/hydra/env/default.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/conf/hydra/help/default.yaml,sha256=AUD1lPqLfdy2r53PCTFHNUuowx9jJMIRbC4lgvHCbH8,849
+hydra/conf/hydra/hydra_help/default.yaml,sha256=ze_nZtRYnL9NbKsyVuZPMlOopBCA_KhA4Ef0yzzh_Wo,642
+hydra/conf/hydra/hydra_logging/default.yaml,sha256=MeOAlUeXmwvu_CuhjQ9VxAFMjeopDm8_9ra2vp-B3BA,306
+hydra/conf/hydra/hydra_logging/disabled.yaml,sha256=mRf0I709p7OCmm0y5QTxMKwQumK13Ma-N4VmTXe_yu4,63
+hydra/conf/hydra/hydra_logging/hydra_debug.yaml,sha256=xA2oHZaKsrhjeMq7w1KWOnGdQeIhAxyh_qFO_sPZ8BQ,453
+hydra/conf/hydra/hydra_logging/none.yaml,sha256=gSCRtxF28DB3-gedm1zek7kiN8kyaNYD_m6tRJUaol4,54
+hydra/conf/hydra/job_logging/default.yaml,sha256=7fW0NU3vyIKV6HbtiLuCLCT0gtk3y-QXgwBsCPW2ooA,477
+hydra/conf/hydra/job_logging/disabled.yaml,sha256=mRf0I709p7OCmm0y5QTxMKwQumK13Ma-N4VmTXe_yu4,63
+hydra/conf/hydra/job_logging/none.yaml,sha256=gSCRtxF28DB3-gedm1zek7kiN8kyaNYD_m6tRJUaol4,54
+hydra/conf/hydra/job_logging/stdout.yaml,sha256=7hHFp_oMHxrMKILBbc2fJySl_7y2u_9RbEentTs2zRY,280
+hydra/conf/hydra/output/default.yaml,sha256=reeuSYxBy0_oqnzUM9sSULVAzjb8p7IsxJBHUccxtD8,152
+hydra/core/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/core/__pycache__/__init__.cpython-312.pyc,,
+hydra/core/__pycache__/config_loader.cpython-312.pyc,,
+hydra/core/__pycache__/config_search_path.cpython-312.pyc,,
+hydra/core/__pycache__/config_store.cpython-312.pyc,,
+hydra/core/__pycache__/default_element.cpython-312.pyc,,
+hydra/core/__pycache__/global_hydra.cpython-312.pyc,,
+hydra/core/__pycache__/hydra_config.cpython-312.pyc,,
+hydra/core/__pycache__/object_type.cpython-312.pyc,,
+hydra/core/__pycache__/plugins.cpython-312.pyc,,
+hydra/core/__pycache__/singleton.cpython-312.pyc,,
+hydra/core/__pycache__/utils.cpython-312.pyc,,
+hydra/core/config_loader.py,sha256=Tad4xnUwFilgY26eDyW2ufKTfH3unABQRyZf3GHlVAc,1577
+hydra/core/config_search_path.py,sha256=VGfSUlZTuE1XzMFoTluk6TflRWilMC237c3HIuDZa5U,2070
+hydra/core/config_store.py,sha256=jricEy6sBv-IfOxBrv4oYxHR1Afl9aAfiiqdtcoFaN0,4609
+hydra/core/default_element.py,sha256=dWnHKVU7L9C2n0uQevrgpXnGXG3nGPrw8yFACFujyrA,18196
+hydra/core/global_hydra.py,sha256=iEap-YFkxNhkbjuu5vElm7-nWpAmyB9x8Yk420YFsOc,1324
+hydra/core/hydra_config.py,sha256=H6kHPrsG8Cuyd8f4vvrWhqS4RTW1RcK-pdSUodPJnPI,1561
+hydra/core/object_type.py,sha256=usNw3bC9dhY0cNCRgjd0V8rKka2KswBDukSWGM9uVgk,166
+hydra/core/override_parser/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/core/override_parser/__pycache__/__init__.cpython-312.pyc,,
+hydra/core/override_parser/__pycache__/overrides_parser.cpython-312.pyc,,
+hydra/core/override_parser/__pycache__/overrides_visitor.cpython-312.pyc,,
+hydra/core/override_parser/__pycache__/types.cpython-312.pyc,,
+hydra/core/override_parser/overrides_parser.py,sha256=nK8k8aDznA6zozWdSih8KIJN5qKUJadFLuW8SqQveno,4720
+hydra/core/override_parser/overrides_visitor.py,sha256=Mz3LJfUUMad5oDYPvJ4ikd3VF2g6SwpWF1-HknxAUUM,15505
+hydra/core/override_parser/types.py,sha256=1Yn1g1tap6uw4NdSr4nwMDwoqkQ3TCBUkyy1Bk6CELc,15969
+hydra/core/plugins.py,sha256=uwNG8A-68rAHUybO2rPimnmn94JTRU4FxfrQAgS3vr0,10390
+hydra/core/singleton.py,sha256=JFVdknkZ57Q91clfnvNUjOL0k85ksOXfTgFozBjbwTk,1338
+hydra/core/utils.py,sha256=vetFUh9jAa-Zfx-XetioCKcSDf7jk4mJkVSY8sjMONg,10502
+hydra/errors.py,sha256=UUYsCUnmruoxXJ4rdEZzEEhGlmO0XpmitPV52rWgCj4,1071
+hydra/experimental/__init__.py,sha256=LXsinWfSh6NWcg5ykYlh39w6YP4K0eTL66enyb5msx4,293
+hydra/experimental/__pycache__/__init__.cpython-312.pyc,,
+hydra/experimental/__pycache__/callback.cpython-312.pyc,,
+hydra/experimental/__pycache__/callbacks.cpython-312.pyc,,
+hydra/experimental/__pycache__/compose.cpython-312.pyc,,
+hydra/experimental/__pycache__/initialize.cpython-312.pyc,,
+hydra/experimental/callback.py,sha256=C81NmVIkrO_x_Hx6cP9Gk2Bha1eZUZcUFqQ7h5jt5Lw,2378
+hydra/experimental/callbacks.py,sha256=hNeJHD6P97RCuWKSdh2PQM_0RAnTApWS6-5itREpWQY,2336
+hydra/experimental/compose.py,sha256=1R8-RGoR8ShhL6XFOgSjtoYsMw2X3x99fOsER13JspY,845
+hydra/experimental/initialize.py,sha256=qRJgHq3ny53fWyMg2WRRs6mTVMJveh-uDafNOtd-sKE,4280
+hydra/extra/__pycache__/pytest_plugin.cpython-312.pyc,,
+hydra/extra/pytest_plugin.py,sha256=XwdmKS_Xat8PgtXcZGsLECC_KV17SlWB5HIjgytEwlQ,2612
+hydra/grammar/.gitignore,sha256=JvbRQEPLotalz9jq9ech5OCzehPqN1O-hKph7r5X0X8,21
+hydra/grammar/OverrideLexer.g4,sha256=SLf4ErlQqLhuqYcxm1-csIdcu-60bvVxSpiZ5EVnlEY,2825
+hydra/grammar/OverrideParser.g4,sha256=3_p5QSlZ8KloLGKQecC0_rZlFGe6t3cfLQz-EMHwiB8,3115
+hydra/grammar/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/grammar/__pycache__/__init__.cpython-312.pyc,,
+hydra/grammar/gen/.gitignore,sha256=vdSMzqaxj4aI7cH5OV3jvNZV3zoOSCfHoloOw1mjc1g,15
+hydra/grammar/gen/OverrideLexer.interp,sha256=CAB_gEC1QNK8Z2PWuVfqdtJ2JB8UohDbFU9gIOCgJi8,14138
+hydra/grammar/gen/OverrideLexer.py,sha256=QnpWqy5IwvVEwj6lUMtCQNOX7TTAxyuIiViIQpcb3n4,16474
+hydra/grammar/gen/OverrideLexer.tokens,sha256=ACZgg3CkJwkQxl_n0s6d_3rRUzkbHEchRf7FHa2xZoQ,289
+hydra/grammar/gen/OverrideParser.interp,sha256=Xw3O4PVsBEG721C5fZHbRaNDXYYnNucMfYBnlh6nipI,5685
+hydra/grammar/gen/OverrideParser.py,sha256=EUpl4DORSkb_uOk40yISotNgzm9PBmc4D37KcOurmPU,50516
+hydra/grammar/gen/OverrideParser.tokens,sha256=ACZgg3CkJwkQxl_n0s6d_3rRUzkbHEchRf7FHa2xZoQ,289
+hydra/grammar/gen/OverrideParserListener.py,sha256=ebXxzEWA3YV3Z6duMMb4OljCOSSpR2gGhWztc8IL-iM,4476
+hydra/grammar/gen/OverrideParserVisitor.py,sha256=N6Ga-PNK5KM0s9YLPXO07k0dCk-2r5kUMUzh0ulBpeM,2834
+hydra/grammar/gen/__pycache__/OverrideLexer.cpython-312.pyc,,
+hydra/grammar/gen/__pycache__/OverrideParser.cpython-312.pyc,,
+hydra/grammar/gen/__pycache__/OverrideParserListener.cpython-312.pyc,,
+hydra/grammar/gen/__pycache__/OverrideParserVisitor.cpython-312.pyc,,
+hydra/initialize.py,sha256=HgB87jmyNJwImdGQ1xyw_B1z_Hd8QsIwEUFWOBgy6Q4,6035
+hydra/main.py,sha256=t6_RYPzn-nrp_6Rae3dVxONXVu4OVgNNtS9q2OKLcOA,3999
+hydra/plugins/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/plugins/__pycache__/__init__.cpython-312.pyc,,
+hydra/plugins/__pycache__/completion_plugin.cpython-312.pyc,,
+hydra/plugins/__pycache__/config_source.cpython-312.pyc,,
+hydra/plugins/__pycache__/launcher.cpython-312.pyc,,
+hydra/plugins/__pycache__/plugin.cpython-312.pyc,,
+hydra/plugins/__pycache__/search_path_plugin.cpython-312.pyc,,
+hydra/plugins/__pycache__/sweeper.cpython-312.pyc,,
+hydra/plugins/completion_plugin.py,sha256=_1VjimIuA0QDJm_LI98SoOPpi_L6Oad8ljP5FRbAU-I,11275
+hydra/plugins/config_source.py,sha256=W1E60f-J18e_29G6PV32P-MlWAY438uZGbjMkQHMtHQ,5109
+hydra/plugins/launcher.py,sha256=v1ojrXmKmG69lbubCppepJt6URDJwFnhO4gTTZuo6BM,974
+hydra/plugins/plugin.py,sha256=eTvzaLxlJCmbMbroP9Z1u8wxNWa4R0qPAGqLzh6WM7M,120
+hydra/plugins/search_path_plugin.py,sha256=bn78U0cxqP2QZUkCh23LbtR0rDthewm8iOCGfv5gcF8,333
+hydra/plugins/sweeper.py,sha256=J6fVEpbiiDxHCLadLX1wDX3484oxYoDv0q0Jv9TNfaY,2108
+hydra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/test_utils/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/test_utils/__pycache__/__init__.cpython-312.pyc,,
+hydra/test_utils/__pycache__/a_module.cpython-312.pyc,,
+hydra/test_utils/__pycache__/completion.cpython-312.pyc,,
+hydra/test_utils/__pycache__/config_source_common_tests.cpython-312.pyc,,
+hydra/test_utils/__pycache__/example_app.cpython-312.pyc,,
+hydra/test_utils/__pycache__/launcher_common_tests.cpython-312.pyc,,
+hydra/test_utils/__pycache__/test_utils.cpython-312.pyc,,
+hydra/test_utils/a_module.py,sha256=QHSQjj55BP3g0uz7ESTGNn_p6TJirYdFer8Xbh5M6Wg,228
+hydra/test_utils/completion.py,sha256=ctgyUAoLRKQ05BrNweexBvA-9aeITu1HrUFAtfIrCi0,344
+hydra/test_utils/config_source_common_tests.py,sha256=zWi3KaWhEJ76EnN-5Q6qiuoNvqUpM5LlbzvgOJI1YLk,10900
+hydra/test_utils/configs/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/test_utils/configs/__pycache__/__init__.cpython-312.pyc,,
+hydra/test_utils/configs/accessing_hydra_config.yaml,sha256=f8yFOBhVJ3I0hga2uDxi-xHk7QUqIuYozrOIkrvh4G8,92
+hydra/test_utils/configs/completion_test/additional_searchpath.yaml,sha256=RGca4IOnI2g0ha3W29uMBrw4uKKaaAu-r43SFKpb3JY,201
+hydra/test_utils/configs/completion_test/config.yaml,sha256=onD8qwP45YO6uUiaNK6sdQYteWtCeu6stg_JUxWc4Zs,276
+hydra/test_utils/configs/completion_test/group/dict.yaml,sha256=oWGInDFSV2mNHsuy5WaltWR-0jzJr5OsgVvdx6AxgS8,117
+hydra/test_utils/configs/completion_test/group/list.yaml,sha256=2-PX667W1Zto3zIrEvYD4UTH9jEtWR6FEIthukyiiRs,106
+hydra/test_utils/configs/completion_test/hydra/launcher/fairtask.yaml,sha256=5d7flmlKaueWrLwxJRrly8h8Yu14z-zmEH6oVcUAB6w,44
+hydra/test_utils/configs/completion_test/missing_default.yaml,sha256=BXNHsVCvz997Bd1JV43lhwlzYOGDnGA7hcA5AGcXMks,25
+hydra/test_utils/configs/completion_test/test_hydra/launcher/fairtask.yaml,sha256=5d7flmlKaueWrLwxJRrly8h8Yu14z-zmEH6oVcUAB6w,44
+hydra/test_utils/configs/completion_test_additional_file/additional_group/file_opt_additional.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/test_utils/configs/completion_test_additional_file/group/file_opt.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/test_utils/configs/completion_test_additional_package/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/test_utils/configs/completion_test_additional_package/__pycache__/__init__.cpython-312.pyc,,
+hydra/test_utils/configs/completion_test_additional_package/additional_group/pkg_opt_additional.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/test_utils/configs/completion_test_additional_package/group/pkg_opt.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra/test_utils/configs/compose.yaml,sha256=PbvagKNJBdD8K9iV5kHd1k0wSYUPxfCjTv5rlkpiAwY,66
+hydra/test_utils/configs/conf.zip,sha256=XEcej6nwGh_GVQdJliLaZt_yvpbxv40AlH8UMyJwpLs,210
+hydra/test_utils/configs/config.yaml,sha256=jvSKJD57W9fusuDdeWzGMmEkue2eB4Y8z67YK4R1fo0,45
+hydra/test_utils/configs/config.yml,sha256=OXO4U6morVmVmXOIP1_09a7VaWKWLK_8itBFpdBIXLY,55
+hydra/test_utils/configs/custom_resolver.yaml,sha256=1vVJqYjuhrLZljWaZSiRldUNIclX01dup0C-wfwXhnk,26
+hydra/test_utils/configs/db/mysql.yaml,sha256=OhvOLGF11LrYqD7F4hElfJDGWrRAiN2P5FzzebYvgWM,42
+hydra/test_utils/configs/db/postgresql.yaml,sha256=YLxrF76CCl4ezdCkxnVAH2xHS6w_1-R2pko748n7B_c,70
+hydra/test_utils/configs/db/validated_mysql.yaml,sha256=4t357FDiODkD7eY1YveNsbuiLpl6tLnh3zQH9viHCYk,68
+hydra/test_utils/configs/db/validated_postgresql.yaml,sha256=NaMWjp4CC_M2Ef5njP0g-5F7docESNDRJ9q66Rig0G8,101
+hydra/test_utils/configs/db_conf.yaml,sha256=EwJbm3o9wfYfPc5WdU6anorcatikENpMSOrLYMSPe5c,24
+hydra/test_utils/configs/defaults_not_list.yaml,sha256=TGijnGK1kGosfiL11q6QCBaOd1h5AoYqRwW4rm4srY4,41
+hydra/test_utils/configs/group1/abc.cde.yaml,sha256=38OuMSH0NPr_GHZLyRZj_MKQ3Eh6DkmimFSH66_3Y8E,28
+hydra/test_utils/configs/group1/file1.yaml,sha256=YQt7eI9ChNBTwhWz6TaMal5wEbrEwYIHx65Vy1eJoPU,28
+hydra/test_utils/configs/group1/file2.yaml,sha256=wAju0MHYv7R9IrF4MukME4pqGq0TsEddGfu1mghEOE8,28
+hydra/test_utils/configs/group2/file1.yaml,sha256=1nGQpv2YZPZBARi-PBsBxM9USDisHC9gS3idm1xxU4c,29
+hydra/test_utils/configs/group2/file2.yaml,sha256=CxJUCyfUgvv129J-CuEQobot0H0Y0W9yq_hf7fjdcSk,29
+hydra/test_utils/configs/missing-default.yaml,sha256=h_tYW2f9FHU194574y4nW1eTcOqRJ2NNFdWWD74Ztb8,25
+hydra/test_utils/configs/missing-optional-default.yaml,sha256=SnHTJeh9op11czTFCIoMFwCci9G3Wtr6S-1DQAHqxbs,36
+hydra/test_utils/configs/missing_init_py/.gitignore,sha256=kCpRPdl3S_jqYYZaOrc0-xa6-l3KqVjNRXc6jCkd_-Q,12
+hydra/test_utils/configs/missing_init_py/test.yaml,sha256=RPxWgmo0Tv8QFgW5AMXByB91h7259MlDoTZKqrWso-I,6
+hydra/test_utils/configs/optional-default.yaml,sha256=jwH3O7Wn5fTdDYpXzCMFxSY8KEkU2bJPSZ3d_wLaF3Y,37
+hydra/test_utils/configs/overriding_output_dir.yaml,sha256=30luCOSutKoCnSKOf0ixoUkRagpUejbserz4VH5mEgk,27
+hydra/test_utils/configs/overriding_run_dir.yaml,sha256=qkstQzIBVkqOJFlziQdurKeNUshx3WoJZG13WsVg1js,27
+hydra/test_utils/configs/package_tests/__init__.py,sha256=nlNcRa8JrCTvjVYxoVH3f-uv5yqSrWv-stlB8MSlTjU,71
+hydra/test_utils/configs/package_tests/__pycache__/__init__.cpython-312.pyc,,
+hydra/test_utils/configs/package_tests/group1/option1.yaml,sha256=IvqLmZEXrlkuj0ZTGo5NzPGfi2UxXKUAmKx_xVlpHt4,41
+hydra/test_utils/configs/package_tests/group1/option2.yaml,sha256=ZY-wEvZgZ9nQwB8oytNd_0uzgoxqUk7sza5f1PC4RLI,41
+hydra/test_utils/configs/package_tests/group2/option1.yaml,sha256=mPUhhwInAsKJEA1Z9laGXcbi8y36vPlkqP9LTexQwwc,41
+hydra/test_utils/configs/package_tests/group2/option2.yaml,sha256=LLBCZrMTnfsAFNaG2uOORaHm8lmbMWKtTzDpQFDkNdw,41
+hydra/test_utils/configs/package_tests/pkg_override.yaml,sha256=K7UZYvrYuPJnvVbC6ukhJDWvoQfSV84l3iUFWXs1Psw,55
+hydra/test_utils/configs/package_tests/two_packages_one_group.yaml,sha256=D8qyM20PXYvYC43lokYet8WdknJG-itUnYfDD9okWRI,60
+hydra/test_utils/configs/schema_key_error.yaml,sha256=BgPV_otZ8OxZfcn3xvSquLGdxM7f44KWNPJ2jFcxafU,56
+hydra/test_utils/configs/schema_validation_error.yaml,sha256=T61LUtehGtAMU_t44J9btkD6vxjDikdNGWcS2AKGHjY,54
+hydra/test_utils/configs/some_config.yaml,sha256=WfxBETosvbSbpDYGusT2KQk2wgHOaCo0WaXBGUzU3oI,38
+hydra/test_utils/configs/top_level_list/file1.yaml,sha256=JPFXvrO7aYFybvktEExKoefg40ux6Eo7IzoCCpY9_VM,4
+hydra/test_utils/configs/unspecified_mandatory_default.yaml,sha256=UOFn7b-MBSvyuuAoco3t3uEHDMWiTxgM5-iOGhcVejQ,26
+hydra/test_utils/example_app.py,sha256=EeviU2MudEMera8AfGJ1wQ5WyWqwu6NQ7GF4KvVpzFo,323
+hydra/test_utils/launcher_common_tests.py,sha256=JE-a9e6QzzwIEwGBCxn501HTOvQOl1054CzgL8bOUiw,23552
+hydra/test_utils/test_utils.py,sha256=jL_MWw3nuII4JrS_GOoPHD0OFcgpx2ezmAAy2aSD7hU,15335
+hydra/types.py,sha256=ek7sEeAiaDpkVmndhHFUXSQIJnVKi4IqUf-JYhy86xY,2913
+hydra/utils.py,sha256=IIILg5TmAZi4F_if49eliLZ3SOxY95ey5wKQ_DPGAs0,3272
+hydra/version.py,sha256=07N_T8N48zL9VAifYpP39T2UZ_U15ttnlysdGWDKoh4,2573
+hydra_core-1.3.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+hydra_core-1.3.2.dist-info/LICENSE,sha256=UkEte8fOQVfqYou6rLiCngqcs8WPV_mRdhJryM8r_IU,1086
+hydra_core-1.3.2.dist-info/METADATA,sha256=OZBGy_mufrq439AJ4rT3SCEscQoOdcpQGnK7stRW4uc,5480
+hydra_core-1.3.2.dist-info/RECORD,,
+hydra_core-1.3.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+hydra_core-1.3.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
+hydra_core-1.3.2.dist-info/entry_points.txt,sha256=rDwQ60t46fQ9gkvlsjN-RvQcJdbDUhRdSxPANxU_32E,52
+hydra_core-1.3.2.dist-info/top_level.txt,sha256=izgzyEMiRL3xiwDnTKYJY7UL0mWcGbI7tqJ9o3FMbQI,6
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/REQUESTED b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/WHEEL b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..57e3d840d59a650ac5bccbad5baeec47d155f0ad
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.38.4)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/entry_points.txt b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9fdfdc88316d9a34270bff43520752ae1a06f636
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/entry_points.txt
@@ -0,0 +1,2 @@
+[pytest11]
+hydra_pytest = hydra.extra.pytest_plugin
diff --git a/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/top_level.txt b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..83f02065ddfc215f8f31d8b9c7fbdf33aa6031b4
--- /dev/null
+++ b/lib/python3.12/site-packages/hydra_core-1.3.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+hydra
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/INSTALLER b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/License.txt b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/License.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b491c70e0aef319022ded661e111ddbd45b8a17f
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/License.txt
@@ -0,0 +1,1568 @@
+End User License Agreement
+--------------------------
+
+
+Preface
+-------
+
+The Software License Agreement in Chapter 1 and the Supplement
+in Chapter 2 contain license terms and conditions that govern
+the use of NVIDIA software. By accepting this agreement, you
+agree to comply with all the terms and conditions applicable
+to the product(s) included herein.
+
+
+NVIDIA Driver
+
+
+Description
+
+This package contains the operating system driver and
+fundamental system software components for NVIDIA GPUs.
+
+
+NVIDIA CUDA Toolkit
+
+
+Description
+
+The NVIDIA CUDA Toolkit provides command-line and graphical
+tools for building, debugging and optimizing the performance
+of applications accelerated by NVIDIA GPUs, runtime and math
+libraries, and documentation including programming guides,
+user manuals, and API references.
+
+
+Default Install Location of CUDA Toolkit
+
+Windows platform:
+
+%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v#.#
+
+Linux platform:
+
+/usr/local/cuda-#.#
+
+Mac platform:
+
+/Developer/NVIDIA/CUDA-#.#
+
+
+NVIDIA CUDA Samples
+
+
+Description
+
+This package includes over 100+ CUDA examples that demonstrate
+various CUDA programming principles, and efficient CUDA
+implementation of algorithms in specific application domains.
+
+
+Default Install Location of CUDA Samples
+
+Windows platform:
+
+%ProgramData%\NVIDIA Corporation\CUDA Samples\v#.#
+
+Linux platform:
+
+/usr/local/cuda-#.#/samples
+
+and
+
+$HOME/NVIDIA_CUDA-#.#_Samples
+
+Mac platform:
+
+/Developer/NVIDIA/CUDA-#.#/samples
+
+
+NVIDIA Nsight Visual Studio Edition (Windows only)
+
+
+Description
+
+NVIDIA Nsight Development Platform, Visual Studio Edition is a
+development environment integrated into Microsoft Visual
+Studio that provides tools for debugging, profiling, analyzing
+and optimizing your GPU computing and graphics applications.
+
+
+Default Install Location of Nsight Visual Studio Edition
+
+Windows platform:
+
+%ProgramFiles(x86)%\NVIDIA Corporation\Nsight Visual Studio Edition #.#
+
+
+1. License Agreement for NVIDIA Software Development Kits
+---------------------------------------------------------
+
+
+Release Date: July 26, 2018
+---------------------------
+
+
+Important NoticeRead before downloading, installing,
+copying or using the licensed software:
+-------------------------------------------------------
+
+This license agreement, including exhibits attached
+("Agreement”) is a legal agreement between you and NVIDIA
+Corporation ("NVIDIA") and governs your use of a NVIDIA
+software development kit (“SDK”).
+
+Each SDK has its own set of software and materials, but here
+is a description of the types of items that may be included in
+a SDK: source code, header files, APIs, data sets and assets
+(examples include images, textures, models, scenes, videos,
+native API input/output files), binary software, sample code,
+libraries, utility programs, programming code and
+documentation.
+
+This Agreement can be accepted only by an adult of legal age
+of majority in the country in which the SDK is used.
+
+If you are entering into this Agreement on behalf of a company
+or other legal entity, you represent that you have the legal
+authority to bind the entity to this Agreement, in which case
+“you” will mean the entity you represent.
+
+If you don’t have the required age or authority to accept
+this Agreement, or if you don’t accept all the terms and
+conditions of this Agreement, do not download, install or use
+the SDK.
+
+You agree to use the SDK only for purposes that are permitted
+by (a) this Agreement, and (b) any applicable law, regulation
+or generally accepted practices or guidelines in the relevant
+jurisdictions.
+
+
+1.1. License
+
+
+1.1.1. License Grant
+
+Subject to the terms of this Agreement, NVIDIA hereby grants
+you a non-exclusive, non-transferable license, without the
+right to sublicense (except as expressly provided in this
+Agreement) to:
+
+ 1. Install and use the SDK,
+
+ 2. Modify and create derivative works of sample source code
+ delivered in the SDK, and
+
+ 3. Distribute those portions of the SDK that are identified
+ in this Agreement as distributable, as incorporated in
+ object code format into a software application that meets
+ the distribution requirements indicated in this Agreement.
+
+
+1.1.2. Distribution Requirements
+
+These are the distribution requirements for you to exercise
+the distribution grant:
+
+ 1. Your application must have material additional
+ functionality, beyond the included portions of the SDK.
+
+ 2. The distributable portions of the SDK shall only be
+ accessed by your application.
+
+ 3. The following notice shall be included in modifications
+ and derivative works of sample source code distributed:
+ “This software contains source code provided by NVIDIA
+ Corporation.”
+
+ 4. Unless a developer tool is identified in this Agreement
+ as distributable, it is delivered for your internal use
+ only.
+
+ 5. The terms under which you distribute your application
+ must be consistent with the terms of this Agreement,
+ including (without limitation) terms relating to the
+ license grant and license restrictions and protection of
+ NVIDIA’s intellectual property rights. Additionally, you
+ agree that you will protect the privacy, security and
+ legal rights of your application users.
+
+ 6. You agree to notify NVIDIA in writing of any known or
+ suspected distribution or use of the SDK not in compliance
+ with the requirements of this Agreement, and to enforce
+ the terms of your agreements with respect to distributed
+ SDK.
+
+
+1.1.3. Authorized Users
+
+You may allow employees and contractors of your entity or of
+your subsidiary(ies) to access and use the SDK from your
+secure network to perform work on your behalf.
+
+If you are an academic institution you may allow users
+enrolled or employed by the academic institution to access and
+use the SDK from your secure network.
+
+You are responsible for the compliance with the terms of this
+Agreement by your authorized users. If you become aware that
+your authorized users didn’t follow the terms of this
+Agreement, you agree to take reasonable steps to resolve the
+non-compliance and prevent new occurrences.
+
+
+1.1.4. Pre-Release SDK
+
+The SDK versions identified as alpha, beta, preview or
+otherwise as pre-release, may not be fully functional, may
+contain errors or design flaws, and may have reduced or
+different security, privacy, accessibility, availability, and
+reliability standards relative to commercial versions of
+NVIDIA software and materials. Use of a pre-release SDK may
+result in unexpected results, loss of data, project delays or
+other unpredictable damage or loss.
+
+You may use a pre-release SDK at your own risk, understanding
+that pre-release SDKs are not intended for use in production
+or business-critical systems.
+
+NVIDIA may choose not to make available a commercial version
+of any pre-release SDK. NVIDIA may also choose to abandon
+development and terminate the availability of a pre-release
+SDK at any time without liability.
+
+
+1.1.5. Updates
+
+NVIDIA may, at its option, make available patches, workarounds
+or other updates to this SDK. Unless the updates are provided
+with their separate governing terms, they are deemed part of
+the SDK licensed to you as provided in this Agreement. You
+agree that the form and content of the SDK that NVIDIA
+provides may change without prior notice to you. While NVIDIA
+generally maintains compatibility between versions, NVIDIA may
+in some cases make changes that introduce incompatibilities in
+future versions of the SDK.
+
+
+1.1.6. Third Party Licenses
+
+The SDK may come bundled with, or otherwise include or be
+distributed with, third party software licensed by a NVIDIA
+supplier and/or open source software provided under an open
+source license. Use of third party software is subject to the
+third-party license terms, or in the absence of third party
+terms, the terms of this Agreement. Copyright to third party
+software is held by the copyright holders indicated in the
+third-party software or license.
+
+
+1.1.7. Reservation of Rights
+
+NVIDIA reserves all rights, title, and interest in and to the
+SDK, not expressly granted to you under this Agreement.
+
+
+1.2. Limitations
+
+The following license limitations apply to your use of the
+SDK:
+
+ 1. You may not reverse engineer, decompile or disassemble,
+ or remove copyright or other proprietary notices from any
+ portion of the SDK or copies of the SDK.
+
+ 2. Except as expressly provided in this Agreement, you may
+ not copy, sell, rent, sublicense, transfer, distribute,
+ modify, or create derivative works of any portion of the
+ SDK. For clarity, you may not distribute or sublicense the
+ SDK as a stand-alone product.
+
+ 3. Unless you have an agreement with NVIDIA for this
+ purpose, you may not indicate that an application created
+ with the SDK is sponsored or endorsed by NVIDIA.
+
+ 4. You may not bypass, disable, or circumvent any
+ encryption, security, digital rights management or
+ authentication mechanism in the SDK.
+
+ 5. You may not use the SDK in any manner that would cause it
+ to become subject to an open source software license. As
+ examples, licenses that require as a condition of use,
+ modification, and/or distribution that the SDK be:
+
+ a. Disclosed or distributed in source code form;
+
+ b. Licensed for the purpose of making derivative works;
+ or
+
+ c. Redistributable at no charge.
+
+ 6. Unless you have an agreement with NVIDIA for this
+ purpose, you may not use the SDK with any system or
+ application where the use or failure of the system or
+ application can reasonably be expected to threaten or
+ result in personal injury, death, or catastrophic loss.
+ Examples include use in avionics, navigation, military,
+ medical, life support or other life critical applications.
+ NVIDIA does not design, test or manufacture the SDK for
+ these critical uses and NVIDIA shall not be liable to you
+ or any third party, in whole or in part, for any claims or
+ damages arising from such uses.
+
+ 7. You agree to defend, indemnify and hold harmless NVIDIA
+ and its affiliates, and their respective employees,
+ contractors, agents, officers and directors, from and
+ against any and all claims, damages, obligations, losses,
+ liabilities, costs or debt, fines, restitutions and
+ expenses (including but not limited to attorney’s fees
+ and costs incident to establishing the right of
+ indemnification) arising out of or related to your use of
+ the SDK outside of the scope of this Agreement, or not in
+ compliance with its terms.
+
+
+1.3. Ownership
+
+ 1. NVIDIA or its licensors hold all rights, title and
+ interest in and to the SDK and its modifications and
+ derivative works, including their respective intellectual
+ property rights, subject to your rights described in this
+ section. This SDK may include software and materials from
+ NVIDIA’s licensors, and these licensors are intended
+ third party beneficiaries that may enforce this Agreement
+ with respect to their intellectual property rights.
+
+ 2. You hold all rights, title and interest in and to your
+ applications and your derivative works of the sample
+ source code delivered in the SDK, including their
+ respective intellectual property rights, subject to
+ NVIDIA’s rights described in this section.
+
+ 3. You may, but don’t have to, provide to NVIDIA
+ suggestions, feature requests or other feedback regarding
+ the SDK, including possible enhancements or modifications
+ to the SDK. For any feedback that you voluntarily provide,
+ you hereby grant NVIDIA and its affiliates a perpetual,
+ non-exclusive, worldwide, irrevocable license to use,
+ reproduce, modify, license, sublicense (through multiple
+ tiers of sublicensees), and distribute (through multiple
+ tiers of distributors) it without the payment of any
+ royalties or fees to you. NVIDIA will use feedback at its
+ choice. NVIDIA is constantly looking for ways to improve
+ its products, so you may send feedback to NVIDIA through
+ the developer portal at https://developer.nvidia.com.
+
+
+1.4. No Warranties
+
+THE SDK IS PROVIDED BY NVIDIA “AS IS” AND “WITH ALL
+FAULTS.” TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND
+ITS AFFILIATES EXPRESSLY DISCLAIM ALL WARRANTIES OF ANY KIND
+OR NATURE, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING,
+BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR THE
+ABSENCE OF ANY DEFECTS THEREIN, WHETHER LATENT OR PATENT. NO
+WARRANTY IS MADE ON THE BASIS OF TRADE USAGE, COURSE OF
+DEALING OR COURSE OF TRADE.
+
+
+1.5. Limitation of Liability
+
+TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND ITS
+AFFILIATES SHALL NOT BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+PUNITIVE OR CONSEQUENTIAL DAMAGES, OR ANY LOST PROFITS, LOSS
+OF USE, LOSS OF DATA OR LOSS OF GOODWILL, OR THE COSTS OF
+PROCURING SUBSTITUTE PRODUCTS, ARISING OUT OF OR IN CONNECTION
+WITH THIS AGREEMENT OR THE USE OR PERFORMANCE OF THE SDK,
+WHETHER SUCH LIABILITY ARISES FROM ANY CLAIM BASED UPON BREACH
+OF CONTRACT, BREACH OF WARRANTY, TORT (INCLUDING NEGLIGENCE),
+PRODUCT LIABILITY OR ANY OTHER CAUSE OF ACTION OR THEORY OF
+LIABILITY. IN NO EVENT WILL NVIDIA’S AND ITS AFFILIATES
+TOTAL CUMULATIVE LIABILITY UNDER OR ARISING OUT OF THIS
+AGREEMENT EXCEED US$10.00. THE NATURE OF THE LIABILITY OR THE
+NUMBER OF CLAIMS OR SUITS SHALL NOT ENLARGE OR EXTEND THIS
+LIMIT.
+
+These exclusions and limitations of liability shall apply
+regardless if NVIDIA or its affiliates have been advised of
+the possibility of such damages, and regardless of whether a
+remedy fails its essential purpose. These exclusions and
+limitations of liability form an essential basis of the
+bargain between the parties, and, absent any of these
+exclusions or limitations of liability, the provisions of this
+Agreement, including, without limitation, the economic terms,
+would be substantially different.
+
+
+1.6. Termination
+
+ 1. This Agreement will continue to apply until terminated by
+ either you or NVIDIA as described below.
+
+ 2. If you want to terminate this Agreement, you may do so by
+ stopping to use the SDK.
+
+ 3. NVIDIA may, at any time, terminate this Agreement if:
+
+ a. (i) you fail to comply with any term of this
+ Agreement and the non-compliance is not fixed within
+ thirty (30) days following notice from NVIDIA (or
+ immediately if you violate NVIDIA’s intellectual
+ property rights);
+
+ b. (ii) you commence or participate in any legal
+ proceeding against NVIDIA with respect to the SDK; or
+
+ c. (iii) NVIDIA decides to no longer provide the SDK in
+ a country or, in NVIDIA’s sole discretion, the
+ continued use of it is no longer commercially viable.
+
+ 4. Upon any termination of this Agreement, you agree to
+ promptly discontinue use of the SDK and destroy all copies
+ in your possession or control. Your prior distributions in
+ accordance with this Agreement are not affected by the
+ termination of this Agreement. Upon written request, you
+ will certify in writing that you have complied with your
+ commitments under this section. Upon any termination of
+ this Agreement all provisions survive except for the
+ license grant provisions.
+
+
+1.7. General
+
+If you wish to assign this Agreement or your rights and
+obligations, including by merger, consolidation, dissolution
+or operation of law, contact NVIDIA to ask for permission. Any
+attempted assignment not approved by NVIDIA in writing shall
+be void and of no effect. NVIDIA may assign, delegate or
+transfer this Agreement and its rights and obligations, and if
+to a non-affiliate you will be notified.
+
+You agree to cooperate with NVIDIA and provide reasonably
+requested information to verify your compliance with this
+Agreement.
+
+This Agreement will be governed in all respects by the laws of
+the United States and of the State of Delaware as those laws
+are applied to contracts entered into and performed entirely
+within Delaware by Delaware residents, without regard to the
+conflicts of laws principles. The United Nations Convention on
+Contracts for the International Sale of Goods is specifically
+disclaimed. You agree to all terms of this Agreement in the
+English language.
+
+The state or federal courts residing in Santa Clara County,
+California shall have exclusive jurisdiction over any dispute
+or claim arising out of this Agreement. Notwithstanding this,
+you agree that NVIDIA shall still be allowed to apply for
+injunctive remedies or an equivalent type of urgent legal
+relief in any jurisdiction.
+
+If any court of competent jurisdiction determines that any
+provision of this Agreement is illegal, invalid or
+unenforceable, such provision will be construed as limited to
+the extent necessary to be consistent with and fully
+enforceable under the law and the remaining provisions will
+remain in full force and effect. Unless otherwise specified,
+remedies are cumulative.
+
+Each party acknowledges and agrees that the other is an
+independent contractor in the performance of this Agreement.
+
+The SDK has been developed entirely at private expense and is
+“commercial items” consisting of “commercial computer
+software” and “commercial computer software
+documentation” provided with RESTRICTED RIGHTS. Use,
+duplication or disclosure by the U.S. Government or a U.S.
+Government subcontractor is subject to the restrictions in
+this Agreement pursuant to DFARS 227.7202-3(a) or as set forth
+in subparagraphs (c)(1) and (2) of the Commercial Computer
+Software - Restricted Rights clause at FAR 52.227-19, as
+applicable. Contractor/manufacturer is NVIDIA, 2788 San Tomas
+Expressway, Santa Clara, CA 95051.
+
+The SDK is subject to United States export laws and
+regulations. You agree that you will not ship, transfer or
+export the SDK into any country, or use the SDK in any manner,
+prohibited by the United States Bureau of Industry and
+Security or economic sanctions regulations administered by the
+U.S. Department of Treasury’s Office of Foreign Assets
+Control (OFAC), or any applicable export laws, restrictions or
+regulations. These laws include restrictions on destinations,
+end users and end use. By accepting this Agreement, you
+confirm that you are not a resident or citizen of any country
+currently embargoed by the U.S. and that you are not otherwise
+prohibited from receiving the SDK.
+
+Any notice delivered by NVIDIA to you under this Agreement
+will be delivered via mail, email or fax. You agree that any
+notices that NVIDIA sends you electronically will satisfy any
+legal communication requirements. Please direct your legal
+notices or other correspondence to NVIDIA Corporation, 2788
+San Tomas Expressway, Santa Clara, California 95051, United
+States of America, Attention: Legal Department.
+
+This Agreement and any exhibits incorporated into this
+Agreement constitute the entire agreement of the parties with
+respect to the subject matter of this Agreement and supersede
+all prior negotiations or documentation exchanged between the
+parties relating to this SDK license. Any additional and/or
+conflicting terms on documents issued by you are null, void,
+and invalid. Any amendment or waiver under this Agreement
+shall be in writing and signed by representatives of both
+parties.
+
+
+2. CUDA Toolkit Supplement to Software License Agreement for
+NVIDIA Software Development Kits
+------------------------------------------------------------
+
+
+Release date: August 16, 2018
+-----------------------------
+
+The terms in this supplement govern your use of the NVIDIA
+CUDA Toolkit SDK under the terms of your license agreement
+(“Agreement”) as modified by this supplement. Capitalized
+terms used but not defined below have the meaning assigned to
+them in the Agreement.
+
+This supplement is an exhibit to the Agreement and is
+incorporated as an integral part of the Agreement. In the
+event of conflict between the terms in this supplement and the
+terms in the Agreement, the terms in this supplement govern.
+
+
+2.1. License Scope
+
+The SDK is licensed for you to develop applications only for
+use in systems with NVIDIA GPUs.
+
+
+2.2. Distribution
+
+The portions of the SDK that are distributable under the
+Agreement are listed in Attachment A.
+
+
+2.3. Operating Systems
+
+Those portions of the SDK designed exclusively for use on the
+Linux or FreeBSD operating systems, or other operating systems
+derived from the source code to these operating systems, may
+be copied and redistributed for use in accordance with this
+Agreement, provided that the object code files are not
+modified in any way (except for unzipping of compressed
+files).
+
+
+2.4. Audio and Video Encoders and Decoders
+
+You acknowledge and agree that it is your sole responsibility
+to obtain any additional third-party licenses required to
+make, have made, use, have used, sell, import, and offer for
+sale your products or services that include or incorporate any
+third-party software and content relating to audio and/or
+video encoders and decoders from, including but not limited
+to, Microsoft, Thomson, Fraunhofer IIS, Sisvel S.p.A.,
+MPEG-LA, and Coding Technologies. NVIDIA does not grant to you
+under this Agreement any necessary patent or other rights with
+respect to any audio and/or video encoders and decoders.
+
+
+2.5. Licensing
+
+If the distribution terms in this Agreement are not suitable
+for your organization, or for any questions regarding this
+Agreement, please contact NVIDIA at
+nvidia-compute-license-questions@nvidia.com.
+
+
+2.6. Attachment A
+
+The following portions of the SDK are distributable under the
+Agreement:
+
+Component
+
+CUDA Runtime
+
+Windows
+
+cudart.dll, cudart_static.lib, cudadevrt.lib
+
+Mac OSX
+
+libcudart.dylib, libcudart_static.a, libcudadevrt.a
+
+Linux
+
+libcudart.so, libcudart_static.a, libcudadevrt.a
+
+Android
+
+libcudart.so, libcudart_static.a, libcudadevrt.a
+
+Component
+
+CUDA FFT Library
+
+Windows
+
+cufft.dll, cufftw.dll, cufft.lib, cufftw.lib
+
+Mac OSX
+
+libcufft.dylib, libcufft_static.a, libcufftw.dylib,
+libcufftw_static.a
+
+Linux
+
+libcufft.so, libcufft_static.a, libcufftw.so,
+libcufftw_static.a
+
+Android
+
+libcufft.so, libcufft_static.a, libcufftw.so,
+libcufftw_static.a
+
+Component
+
+CUDA BLAS Library
+
+Windows
+
+cublas.dll, cublasLt.dll
+
+Mac OSX
+
+libcublas.dylib, libcublasLt.dylib, libcublas_static.a,
+libcublasLt_static.a
+
+Linux
+
+libcublas.so, libcublasLt.so, libcublas_static.a,
+libcublasLt_static.a
+
+Android
+
+libcublas.so, libcublasLt.so, libcublas_static.a,
+libcublasLt_static.a
+
+Component
+
+NVIDIA "Drop-in" BLAS Library
+
+Windows
+
+nvblas.dll
+
+Mac OSX
+
+libnvblas.dylib
+
+Linux
+
+libnvblas.so
+
+Component
+
+CUDA Sparse Matrix Library
+
+Windows
+
+cusparse.dll, cusparse.lib
+
+Mac OSX
+
+libcusparse.dylib, libcusparse_static.a
+
+Linux
+
+libcusparse.so, libcusparse_static.a
+
+Android
+
+libcusparse.so, libcusparse_static.a
+
+Component
+
+CUDA Linear Solver Library
+
+Windows
+
+cusolver.dll, cusolver.lib
+
+Mac OSX
+
+libcusolver.dylib, libcusolver_static.a
+
+Linux
+
+libcusolver.so, libcusolver_static.a
+
+Android
+
+libcusolver.so, libcusolver_static.a
+
+Component
+
+CUDA Random Number Generation Library
+
+Windows
+
+curand.dll, curand.lib
+
+Mac OSX
+
+libcurand.dylib, libcurand_static.a
+
+Linux
+
+libcurand.so, libcurand_static.a
+
+Android
+
+libcurand.so, libcurand_static.a
+
+Component
+
+CUDA Accelerated Graph Library
+
+Component
+
+NVIDIA Performance Primitives Library
+
+Windows
+
+nppc.dll, nppc.lib, nppial.dll, nppial.lib, nppicc.dll,
+nppicc.lib, nppicom.dll, nppicom.lib, nppidei.dll,
+nppidei.lib, nppif.dll, nppif.lib, nppig.dll, nppig.lib,
+nppim.dll, nppim.lib, nppist.dll, nppist.lib, nppisu.dll,
+nppisu.lib, nppitc.dll, nppitc.lib, npps.dll, npps.lib
+
+Mac OSX
+
+libnppc.dylib, libnppc_static.a, libnppial.dylib,
+libnppial_static.a, libnppicc.dylib, libnppicc_static.a,
+libnppicom.dylib, libnppicom_static.a, libnppidei.dylib,
+libnppidei_static.a, libnppif.dylib, libnppif_static.a,
+libnppig.dylib, libnppig_static.a, libnppim.dylib,
+libnppisu_static.a, libnppitc.dylib, libnppitc_static.a,
+libnpps.dylib, libnpps_static.a
+
+Linux
+
+libnppc.so, libnppc_static.a, libnppial.so,
+libnppial_static.a, libnppicc.so, libnppicc_static.a,
+libnppicom.so, libnppicom_static.a, libnppidei.so,
+libnppidei_static.a, libnppif.so, libnppif_static.a
+libnppig.so, libnppig_static.a, libnppim.so,
+libnppim_static.a, libnppist.so, libnppist_static.a,
+libnppisu.so, libnppisu_static.a, libnppitc.so
+libnppitc_static.a, libnpps.so, libnpps_static.a
+
+Android
+
+libnppc.so, libnppc_static.a, libnppial.so,
+libnppial_static.a, libnppicc.so, libnppicc_static.a,
+libnppicom.so, libnppicom_static.a, libnppidei.so,
+libnppidei_static.a, libnppif.so, libnppif_static.a
+libnppig.so, libnppig_static.a, libnppim.so,
+libnppim_static.a, libnppist.so, libnppist_static.a,
+libnppisu.so, libnppisu_static.a, libnppitc.so
+libnppitc_static.a, libnpps.so, libnpps_static.a
+
+Component
+
+NVIDIA JPEG Library
+
+Linux
+
+libnvjpeg.so, libnvjpeg_static.a
+
+Component
+
+Internal common library required for statically linking to
+cuBLAS, cuSPARSE, cuFFT, cuRAND, nvJPEG and NPP
+
+Mac OSX
+
+libculibos.a
+
+Linux
+
+libculibos.a
+
+Component
+
+NVIDIA Runtime Compilation Library and Header
+
+All
+
+nvrtc.h
+
+Windows
+
+nvrtc.dll, nvrtc-builtins.dll
+
+Mac OSX
+
+libnvrtc.dylib, libnvrtc-builtins.dylib
+
+Linux
+
+libnvrtc.so, libnvrtc-builtins.so
+
+Component
+
+NVIDIA Optimizing Compiler Library
+
+Windows
+
+nvvm.dll
+
+Mac OSX
+
+libnvvm.dylib
+
+Linux
+
+libnvvm.so
+
+Component
+
+NVIDIA Common Device Math Functions Library
+
+Windows
+
+libdevice.10.bc
+
+Mac OSX
+
+libdevice.10.bc
+
+Linux
+
+libdevice.10.bc
+
+Component
+
+CUDA Occupancy Calculation Header Library
+
+All
+
+cuda_occupancy.h
+
+Component
+
+CUDA Half Precision Headers
+
+All
+
+cuda_fp16.h, cuda_fp16.hpp
+
+Component
+
+CUDA Profiling Tools Interface (CUPTI) Library
+
+Windows
+
+cupti.dll
+
+Mac OSX
+
+libcupti.dylib
+
+Linux
+
+libcupti.so
+
+Component
+
+NVIDIA Tools Extension Library
+
+Windows
+
+nvToolsExt.dll, nvToolsExt.lib
+
+Mac OSX
+
+libnvToolsExt.dylib
+
+Linux
+
+libnvToolsExt.so
+
+Component
+
+NVIDIA CUDA Driver Libraries
+
+Linux
+
+libcuda.so, libnvidia-fatbinaryloader.so,
+libnvidia-ptxjitcompiler.so
+
+The NVIDIA CUDA Driver Libraries are only distributable in
+applications that meet this criteria:
+
+ 1. The application was developed starting from a NVIDIA CUDA
+ container obtained from Docker Hub or the NVIDIA GPU
+ Cloud, and
+
+ 2. The resulting application is packaged as a Docker
+ container and distributed to users on Docker Hub or the
+ NVIDIA GPU Cloud only.
+
+
+2.7. Attachment B
+
+
+Additional Licensing Obligations
+
+The following third party components included in the SOFTWARE
+are licensed to Licensee pursuant to the following terms and
+conditions:
+
+ 1. Licensee's use of the GDB third party component is
+ subject to the terms and conditions of GNU GPL v3:
+
+ This product includes copyrighted third-party software licensed
+ under the terms of the GNU General Public License v3 ("GPL v3").
+ All third-party software packages are copyright by their respective
+ authors. GPL v3 terms and conditions are hereby incorporated into
+ the Agreement by this reference: http://www.gnu.org/licenses/gpl.txt
+
+ Consistent with these licensing requirements, the software
+ listed below is provided under the terms of the specified
+ open source software licenses. To obtain source code for
+ software provided under licenses that require
+ redistribution of source code, including the GNU General
+ Public License (GPL) and GNU Lesser General Public License
+ (LGPL), contact oss-requests@nvidia.com. This offer is
+ valid for a period of three (3) years from the date of the
+ distribution of this product by NVIDIA CORPORATION.
+
+ Component License
+ CUDA-GDB GPL v3
+
+ 2. Licensee represents and warrants that any and all third
+ party licensing and/or royalty payment obligations in
+ connection with Licensee's use of the H.264 video codecs
+ are solely the responsibility of Licensee.
+
+ 3. Licensee's use of the Thrust library is subject to the
+ terms and conditions of the Apache License Version 2.0.
+ All third-party software packages are copyright by their
+ respective authors. Apache License Version 2.0 terms and
+ conditions are hereby incorporated into the Agreement by
+ this reference.
+ http://www.apache.org/licenses/LICENSE-2.0.html
+
+ In addition, Licensee acknowledges the following notice:
+ Thrust includes source code from the Boost Iterator,
+ Tuple, System, and Random Number libraries.
+
+ Boost Software License - Version 1.0 - August 17th, 2003
+ . . . .
+
+ Permission is hereby granted, free of charge, to any person or
+ organization obtaining a copy of the software and accompanying
+ documentation covered by this license (the "Software") to use,
+ reproduce, display, distribute, execute, and transmit the Software,
+ and to prepare derivative works of the Software, and to permit
+ third-parties to whom the Software is furnished to do so, all
+ subject to the following:
+
+ The copyright notices in the Software and this entire statement,
+ including the above license grant, this restriction and the following
+ disclaimer, must be included in all copies of the Software, in whole
+ or in part, and all derivative works of the Software, unless such
+ copies or derivative works are solely in the form of machine-executable
+ object code generated by a source language processor.
+
+ 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, TITLE AND
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
+ OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ 4. Licensee's use of the LLVM third party component is
+ subject to the following terms and conditions:
+
+ ======================================================
+ LLVM Release License
+ ======================================================
+ University of Illinois/NCSA
+ Open Source License
+
+ Copyright (c) 2003-2010 University of Illinois at Urbana-Champaign.
+ All rights reserved.
+
+ Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal with 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:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at Urbana-
+ Champaign, nor the names of its contributors may be used to endorse or
+ promote products derived from this Software without specific prior
+ written permission.
+
+ 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 CONTRIBUTORS 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 WITH THE SOFTWARE.
+
+ 5. Licensee's use (e.g. nvprof) of the PCRE third party
+ component is subject to the following terms and
+ conditions:
+
+ ------------
+ PCRE LICENCE
+ ------------
+ PCRE is a library of functions to support regular expressions whose syntax
+ and semantics are as close as possible to those of the Perl 5 language.
+ Release 8 of PCRE is distributed under the terms of the "BSD" licence, as
+ specified below. The documentation for PCRE, supplied in the "doc"
+ directory, is distributed under the same terms as the software itself. The
+ basic library functions are written in C and are freestanding. Also
+ included in the distribution is a set of C++ wrapper functions, and a just-
+ in-time compiler that can be used to optimize pattern matching. These are
+ both optional features that can be omitted when the library is built.
+
+ THE BASIC LIBRARY FUNCTIONS
+ ---------------------------
+ Written by: Philip Hazel
+ Email local part: ph10
+ Email domain: cam.ac.uk
+ University of Cambridge Computing Service,
+ Cambridge, England.
+ Copyright (c) 1997-2012 University of Cambridge
+ All rights reserved.
+
+ PCRE JUST-IN-TIME COMPILATION SUPPORT
+ -------------------------------------
+ Written by: Zoltan Herczeg
+ Email local part: hzmester
+ Emain domain: freemail.hu
+ Copyright(c) 2010-2012 Zoltan Herczeg
+ All rights reserved.
+
+ STACK-LESS JUST-IN-TIME COMPILER
+ --------------------------------
+ Written by: Zoltan Herczeg
+ Email local part: hzmester
+ Emain domain: freemail.hu
+ Copyright(c) 2009-2012 Zoltan Herczeg
+ All rights reserved.
+
+ THE C++ WRAPPER FUNCTIONS
+ -------------------------
+ Contributed by: Google Inc.
+ Copyright (c) 2007-2012, Google Inc.
+ All rights reserved.
+
+ THE "BSD" LICENCE
+ -----------------
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the name of Google
+ Inc. nor the names of their contributors may be used to endorse or
+ promote products derived from this software without specific prior
+ written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ 6. Some of the cuBLAS library routines were written by or
+ derived from code written by Vasily Volkov and are subject
+ to the Modified Berkeley Software Distribution License as
+ follows:
+
+ Copyright (c) 2007-2009, Regents of the University of California
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the University of California, Berkeley nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ 7. Some of the cuBLAS library routines were written by or
+ derived from code written by Davide Barbieri and are
+ subject to the Modified Berkeley Software Distribution
+ License as follows:
+
+ Copyright (c) 2008-2009 Davide Barbieri @ University of Rome Tor Vergata.
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ 8. Some of the cuBLAS library routines were derived from
+ code developed by the University of Tennessee and are
+ subject to the Modified Berkeley Software Distribution
+ License as follows:
+
+ Copyright (c) 2010 The University of Tennessee.
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer listed in this license in the documentation and/or
+ other materials provided with the distribution.
+ * Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 9. Some of the cuBLAS library routines were written by or
+ derived from code written by Jonathan Hogg and are subject
+ to the Modified Berkeley Software Distribution License as
+ follows:
+
+ Copyright (c) 2012, The Science and Technology Facilities Council (STFC).
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the STFC nor the names of its contributors
+ may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE STFC BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 10. Some of the cuBLAS library routines were written by or
+ derived from code written by Ahmad M. Abdelfattah, David
+ Keyes, and Hatem Ltaief, and are subject to the Apache
+ License, Version 2.0, as follows:
+
+ -- (C) Copyright 2013 King Abdullah University of Science and Technology
+ Authors:
+ Ahmad Abdelfattah (ahmad.ahmad@kaust.edu.sa)
+ David Keyes (david.keyes@kaust.edu.sa)
+ Hatem Ltaief (hatem.ltaief@kaust.edu.sa)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the King Abdullah University of Science and
+ Technology nor the names of its contributors may be used to endorse
+ or promote products derived from this software without specific prior
+ written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
+
+ 11. Some of the cuSPARSE library routines were written by or
+ derived from code written by Li-Wen Chang and are subject
+ to the NCSA Open Source License as follows:
+
+ Copyright (c) 2012, University of Illinois.
+
+ All rights reserved.
+
+ Developed by: IMPACT Group, University of Illinois, http://impact.crhc.illinois.edu
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal with 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:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimers in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the names of IMPACT Group, University of Illinois, nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this Software without specific prior
+ written permission.
+
+ 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 CONTRIBUTORS 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 WITH THE
+ SOFTWARE.
+
+ 12. Some of the cuRAND library routines were written by or
+ derived from code written by Mutsuo Saito and Makoto
+ Matsumoto and are subject to the following license:
+
+ Copyright (c) 2009, 2010 Mutsuo Saito, Makoto Matsumoto and Hiroshima
+ University. All rights reserved.
+
+ Copyright (c) 2011 Mutsuo Saito, Makoto Matsumoto, Hiroshima
+ University and University of Tokyo. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the Hiroshima University nor the names of
+ its contributors may be used to endorse or promote products
+ derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 13. Some of the cuRAND library routines were derived from
+ code developed by D. E. Shaw Research and are subject to
+ the following license:
+
+ Copyright 2010-2011, D. E. Shaw Research.
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions, and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of D. E. Shaw Research nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 14. Some of the Math library routines were written by or
+ derived from code developed by Norbert Juffa and are
+ subject to the following license:
+
+ Copyright (c) 2015-2017, Norbert Juffa
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 15. Licensee's use of the lz4 third party component is
+ subject to the following terms and conditions:
+
+ Copyright (C) 2011-2013, Yann Collet.
+ BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ 16. The NPP library uses code from the Boost Math Toolkit,
+ and is subject to the following license:
+
+ Boost Software License - Version 1.0 - August 17th, 2003
+ . . . .
+
+ Permission is hereby granted, free of charge, to any person or
+ organization obtaining a copy of the software and accompanying
+ documentation covered by this license (the "Software") to use,
+ reproduce, display, distribute, execute, and transmit the Software,
+ and to prepare derivative works of the Software, and to permit
+ third-parties to whom the Software is furnished to do so, all
+ subject to the following:
+
+ The copyright notices in the Software and this entire statement,
+ including the above license grant, this restriction and the following
+ disclaimer, must be included in all copies of the Software, in whole
+ or in part, and all derivative works of the Software, unless such
+ copies or derivative works are solely in the form of machine-executable
+ object code generated by a source language processor.
+
+ 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, TITLE AND
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
+ OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ 17. Portions of the Nsight Eclipse Edition is subject to the
+ following license:
+
+ The Eclipse Foundation makes available all content in this plug-in
+ ("Content"). Unless otherwise indicated below, the Content is provided
+ to you under the terms and conditions of the Eclipse Public License
+ Version 1.0 ("EPL"). A copy of the EPL is available at http://
+ www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program"
+ will mean the Content.
+
+ If you did not receive this Content directly from the Eclipse
+ Foundation, the Content is being redistributed by another party
+ ("Redistributor") and different terms and conditions may apply to your
+ use of any object code in the Content. Check the Redistributor's
+ license that was provided with the Content. If no such license exists,
+ contact the Redistributor. Unless otherwise indicated below, the terms
+ and conditions of the EPL still apply to any source code in the
+ Content and such source code may be obtained at http://www.eclipse.org.
+
+ 18. Some of the cuBLAS library routines uses code from
+ OpenAI, which is subject to the following license:
+
+ License URL
+ https://github.com/openai/openai-gemm/blob/master/LICENSE
+
+ License Text
+ The MIT License
+
+ Copyright (c) 2016 OpenAI (http://openai.com), 2016 Google Inc.
+
+ 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.
+
+ 19. Licensee's use of the Visual Studio Setup Configuration
+ Samples is subject to the following license:
+
+ The MIT License (MIT)
+ Copyright (C) Microsoft Corporation. All rights reserved.
+
+ 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.
+
+ 20. Licensee's use of linmath.h header for CPU functions for
+ GL vector/matrix operations from lunarG is subject to the
+ Apache License Version 2.0.
+
+ 21. The DX12-CUDA sample uses the d3dx12.h header, which is
+ subject to the MIT license .
+
+-----------------
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/METADATA b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..c0d874a42fb5a6bdb8f0cb78bb40f3d9cb708137
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/METADATA
@@ -0,0 +1,35 @@
+Metadata-Version: 2.1
+Name: nvidia-cuda-cupti-cu12
+Version: 12.4.127
+Summary: CUDA profiling tools runtime libs.
+Home-page: https://developer.nvidia.com/cuda-zone
+Author: Nvidia CUDA Installer Team
+Author-email: cuda_installer@nvidia.com
+License: NVIDIA Proprietary Software
+Keywords: cuda,nvidia,runtime,machine learning,deep learning
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Education
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: Other/Proprietary License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Topic :: Scientific/Engineering
+Classifier: Topic :: Scientific/Engineering :: Mathematics
+Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
+Classifier: Topic :: Software Development
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX :: Linux
+Requires-Python: >=3
+License-File: License.txt
+
+Provides libraries to enable third party tools using GPU profiling APIs.
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/RECORD b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..fe9075741deb35f37fbe0a17bf262a44ee0e977c
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/RECORD
@@ -0,0 +1,53 @@
+nvidia/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+nvidia/__pycache__/__init__.cpython-312.pyc,,
+nvidia/cuda_cupti/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+nvidia/cuda_cupti/__pycache__/__init__.cpython-312.pyc,,
+nvidia/cuda_cupti/include/Openacc/cupti_openacc.h,sha256=Z0OM5e_hbd3cxdXyn3SCHqBBQawLg4QORnlm57Cr2-M,3513
+nvidia/cuda_cupti/include/Openmp/cupti_openmp.h,sha256=E1WNmeb_7HaUSmBegtUNe4IV1i7pXeNxgzIlyKn1zrM,3491
+nvidia/cuda_cupti/include/Openmp/omp-tools.h,sha256=AmuC_xPC7VPu3B-W4PmXuCNufFawhY8PjNXePaQFAOg,37403
+nvidia/cuda_cupti/include/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+nvidia/cuda_cupti/include/__pycache__/__init__.cpython-312.pyc,,
+nvidia/cuda_cupti/include/cuda_stdint.h,sha256=XbFOk9CtJjKqk7PpYNqbSVsDxAsVM8avA4rWpPi0BjQ,4093
+nvidia/cuda_cupti/include/cupti.h,sha256=JkVyAGTIMYzwm62dfVqas3nMcILhgP_Wdz6fh4_NED0,4697
+nvidia/cuda_cupti/include/cupti_activity.h,sha256=RB7VYrdiOBpdH_LbXb2o-CcGthnk3-NE2Cqq-jQbN7Q,210611
+nvidia/cuda_cupti/include/cupti_activity_deprecated.h,sha256=rYJsoAJxA2BTT50-olN8EYcSzdlXBpRbR1ATLG3rVIM,121526
+nvidia/cuda_cupti/include/cupti_callbacks.h,sha256=zrEVRb0hubSfD69QUmHsJiL8oAfvqyuKGcTVRihQrnc,29729
+nvidia/cuda_cupti/include/cupti_checkpoint.h,sha256=rTz8JoWxqESBXyZWUhZJGm4xeYcx4OJOtJ7Ld13T_b0,5264
+nvidia/cuda_cupti/include/cupti_common.h,sha256=85m74bxUgXp3tEaPQpezeazmpsNMw41PsjNSYmQdT20,3514
+nvidia/cuda_cupti/include/cupti_driver_cbid.h,sha256=dHKyQYZbBbdlxixzFkIoNHg5IfGXdgriyjN1Bu1i6g4,74462
+nvidia/cuda_cupti/include/cupti_events.h,sha256=f7lLGmD2e8FzvMhRgnn0-v7U0vTpUkiQHIpQxgARGb0,51896
+nvidia/cuda_cupti/include/cupti_metrics.h,sha256=iLAOlDrcbHEsIIUmgq0Tp1ZOY9O3Ot3wj2-bI8iYbSs,32148
+nvidia/cuda_cupti/include/cupti_nvtx_cbid.h,sha256=_azPtR1g4qivvX7qbvHRUg0RHCWF7iEOJyHMN9qZe9E,5912
+nvidia/cuda_cupti/include/cupti_pcsampling.h,sha256=ycJHT36DmPIaVzHsB3xxjXkhFyEfMCJOl3LbCsHFgyA,32144
+nvidia/cuda_cupti/include/cupti_pcsampling_util.h,sha256=lx8CaNXowJe5Zvc06LE-u_Zry_jODs1mM6j9Q5WIX9E,12430
+nvidia/cuda_cupti/include/cupti_profiler_target.h,sha256=JsceoDuhllWNEzaO0xxT81dJ55NrbF0UtRJJgit0P_E,32131
+nvidia/cuda_cupti/include/cupti_result.h,sha256=a-C4Y7LAYCiCT1ngOfoDuTi2stEG1YTafwwn6UfL-LU,12603
+nvidia/cuda_cupti/include/cupti_runtime_cbid.h,sha256=11pXl0MdmTtxUngel-ru4JdqWvF_gEIG14aQExRyfzI,46436
+nvidia/cuda_cupti/include/cupti_sass_metrics.h,sha256=3RW9snJuFQdOhrEn3wDJOru05q0V_zssWrqD7tvVJKw,19674
+nvidia/cuda_cupti/include/cupti_target.h,sha256=x4Vz1Upb6m9ixmVpmGaKQldDWYQI3OZ-ocEXGzNK0EE,1263
+nvidia/cuda_cupti/include/cupti_version.h,sha256=sjd-aUoTGkEWyvA2VUWIpZwXyXAaclqC8gbwNnuK5D0,4425
+nvidia/cuda_cupti/include/generated_cudaGL_meta.h,sha256=dfd2QuaRdEjbStOKvaQLi1Md_qrpRQh8PfyZznJ8bWY,3115
+nvidia/cuda_cupti/include/generated_cudaVDPAU_meta.h,sha256=fAedsoQxaU3hIAApAWDOKsa9kgcuQw4tdyf8klLm-3k,1453
+nvidia/cuda_cupti/include/generated_cuda_gl_interop_meta.h,sha256=LXOqvQCej0sCgAT1LUKKYZ466EFxN4hIwf9oIhXOLF0,2250
+nvidia/cuda_cupti/include/generated_cuda_meta.h,sha256=hawYpDe0xpaDFDnClXI91JjwCRxWb-AS0FS8ydUMgxc,94639
+nvidia/cuda_cupti/include/generated_cuda_runtime_api_meta.h,sha256=D8CbAN3-jLuF2KGfsBHXEELSgL92KrUAiDvugWE8B8M,69706
+nvidia/cuda_cupti/include/generated_cuda_vdpau_interop_meta.h,sha256=8OLqWN26aEYpTWUXtbHJvA5GYhVv3ybYVOTW7yK37z8,1367
+nvidia/cuda_cupti/include/generated_cudart_removed_meta.h,sha256=X3I5WXmhtsJNNlgY7coJ5vg4t11G5FRR6Xo7MboIeck,5172
+nvidia/cuda_cupti/include/generated_nvtx_meta.h,sha256=YHb_RD8g3s4m8PJn7Z0wnxvUHarl7BOAX5ADr-BL3HI,7513
+nvidia/cuda_cupti/include/nvperf_common.h,sha256=BqPml9AxyN10-ptWT3hQzh2JUWqQX57Q5BjQ3ZuaKNs,17255
+nvidia/cuda_cupti/include/nvperf_cuda_host.h,sha256=aBnyIr_hexPDGBkP6WSujN1mI_DYP25sEIXWYY1O7VI,8298
+nvidia/cuda_cupti/include/nvperf_host.h,sha256=afdHG6eraeo4ltlF9ihskqhU7IccxcRCaZDZ6_ikjkg,68506
+nvidia/cuda_cupti/include/nvperf_target.h,sha256=ZDA-JI459tLBW4iLLCQjYYRAMeHwfqDIgXbVqVLDYZ4,22539
+nvidia/cuda_cupti/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+nvidia/cuda_cupti/lib/__pycache__/__init__.cpython-312.pyc,,
+nvidia/cuda_cupti/lib/libcheckpoint.so,sha256=cCTAB7_UNqvoU7zKMCHkklcmM1GGr3atZmZoZksdAKM,1501336
+nvidia/cuda_cupti/lib/libcupti.so.12,sha256=-yp8WxXITflQXdR-VT_kbzEhpX0wOR_KJBedIC9z8_c,7748112
+nvidia/cuda_cupti/lib/libnvperf_host.so,sha256=tZsmsdNdAik8jdiVaro3V8FGa3FzLGaHq6QSxQ2VC2k,28132984
+nvidia/cuda_cupti/lib/libnvperf_target.so,sha256=ztN3NKnf_9XyEogyuHjyOAcTvqYBn6lE0psxejPTeYw,5592368
+nvidia/cuda_cupti/lib/libpcsamplingutil.so,sha256=ZDY0bEGLzy-pA3yfFtc6jfvo-Cu8vWwUCQYatGJrb0Q,912728
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/License.txt,sha256=rW9YU_ugyg0VnQ9Y1JrkmDDC-Mk_epJki5zpCttMbM0,59262
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/METADATA,sha256=UiXYPD5hc55tQSSNiYNq5AqkD68jq1KHNCtG-PJvPds,1553
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/RECORD,,
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/WHEEL,sha256=XDTs3wIbcE-BcRO08VJlZpA6z9OaC1mOKPCGGGwuM2g,109
+nvidia_cuda_cupti_cu12-12.4.127.dist-info/top_level.txt,sha256=fTkAtiFuL16nUrB9ytDDtpytz2t0B4NvYTnRzwAhO14,7
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/WHEEL b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..e6c30e957cfb045017a9fef3430bb8ee87c4a074
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: true
+Tag: py3-none-manylinux2014_x86_64
+
diff --git a/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/top_level.txt b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..862f7abf232cdfbb928609856247292e81c9decb
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia_cuda_cupti_cu12-12.4.127.dist-info/top_level.txt
@@ -0,0 +1 @@
+nvidia
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/INSTALLER b/lib/python3.12/site-packages/parso-0.8.5.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/METADATA b/lib/python3.12/site-packages/parso-0.8.5.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..3b138239f9d1e61575fa049beedeb7039d6e03d3
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/METADATA
@@ -0,0 +1,312 @@
+Metadata-Version: 2.4
+Name: parso
+Version: 0.8.5
+Summary: A Python Parser
+Home-page: https://github.com/davidhalter/parso
+Author: David Halter
+Author-email: davidhalter88@gmail.com
+Maintainer: David Halter
+Maintainer-email: davidhalter88@gmail.com
+License: MIT
+Keywords: python parser parsing
+Platform: any
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Plugins
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
+Classifier: Topic :: Utilities
+Classifier: Typing :: Typed
+Requires-Python: >=3.6
+License-File: LICENSE.txt
+License-File: AUTHORS.txt
+Provides-Extra: testing
+Requires-Dist: pytest; extra == "testing"
+Requires-Dist: docopt; extra == "testing"
+Provides-Extra: qa
+Requires-Dist: flake8==5.0.4; extra == "qa"
+Requires-Dist: mypy==0.971; extra == "qa"
+Requires-Dist: types-setuptools==67.2.0.1; extra == "qa"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: maintainer
+Dynamic: maintainer-email
+Dynamic: platform
+Dynamic: provides-extra
+Dynamic: requires-python
+Dynamic: summary
+
+###################################################################
+parso - A Python Parser
+###################################################################
+
+
+.. image:: https://github.com/davidhalter/parso/workflows/Build/badge.svg?branch=master
+ :target: https://github.com/davidhalter/parso/actions
+ :alt: GitHub Actions build status
+
+.. image:: https://coveralls.io/repos/github/davidhalter/parso/badge.svg?branch=master
+ :target: https://coveralls.io/github/davidhalter/parso?branch=master
+ :alt: Coverage Status
+
+.. image:: https://pepy.tech/badge/parso
+ :target: https://pepy.tech/project/parso
+ :alt: PyPI Downloads
+
+.. image:: https://raw.githubusercontent.com/davidhalter/parso/master/docs/_static/logo_characters.png
+
+Parso is a Python parser that supports error recovery and round-trip parsing
+for different Python versions (in multiple Python versions). Parso is also able
+to list multiple syntax errors in your python file.
+
+Parso has been battle-tested by jedi_. It was pulled out of jedi to be useful
+for other projects as well.
+
+Parso consists of a small API to parse Python and analyse the syntax tree.
+
+A simple example:
+
+.. code-block:: python
+
+ >>> import parso
+ >>> module = parso.parse('hello + 1', version="3.9")
+ >>> expr = module.children[0]
+ >>> expr
+ PythonNode(arith_expr, [, , ])
+ >>> print(expr.get_code())
+ hello + 1
+ >>> name = expr.children[0]
+ >>> name
+
+ >>> name.end_pos
+ (1, 5)
+ >>> expr.end_pos
+ (1, 9)
+
+To list multiple issues:
+
+.. code-block:: python
+
+ >>> grammar = parso.load_grammar()
+ >>> module = grammar.parse('foo +\nbar\ncontinue')
+ >>> error1, error2 = grammar.iter_errors(module)
+ >>> error1.message
+ 'SyntaxError: invalid syntax'
+ >>> error2.message
+ "SyntaxError: 'continue' not properly in loop"
+
+Resources
+=========
+
+- `Testing `_
+- `PyPI `_
+- `Docs `_
+- Uses `semantic versioning `_
+
+Installation
+============
+
+.. code-block:: bash
+
+ pip install parso
+
+Future
+======
+
+- There will be better support for refactoring and comments. Stay tuned.
+- There's a WIP PEP8 validator. It's however not in a good shape, yet.
+
+Known Issues
+============
+
+- `async`/`await` are already used as keywords in Python3.6.
+- `from __future__ import print_function` is not ignored.
+
+
+Acknowledgements
+================
+
+- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
+ (originally used in lib2to3).
+- Salome Schneider for the extremely awesome parso logo.
+
+
+.. _jedi: https://github.com/davidhalter/jedi
+
+
+.. :changelog:
+
+Changelog
+---------
+
+Unreleased
+++++++++++
+
+0.8.5 (2025-08-23)
+++++++++++++++++++
+
+- Add a fallback grammar for Python 3.14+
+
+0.8.4 (2024-04-05)
+++++++++++++++++++
+
+- Add basic support for Python 3.13
+
+0.8.3 (2021-11-30)
+++++++++++++++++++
+
+- Add basic support for Python 3.11 and 3.12
+
+0.8.2 (2021-03-30)
+++++++++++++++++++
+
+- Various small bugfixes
+
+0.8.1 (2020-12-10)
+++++++++++++++++++
+
+- Various small bugfixes
+
+0.8.0 (2020-08-05)
+++++++++++++++++++
+
+- Dropped Support for Python 2.7, 3.4, 3.5
+- It's possible to use ``pathlib.Path`` objects now in the API
+- The stubs are gone, we are now using annotations
+- ``namedexpr_test`` nodes are now a proper class called ``NamedExpr``
+- A lot of smaller refactorings
+
+0.7.1 (2020-07-24)
+++++++++++++++++++
+
+- Fixed a couple of smaller bugs (mostly syntax error detection in
+ ``Grammar.iter_errors``)
+
+This is going to be the last release that supports Python 2.7, 3.4 and 3.5.
+
+0.7.0 (2020-04-13)
+++++++++++++++++++
+
+- Fix a lot of annoying bugs in the diff parser. The fuzzer did not find
+ issues anymore even after running it for more than 24 hours (500k tests).
+- Small grammar change: suites can now contain newlines even after a newline.
+ This should really not matter if you don't use error recovery. It allows for
+ nicer error recovery.
+
+0.6.2 (2020-02-27)
+++++++++++++++++++
+
+- Bugfixes
+- Add Grammar.refactor (might still be subject to change until 0.7.0)
+
+0.6.1 (2020-02-03)
+++++++++++++++++++
+
+- Add ``parso.normalizer.Issue.end_pos`` to make it possible to know where an
+ issue ends
+
+0.6.0 (2020-01-26)
+++++++++++++++++++
+
+- Dropped Python 2.6/Python 3.3 support
+- del_stmt names are now considered as a definition
+ (for ``name.is_definition()``)
+- Bugfixes
+
+0.5.2 (2019-12-15)
+++++++++++++++++++
+
+- Add include_setitem to get_definition/is_definition and get_defined_names (#66)
+- Fix named expression error listing (#89, #90)
+- Fix some f-string tokenizer issues (#93)
+
+0.5.1 (2019-07-13)
+++++++++++++++++++
+
+- Fix: Some unicode identifiers were not correctly tokenized
+- Fix: Line continuations in f-strings are now working
+
+0.5.0 (2019-06-20)
+++++++++++++++++++
+
+- **Breaking Change** comp_for is now called sync_comp_for for all Python
+ versions to be compatible with the Python 3.8 Grammar
+- Added .pyi stubs for a lot of the parso API
+- Small FileIO changes
+
+0.4.0 (2019-04-05)
+++++++++++++++++++
+
+- Python 3.8 support
+- FileIO support, it's now possible to use abstract file IO, support is alpha
+
+0.3.4 (2019-02-13)
++++++++++++++++++++
+
+- Fix an f-string tokenizer error
+
+0.3.3 (2019-02-06)
++++++++++++++++++++
+
+- Fix async errors in the diff parser
+- A fix in iter_errors
+- This is a very small bugfix release
+
+0.3.2 (2019-01-24)
++++++++++++++++++++
+
+- 20+ bugfixes in the diff parser and 3 in the tokenizer
+- A fuzzer for the diff parser, to give confidence that the diff parser is in a
+ good shape.
+- Some bugfixes for f-string
+
+0.3.1 (2018-07-09)
++++++++++++++++++++
+
+- Bugfixes in the diff parser and keyword-only arguments
+
+0.3.0 (2018-06-30)
++++++++++++++++++++
+
+- Rewrote the pgen2 parser generator.
+
+0.2.1 (2018-05-21)
++++++++++++++++++++
+
+- A bugfix for the diff parser.
+- Grammar files can now be loaded from a specific path.
+
+0.2.0 (2018-04-15)
++++++++++++++++++++
+
+- f-strings are now parsed as a part of the normal Python grammar. This makes
+ it way easier to deal with them.
+
+0.1.1 (2017-11-05)
++++++++++++++++++++
+
+- Fixed a few bugs in the caching layer
+- Added support for Python 3.7
+
+0.1.0 (2017-09-04)
++++++++++++++++++++
+
+- Pulling the library out of Jedi. Some APIs will definitely change.
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/RECORD b/lib/python3.12/site-packages/parso-0.8.5.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..3e6c48935a198a87eee254cecf7697e90e008f75
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/RECORD
@@ -0,0 +1,59 @@
+parso-0.8.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+parso-0.8.5.dist-info/METADATA,sha256=McgMidhsXJ8k0WRgK-EX_RHdEpy7WDAZ-nZeRbAsSV8,8256
+parso-0.8.5.dist-info/RECORD,,
+parso-0.8.5.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
+parso-0.8.5.dist-info/licenses/AUTHORS.txt,sha256=SDCgu8hXlBBcjPPyUT-SKW20_IM2MxW-95hKFaRIqyI,2029
+parso-0.8.5.dist-info/licenses/LICENSE.txt,sha256=-meXMHN1PRdiTK-GhNXugW1wyJ2RLFvKfKDwjnsVDts,4176
+parso-0.8.5.dist-info/top_level.txt,sha256=GOOKQCPcnr0_7IRArxyI0CX5LLu4WLlzIRAVWS-vJ4s,6
+parso/__init__.py,sha256=gJK-bMRgg9_JFuvnfK69nrxOZSVqr32rbnoxJxkp0lE,1607
+parso/__pycache__/__init__.cpython-312.pyc,,
+parso/__pycache__/_compatibility.cpython-312.pyc,,
+parso/__pycache__/cache.cpython-312.pyc,,
+parso/__pycache__/file_io.cpython-312.pyc,,
+parso/__pycache__/grammar.cpython-312.pyc,,
+parso/__pycache__/normalizer.cpython-312.pyc,,
+parso/__pycache__/parser.cpython-312.pyc,,
+parso/__pycache__/tree.cpython-312.pyc,,
+parso/__pycache__/utils.cpython-312.pyc,,
+parso/_compatibility.py,sha256=y-fATJ1dyaoVry175CMDBA088IGTxChkCKD2dUAnsrU,70
+parso/cache.py,sha256=KyQBZdTuBXhDjLmwTSLOgyQoq4NLt_wNr1882DTkOW4,8452
+parso/file_io.py,sha256=2SbXQuMpjAaQ0OYvxZXOgl-oU945-CrIei3eEamWWmk,1023
+parso/grammar.py,sha256=YWet-eatprA6sDbLvIHmfxJ7ygO4dl37Gxn_0-P4-JM,11081
+parso/normalizer.py,sha256=geYG9UZQ6ZpafTc_CiXQoBt8VImdBsiNw6_GJLeSGbg,5597
+parso/parser.py,sha256=qlIrRikSxAccfsC6B6Y9sPWyEhR0HIBaCbNveV1OcAE,7182
+parso/pgen2/__init__.py,sha256=kFfRZsSReM49V0YIJ_cG0_TMTew2t4IMbG95KO2BI8E,382
+parso/pgen2/__pycache__/__init__.cpython-312.pyc,,
+parso/pgen2/__pycache__/generator.cpython-312.pyc,,
+parso/pgen2/__pycache__/grammar_parser.cpython-312.pyc,,
+parso/pgen2/generator.py,sha256=PHjCpx7QM2duGZqGw5GOQQIgc6RE3jcKV7IwXcOgJhw,14580
+parso/pgen2/grammar_parser.py,sha256=knJh3a40_JxUkb0HePG78ZZoqjpPNk3uZwNOz2EkkV4,5515
+parso/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+parso/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+parso/python/__pycache__/__init__.cpython-312.pyc,,
+parso/python/__pycache__/diff.cpython-312.pyc,,
+parso/python/__pycache__/errors.cpython-312.pyc,,
+parso/python/__pycache__/parser.cpython-312.pyc,,
+parso/python/__pycache__/pep8.cpython-312.pyc,,
+parso/python/__pycache__/prefix.cpython-312.pyc,,
+parso/python/__pycache__/token.cpython-312.pyc,,
+parso/python/__pycache__/tokenize.cpython-312.pyc,,
+parso/python/__pycache__/tree.cpython-312.pyc,,
+parso/python/diff.py,sha256=jyrqWRKklyPPezZRKRHxoKbhkywiCUoGH_K1HcRSyMA,34206
+parso/python/errors.py,sha256=Vlmxc0MLUNTYnVNMEWVO68JTpkmXzLEG_sh7rkEt6dA,49113
+parso/python/grammar310.txt,sha256=QwXaHqJcJ_zgi9FAAbdv1U_kKgcku9UWjHZoClbtpb4,7511
+parso/python/grammar311.txt,sha256=QwXaHqJcJ_zgi9FAAbdv1U_kKgcku9UWjHZoClbtpb4,7511
+parso/python/grammar312.txt,sha256=QwXaHqJcJ_zgi9FAAbdv1U_kKgcku9UWjHZoClbtpb4,7511
+parso/python/grammar313.txt,sha256=QwXaHqJcJ_zgi9FAAbdv1U_kKgcku9UWjHZoClbtpb4,7511
+parso/python/grammar314.txt,sha256=QwXaHqJcJ_zgi9FAAbdv1U_kKgcku9UWjHZoClbtpb4,7511
+parso/python/grammar36.txt,sha256=ezjXEeLpG9BBMrN0rbM3Z77mcn0XESxSlAaZEy2er-k,6948
+parso/python/grammar37.txt,sha256=Ke73_sTcivtBt2rkJaoNYiXa_zLenhCr96HOVPpZB_E,6804
+parso/python/grammar38.txt,sha256=OhPReVYqhsX2RWyVryca3RUGcvLb-R1dcbwdbgPIvBI,7591
+parso/python/grammar39.txt,sha256=cVrVbF9Pg5UJLFi2tvLetPkG-BOAkpqDa9hqslNjSHU,7499
+parso/python/parser.py,sha256=5OMU32ybPF6kcKUdbcfNNkDOK8hJy0B7fqi6b-Gfwqw,8108
+parso/python/pep8.py,sha256=tsuRslXZvfio8LTBIAbfExjBIT1f3Xjx3igt28fm3G4,33779
+parso/python/prefix.py,sha256=BM93VenBA1Vs-qk2AJSLBMJNn5BDbyVZLIZ5ScT4FIU,2743
+parso/python/token.py,sha256=0dzmQf6L59bEJb9MXYbrDtq3bAHNdTuk-PmOhox81G4,909
+parso/python/tokenize.py,sha256=kqmG8SEdkbLG3Gf6gQyeQZerp4yhzzXX14FCYOZJ5mI,25795
+parso/python/tree.py,sha256=bwJ54y4Nt_ebUqXiFE7QZbBplNmicL1JDvy-8o-Av7o,37226
+parso/tree.py,sha256=deZ68uAq0jodEeumJpBYWXWciIXcBYfpsLpe1f1WLO8,16153
+parso/utils.py,sha256=qW8kJuw9pyK8WaIi37FX44kNjAaIgu15EGv7V_R4PmE,6620
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/WHEEL b/lib/python3.12/site-packages/parso-0.8.5.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..5f133dbb5cfac001f2e84cda817210c03ce6484e
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: setuptools (80.9.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/AUTHORS.txt b/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/AUTHORS.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9737530ba98e52501d37f2764f896d66dd1c6587
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/AUTHORS.txt
@@ -0,0 +1,58 @@
+Main Authors
+============
+
+David Halter (@davidhalter)
+
+Code Contributors
+=================
+Alisdair Robertson (@robodair)
+Bryan Forbes (@bryanforbes)
+
+
+Code Contributors (to Jedi and therefore possibly to this library)
+==================================================================
+
+Takafumi Arakaki (@tkf)
+Danilo Bargen (@dbrgn)
+Laurens Van Houtven (@lvh) <_@lvh.cc>
+Aldo Stracquadanio (@Astrac)
+Jean-Louis Fuchs (@ganwell)
+tek (@tek)
+Yasha Borevich (@jjay)
+Aaron Griffin
+andviro (@andviro)
+Mike Gilbert (@floppym)
+Aaron Meurer (@asmeurer)
+Lubos Trilety
+Akinori Hattori (@hattya)
+srusskih (@srusskih)
+Steven Silvester (@blink1073)
+Colin Duquesnoy (@ColinDuquesnoy)
+Jorgen Schaefer (@jorgenschaefer)
+Fredrik Bergroth (@fbergroth)
+Mathias Fußenegger (@mfussenegger)
+Syohei Yoshida (@syohex)
+ppalucky (@ppalucky)
+immerrr (@immerrr) immerrr@gmail.com
+Albertas Agejevas (@alga)
+Savor d'Isavano (@KenetJervet)
+Phillip Berndt (@phillipberndt)
+Ian Lee (@IanLee1521)
+Farkhad Khatamov (@hatamov)
+Kevin Kelley (@kelleyk)
+Sid Shanker (@squidarth)
+Reinoud Elhorst (@reinhrst)
+Guido van Rossum (@gvanrossum)
+Dmytro Sadovnychyi (@sadovnychyi)
+Cristi Burcă (@scribu)
+bstaint (@bstaint)
+Mathias Rav (@Mortal)
+Daniel Fiterman (@dfit99)
+Simon Ruggier (@sruggier)
+Élie Gouzien (@ElieGouzien)
+Tim Gates (@timgates42)
+Batuhan Taskaya (@isidentical)
+Jocelyn Boullier (@Kazy)
+
+
+Note: (@user) means a github user name.
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/LICENSE.txt b/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..08c41db014ea40b0f01e9f5c56793b3c1d138aea
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/licenses/LICENSE.txt
@@ -0,0 +1,86 @@
+All contributions towards parso are MIT licensed.
+
+Some Python files have been taken from the standard library and are therefore
+PSF licensed. Modifications on these files are dual licensed (both MIT and
+PSF). These files are:
+
+- parso/pgen2/*
+- parso/tokenize.py
+- parso/token.py
+- test/test_pgen2.py
+
+Also some test files under test/normalizer_issue_files have been copied from
+https://github.com/PyCQA/pycodestyle (Expat License == MIT License).
+
+-------------------------------------------------------------------------------
+The MIT License (MIT)
+
+Copyright (c) <2013-2017>
+
+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.
+
+-------------------------------------------------------------------------------
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved"
+are retained in Python alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff --git a/lib/python3.12/site-packages/parso-0.8.5.dist-info/top_level.txt b/lib/python3.12/site-packages/parso-0.8.5.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0e2334404713dd9c161c1d61e779798c4476a237
--- /dev/null
+++ b/lib/python3.12/site-packages/parso-0.8.5.dist-info/top_level.txt
@@ -0,0 +1 @@
+parso
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/INSTALLER b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/METADATA b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..155ce8b6f2797dca15e2e2062427319ec8a86d93
--- /dev/null
+++ b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/METADATA
@@ -0,0 +1,40 @@
+Metadata-Version: 2.4
+Name: python-multipart
+Version: 0.0.20
+Summary: A streaming multipart parser for Python
+Project-URL: Homepage, https://github.com/Kludex/python-multipart
+Project-URL: Documentation, https://kludex.github.io/python-multipart/
+Project-URL: Changelog, https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md
+Project-URL: Source, https://github.com/Kludex/python-multipart
+Author-email: Andrew Dunham , Marcelo Trylesinski
+License-Expression: Apache-2.0
+License-File: LICENSE.txt
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+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: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+
+# [Python-Multipart](https://kludex.github.io/python-multipart/)
+
+[](https://pypi.python.org/pypi/python-multipart)
+[](https://pypi.org/project/python-multipart)
+
+---
+
+`python-multipart` is an Apache2-licensed streaming multipart parser for Python.
+Test coverage is currently 100%.
+
+## Why?
+
+Because streaming uploads are awesome for large files.
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/RECORD b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..c44a464bcfca7ca094437d4060e95a16dcfaed6f
--- /dev/null
+++ b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/RECORD
@@ -0,0 +1,23 @@
+multipart/__init__.py,sha256=_ttxOAFnTN4jeac-_8NeXpaXYYo0PPEIp8Ogo4YFNHE,935
+multipart/__pycache__/__init__.cpython-312.pyc,,
+multipart/__pycache__/decoders.cpython-312.pyc,,
+multipart/__pycache__/exceptions.cpython-312.pyc,,
+multipart/__pycache__/multipart.cpython-312.pyc,,
+multipart/decoders.py,sha256=XvkAwTU9UFPiXkc0hkvovHf0W6H3vK-2ieWlhav02hQ,40
+multipart/exceptions.py,sha256=6D_X-seiOmMAlIeiGlPGUs8-vpcvIGJeQycFMDb1f7A,42
+multipart/multipart.py,sha256=8fDH14j_VMbrch_58wlzi63XNARGv80kOZAyN72aG7A,41
+python_multipart-0.0.20.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+python_multipart-0.0.20.dist-info/METADATA,sha256=h2GtPOVShbVkpBUrjp5KE3t6eiJJhd0_WCaCXrb5TgU,1817
+python_multipart-0.0.20.dist-info/RECORD,,
+python_multipart-0.0.20.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+python_multipart-0.0.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
+python_multipart-0.0.20.dist-info/licenses/LICENSE.txt,sha256=qOgzF2zWF9rwC51tOfoVyo7evG0WQwec0vSJPAwom-I,556
+python_multipart/__init__.py,sha256=Nlw6Yrc__qXnCZLo17OzbJR2w2mwiSFk69IG4Wl35EU,512
+python_multipart/__pycache__/__init__.cpython-312.pyc,,
+python_multipart/__pycache__/decoders.cpython-312.pyc,,
+python_multipart/__pycache__/exceptions.cpython-312.pyc,,
+python_multipart/__pycache__/multipart.cpython-312.pyc,,
+python_multipart/decoders.py,sha256=JM43FMNn_EKP0MI2ZkuZHhNa0MOASoIR0U5TvdG585k,6669
+python_multipart/exceptions.py,sha256=a9buSOv_eiHZoukEJhdWX9LJYSJ6t7XOK3ZEaWoQZlk,992
+python_multipart/multipart.py,sha256=pk3o3eB3KXbNxzOBxbEjCdz-1ESEZIMXVIfl12grG-o,76427
+python_multipart/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/REQUESTED b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/WHEEL b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..12228d414b6cfed7c39d3781c85c63256a1d7fb5
--- /dev/null
+++ b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: hatchling 1.27.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/licenses/LICENSE.txt b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/licenses/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..303a1bf5015ca5c7374a9bde74dee5e2b205fbe3
--- /dev/null
+++ b/lib/python3.12/site-packages/python_multipart-0.0.20.dist-info/licenses/LICENSE.txt
@@ -0,0 +1,14 @@
+Copyright 2012, Andrew Dunham
+
+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
+
+ https://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/lib/python3.12/site-packages/ray-2.52.1.dist-info/INSTALLER b/lib/python3.12/site-packages/ray-2.52.1.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/METADATA b/lib/python3.12/site-packages/ray-2.52.1.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..7728c201e65efb548a5cfe1631ffb4d1e615a4f4
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/METADATA
@@ -0,0 +1,442 @@
+Metadata-Version: 2.4
+Name: ray
+Version: 2.52.1
+Summary: Ray provides a simple, universal API for building distributed applications.
+Home-page: https://github.com/ray-project/ray
+Author: Ray Team
+Author-email: ray-dev@googlegroups.com
+License: Apache 2.0
+Keywords: ray distributed parallel machine-learning hyperparameter-tuningreinforcement-learning deep-learning serving python
+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
+Requires-Python: >=3.9
+Requires-Dist: click!=8.3.*,>=7.0
+Requires-Dist: filelock
+Requires-Dist: jsonschema
+Requires-Dist: msgpack<2.0.0,>=1.0.0
+Requires-Dist: packaging
+Requires-Dist: protobuf>=3.20.3
+Requires-Dist: pyyaml
+Requires-Dist: requests
+Provides-Extra: cgraph
+Requires-Dist: cupy-cuda12x; sys_platform != "darwin" and extra == "cgraph"
+Provides-Extra: client
+Requires-Dist: grpcio!=1.56.0; sys_platform == "darwin" and extra == "client"
+Requires-Dist: grpcio; extra == "client"
+Provides-Extra: data
+Requires-Dist: numpy>=1.20; extra == "data"
+Requires-Dist: pandas>=1.3; extra == "data"
+Requires-Dist: pyarrow>=9.0.0; extra == "data"
+Requires-Dist: fsspec; extra == "data"
+Provides-Extra: default
+Requires-Dist: aiohttp>=3.7; extra == "default"
+Requires-Dist: aiohttp_cors; extra == "default"
+Requires-Dist: colorful; extra == "default"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "default"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "default"
+Requires-Dist: requests; extra == "default"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "default"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "default"
+Requires-Dist: opencensus; extra == "default"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "default"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "default"
+Requires-Dist: opentelemetry-proto; extra == "default"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "default"
+Requires-Dist: prometheus_client>=0.7.1; extra == "default"
+Requires-Dist: smart_open; extra == "default"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "default"
+Provides-Extra: observability
+Requires-Dist: memray; sys_platform != "win32" and extra == "observability"
+Provides-Extra: serve
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "serve"
+Requires-Dist: requests; extra == "serve"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "serve"
+Requires-Dist: opencensus; extra == "serve"
+Requires-Dist: watchfiles; extra == "serve"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "serve"
+Requires-Dist: aiohttp>=3.7; extra == "serve"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "serve"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "serve"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "serve"
+Requires-Dist: fastapi; extra == "serve"
+Requires-Dist: opentelemetry-proto; extra == "serve"
+Requires-Dist: uvicorn[standard]; extra == "serve"
+Requires-Dist: prometheus_client>=0.7.1; extra == "serve"
+Requires-Dist: aiohttp_cors; extra == "serve"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "serve"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "serve"
+Requires-Dist: smart_open; extra == "serve"
+Requires-Dist: colorful; extra == "serve"
+Requires-Dist: starlette; extra == "serve"
+Provides-Extra: tune
+Requires-Dist: pandas; extra == "tune"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "tune"
+Requires-Dist: tensorboardX>=1.9; extra == "tune"
+Requires-Dist: requests; extra == "tune"
+Requires-Dist: pyarrow>=9.0.0; extra == "tune"
+Requires-Dist: fsspec; extra == "tune"
+Provides-Extra: adag
+Requires-Dist: cupy-cuda12x; sys_platform != "darwin" and extra == "adag"
+Provides-Extra: serve-grpc
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "serve-grpc"
+Requires-Dist: requests; extra == "serve-grpc"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "serve-grpc"
+Requires-Dist: opencensus; extra == "serve-grpc"
+Requires-Dist: watchfiles; extra == "serve-grpc"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "serve-grpc"
+Requires-Dist: aiohttp>=3.7; extra == "serve-grpc"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "serve-grpc"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "serve-grpc"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "serve-grpc"
+Requires-Dist: fastapi; extra == "serve-grpc"
+Requires-Dist: opentelemetry-proto; extra == "serve-grpc"
+Requires-Dist: uvicorn[standard]; extra == "serve-grpc"
+Requires-Dist: prometheus_client>=0.7.1; extra == "serve-grpc"
+Requires-Dist: aiohttp_cors; extra == "serve-grpc"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "serve-grpc"
+Requires-Dist: pyOpenSSL; extra == "serve-grpc"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "serve-grpc"
+Requires-Dist: smart_open; extra == "serve-grpc"
+Requires-Dist: colorful; extra == "serve-grpc"
+Requires-Dist: starlette; extra == "serve-grpc"
+Provides-Extra: serve-async-inference
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "serve-async-inference"
+Requires-Dist: requests; extra == "serve-async-inference"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "serve-async-inference"
+Requires-Dist: opencensus; extra == "serve-async-inference"
+Requires-Dist: watchfiles; extra == "serve-async-inference"
+Requires-Dist: celery; extra == "serve-async-inference"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "serve-async-inference"
+Requires-Dist: aiohttp>=3.7; extra == "serve-async-inference"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "serve-async-inference"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "serve-async-inference"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "serve-async-inference"
+Requires-Dist: fastapi; extra == "serve-async-inference"
+Requires-Dist: opentelemetry-proto; extra == "serve-async-inference"
+Requires-Dist: uvicorn[standard]; extra == "serve-async-inference"
+Requires-Dist: prometheus_client>=0.7.1; extra == "serve-async-inference"
+Requires-Dist: aiohttp_cors; extra == "serve-async-inference"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "serve-async-inference"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "serve-async-inference"
+Requires-Dist: smart_open; extra == "serve-async-inference"
+Requires-Dist: colorful; extra == "serve-async-inference"
+Requires-Dist: starlette; extra == "serve-async-inference"
+Provides-Extra: cpp
+Requires-Dist: ray-cpp==2.52.1; extra == "cpp"
+Provides-Extra: rllib
+Requires-Dist: pandas; extra == "rllib"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "rllib"
+Requires-Dist: tensorboardX>=1.9; extra == "rllib"
+Requires-Dist: requests; extra == "rllib"
+Requires-Dist: pyarrow>=9.0.0; extra == "rllib"
+Requires-Dist: fsspec; extra == "rllib"
+Requires-Dist: dm_tree; extra == "rllib"
+Requires-Dist: gymnasium==1.1.1; extra == "rllib"
+Requires-Dist: lz4; extra == "rllib"
+Requires-Dist: ormsgpack==1.7.0; extra == "rllib"
+Requires-Dist: pyyaml; extra == "rllib"
+Requires-Dist: scipy; extra == "rllib"
+Provides-Extra: train
+Requires-Dist: pandas; extra == "train"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "train"
+Requires-Dist: tensorboardX>=1.9; extra == "train"
+Requires-Dist: requests; extra == "train"
+Requires-Dist: pyarrow>=9.0.0; extra == "train"
+Requires-Dist: fsspec; extra == "train"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "train"
+Provides-Extra: air
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "air"
+Requires-Dist: pyarrow>=9.0.0; extra == "air"
+Requires-Dist: requests; extra == "air"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "air"
+Requires-Dist: opencensus; extra == "air"
+Requires-Dist: watchfiles; extra == "air"
+Requires-Dist: tensorboardX>=1.9; extra == "air"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "air"
+Requires-Dist: aiohttp>=3.7; extra == "air"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "air"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "air"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "air"
+Requires-Dist: fastapi; extra == "air"
+Requires-Dist: pandas; extra == "air"
+Requires-Dist: opentelemetry-proto; extra == "air"
+Requires-Dist: uvicorn[standard]; extra == "air"
+Requires-Dist: pandas>=1.3; extra == "air"
+Requires-Dist: prometheus_client>=0.7.1; extra == "air"
+Requires-Dist: aiohttp_cors; extra == "air"
+Requires-Dist: fsspec; extra == "air"
+Requires-Dist: numpy>=1.20; extra == "air"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "air"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "air"
+Requires-Dist: smart_open; extra == "air"
+Requires-Dist: colorful; extra == "air"
+Requires-Dist: starlette; extra == "air"
+Provides-Extra: all
+Requires-Dist: scipy; extra == "all"
+Requires-Dist: memray; sys_platform != "win32" and extra == "all"
+Requires-Dist: grpcio!=1.56.0; sys_platform == "darwin" and extra == "all"
+Requires-Dist: pyarrow>=9.0.0; extra == "all"
+Requires-Dist: requests; extra == "all"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "all"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "all"
+Requires-Dist: opencensus; extra == "all"
+Requires-Dist: watchfiles; extra == "all"
+Requires-Dist: celery; extra == "all"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "all"
+Requires-Dist: aiohttp>=3.7; extra == "all"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "all"
+Requires-Dist: tensorboardX>=1.9; extra == "all"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "all"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "all"
+Requires-Dist: opentelemetry-proto; extra == "all"
+Requires-Dist: fastapi; extra == "all"
+Requires-Dist: pandas; extra == "all"
+Requires-Dist: dm_tree; extra == "all"
+Requires-Dist: uvicorn[standard]; extra == "all"
+Requires-Dist: pandas>=1.3; extra == "all"
+Requires-Dist: prometheus_client>=0.7.1; extra == "all"
+Requires-Dist: aiohttp_cors; extra == "all"
+Requires-Dist: grpcio; extra == "all"
+Requires-Dist: fsspec; extra == "all"
+Requires-Dist: lz4; extra == "all"
+Requires-Dist: numpy>=1.20; extra == "all"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "all"
+Requires-Dist: cupy-cuda12x; sys_platform != "darwin" and extra == "all"
+Requires-Dist: pyOpenSSL; extra == "all"
+Requires-Dist: pyyaml; extra == "all"
+Requires-Dist: ormsgpack==1.7.0; extra == "all"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "all"
+Requires-Dist: gymnasium==1.1.1; extra == "all"
+Requires-Dist: smart_open; extra == "all"
+Requires-Dist: colorful; extra == "all"
+Requires-Dist: starlette; extra == "all"
+Provides-Extra: all-cpp
+Requires-Dist: scipy; extra == "all-cpp"
+Requires-Dist: memray; sys_platform != "win32" and extra == "all-cpp"
+Requires-Dist: pyarrow>=9.0.0; extra == "all-cpp"
+Requires-Dist: grpcio!=1.56.0; sys_platform == "darwin" and extra == "all-cpp"
+Requires-Dist: requests; extra == "all-cpp"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "all-cpp"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "all-cpp"
+Requires-Dist: ray-cpp==2.52.1; extra == "all-cpp"
+Requires-Dist: opencensus; extra == "all-cpp"
+Requires-Dist: watchfiles; extra == "all-cpp"
+Requires-Dist: celery; extra == "all-cpp"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "all-cpp"
+Requires-Dist: aiohttp>=3.7; extra == "all-cpp"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "all-cpp"
+Requires-Dist: tensorboardX>=1.9; extra == "all-cpp"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "all-cpp"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "all-cpp"
+Requires-Dist: opentelemetry-proto; extra == "all-cpp"
+Requires-Dist: fastapi; extra == "all-cpp"
+Requires-Dist: pandas; extra == "all-cpp"
+Requires-Dist: dm_tree; extra == "all-cpp"
+Requires-Dist: uvicorn[standard]; extra == "all-cpp"
+Requires-Dist: pandas>=1.3; extra == "all-cpp"
+Requires-Dist: prometheus_client>=0.7.1; extra == "all-cpp"
+Requires-Dist: aiohttp_cors; extra == "all-cpp"
+Requires-Dist: grpcio; extra == "all-cpp"
+Requires-Dist: smart_open; extra == "all-cpp"
+Requires-Dist: fsspec; extra == "all-cpp"
+Requires-Dist: lz4; extra == "all-cpp"
+Requires-Dist: numpy>=1.20; extra == "all-cpp"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "all-cpp"
+Requires-Dist: cupy-cuda12x; sys_platform != "darwin" and extra == "all-cpp"
+Requires-Dist: pyOpenSSL; extra == "all-cpp"
+Requires-Dist: pyyaml; extra == "all-cpp"
+Requires-Dist: ormsgpack==1.7.0; extra == "all-cpp"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "all-cpp"
+Requires-Dist: gymnasium==1.1.1; extra == "all-cpp"
+Requires-Dist: colorful; extra == "all-cpp"
+Requires-Dist: starlette; extra == "all-cpp"
+Provides-Extra: llm
+Requires-Dist: pybind11; extra == "llm"
+Requires-Dist: pyarrow>=9.0.0; extra == "llm"
+Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3; extra == "llm"
+Requires-Dist: requests; extra == "llm"
+Requires-Dist: virtualenv!=20.21.1,>=20.0.24; extra == "llm"
+Requires-Dist: hf_transfer; extra == "llm"
+Requires-Dist: opencensus; extra == "llm"
+Requires-Dist: watchfiles; extra == "llm"
+Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "llm"
+Requires-Dist: aiohttp>=3.7; extra == "llm"
+Requires-Dist: py-spy>=0.2.0; python_version < "3.12" and extra == "llm"
+Requires-Dist: typer; extra == "llm"
+Requires-Dist: jsonref>=1.1.0; extra == "llm"
+Requires-Dist: opentelemetry-exporter-prometheus; extra == "llm"
+Requires-Dist: grpcio>=1.32.0; python_version < "3.10" and extra == "llm"
+Requires-Dist: meson; extra == "llm"
+Requires-Dist: fastapi; extra == "llm"
+Requires-Dist: opentelemetry-proto; extra == "llm"
+Requires-Dist: uvicorn[standard]; extra == "llm"
+Requires-Dist: pandas>=1.3; extra == "llm"
+Requires-Dist: prometheus_client>=0.7.1; extra == "llm"
+Requires-Dist: aiohttp_cors; extra == "llm"
+Requires-Dist: ninja; extra == "llm"
+Requires-Dist: vllm[audio]>=0.11.0; extra == "llm"
+Requires-Dist: fsspec; extra == "llm"
+Requires-Dist: numpy>=1.20; extra == "llm"
+Requires-Dist: async-timeout; python_version < "3.11" and extra == "llm"
+Requires-Dist: grpcio>=1.42.0; python_version >= "3.10" and extra == "llm"
+Requires-Dist: py-spy>=0.4.0; python_version >= "3.12" and extra == "llm"
+Requires-Dist: jsonschema; extra == "llm"
+Requires-Dist: nixl>=0.6.1; extra == "llm"
+Requires-Dist: smart_open; extra == "llm"
+Requires-Dist: colorful; extra == "llm"
+Requires-Dist: starlette; extra == "llm"
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: provides-extra
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
+
+.. image:: https://github.com/ray-project/ray/raw/master/doc/source/images/ray_header_logo.png
+
+.. image:: https://readthedocs.org/projects/ray/badge/?version=master
+ :target: http://docs.ray.io/en/master/?badge=master
+
+.. image:: https://img.shields.io/badge/Ray-Join%20Slack-blue
+ :target: https://www.ray.io/join-slack
+
+.. image:: https://img.shields.io/badge/Discuss-Ask%20Questions-blue
+ :target: https://discuss.ray.io/
+
+.. image:: https://img.shields.io/twitter/follow/raydistributed.svg?style=social&logo=twitter
+ :target: https://x.com/raydistributed
+
+.. image:: https://img.shields.io/badge/Get_started_for_free-3C8AE9?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABKElEQVQ4Ea2TvWoCQRRGnWCVWChIIlikC9hpJdikSbGgaONbpAoY8gKBdAGfwkfwKQypLQ1sEGyMYhN1Pd%2B6A8PqwBZeOHt%2FvsvMnd3ZXBRFPQjBZ9K6OY8ZxF%2B0IYw9PW3qz8aY6lk92bZ%2BVqSI3oC9T7%2FyCVnrF1ngj93us%2B540sf5BrCDfw9b6jJ5lx%2FyjtGKBBXc3cnqx0INN4ImbI%2Bl%2BPnI8zWfFEr4chLLrWHCp9OO9j19Kbc91HX0zzzBO8EbLK2Iv4ZvNO3is3h6jb%2BCwO0iL8AaWqB7ILPTxq3kDypqvBuYuwswqo6wgYJbT8XxBPZ8KS1TepkFdC79TAHHce%2F7LbVioi3wEfTpmeKtPRGEeoldSP%2FOeoEftpP4BRbgXrYZefsAI%2BP9JU7ImyEAAAAASUVORK5CYII%3D
+ :target: https://www.anyscale.com/ray-on-anyscale?utm_source=github&utm_medium=ray_readme&utm_campaign=get_started_badge
+
+Ray is a unified framework for scaling AI and Python applications. Ray consists of a core distributed runtime and a set of AI libraries for simplifying ML compute:
+
+.. image:: https://github.com/ray-project/ray/raw/master/doc/source/images/what-is-ray-padded.svg
+
+..
+ https://docs.google.com/drawings/d/1Pl8aCYOsZCo61cmp57c7Sja6HhIygGCvSZLi_AuBuqo/edit
+
+Learn more about `Ray AI Libraries`_:
+
+- `Data`_: Scalable Datasets for ML
+- `Train`_: Distributed Training
+- `Tune`_: Scalable Hyperparameter Tuning
+- `RLlib`_: Scalable Reinforcement Learning
+- `Serve`_: Scalable and Programmable Serving
+
+Or more about `Ray Core`_ and its key abstractions:
+
+- `Tasks`_: Stateless functions executed in the cluster.
+- `Actors`_: Stateful worker processes created in the cluster.
+- `Objects`_: Immutable values accessible across the cluster.
+
+Learn more about Monitoring and Debugging:
+
+- Monitor Ray apps and clusters with the `Ray Dashboard `__.
+- Debug Ray apps with the `Ray Distributed Debugger `__.
+
+Ray runs on any machine, cluster, cloud provider, and Kubernetes, and features a growing
+`ecosystem of community integrations`_.
+
+Install Ray with: ``pip install ray``. For nightly wheels, see the
+`Installation page `__.
+
+.. _`Serve`: https://docs.ray.io/en/latest/serve/index.html
+.. _`Data`: https://docs.ray.io/en/latest/data/dataset.html
+.. _`Workflow`: https://docs.ray.io/en/latest/workflows/
+.. _`Train`: https://docs.ray.io/en/latest/train/train.html
+.. _`Tune`: https://docs.ray.io/en/latest/tune/index.html
+.. _`RLlib`: https://docs.ray.io/en/latest/rllib/index.html
+.. _`ecosystem of community integrations`: https://docs.ray.io/en/latest/ray-overview/ray-libraries.html
+
+
+Why Ray?
+--------
+
+Today's ML workloads are increasingly compute-intensive. As convenient as they are, single-node development environments such as your laptop cannot scale to meet these demands.
+
+Ray is a unified way to scale Python and AI applications from a laptop to a cluster.
+
+With Ray, you can seamlessly scale the same code from a laptop to a cluster. Ray is designed to be general-purpose, meaning that it can performantly run any kind of workload. If your application is written in Python, you can scale it with Ray, no other infrastructure required.
+
+More Information
+----------------
+
+- `Documentation`_
+- `Ray Architecture whitepaper`_
+- `Exoshuffle: large-scale data shuffle in Ray`_
+- `Ownership: a distributed futures system for fine-grained tasks`_
+- `RLlib paper`_
+- `Tune paper`_
+
+*Older documents:*
+
+- `Ray paper`_
+- `Ray HotOS paper`_
+- `Ray Architecture v1 whitepaper`_
+
+.. _`Ray AI Libraries`: https://docs.ray.io/en/latest/ray-air/getting-started.html
+.. _`Ray Core`: https://docs.ray.io/en/latest/ray-core/walkthrough.html
+.. _`Tasks`: https://docs.ray.io/en/latest/ray-core/tasks.html
+.. _`Actors`: https://docs.ray.io/en/latest/ray-core/actors.html
+.. _`Objects`: https://docs.ray.io/en/latest/ray-core/objects.html
+.. _`Documentation`: http://docs.ray.io/en/latest/index.html
+.. _`Ray Architecture v1 whitepaper`: https://docs.google.com/document/d/1lAy0Owi-vPz2jEqBSaHNQcy2IBSDEHyXNOQZlGuj93c/preview
+.. _`Ray Architecture whitepaper`: https://docs.google.com/document/d/1tBw9A4j62ruI5omIJbMxly-la5w4q_TjyJgJL_jN2fI/preview
+.. _`Exoshuffle: large-scale data shuffle in Ray`: https://arxiv.org/abs/2203.05072
+.. _`Ownership: a distributed futures system for fine-grained tasks`: https://www.usenix.org/system/files/nsdi21-wang.pdf
+.. _`Ray paper`: https://arxiv.org/abs/1712.05889
+.. _`Ray HotOS paper`: https://arxiv.org/abs/1703.03924
+.. _`RLlib paper`: https://arxiv.org/abs/1712.09381
+.. _`Tune paper`: https://arxiv.org/abs/1807.05118
+
+Getting Involved
+----------------
+
+.. list-table::
+ :widths: 25 50 25 25
+ :header-rows: 1
+
+ * - Platform
+ - Purpose
+ - Estimated Response Time
+ - Support Level
+ * - `Discourse Forum`_
+ - For discussions about development and questions about usage.
+ - < 1 day
+ - Community
+ * - `GitHub Issues`_
+ - For reporting bugs and filing feature requests.
+ - < 2 days
+ - Ray OSS Team
+ * - `Slack`_
+ - For collaborating with other Ray users.
+ - < 2 days
+ - Community
+ * - `StackOverflow`_
+ - For asking questions about how to use Ray.
+ - 3-5 days
+ - Community
+ * - `Meetup Group`_
+ - For learning about Ray projects and best practices.
+ - Monthly
+ - Ray DevRel
+ * - `Twitter`_
+ - For staying up-to-date on new features.
+ - Daily
+ - Ray DevRel
+
+.. _`Discourse Forum`: https://discuss.ray.io/
+.. _`GitHub Issues`: https://github.com/ray-project/ray/issues
+.. _`StackOverflow`: https://stackoverflow.com/questions/tagged/ray
+.. _`Meetup Group`: https://www.meetup.com/Bay-Area-Ray-Meetup/
+.. _`Twitter`: https://x.com/raydistributed
+.. _`Slack`: https://www.ray.io/join-slack?utm_source=github&utm_medium=ray_readme&utm_campaign=getting_involved
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/RECORD b/lib/python3.12/site-packages/ray-2.52.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..1cef673da6701535c874796e0ebc5c6096ecdb55
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/RECORD
@@ -0,0 +1,4871 @@
+../../../bin/ray,sha256=C9V05DbIE0tCouyEGGLeupJTWhcZUYtr7K2e3iEGlhk,254
+../../../bin/serve,sha256=ApdHl81vyOuf0AHL8Z1DFgchFFLEnYYYTHRKn00y6LE,250
+../../../bin/tune,sha256=ciP0qydQlSSBAj-x63NZGOP-zU6u0wHKFFG4pzYGIfg,253
+ray-2.52.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray-2.52.1.dist-info/METADATA,sha256=9ZbIfO555OXtQZZE5YfxlVvENlASuppNWbJyF_9kWyM,21837
+ray-2.52.1.dist-info/RECORD,,
+ray-2.52.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray-2.52.1.dist-info/WHEEL,sha256=6TsICjgOR7isz_jYr-ssV7RSRmh1_0Z7_b5ESlzfzVY,104
+ray-2.52.1.dist-info/entry_points.txt,sha256=HzQIZGspUzWojBmR9Xvb28m4bPhi-yJjV1U1pdlRkyY,111
+ray-2.52.1.dist-info/top_level.txt,sha256=GJvmpjCEmVfQsBskWDIj2hraOPW3F8A46rlbd9c3vc0,4
+ray/__init__.py,sha256=VOgUobwPfCapLKLkEbVtcwTC0-HkSQ38b9yoG-WkgOg,7661
+ray/__pycache__/__init__.cpython-312.pyc,,
+ray/__pycache__/_version.cpython-312.pyc,,
+ray/__pycache__/actor.cpython-312.pyc,,
+ray/__pycache__/client_builder.cpython-312.pyc,,
+ray/__pycache__/cluster_utils.cpython-312.pyc,,
+ray/__pycache__/cross_language.cpython-312.pyc,,
+ray/__pycache__/exceptions.cpython-312.pyc,,
+ray/__pycache__/job_config.cpython-312.pyc,,
+ray/__pycache__/remote_function.cpython-312.pyc,,
+ray/__pycache__/runtime_context.cpython-312.pyc,,
+ray/__pycache__/setup-dev.cpython-312.pyc,,
+ray/__pycache__/types.cpython-312.pyc,,
+ray/_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_common/__pycache__/__init__.cpython-312.pyc,,
+ray/_common/__pycache__/constants.cpython-312.pyc,,
+ray/_common/__pycache__/deprecation.cpython-312.pyc,,
+ray/_common/__pycache__/filters.cpython-312.pyc,,
+ray/_common/__pycache__/formatters.cpython-312.pyc,,
+ray/_common/__pycache__/network_utils.cpython-312.pyc,,
+ray/_common/__pycache__/pydantic_compat.cpython-312.pyc,,
+ray/_common/__pycache__/ray_constants.cpython-312.pyc,,
+ray/_common/__pycache__/ray_option_utils.cpython-312.pyc,,
+ray/_common/__pycache__/retry.cpython-312.pyc,,
+ray/_common/__pycache__/serialization.cpython-312.pyc,,
+ray/_common/__pycache__/signature.cpython-312.pyc,,
+ray/_common/__pycache__/test_utils.cpython-312.pyc,,
+ray/_common/__pycache__/utils.cpython-312.pyc,,
+ray/_common/constants.py,sha256=G0Wt7SmWzDCbXmp6MjH3lc0WiD8Q25wl7JEdOAZAWhU,343
+ray/_common/deprecation.py,sha256=xIhQO1VjNzgYy7h_Wg3qZ4SfBv003xfVY2cU_5qsi_U,4883
+ray/_common/filters.py,sha256=ZB8lAsvICQFfRiZeQLSuAaFnJDuZ6tDQNZmvQRBV0v4,1962
+ray/_common/formatters.py,sha256=q8BOWvHgcRU8UsK7x_biFvcNHNRVw44BLlBvMabx3F4,4585
+ray/_common/network_utils.py,sha256=fv4WtXQAPEkPjxJ6isENG5xx1cBSConQaVQdrzwZzCA,3099
+ray/_common/pydantic_compat.py,sha256=818OJn_pmwdL2GJQCZaR8jEjOEnmore6nK7PDJcj6YI,3067
+ray/_common/ray_constants.py,sha256=gdIwSG7kK6oUKNxCUSHmI2OSqQ4dNOottdF-8bXQntM,213
+ray/_common/ray_option_utils.py,sha256=yRLc9flF25v20HH19_6V4O9PlKeP1cbxGyPqxNG38r0,14743
+ray/_common/retry.py,sha256=72BIf2SJYS4cOKEqoIuulelowKokJtCM-1uDe-f7klM,2683
+ray/_common/serialization.py,sha256=JYidd4HyewNdQCwa1LsxzPbyGamOMbA8npJ9j2tBtuI,1010
+ray/_common/signature.py,sha256=Ij0PgmvRHKgapPAke7DKf-mqB-1ii1YWXvMhrTsFjDQ,5829
+ray/_common/test_utils.py,sha256=JN8Rv4aR3dqgmsaY8pL3uPXF8igP23x0n2dRcT69sWs,7401
+ray/_common/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_common/usage/__pycache__/__init__.cpython-312.pyc,,
+ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc,,
+ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc,,
+ray/_common/usage/usage_constants.py,sha256=wYcxpwBfh6U-qNQ2BlAp1W938ie486rghfNwOXtOQPk,2421
+ray/_common/usage/usage_lib.py,sha256=wUn8BrcodjZx58uNcBh3PBtgWz3pYb_DldyE8z5Macs,35846
+ray/_common/utils.py,sha256=7ovGI-nmg_riHdpQTIc22-h47huUxT-6wkA9DNGnxJc,12440
+ray/_private/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/__pycache__/arrow_serialization.cpython-312.pyc,,
+ray/_private/__pycache__/arrow_utils.cpython-312.pyc,,
+ray/_private/__pycache__/async_compat.cpython-312.pyc,,
+ray/_private/__pycache__/async_utils.cpython-312.pyc,,
+ray/_private/__pycache__/authentication_test_utils.cpython-312.pyc,,
+ray/_private/__pycache__/auto_init_hook.cpython-312.pyc,,
+ray/_private/__pycache__/client_mode_hook.cpython-312.pyc,,
+ray/_private/__pycache__/collections_utils.cpython-312.pyc,,
+ray/_private/__pycache__/compat.cpython-312.pyc,,
+ray/_private/__pycache__/conftest_utils.cpython-312.pyc,,
+ray/_private/__pycache__/custom_types.cpython-312.pyc,,
+ray/_private/__pycache__/dict.cpython-312.pyc,,
+ray/_private/__pycache__/external_storage.cpython-312.pyc,,
+ray/_private/__pycache__/function_manager.cpython-312.pyc,,
+ray/_private/__pycache__/gc_collect_manager.cpython-312.pyc,,
+ray/_private/__pycache__/gcs_pubsub.cpython-312.pyc,,
+ray/_private/__pycache__/gcs_utils.cpython-312.pyc,,
+ray/_private/__pycache__/grpc_utils.cpython-312.pyc,,
+ray/_private/__pycache__/inspect_util.cpython-312.pyc,,
+ray/_private/__pycache__/internal_api.cpython-312.pyc,,
+ray/_private/__pycache__/label_utils.cpython-312.pyc,,
+ray/_private/__pycache__/log.cpython-312.pyc,,
+ray/_private/__pycache__/log_monitor.cpython-312.pyc,,
+ray/_private/__pycache__/logging_utils.cpython-312.pyc,,
+ray/_private/__pycache__/memory_monitor.cpython-312.pyc,,
+ray/_private/__pycache__/metrics_agent.cpython-312.pyc,,
+ray/_private/__pycache__/node.cpython-312.pyc,,
+ray/_private/__pycache__/object_ref_generator.cpython-312.pyc,,
+ray/_private/__pycache__/parameter.cpython-312.pyc,,
+ray/_private/__pycache__/path_utils.cpython-312.pyc,,
+ray/_private/__pycache__/process_watcher.cpython-312.pyc,,
+ray/_private/__pycache__/profiling.cpython-312.pyc,,
+ray/_private/__pycache__/prometheus_exporter.cpython-312.pyc,,
+ray/_private/__pycache__/protobuf_compat.cpython-312.pyc,,
+ray/_private/__pycache__/ray_client_microbenchmark.cpython-312.pyc,,
+ray/_private/__pycache__/ray_cluster_perf.cpython-312.pyc,,
+ray/_private/__pycache__/ray_constants.cpython-312.pyc,,
+ray/_private/__pycache__/ray_experimental_perf.cpython-312.pyc,,
+ray/_private/__pycache__/ray_microbenchmark_helpers.cpython-312.pyc,,
+ray/_private/__pycache__/ray_perf.cpython-312.pyc,,
+ray/_private/__pycache__/ray_process_reaper.cpython-312.pyc,,
+ray/_private/__pycache__/resource_and_label_spec.cpython-312.pyc,,
+ray/_private/__pycache__/resource_isolation_config.cpython-312.pyc,,
+ray/_private/__pycache__/serialization.cpython-312.pyc,,
+ray/_private/__pycache__/services.cpython-312.pyc,,
+ray/_private/__pycache__/state.cpython-312.pyc,,
+ray/_private/__pycache__/state_api_test_utils.cpython-312.pyc,,
+ray/_private/__pycache__/test_utils.cpython-312.pyc,,
+ray/_private/__pycache__/tls_utils.cpython-312.pyc,,
+ray/_private/__pycache__/utils.cpython-312.pyc,,
+ray/_private/__pycache__/worker.cpython-312.pyc,,
+ray/_private/accelerators/__init__.py,sha256=Nfc8eg0scyKhoAx9pOt8lF_d2dGYFGml0t-lf7hHHi8,3268
+ray/_private/accelerators/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/accelerator.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/amd_gpu.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/hpu.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/intel_gpu.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/neuron.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/npu.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/nvidia_gpu.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/rbln.cpython-312.pyc,,
+ray/_private/accelerators/__pycache__/tpu.cpython-312.pyc,,
+ray/_private/accelerators/accelerator.py,sha256=7UjYLw90zFzcbGy15IAzSBabcBgo7Z7SIQ2CE57C3nA,5743
+ray/_private/accelerators/amd_gpu.py,sha256=d7eJWRxigQ7J8-pQOkLLThU2ej1YeuO8hOnN1X-ltSQ,5021
+ray/_private/accelerators/hpu.py,sha256=iOt_6hd57pixHxaLcjt47XSeb-RZqaUsf73t8Mx8Iek,3701
+ray/_private/accelerators/intel_gpu.py,sha256=38ugVUTogY3xkJqUD8Nluuk2_b_6FiUP3JcKVgaBuVI,3176
+ray/_private/accelerators/neuron.py,sha256=al2WvKAz1wcE_XjnQfRWsCVIAuYLylmeONZqt4P4loo,4503
+ray/_private/accelerators/npu.py,sha256=tK9CqENLFGPyoAng4spoULhacKnC0jmK6sqfXtoqe7I,2879
+ray/_private/accelerators/nvidia_gpu.py,sha256=AEodc2q52F7esqskIaaOfM1eVmCKCQSa2kGyWaMbm2g,4087
+ray/_private/accelerators/rbln.py,sha256=nsM30aGvyDrbbeKbFkzHi509b1SyQf3_XWXpJUKXu9E,2533
+ray/_private/accelerators/tpu.py,sha256=oyEmdDoOrNUi5Pt2265KiqJe_GB4V3z91zLkEqyGQWg,24928
+ray/_private/arrow_serialization.py,sha256=Jd4cx2drO2M3yta3k3vYE69swhjUkxYinAu6HnxTn18,27723
+ray/_private/arrow_utils.py,sha256=Vb1w1DmWbedPwzu17dCHFvJEmnOLP93IBkRM6olmTfc,4771
+ray/_private/async_compat.py,sha256=apwUkhuQ2tpaAf5C-YAStn17TtoudHIP8RIFjzvZTBQ,1411
+ray/_private/async_utils.py,sha256=sHHqklv7NYpjp8lOOG8QCkNcerAxGg1zyLUvDdR7GDk,1883
+ray/_private/authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/authentication/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/authentication_constants.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/authentication_token_generator.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/authentication_token_setup.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/authentication_utils.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/grpc_authentication_client_interceptor.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/grpc_authentication_server_interceptor.cpython-312.pyc,,
+ray/_private/authentication/__pycache__/http_token_authentication.cpython-312.pyc,,
+ray/_private/authentication/authentication_constants.py,sha256=bc4hU4lOL9hYkgMMzxZMUHhX1ud9JTZE3YCRa7NZ5Io,604
+ray/_private/authentication/authentication_token_generator.py,sha256=YWqm8kHostOnCkAnLyAh1JQsavlsCDxzU1roR1fjVek,251
+ray/_private/authentication/authentication_token_setup.py,sha256=Hfyes2tcd9ZjX0hcsE33yMQ2ySyGNGtlxsqWE9s4RAI,3504
+ray/_private/authentication/authentication_utils.py,sha256=oV9kmM48kZ7tv29AJQK84PhcEu2zQ8mm2kEV0ix004U,1157
+ray/_private/authentication/grpc_authentication_client_interceptor.py,sha256=ZQF7iuzX2J3DtbXlivJ-lbgIo0JiynFAUGQXVZnH6ak,4774
+ray/_private/authentication/grpc_authentication_server_interceptor.py,sha256=U8135Sls9yWZvesKTPgu0VUlXwyKTZAsmTDe4leFgh8,7365
+ray/_private/authentication/http_token_authentication.py,sha256=Q1LF0tHOFQDOW5c77iTS7WghrtZvwpv9s16LPRDS0Jo,4503
+ray/_private/authentication_test_utils.py,sha256=_zKSxyMXBjK15p6qM9R83Zn18bRxS6JAdHzY0tXfEkU,5202
+ray/_private/auto_init_hook.py,sha256=bRvPtr5a0S1q_NOTjKe50K4bc0AYpvxbvnDTcSTdTtc,793
+ray/_private/client_mode_hook.py,sha256=yXf6EJSaLzuaKcj5_BAfxUkHv1V0OOa2-YCkuChpprw,6450
+ray/_private/collections_utils.py,sha256=MbM42Y4jvTPGyNDtobFFDxjLjRZxqmjWLi9W3i-MEHE,277
+ray/_private/compat.py,sha256=UnC8qxGUeAU4hWqhF1nGecRrBkYC-8yUsidlKMz5v7g,1343
+ray/_private/conftest_utils.py,sha256=92hVLjQ-FyzC35_HcR03jhqrSGGqB7YBnm1vm_OLauw,417
+ray/_private/custom_types.py,sha256=5LShTHT9A45y0NTLHbEQMsD1iPAzR8_euzrmEMIfANE,5027
+ray/_private/dict.py,sha256=FACFqxBCWLeOG_dQ-m080ykso0dfWSf5ilYO4mJzhh8,8463
+ray/_private/event/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/event/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/event/__pycache__/event_logger.cpython-312.pyc,,
+ray/_private/event/__pycache__/export_event_logger.cpython-312.pyc,,
+ray/_private/event/event_logger.py,sha256=vrCBM6tE1JxlsE4vp7vhlsiKDAeuAdscVQ0E0rCyQ_o,6184
+ray/_private/event/export_event_logger.py,sha256=leXFq1wyqBB-K1kgROPRYt33WvHNaIOS_Br7aSCtyhY,9091
+ray/_private/external_storage.py,sha256=wIZoEoY5Sz0oVOHzQ9OuULUF8-45fcjG7mmBt_KI-2c,24748
+ray/_private/function_manager.py,sha256=CdMrZVcOCfBFaNWkklnTlJwMfdMgb1mAGOrWnhY3sKE,29610
+ray/_private/gc_collect_manager.py,sha256=RDVWH9ymn_Ae1UV6Mzo9rEByi1EzW_jVjJO7KyWikl4,2203
+ray/_private/gcs_pubsub.py,sha256=a77TsssTDX4AvrFutLt_4RxFrXJRxs-DEU1iml0qrRk,9446
+ray/_private/gcs_utils.py,sha256=Tz0N3agPN5rgpzHyNY11E97WDVAxw7Ft9KGiQ4ZPu2Y,4270
+ray/_private/grpc_utils.py,sha256=AvVDcnkAYx2VqJZYjvoc0_cSeApvYqNtHu_Q7XuhnJ0,5706
+ray/_private/inspect_util.py,sha256=Qyqz0xhcpPobIpAq1yXATbAf8dgD3i9lGjVG1cp0fJU,1560
+ray/_private/internal_api.py,sha256=cIzcUGire1c6loxUnqeiIkMdz_0UTqwnKC4RrUSmIGY,9204
+ray/_private/label_utils.py,sha256=CvcLWdZdeY8HbvJdf38MUZ189v9nQk7QSM4sBTFjcng,8622
+ray/_private/log.py,sha256=ufxXoHtFmFVy32ZR9WhSyuKmW5p_r-rhfXwVKKlrvjA,4780
+ray/_private/log_monitor.py,sha256=QJ4iM1QMoWzOlwZ24bp7ID0mXVz1mVxWM6m7mKbG3A0,24538
+ray/_private/logging_utils.py,sha256=zxDA4j-lK0zajUIFyKpvcvAs2hCOKJJ3QtcFRwgobNE,1885
+ray/_private/memory_monitor.py,sha256=qfSW3Ok-VYoI-o6nkOUf_AVBaU-7JPNaDy7FFhZ2WfU,6086
+ray/_private/metrics_agent.py,sha256=FFhLggayDQePxpXcUnQa27aOOyuYoo3pOePo7eZbqMI,34241
+ray/_private/node.py,sha256=lqhKl23b9RejxmVji586992zvjE2Pl_ps5ih22QX7gQ,74618
+ray/_private/object_ref_generator.py,sha256=a5inBBqEfjdL1LKjuUmNfQquclVtb0-EJL14ugzL3OI,10554
+ray/_private/parameter.py,sha256=ED0DTAmJoQzGmCoLC_l0P2rUX4SErpYVJ0_WE77MKII,22122
+ray/_private/path_utils.py,sha256=Sg1cSoEAYz-Q1XPHjo8IBvn3M35e2rcVRWRj8uYhp5M,1128
+ray/_private/process_watcher.py,sha256=z4sd82h9JWzJeizNqSoStgss6Kse_ACyxCRR86PTIyQ,7500
+ray/_private/profiling.py,sha256=vVs87pynjGXGrxCVZoZq8KgPMWOSectRgsmF-msdtm8,8786
+ray/_private/prometheus_exporter.py,sha256=7hdYfhQ4bz1dUpZeaT9jbzlDmG0ztVeXReK3MBcaLZg,13521
+ray/_private/protobuf_compat.py,sha256=otIASMKEZqdEEzoCwuu4dlKYw8qy6mTsZLHGtLcp5CY,1936
+ray/_private/ray_client_microbenchmark.py,sha256=0r5YblHrU_KJu-gzWvvjTefhpK3XQL74kHE0yPwuLOE,2901
+ray/_private/ray_cluster_perf.py,sha256=W9Xglrh2yZmpmM9C0RrL7sJ2h9HrzcdEOHnfXCS68FU,1208
+ray/_private/ray_constants.py,sha256=3bxP8vaV1xn2LzwC0o6j028n9IM8rt28qBDgW6egnO0,24609
+ray/_private/ray_experimental_perf.py,sha256=QxqDrtnwYlhQSr-ZPn8y_tpOJs9k2X_W9hRf1FPeKPc,10945
+ray/_private/ray_logging/__init__.py,sha256=TddjQTuLFJLr-8OhYZNSqPwhbXYFXs8ZI85yc5wC_xg,13219
+ray/_private/ray_logging/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/ray_logging/__pycache__/constants.cpython-312.pyc,,
+ray/_private/ray_logging/__pycache__/default_impl.cpython-312.pyc,,
+ray/_private/ray_logging/__pycache__/logging_config.cpython-312.pyc,,
+ray/_private/ray_logging/constants.py,sha256=J_gyyWOxreGgau3kTcFF1AQuw5bxeNv067aQVq9Ww6Y,1317
+ray/_private/ray_logging/default_impl.py,sha256=U7yTVUfIsQKP3MBwXuJjT3sM3Kl9YIQ441_RR_pJ4yA,156
+ray/_private/ray_logging/logging_config.py,sha256=gu0RBLQEKvY_HkQlZlvzHZS8mIFY1n3bZd4d3xEqen8,6464
+ray/_private/ray_microbenchmark_helpers.py,sha256=34BCPDh53E8nSsBwgiaz7m-XjXbkoHLi9yrxgxaC7vI,2550
+ray/_private/ray_perf.py,sha256=dIEM_XlLlyZKz8atidagazHiX9UX4wlFsF_OeixQTAo,8982
+ray/_private/ray_process_reaper.py,sha256=g1aG_umFCw3dGql89o7Vuhlu8_ebqy60f6MUQEZUJN4,2069
+ray/_private/resource_and_label_spec.py,sha256=PhAJVZLiUyD8sHMR04f6NgdvMiI51AhYlw8_adsRNU4,19661
+ray/_private/resource_isolation_config.py,sha256=ygRbUp-sjq4qXQ7OAtdW3IazGPZu1q7gYTIXrTX7-EQ,13648
+ray/_private/runtime_env/__init__.py,sha256=FufsPD2IbH6u2OCTWBnZuhtn9qG4wc7EX7MaYJs73Fg,221
+ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/context.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/pip.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/plugin.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/plugin_schema_manager.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/protocol.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/py_executable.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/py_modules.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/rocprof_sys.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/setup_hook.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/uri_cache.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/utils.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/uv.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/uv_runtime_env_hook.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/validation.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/virtualenv_utils.cpython-312.pyc,,
+ray/_private/runtime_env/__pycache__/working_dir.cpython-312.pyc,,
+ray/_private/runtime_env/_clonevirtualenv.py,sha256=Jv10azUwb2sIOoSyBS26XoLXhZdF0HK_HaGHSZUkpnI,10970
+ray/_private/runtime_env/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/__pycache__/main.cpython-312.pyc,,
+ray/_private/runtime_env/agent/__pycache__/runtime_env_agent.cpython-312.pyc,,
+ray/_private/runtime_env/agent/__pycache__/runtime_env_consts.cpython-312.pyc,,
+ray/_private/runtime_env/agent/main.py,sha256=BHx7XAzXfxy8loDF0bOG-cU1o-O_hGz0-Ec0K3AvmIU,7971
+ray/_private/runtime_env/agent/runtime_env_agent.py,sha256=A6rq2E-YBdbrP34s3GWS0OWT4qXTgugJnkQq7m0YstU,26193
+ray/_private/runtime_env/agent/runtime_env_consts.py,sha256=V5VegPHpS3RftGRZ-Y0EIFfiQT93kaRyM8fzQm0XUEo,750
+ray/_private/runtime_env/agent/thirdparty_files/__pycache__/typing_extensions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/METADATA,sha256=NSXlhJwAfi380eEjAo7BQ4P_TVal9xi0qkyZWibMsVM,5915
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/RECORD,sha256=IAgr_a28hPlzJnG5WLMDjMZlrRJUBO3NEGCh2eKnL1M,1209
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__init__.py,sha256=x7kktHEtaD9quBcWDJPuLeKyjuVAI-Jj14S9B_5hcTs,361
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__pycache__/_staggered.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__pycache__/impl.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__pycache__/types.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/__pycache__/utils.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/_staggered.py,sha256=edfVowFx-P-ywJjIEF3MdPtEMVODujV6CeMYr65otac,6900
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/impl.py,sha256=Dlcm2mTJ28ucrGnxkb_fo9CZzLAkOOBizOt7dreBbXE,9681
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/types.py,sha256=YZJIAnyoV4Dz0WFtlaf_OyE4EW7Xus1z7aIfNI6tDDQ,425
+ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs/utils.py,sha256=on9GxIR0LhEfZu8P6Twi9hepX9zDanuZM20MWsb3xlQ,3028
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/METADATA,sha256=3xr8ZyYTInh909TqCdZhKIC37g5nTgyP-Nj_yglCs5A,8135
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/RECORD,sha256=X_HwyHbPrFDJw4GtmIIdJxkgtWmqCMvcFMteuMFIefw,9920
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/LICENSE.txt,sha256=n4DQ2311WpQdtFchcsJw7L2PCCuiFd3QlZhZQu2Uqes,588
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/vendor/llhttp/LICENSE,sha256=68qFTgE0zSVtZzYnwgSZ9CV363S6zwi58ltianPJEnc,1105
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/.hash/_cparser.pxd.hash,sha256=pjs-sEXNw_eijXGAedwG-BHnlFp8B7sOCgUagIWaU2A,121
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/.hash/_find_header.pxd.hash,sha256=_mbpD6vM-CVCKq3ulUvsOAz5Wdo88wrDzfpOsMQaMNA,125
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/.hash/_http_parser.pyx.hash,sha256=ju4DG_uNv8rTD6pu3IunE1ysx3ZbH4OjiQHUb_URSoA,125
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/.hash/_http_writer.pyx.hash,sha256=9txOh7t7c3y-vLmiuEY5dltmXvEo0CYyU4U853yyv9E,125
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/.hash/hdrs.py.hash,sha256=v6IaKbsxjsdQxBzhb5AjP0x_9G3rUe84D7avf7AI4cs,116
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__init__.py,sha256=YJ2jOOSU0hSTbloGbi5-jtcDfSmpBp2RTQEQAt0ccOA,8302
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/_cookie_helpers.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/abc.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/base_protocol.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_exceptions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_middleware_digest_auth.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_middlewares.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_proto.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_reqrep.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/client_ws.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/compression_utils.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/connector.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/cookiejar.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/formdata.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/hdrs.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/helpers.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/http.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/http_exceptions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/http_parser.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/http_websocket.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/http_writer.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/log.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/multipart.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/payload.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/payload_streamer.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/pytest_plugin.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/resolver.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/streams.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/tcp_helpers.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/test_utils.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/tracing.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/typedefs.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_app.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_exceptions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_fileresponse.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_log.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_middlewares.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_protocol.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_request.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_response.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_routedef.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_runner.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_server.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_urldispatcher.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/web_ws.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/__pycache__/worker.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_cookie_helpers.py,sha256=INC-1MTQU7yJqBVmV48Fw30kzZH47KdVUrP_bbfpGvs,13647
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_cparser.pxd,sha256=UnbUYCHg4NdXfgyRVYAMv2KTLWClB4P-xCrvtj_r7ew,4295
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_find_header.pxd,sha256=0GfwFCPN2zxEKTO1_MA5sYq2UfzsG8kcV3aTqvwlz3g,68
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_headers.pxi,sha256=n701k28dVPjwRnx5j6LpJhLTfj7dqu2vJt7f0O60Oyg,2007
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_http_parser.cpython-312-x86_64-linux-gnu.so,sha256=3YrWjkYJoctR2UCDpht9IcnT4PE18AnSoHlgI11fN5k,2824440
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_http_parser.pyx,sha256=tmA1PaJn7H8U1nyXtoHJV44pxYVzqXAf1UgJaYPaw28,28219
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_http_writer.cpython-312-x86_64-linux-gnu.so,sha256=mWC-4rsbntVD1V5ZEKEpSW_sm63V0XRe1fDR0lygipo,539144
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_http_writer.pyx,sha256=VlFEBM6HoVv8a0AAJtc6JwFlsv2-cDE8-gB94p3dfhQ,4664
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/.hash/mask.pxd.hash,sha256=Y0zBddk_ck3pi9-BFzMcpkcvCKvwvZ4GTtZFb9u1nxQ,128
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/.hash/mask.pyx.hash,sha256=90owpXYM8_kIma4KUcOxhWSk-Uv4NVMBoCYeFM1B3d0,128
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/.hash/reader_c.pxd.hash,sha256=5xf3oobk6vx4xbJm-xtZ1_QufB8fYFtLQV2MNdqUc1w,132
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__init__.py,sha256=Mar3R9_vBN_Ea4lsW7iTAVXD7OKswKPGqF5xgSyt77k,44
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/helpers.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/models.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/reader.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/reader_c.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/reader_py.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/__pycache__/writer.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/helpers.py,sha256=P-XLv8IUaihKzDenVUqfKU5DJbWE5HvG8uhvUZK8Ic4,5038
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/mask.cpython-312-x86_64-linux-gnu.so,sha256=EpRwPJm1K1yavMCd9llAWdT4AsqKx_QEN0rb0eJH_Kc,263512
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/mask.pxd,sha256=sBmZ1Amym9kW4Ge8lj1fLZ7mPPya4LzLdpkQExQXv5M,112
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/mask.pyx,sha256=BHjOtV0O0w7xp9p0LNADRJvGmgfPn9sGeJvSs0fL__4,1397
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/models.py,sha256=XAzjs_8JYszWXIgZ6R3ZRrF-tX9Q_6LiD49WRYojopM,2121
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/reader.py,sha256=eC4qS0c5sOeQ2ebAHLaBpIaTVFaSKX79pY2xvh3Pqyw,1030
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/reader_c.cpython-312-x86_64-linux-gnu.so,sha256=G-fK_d5U4UcA5kBo53v_5AYeOxksxvuy5b3y_XrZD04,1824528
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/reader_c.pxd,sha256=nl_njtDrzlQU0rjgGGjZDB-swguE0tX_bCPobkShVa4,2625
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/reader_c.py,sha256=gSsE_iSBr7-ORvOmgkCT7Jpj4_j3854i_Cp88Se1_6E,18791
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/reader_py.py,sha256=gSsE_iSBr7-ORvOmgkCT7Jpj4_j3854i_Cp88Se1_6E,18791
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/_websocket/writer.py,sha256=2OvSktPmNh_g20h1cXJt2Xu8u6IvswnPjdur7OwBbJk,11261
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/abc.py,sha256=M66F4S6m00bIEn7y4ha_XLTMDmVQ9dPihfOVB0pGfOo,7149
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/base_protocol.py,sha256=Tp8cxUPQvv9kUPk3w6lAzk6d2MAzV3scwI_3Go3C47c,3025
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client.py,sha256=fOQfwcIUL1NGAVRV4DDj6-wipBzeD8KZpmzhO-LLKp4,58357
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_exceptions.py,sha256=uyKbxI2peZhKl7lELBMx3UeusNkfpemPWpGFq0r6JeM,11367
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_middleware_digest_auth.py,sha256=BIoQJ5eWL5NNkPOmezTGrceWIho8ETDvS8NKvX-3Xdw,17088
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_middlewares.py,sha256=kP5N9CMzQPMGPIEydeVUiLUTLsw8Vl8Gr4qAWYdu3vM,1918
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_proto.py,sha256=56_WtLStZGBFPYKzgEgY6v24JkhV1y6JEmmuxeJT2So,12110
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_reqrep.py,sha256=eEREDrZ0M8ZFTt1wjHduR-P8_sm40K65gNz-iMGYask,53391
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/client_ws.py,sha256=1CIjIXwyzOMIYw6AjUES4-qUwbyVHW1seJKQfg_Rta8,15109
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/compression_utils.py,sha256=Cmn4bim6iDYUST1Fp66EBRDzIz_3gUQBLg4HkbEljrc,10408
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/connector.py,sha256=WQetKoSW7XnHA9r4o9OWwO3-n7ymOwBd2Tg_xHNw0Bs,68456
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/cookiejar.py,sha256=e28ZMQwJ5P0vbPX1OX4Se7-k3zeGvocFEqzGhwpG53k,18922
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/formdata.py,sha256=xqYMbUo1qoLYPuzY92XeR4pyEe-w-DNcToARDF3GUhA,6384
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/hdrs.py,sha256=2rj5MyA-6yRdYPhW5UKkW4iNWhEAlGIOSBH5D4FmKNE,5111
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/helpers.py,sha256=Q1307PCEnWz4RP8crUw8dk58c0YF2Ei3JywkKfRxz5E,30629
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/http.py,sha256=8o8j8xH70OWjnfTWA9V44NR785QPxEPrUtzMXiAVpwc,1842
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/http_exceptions.py,sha256=AZafFHgtAkAgrKZf8zYPU8VX2dq32-VAoP-UZxBLU0c,2960
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/http_parser.py,sha256=fACBNI47n9hnVPWfm5AJufuezsoYOF_VLp4bptYjvQI,37377
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/http_websocket.py,sha256=8VXFKw6KQUEmPg48GtRMB37v0gTK7A0inoxXuDxMZEc,842
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/http_writer.py,sha256=fbRtKPYSqRbtAdr_gqpjF2-4sI1ESL8dPDF-xY_mAMY,12446
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/log.py,sha256=BbNKx9e3VMIm0xYjZI0IcBBoS7wjdeIeSaiJE7-qK2g,325
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/multipart.py,sha256=6q6QRjKFVqaWzTbc7bkuBtXsTaQq5b2BhHxLBvAElac,40040
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/payload.py,sha256=O6nsYNULL7AeM2cyJ6TYX73ncVnL5xJwt5AegxwMKqw,40874
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/payload_streamer.py,sha256=ZzEYyfzcjGWkVkK3XR2pBthSCSIykYvY3Wr5cGQ2eTc,2211
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/pytest_plugin.py,sha256=z4XwqmsKdyJCKxbGiA5kFf90zcedvomqk4RqjZbhKNk,12901
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/resolver.py,sha256=gsrfUpFf8iHlcHfJvY-1fiBHW3PRvRVNb5lNZBg3zlY,10031
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/streams.py,sha256=cQxo6Fyu_HDWDpbezGRVPIVYtVtTbSLRF7g511DNmSs,22601
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/tcp_helpers.py,sha256=BSadqVWaBpMFDRWnhaaR941N9MiDZ7bdTrxgCb0CW-M,961
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/test_utils.py,sha256=ZJSzZWjC76KSbtwddTKcP6vHpUl_ozfAf3F93ewmHRU,23016
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/tracing.py,sha256=-6aaW6l0J9uJD45LzR4cijYH0j62pt0U_nn_aVzFku4,14558
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/typedefs.py,sha256=wUlqwe9Mw9W8jT3HsYJcYk00qP3EMPz3nTkYXmeNN48,1657
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web.py,sha256=JzSNmejg5G6YeFAnkIgZfytqbU86sNu844yYKmoUpqs,17852
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_app.py,sha256=lGU_aAMN-h3wy-LTTHi6SeKH8ydt1G51BXcCspgD5ZA,19452
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_exceptions.py,sha256=7nIuiwhZ39vJJ9KrWqArA5QcWbUdqkz2CLwEpJapeN8,10360
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_fileresponse.py,sha256=Xzau8EMrWNrFg3u46h4UEteg93G4zYq94CU6vy0HiqE,16362
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_log.py,sha256=rX5D7xLOX2B6BMdiZ-chme_KfJfW5IXEoFwLfkfkajs,7865
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_middlewares.py,sha256=sFI0AgeNjdyAjuz92QtMIpngmJSOxrqe2Jfbs4BNUu0,4165
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_protocol.py,sha256=c8a0PKGqfhIAiq2RboMsy1NRza4dnj6gnXIWvJUeCF0,27015
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_request.py,sha256=zN96OlMRlrCFOMRpdh7y9rvHP0Hm8zavC0OFCj0wlSg,29833
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_response.py,sha256=PKcziNU4LmftXqKVvoRMrAbOeVClpSN-iznHsiWezmU,29341
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_routedef.py,sha256=VT1GAx6BrawoDh5RwBwBu5wSABSqgWwAe74AUCyZAEo,6110
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_runner.py,sha256=v1G1nKiOOQgFnTSR4IMc6I9ReEFDMaHtMLvO_roDM-A,11786
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_server.py,sha256=-9WDKUAiR9ll-rSdwXSqG6YjaoW79d1R4y0BGSqgUMA,2888
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_urldispatcher.py,sha256=3ryu1ZOpcq79IYNMd6EjYWmQ_i6JbsJzS_IaV0yoYBg,44203
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/web_ws.py,sha256=lItgmyatkXh0M6EY7JoZnSZkUl6R0wv8B88X4ILqQbU,22739
+ray/_private/runtime_env/agent/thirdparty_files/aiohttp/worker.py,sha256=zT0iWN5Xze194bO6_VjHou0x7lR_k0MviN6Kadnk22g,8152
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/METADATA,sha256=CSR-8dqLxpZyjUcTDnAuQwf299EB1sSFv_nzpxznAI0,3662
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/RECORD,sha256=D0fwXbaMUn7R_i8jx57x6EbhAJ7ATn70fzANuFXtnHQ,703
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/licenses/LICENSE,sha256=b9UkPpLdf5jsacesN3co50kFcJ_1J6W_mNbQJjwE9bY,11332
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal-1.4.0.dist-info/top_level.txt,sha256=z45aNOKGDdrI1roqZY3BGXQ22kJFPHBmVdwtLYLtXC0,10
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__init__.py,sha256=TIkmUG9HTBt4dfq2nISYBiZiRB2xwvFtEZydLP0HPL4,1537
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/aiosignal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057
+ray/_private/runtime_env/agent/thirdparty_files/attr/__init__.pyi,sha256=IZkzIjvtbRqDWGkDBIF9dd12FgDa379JYq3GHnVOvFQ,11309
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_cmp.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_compat.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_config.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_funcs.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_make.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_next_gen.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/_version_info.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/converters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/exceptions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/filters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/setters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/__pycache__/validators.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117
+ray/_private/runtime_env/agent/thirdparty_files/attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368
+ray/_private/runtime_env/agent/thirdparty_files/attr/_compat.py,sha256=x0g7iEUOnBVJC72zyFCgb1eKqyxS-7f2LGnNyZ_r95s,2829
+ray/_private/runtime_env/agent/thirdparty_files/attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843
+ray/_private/runtime_env/agent/thirdparty_files/attr/_funcs.py,sha256=Ix5IETTfz5F01F-12MF_CSFomIn2h8b67EVVz2gCtBE,16479
+ray/_private/runtime_env/agent/thirdparty_files/attr/_make.py,sha256=NRJDGS8syg2h3YNflVNoK2FwR3CpdSZxx8M6lacwljA,104141
+ray/_private/runtime_env/agent/thirdparty_files/attr/_next_gen.py,sha256=BQtCUlzwg2gWHTYXBQvrEYBnzBUrDvO57u0Py6UCPhc,26274
+ray/_private/runtime_env/agent/thirdparty_files/attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469
+ray/_private/runtime_env/agent/thirdparty_files/attr/_version_info.py,sha256=w4R-FYC3NK_kMkGUWJlYP4cVAlH9HRaC-um3fcjYkHM,2222
+ray/_private/runtime_env/agent/thirdparty_files/attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209
+ray/_private/runtime_env/agent/thirdparty_files/attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861
+ray/_private/runtime_env/agent/thirdparty_files/attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643
+ray/_private/runtime_env/agent/thirdparty_files/attr/exceptions.py,sha256=HRFq4iybmv7-DcZwyjl6M1euM2YeJVK_hFxuaBGAngI,1977
+ray/_private/runtime_env/agent/thirdparty_files/attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539
+ray/_private/runtime_env/agent/thirdparty_files/attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795
+ray/_private/runtime_env/agent/thirdparty_files/attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208
+ray/_private/runtime_env/agent/thirdparty_files/attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617
+ray/_private/runtime_env/agent/thirdparty_files/attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584
+ray/_private/runtime_env/agent/thirdparty_files/attr/validators.py,sha256=1BnYGTuYvSucGEI4ju-RPNJteVzG0ZlfWpJiWoSFHQ8,21458
+ray/_private/runtime_env/agent/thirdparty_files/attr/validators.pyi,sha256=ftmW3m4KJ3pQcIXAj-BejT7BY4ZfqrC1G-5W7XvoPds,4082
+ray/_private/runtime_env/agent/thirdparty_files/attrs-25.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/attrs-25.4.0.dist-info/METADATA,sha256=2Rerxj7agcMRxiwdkt6lC2guqHAmkGKCH13nWWK7ZoQ,10473
+ray/_private/runtime_env/agent/thirdparty_files/attrs-25.4.0.dist-info/RECORD,sha256=Ae0xsWI-FFmmKsFFg8E7NMfWEpT_gWxiINEPqFpY47M,3557
+ray/_private/runtime_env/agent/thirdparty_files/attrs-25.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
+ray/_private/runtime_env/agent/thirdparty_files/attrs-25.4.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__init__.py,sha256=RxaAZNwYiEh-fcvHLZNpQ_DWKni73M_jxEPEftiq1Zc,1183
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__init__.pyi,sha256=2gV79g9UxJppGSM48hAZJ6h_MHb70dZoJL31ZNJeZYI,9416
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/converters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/exceptions.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/filters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/setters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/__pycache__/validators.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76
+ray/_private/runtime_env/agent/thirdparty_files/attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76
+ray/_private/runtime_env/agent/thirdparty_files/attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73
+ray/_private/runtime_env/agent/thirdparty_files/attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73
+ray/_private/runtime_env/agent/thirdparty_files/attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/METADATA,sha256=lGwi3J9-LHby1pjwCkMG2-Sagg4rPxoOIv5ShsvsNfE,20333
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/RECORD,sha256=t68Ws4EK0Qk6aVRSpu4-C7VizJMWQaiZFhIHsAOAA6A,993
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/WHEEL,sha256=mX4U4odf6w47aVjwZUmTYd1MF9BbrhVLKlaWSvZwHEk,186
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/licenses/LICENSE,sha256=b9UkPpLdf5jsacesN3co50kFcJ_1J6W_mNbQJjwE9bY,11332
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist-1.8.0.dist-info/top_level.txt,sha256=jivtxsPXA3nK3WBWW2LW5Mtu_GHt8UZA13NeCs2cKuA,11
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/__init__.py,sha256=xAIE2u9ncAbjATGIPfno_OJfe8AQ-1h7z_uc73dYsEA,2108
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/__init__.pyi,sha256=vMEoES1xGegPtVXoCi9XydEeHsyuIq-KdeXwP5PdsaA,1470
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/_frozenlist.cpython-312-x86_64-linux-gnu.so,sha256=UzFjN2uN24Tz86SdErp07aJh4LOp8A0w9EhxkDP4i3E,786872
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/_frozenlist.pyx,sha256=t-aGjuEiVt_MZPBJ0RnraavVmPBK6arz3i48ZvXuYsU,3708
+ray/_private/runtime_env/agent/thirdparty_files/frozenlist/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
+ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/METADATA,sha256=fCwSww9SuiN8TIHllFSASUQCW55hAs8dzKnr9RaEEbA,8378
+ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/RECORD,sha256=i0Q6QatkmOqpa0ER_YTQU-D9aci8kxM9AUlJ_KywCmI,1392
+ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
+ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/licenses/LICENSE.md,sha256=t6M2q_OwThgOwGXN0W5wXQeeHMehT5EKpukYfza5zYc,1541
+ray/_private/runtime_env/agent/thirdparty_files/idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/codec.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/compat.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/core.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/idnadata.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/intranges.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/package_data.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/uts46data.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/idna/codec.py,sha256=M2SGWN7cs_6B32QmKTyTN6xQGZeYQgQ2wiX3_DR6loE,3438
+ray/_private/runtime_env/agent/thirdparty_files/idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316
+ray/_private/runtime_env/agent/thirdparty_files/idna/core.py,sha256=P26_XVycuMTZ1R2mNK1ZREVzM5mvTzdabBXfyZVU1Lc,13246
+ray/_private/runtime_env/agent/thirdparty_files/idna/idnadata.py,sha256=SG8jhaGE53iiD6B49pt2pwTv_UvClciWE-N54oR2p4U,79623
+ray/_private/runtime_env/agent/thirdparty_files/idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898
+ray/_private/runtime_env/agent/thirdparty_files/idna/package_data.py,sha256=_CUavOxobnbyNG2FLyHoN8QHP3QM9W1tKuw7eq9QwBk,21
+ray/_private/runtime_env/agent/thirdparty_files/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/runtime_env/agent/thirdparty_files/idna/uts46data.py,sha256=H9J35VkD0F9L9mKOqjeNGd2A-Va6FlPoz6Jz4K7h-ps,243725
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/METADATA,sha256=q_zO5zLDpLVGLHpjw8BHBRhNX7yuB6_RlehDOZPYrZQ,5321
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/RECORD,sha256=c8so-Jdt7UWEBgpsLCypYArFEAUDgRj6MRNzVJ4lRSs,1202
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/licenses/LICENSE,sha256=k9Ealo4vDzY3PECBH_bSDhc_WMPKtYhM1mF7v9eVSSo,611
+ray/_private/runtime_env/agent/thirdparty_files/multidict-6.7.0.dist-info/top_level.txt,sha256=-euDElkk5_qkmfIJ7WiqCab02ZlSFZWynejKg59qZQQ,10
+ray/_private/runtime_env/agent/thirdparty_files/multidict/__init__.py,sha256=vrqM7ruZH18zqUQumAaWtGekJFYb_oWvThnAdNuAxg4,1228
+ray/_private/runtime_env/agent/thirdparty_files/multidict/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/multidict/__pycache__/_abc.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/multidict/__pycache__/_compat.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/multidict/__pycache__/_multidict_py.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/multidict/_abc.py,sha256=e_0JDJi7E6LWS0A3gUJ17SkgDLlmg8ffjfylTu_vboc,2402
+ray/_private/runtime_env/agent/thirdparty_files/multidict/_compat.py,sha256=TcRjCStk2iIY1_DwDNj8kNpJRQ9rtLj92Xvk1z2G_ak,422
+ray/_private/runtime_env/agent/thirdparty_files/multidict/_multidict.cpython-312-x86_64-linux-gnu.so,sha256=xnz-7eI31DiuDjqpB2WfX4zbC-ht1BwpL7gTvIUrrXo,848984
+ray/_private/runtime_env/agent/thirdparty_files/multidict/_multidict_py.py,sha256=VGQ58P7VOd6lRf3WVAinb62aD16DPdAWRt68qmiJMXE,39955
+ray/_private/runtime_env/agent/thirdparty_files/multidict/py.typed,sha256=e9bmbH3UFxsabQrnNFPG9qxIXztwbcM6IKDYnvZwprY,15
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/METADATA,sha256=_CPzxSszbVM1zQU_R9kf9jFpSeZa0fHj38zVKE9Nbs4,13745
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/RECORD,sha256=3VFC39giLpRE7nkKLKDFVYHlm3r1CjeechjUUawjJ28,1379
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/licenses/NOTICE,sha256=VtasbIEFwKUTBMIdsGDjYa-ajqCvmnXCOcKLXRNpODg,609
+ray/_private/runtime_env/agent/thirdparty_files/propcache-0.4.1.dist-info/top_level.txt,sha256=pVF_GbqSAITPMiX27kfU3QP9-ufhRvkADmudDxWdF3w,10
+ray/_private/runtime_env/agent/thirdparty_files/propcache/__init__.py,sha256=8kebeGvYn7s-ow1AFmK0A4EvonZMpyM7Lkzs2Ktia3Y,965
+ray/_private/runtime_env/agent/thirdparty_files/propcache/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/propcache/__pycache__/_helpers.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/propcache/__pycache__/_helpers_py.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/propcache/__pycache__/api.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/propcache/_helpers.py,sha256=68SQm6kETN8Mnt9Ol26LJYgHgmB0mKy1tp92888zN4k,1553
+ray/_private/runtime_env/agent/thirdparty_files/propcache/_helpers_c.cpython-312-x86_64-linux-gnu.so,sha256=tGelusHoANUmokeGexAZA3jwXq-nYzlSbGJv8DEy6ug,745424
+ray/_private/runtime_env/agent/thirdparty_files/propcache/_helpers_c.pyx,sha256=kcJa1U5lh54TPCqAeZ0cVB7URcb3I8ZbJieOrkNhLQE,3265
+ray/_private/runtime_env/agent/thirdparty_files/propcache/_helpers_py.py,sha256=Wixs2zWA-FBU-j4zLPyBUU24FEfPhKk-UunFSp9q95U,1909
+ray/_private/runtime_env/agent/thirdparty_files/propcache/api.py,sha256=wvgB-ypkkI5uf72VVYl2NFGc_TnzUQA2CxC7dTlL5ak,179
+ray/_private/runtime_env/agent/thirdparty_files/propcache/py.typed,sha256=ay5OMO475PlcZ_Fbun9maHW7Y6MBTk0UXL4ztHx3Iug,14
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions-4.15.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions-4.15.0.dist-info/METADATA,sha256=wTg3j-jxiTSsmd4GBTXFPsbBOu7WXpTDJkHafuMZKnI,3259
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions-4.15.0.dist-info/RECORD,sha256=AvcKTtb4HDKYoAJMqdzGgHNgk404g2DOO3aCQ_cZzc4,580
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions-4.15.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions-4.15.0.dist-info/licenses/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
+ray/_private/runtime_env/agent/thirdparty_files/typing_extensions.py,sha256=Qz0R0XDTok0usGXrwb_oSM6n49fOaFZ6tSvqLUwvftg,160429
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/METADATA,sha256=gATDmMXVC53tnEFDwFcOd1skK5FM8kJNbY8GFkCRDLg,75118
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/RECORD,sha256=LT0xkYCXoNZVyWjojWZQyJgMyfa-xSYHc_lNBL9TGqw,1762
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/licenses/NOTICE,sha256=VtasbIEFwKUTBMIdsGDjYa-ajqCvmnXCOcKLXRNpODg,609
+ray/_private/runtime_env/agent/thirdparty_files/yarl-1.22.0.dist-info/top_level.txt,sha256=vf3SJuQh-k7YtvsUrV_OPOrT9Kqn0COlk7IPYyhtGkQ,5
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__init__.py,sha256=woYZp7KGli7_1P_hR7ZU9ckEj6ho41smyP-PLfEL-lk,281
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_parse.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_path.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_query.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_quoters.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_quoting.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_quoting_py.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_url.cpython-312.pyc,,
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_parse.py,sha256=gNt8zxVFGr95ufUQpSMiiZ9vDrvg4zq6MEtT3f6_8J0,7185
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_path.py,sha256=A0FJUylZyzmlT0a3UDOBbK-EzZXCAYuQQBvG9eAC9hs,1291
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_query.py,sha256=nwGAYewdOU8nt5YZNZxqQ4BGES82Y3Y6LanxqTjnZxw,4068
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_quoters.py,sha256=z-BzsXfLnJK-bd-HrGaoKGri9L3GpDv6vxFEtmu-uCM,1154
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_quoting.py,sha256=yKIqFTzFzWLVb08xy1DSxKNjFwo4f-oLlzxTuKwC57M,506
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_quoting_c.cpython-312-x86_64-linux-gnu.so,sha256=TapJ2HY3skYb6kJkICWISGliO6JvMgDiQtRQcD8JMxA,1170216
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_quoting_c.pyx,sha256=X40gvQSUB4l7nPKGeiS6pq2JreM36avLhVeBMxd5zmo,14297
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_quoting_py.py,sha256=7WD7IHhgaJiLZWoIewvB0JRUsbz9McmfZw5TnjlVs9o,6783
+ray/_private/runtime_env/agent/thirdparty_files/yarl/_url.py,sha256=4K5gCdoQtVi9FmnQdssEqafdlJILKxSap8RNCBC4IGE,55608
+ray/_private/runtime_env/agent/thirdparty_files/yarl/py.typed,sha256=ay5OMO475PlcZ_Fbun9maHW7Y6MBTk0UXL4ztHx3Iug,14
+ray/_private/runtime_env/conda.py,sha256=2q6U8FaIEpw_t28fIuRfoNysi1lH7OcISH4plPMxPes,14648
+ray/_private/runtime_env/conda_utils.py,sha256=h9xsriQI1pCAIQsCTf93li12Ug5UXO3nl_MfiP1c-qA,9526
+ray/_private/runtime_env/constants.py,sha256=n59A0vJ6oaWT5zrEXujILathVNaFbW4wX7zEHDK2aKQ,1076
+ray/_private/runtime_env/context.py,sha256=TGk0YXgZ4iRbe_Fqc0lhOZVVQlc81N-vBNj8aDH1dgc,4264
+ray/_private/runtime_env/default_impl.py,sha256=MKaQpvf0FvCu3s8jvZ-s59CWNkKAB9qwtaOd8fl3lQI,122
+ray/_private/runtime_env/dependency_utils.py,sha256=tiAyF-29nsfmqXiHDmKcfsv29rsXHB6F_ecJjUK4i1Q,4462
+ray/_private/runtime_env/image_uri.py,sha256=DlmHNiy0NLOkSnralvaSO-YA-D3qB077687z8H6jAW4,7474
+ray/_private/runtime_env/java_jars.py,sha256=csZ8lqqvA6hYbA69vzYX3sb7X1XzEfXsYf6Cd026y1E,3639
+ray/_private/runtime_env/nsight.py,sha256=VvCWa9-xS4mtBglxXCoBbMMpVnfNb0NMCHN0arT_Mg0,5263
+ray/_private/runtime_env/packaging.py,sha256=DsJsU3Yws4S7Pag-N6sKE9Oab4XrxlXInUvfbibjDno,32846
+ray/_private/runtime_env/pip.py,sha256=GDqdAKApocF7f10_4GUeq6sZGiN3IXL9u8iWZGSK1rU,11998
+ray/_private/runtime_env/plugin.py,sha256=jf-nf38Qv3AUT513LW-XTtOPdM2Bh1nsKqjdMsutvgI,9423
+ray/_private/runtime_env/plugin_schema_manager.py,sha256=R_ZJv7EGMPbLAX7LIBfzsA9lNq5qz7oEHhuZhPop0Jk,3504
+ray/_private/runtime_env/protocol.py,sha256=wXHZn-2DH_voWFhoREJMq1NOJh_3UzCM9ODMBoiUSWY,9516
+ray/_private/runtime_env/py_executable.py,sha256=y2d_T0z7BM2qlA6_UsCkO4dYMjw7cOl1WTp5ZUBDN0w,1504
+ray/_private/runtime_env/py_modules.py,sha256=s7RUZL0wA7Ed89qv17smbNeYgIZjr1rSRKMDom0csT4,8781
+ray/_private/runtime_env/rocprof_sys.py,sha256=K5jggGkELQW8a_A3YMYVEvu0O-GonyJwy9-907ekbpI,6485
+ray/_private/runtime_env/setup_hook.py,sha256=WtdtpnRk3Py_VPo7jvSN-3intL6xaINpPBqYWpy5TlA,6772
+ray/_private/runtime_env/uri_cache.py,sha256=k_JI_nV4NDurIi-Vzlv1pUYc-y0pxlYg-ThWyc_rbJI,4332
+ray/_private/runtime_env/utils.py,sha256=CnZXCEXF_2_AnGSuZ5Lu2gZYMCOsJ3kfeb9uz0HTq-w,3989
+ray/_private/runtime_env/uv.py,sha256=4KX0g8JVw0ehxhHMh_U9D6E8o3x7qfvqE4kIxjiavBA,12336
+ray/_private/runtime_env/uv_runtime_env_hook.py,sha256=KN0ItbydFhNcPBWmX2e0r9vODJZXaiQq-vBWJcvfBpc,15839
+ray/_private/runtime_env/validation.py,sha256=1zNxAV2rvoPTcIgpX59UxgH5radPjZlMNt8QiOefVJ8,17608
+ray/_private/runtime_env/virtualenv_utils.py,sha256=xuNi1ImTUp-TiBX4RKyjq-AT_mt5vkIyV6PDmAFrLVs,3629
+ray/_private/runtime_env/working_dir.py,sha256=NXpqDHUlrxBOWhzhFY6QL2h3QTYw-2H0gJ-1VGdb3rY,8553
+ray/_private/serialization.py,sha256=5UGz_oyMUbqrOVC0WhEsBsfLqp8GVoUHgZqJxxXL7OI,31006
+ray/_private/services.py,sha256=pdIr-LuWyJCU-iSeALsEBIpJW7jqk7dWtUh7MvAC-CA,93871
+ray/_private/state.py,sha256=glj1gDqyh8QNI-lvb50HSomSGGAXDsERXcErR9a15yM,43929
+ray/_private/state_api_test_utils.py,sha256=e16wSdClxR5Xi_OOQt15R0FLPyP5UHoTZeCPhufa0Oc,19557
+ray/_private/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/telemetry/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/telemetry/__pycache__/metric_cardinality.cpython-312.pyc,,
+ray/_private/telemetry/__pycache__/open_telemetry_metric_recorder.cpython-312.pyc,,
+ray/_private/telemetry/metric_cardinality.py,sha256=hjNe-lTWXdmokEoZbpdwAbnCVLOVvQVUWN7RbaYHP6M,2395
+ray/_private/telemetry/open_telemetry_metric_recorder.py,sha256=woFNFtlT7czaI3kmsmf3UDJPpgDYRkpzC5kOfcJXShM,10305
+ray/_private/test_utils.py,sha256=3ve-6EfYrlZIJFKAgSXdhqrXX1uL2uh8tJoG_cjiF8A,70127
+ray/_private/thirdparty/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/thirdparty/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__init__.py,sha256=OZNF6CStZkbIfJnwudZ8uDHRZCzO8yAHb4vLtusuLy8,81
+ray/_private/thirdparty/dacite/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/config.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/core.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/data.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/dataclasses.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/exceptions.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/__pycache__/types.cpython-312.pyc,,
+ray/_private/thirdparty/dacite/config.py,sha256=d0zlO-jqhSvpXW53Gie97sJkPMzbuvHbesIBwU1f1MU,407
+ray/_private/thirdparty/dacite/core.py,sha256=lX2HzgT4lSsbJDIhEacuorX6_JIuExeWjcYEZ73fdaY,5493
+ray/_private/thirdparty/dacite/data.py,sha256=YSkQNaokRQcsBU5scc-g9Uw3EGAm-xabGOEwTgmYUe4,52
+ray/_private/thirdparty/dacite/dataclasses.py,sha256=HYg-Bi-eL8H3QY3ll91QW0LLRjscGMs_JLQ5ITzouUA,1032
+ray/_private/thirdparty/dacite/exceptions.py,sha256=p87iJx6OH_siwS-bULadqWeb1W7VgLqCtpDavV4Sdl0,2587
+ray/_private/thirdparty/dacite/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/thirdparty/dacite/types.py,sha256=sRPDh8FF-rWy_cPtuBWCavqtNJeofh9AQynYEmZ_Sk4,5823
+ray/_private/thirdparty/pathspec/__init__.py,sha256=Fx3qyGKKhKWkAVsb5x1zoMVzCr-4wIw4H9pyokvu7sw,2213
+ray/_private/thirdparty/pathspec/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/__pycache__/compat.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/__pycache__/pathspec.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/__pycache__/pattern.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/__pycache__/util.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/compat.py,sha256=OZJedLvgxHbuKKn5Rnia_UDcyDZfnqCQerLoIvSR_Qc,777
+ray/_private/thirdparty/pathspec/pathspec.py,sha256=6DzqgsaxHFdSa1VChuk0yIhfL-hMekkdvhT6roBU8c0,7027
+ray/_private/thirdparty/pathspec/pattern.py,sha256=p-ibgRLK9kIkiXWoCtq4SangTZEBUaNIO0hgKQY4KEI,4405
+ray/_private/thirdparty/pathspec/patterns/__init__.py,sha256=Falv9rzI0S-Sjc-t-vCS9nUPcKwBptmdNderY9Kok50,184
+ray/_private/thirdparty/pathspec/patterns/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/patterns/__pycache__/gitwildmatch.cpython-312.pyc,,
+ray/_private/thirdparty/pathspec/patterns/gitwildmatch.py,sha256=cJfGcaj9rL9YhVhItJv-xuwYsveraCXFVQm_Xg_iMTE,9947
+ray/_private/thirdparty/pathspec/util.py,sha256=smB9T1UuQ7ZBvZZvdtRBr1C0W6BssfeFkNA-4c-cXRY,17929
+ray/_private/thirdparty/pyamdsmi/__init__.py,sha256=nM1QPuKAKqH1aMeiGk4HAd-6axHTpgQp-lpfcnKxQjk,56
+ray/_private/thirdparty/pyamdsmi/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/pyamdsmi/__pycache__/pyamdsmi.cpython-312.pyc,,
+ray/_private/thirdparty/pyamdsmi/pyamdsmi.py,sha256=a8wRkHtpPK5oL39BRgSmBLsyeMpZsgFmbtFi-ZK0MrA,19979
+ray/_private/thirdparty/pynvml/__init__.py,sha256=TgNfbf6fM6IjY6jdUj-BdUUZzo020PKd5c6JqRndZWI,100
+ray/_private/thirdparty/pynvml/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/pynvml/__pycache__/pynvml.cpython-312.pyc,,
+ray/_private/thirdparty/pynvml/pynvml.py,sha256=HA4fUHckOXZITXcfxi3lA5Koxo86qG7afGgazaUvsX8,264045
+ray/_private/thirdparty/tabulate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/thirdparty/tabulate/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/thirdparty/tabulate/__pycache__/tabulate.cpython-312.pyc,,
+ray/_private/thirdparty/tabulate/tabulate.py,sha256=9ljbv_oiLnG5MFG_70ItjhROStVSok-8Ti1F8ImD1-0,95389
+ray/_private/tls_utils.py,sha256=dzo8OK6E6KPWZej5WdHGtf8eRAvOL5cftiTBDYDmR_M,3355
+ray/_private/utils.py,sha256=n6I1YwOFjONNoBU6tfHUNLn48epcfLB9n7CiXLN8o1U,58445
+ray/_private/worker.py,sha256=XdoOrnxXz6L1KOgWrQn4cVKXnSKY9tCKdCP44640PTI,145052
+ray/_private/workers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/_private/workers/__pycache__/__init__.cpython-312.pyc,,
+ray/_private/workers/__pycache__/default_worker.cpython-312.pyc,,
+ray/_private/workers/__pycache__/setup_worker.cpython-312.pyc,,
+ray/_private/workers/default_worker.py,sha256=RDZ5q4MdAFhKQ14yTvBnSvhTyA9QdXbaID8qh16rMMk,10238
+ray/_private/workers/setup_worker.py,sha256=m5ncsOdAXj6IjshOZt0nhfiFghiLUQ_VOtX4HQI6CHQ,1154
+ray/_raylet.pxd,sha256=51swNxnOc_cyl6qVn0GojIYrlgLzYnX_dwp8hRFHV4Y,5585
+ray/_raylet.pyi,sha256=8VK-onooHO3rsl2ROINfFQ4aX7BAMo5EewoOaFUFd4M,625
+ray/_raylet.so,sha256=aeycxtCb6vaVLbX41ce4mgJGF2DXaiFLN-rNSVK6udE,41779624
+ray/_version.py,sha256=JQDWL3DMYSCa3tW2yY_mCotcvCqXAu1lqKNPr5k8O8c,199
+ray/actor.py,sha256=uh2SIo8Rfp0adDaCdyLFnZr2JqTWP8WY-fVwvqlUdWg,102296
+ray/air/__init__.py,sha256=Csh1wBNLALWtq5Idvw_7WsN4G223U91Vi-h847bYPyg,533
+ray/air/__pycache__/__init__.cpython-312.pyc,,
+ray/air/__pycache__/config.cpython-312.pyc,,
+ray/air/__pycache__/constants.cpython-312.pyc,,
+ray/air/__pycache__/data_batch_type.cpython-312.pyc,,
+ray/air/__pycache__/result.cpython-312.pyc,,
+ray/air/__pycache__/session.cpython-312.pyc,,
+ray/air/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/air/_internal/__pycache__/config.cpython-312.pyc,,
+ray/air/_internal/__pycache__/filelock.cpython-312.pyc,,
+ray/air/_internal/__pycache__/json.cpython-312.pyc,,
+ray/air/_internal/__pycache__/mlflow.cpython-312.pyc,,
+ray/air/_internal/__pycache__/tensorflow_utils.cpython-312.pyc,,
+ray/air/_internal/__pycache__/torch_utils.cpython-312.pyc,,
+ray/air/_internal/__pycache__/uri_utils.cpython-312.pyc,,
+ray/air/_internal/__pycache__/usage.cpython-312.pyc,,
+ray/air/_internal/__pycache__/util.cpython-312.pyc,,
+ray/air/_internal/config.py,sha256=T75Sv59GqB1uo-q5ylMAexoI3U5SvJzzAdWZ92EQAr4,1585
+ray/air/_internal/device_manager/__init__.py,sha256=db6XDVjQu9ZY4Qx-kyhAUj3YiLxl0vuAbEgJwo3w3J8,3310
+ray/air/_internal/device_manager/__pycache__/__init__.cpython-312.pyc,,
+ray/air/_internal/device_manager/__pycache__/cpu.cpython-312.pyc,,
+ray/air/_internal/device_manager/__pycache__/hpu.cpython-312.pyc,,
+ray/air/_internal/device_manager/__pycache__/npu.cpython-312.pyc,,
+ray/air/_internal/device_manager/__pycache__/nvidia_gpu.cpython-312.pyc,,
+ray/air/_internal/device_manager/__pycache__/torch_device_manager.cpython-312.pyc,,
+ray/air/_internal/device_manager/cpu.py,sha256=1Pumfvjt3aCOpRysunQDUhUHPS80aWCfPJdhQ_NUBus,813
+ray/air/_internal/device_manager/hpu.py,sha256=XmCa0NtfWm9D8YvYffECxTY3rsxcQYQPsusSBSxNY80,1481
+ray/air/_internal/device_manager/npu.py,sha256=SgpCsXteL7c0JdvDIiaOORtzkvPoaxuC1MFc9EVBXgA,3478
+ray/air/_internal/device_manager/nvidia_gpu.py,sha256=llLYHvGPq0j1Fes8lDKHLn0v4JMAWDZjddCp06dWr-4,2993
+ray/air/_internal/device_manager/torch_device_manager.py,sha256=dNG8HQ74BQI_CVFGw2ssnSQFunfb6Mn5NbJT7Irvtng,1138
+ray/air/_internal/filelock.py,sha256=UoSfpJdWGZJ12pOVB6BCUn2KOd3wlaEt8DIuFyjV7fc,1430
+ray/air/_internal/json.py,sha256=vLHL8K4BMeogr_LTcw_q8ni82F-L_89RvrbED_mYYTU,908
+ray/air/_internal/mlflow.py,sha256=aue99PbzpfCqLemZPuCwnsegC2xIw_XVg47RQgWYNIA,12627
+ray/air/_internal/tensorflow_utils.py,sha256=_MwYRouv0v9o3thwNMcEPfB2gKobRBGN12wXOqskevg,4825
+ray/air/_internal/torch_utils.py,sha256=7qeetsNwGGehQRDLJWLxRtNwNZciHGe410gJdi-MLgc,20677
+ray/air/_internal/uri_utils.py,sha256=S5DBhJ7UGebNsVI3UGGy58kMwwhWuvU7KFIzEKq9AjM,3096
+ray/air/_internal/usage.py,sha256=pMESth9loKbjqEGrf8mqJX7NGVr-IuF6tSAYTFJCRaw,9200
+ray/air/_internal/util.py,sha256=YDILk5X8oDqBFp5kV0F4pYM3OTrZsfbflEPLfcoykno,3876
+ray/air/config.py,sha256=4hgqMdt37F3BFelRzk-0K9tWqKMhtPsi3KaaDJxjWXQ,28893
+ray/air/constants.py,sha256=UWSXvYmwVVzy_JW1RHvYNWjWJYHjV8C8VtZoVlmCfdk,3211
+ray/air/data_batch_type.py,sha256=vD4v2oi-T1xXdbmd5lG2U6NMkuKGpzYH8GrU4j8EM3s,287
+ray/air/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/examples/__pycache__/__init__.cpython-312.pyc,,
+ray/air/execution/__init__.py,sha256=cixguYR5h_W2aIooIQfmpeQB3rstw5DVEFcAbgd5CEw,460
+ray/air/execution/__pycache__/__init__.cpython-312.pyc,,
+ray/air/execution/_internal/__init__.py,sha256=NH6qtho9rC7Tkf4lWcosAzZ2ductAX8wt6oCbxJMGKg,251
+ray/air/execution/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/air/execution/_internal/__pycache__/actor_manager.cpython-312.pyc,,
+ray/air/execution/_internal/__pycache__/barrier.cpython-312.pyc,,
+ray/air/execution/_internal/__pycache__/event_manager.cpython-312.pyc,,
+ray/air/execution/_internal/__pycache__/tracked_actor.cpython-312.pyc,,
+ray/air/execution/_internal/__pycache__/tracked_actor_task.cpython-312.pyc,,
+ray/air/execution/_internal/actor_manager.py,sha256=GHJU_mRE9Wq_lHAtkpOSWjLO1-MiES2frBjwgG99mO8,35022
+ray/air/execution/_internal/barrier.py,sha256=lcA7Lkx4il0v7CbqhHe9mlQwK4ZcRGVJ7Etydc4NRbY,2951
+ray/air/execution/_internal/event_manager.py,sha256=OAlhQJK9RTU2Rgj6y2Kjjc7robf1QmZ_h7ZRu6VIocw,4933
+ray/air/execution/_internal/tracked_actor.py,sha256=wAixW_J8iKPMPvxEknk5bBDsCam3goMyc1o4mnrnhfY,1715
+ray/air/execution/_internal/tracked_actor_task.py,sha256=iYqDQNCD9W6MRWpzJVzDuA69S5nqgE6paPgZR-1RirM,1261
+ray/air/execution/resources/__init__.py,sha256=cixguYR5h_W2aIooIQfmpeQB3rstw5DVEFcAbgd5CEw,460
+ray/air/execution/resources/__pycache__/__init__.cpython-312.pyc,,
+ray/air/execution/resources/__pycache__/fixed.cpython-312.pyc,,
+ray/air/execution/resources/__pycache__/placement_group.cpython-312.pyc,,
+ray/air/execution/resources/__pycache__/request.cpython-312.pyc,,
+ray/air/execution/resources/__pycache__/resource_manager.cpython-312.pyc,,
+ray/air/execution/resources/fixed.py,sha256=PK7Fja8oPsU2VNnDXEovTfJsETn9_1IOmSomzYCrC8g,5544
+ray/air/execution/resources/placement_group.py,sha256=mWXzYUKvJoU6n-J9EPxxvQvhJoqCmvzhMBCop-Hdng4,8541
+ray/air/execution/resources/request.py,sha256=4gWzO9shDnn5zZoTflZjwmlm1n8_Fo6CcMIIFV28sp4,8541
+ray/air/execution/resources/resource_manager.py,sha256=5wRkNnqIFuImjjsrQmOWGpby70dkryFVZevO0F-_JgE,6231
+ray/air/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/integrations/__pycache__/__init__.cpython-312.pyc,,
+ray/air/integrations/__pycache__/comet.cpython-312.pyc,,
+ray/air/integrations/__pycache__/keras.cpython-312.pyc,,
+ray/air/integrations/__pycache__/mlflow.cpython-312.pyc,,
+ray/air/integrations/__pycache__/wandb.cpython-312.pyc,,
+ray/air/integrations/comet.py,sha256=jnLWDSNGqOegx1wcKZ1N2YljIo2cn7P11HNXHc54mvo,9144
+ray/air/integrations/keras.py,sha256=oKN9ssU3dbhk2a5w1uZAvaCd4E-WjL-XOqLGVHuCCvc,6522
+ray/air/integrations/mlflow.py,sha256=HGaPwpL6GV9lzj_s8GtuDrZmh-j51T6y6JfHOmM34wc,12907
+ray/air/integrations/wandb.py,sha256=8fZ6Ig_CoJkj6SEuAni1kmJIP1rjgjI6WjqRKkiVc5I,29081
+ray/air/result.py,sha256=FM-FacZBiXjVMTHmvq-AGvb4NeKAczyvK1ikO7k-4oE,10982
+ray/air/session.py,sha256=WP3yXyfNlkwowGmN0Zey0QUTMTSNfyH9Ozc-I8Tz0Ok,61
+ray/air/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/util/__pycache__/__init__.cpython-312.pyc,,
+ray/air/util/__pycache__/data_batch_conversion.cpython-312.pyc,,
+ray/air/util/__pycache__/node.cpython-312.pyc,,
+ray/air/util/__pycache__/transform_pyarrow.cpython-312.pyc,,
+ray/air/util/data_batch_conversion.py,sha256=k2P_K2Jc6jGb5wqHXW10BFkfFcdJF8V7FQjdEPkxe3I,12539
+ray/air/util/node.py,sha256=ybbzkBSNSq5L_ZPPrQYb7yvogqT6P_F2MmT8UuXJWaE,2682
+ray/air/util/object_extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/util/object_extensions/__pycache__/__init__.cpython-312.pyc,,
+ray/air/util/object_extensions/__pycache__/arrow.cpython-312.pyc,,
+ray/air/util/object_extensions/__pycache__/pandas.cpython-312.pyc,,
+ray/air/util/object_extensions/arrow.py,sha256=zgHWDm8AZNgl_yB_s3u_jXm9x7IRPt2dUnrpFiF_dS4,4358
+ray/air/util/object_extensions/pandas.py,sha256=isouYndAuTTRBr8jjOKnuQWh3_IFt4K_BW4CFv_myIc,4514
+ray/air/util/tensor_extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/air/util/tensor_extensions/__pycache__/__init__.cpython-312.pyc,,
+ray/air/util/tensor_extensions/__pycache__/arrow.cpython-312.pyc,,
+ray/air/util/tensor_extensions/__pycache__/pandas.cpython-312.pyc,,
+ray/air/util/tensor_extensions/__pycache__/utils.cpython-312.pyc,,
+ray/air/util/tensor_extensions/arrow.py,sha256=eaWINXnTR8qqW8kEztMxyenpxp1RSdtCXxAML2wt3Is,58255
+ray/air/util/tensor_extensions/pandas.py,sha256=Zx_qAJEBrR1gfmgGWL7u81kpzwYwtxbcVaodLdrWQO4,50741
+ray/air/util/tensor_extensions/utils.py,sha256=b5JhPVSUqpAOYdLQdBR6SUreuDGWf2VZQuyu1r4I7Ks,7457
+ray/air/util/transform_pyarrow.py,sha256=3mYzi2vibRmPNlgdG_vEh7eXl11_9it7CO5t9lb9J6E,1670
+ray/autoscaler/__init__.py,sha256=aiHhRCAyGsh8oAsf3fMsFDsoB-s3ZJ3Ec8dChpX52kI,158
+ray/autoscaler/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/__pycache__/batching_node_provider.cpython-312.pyc,,
+ray/autoscaler/__pycache__/command_runner.cpython-312.pyc,,
+ray/autoscaler/__pycache__/launch_and_verify_cluster.cpython-312.pyc,,
+ray/autoscaler/__pycache__/node_launch_exception.cpython-312.pyc,,
+ray/autoscaler/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/__pycache__/tags.cpython-312.pyc,,
+ray/autoscaler/_private/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/autoscaler.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/cli_logger.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/cli_logger_demoall.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/cluster_dump.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/command_runner.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/commands.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/constants.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/docker.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/event_summarizer.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/event_system.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/legacy_info_string.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/load_metrics.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/loader.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/log_timer.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/monitor.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/node_launcher.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/node_provider_availability_tracker.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/node_tracker.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/prom_metrics.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/providers.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/resource_demand_scheduler.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/subprocess_output_util.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/updater.cpython-312.pyc,,
+ray/autoscaler/_private/__pycache__/util.cpython-312.pyc,,
+ray/autoscaler/_private/_azure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/_azure/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/_azure/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/_azure/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/_azure/azure-config-template.json,sha256=PHdS9WWvEsWXe879zMVedYBPmlam4PzJ5zt1Xccbp1E,5200
+ray/autoscaler/_private/_azure/azure-vm-template.json,sha256=AJtY-dHXr7cF6ScAqXLhRTxL04Ft3sj2OK84ZegXDpc,12146
+ray/autoscaler/_private/_azure/config.py,sha256=YMW_-rENWqKZ9iz5P_XEVov770M2_qMu4jfG1b6WDk4,22024
+ray/autoscaler/_private/_azure/node_provider.py,sha256=sVRTZ99Kb-2Zchfr3Chos03GQALMQS4z42mVUt9aIjE,37276
+ray/autoscaler/_private/aliyun/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/aliyun/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/aliyun/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/aliyun/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/aliyun/__pycache__/utils.cpython-312.pyc,,
+ray/autoscaler/_private/aliyun/config.py,sha256=Xs_brOQGEEhBAcNxDWrcq2yPDeOU81DnIOnX-4zMVmA,3822
+ray/autoscaler/_private/aliyun/node_provider.py,sha256=ArOL1hVY_AFgev9QTicH9q3joIsh11RJC0M7iZ57q8o,12726
+ray/autoscaler/_private/aliyun/utils.py,sha256=DqU2FXDBW3o3Js1HivUP-dD9hhhcc8Ql-hInn2pUc9Y,18551
+ray/autoscaler/_private/autoscaler.py,sha256=UvgDp9ix-zHx5wUCXo5iH_vuYc2DiYzFbYPzaVw7Z1g,65646
+ray/autoscaler/_private/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/aws/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/aws/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/aws/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/aws/__pycache__/utils.cpython-312.pyc,,
+ray/autoscaler/_private/aws/cloudwatch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/aws/cloudwatch/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/aws/cloudwatch/__pycache__/cloudwatch_helper.cpython-312.pyc,,
+ray/autoscaler/_private/aws/cloudwatch/cloudwatch_helper.py,sha256=p1FKhKNbzQZmTrb0UORyjUIyVICzyaO7xHdwMCECgOw,32701
+ray/autoscaler/_private/aws/config.py,sha256=QtIblr9uHm2ADZWW5NKvBFtb_awsqZrzHp9PLYz-ERU,45308
+ray/autoscaler/_private/aws/node_provider.py,sha256=tvFJC-tiuZ2tqo8v9OrLh5Lrf20nLOGPeQip1uwQmvA,28326
+ray/autoscaler/_private/aws/utils.py,sha256=lYPrLd_oFTwsqnowa9pw9YQTsTnlSdedkBXra0Npq7Y,5917
+ray/autoscaler/_private/cli_logger.py,sha256=1HoJnyjfpwpo5y7JNM1ub7MfcxgEESBFcI3brEXYwnY,25916
+ray/autoscaler/_private/cli_logger_demoall.py,sha256=UB12VUVWDG-Ey6q13DVnWLQGaVyycp-7kQxdC27vap4,1261
+ray/autoscaler/_private/cluster_dump.py,sha256=KcDsS92ZO1Q31i29VgMsyRgEMue2ggeVdQUwdbNfmJs,19664
+ray/autoscaler/_private/command_runner.py,sha256=jGVVIyLvMidkfMXoUdFws0J58lbd_llteh1YiLomKNo,37051
+ray/autoscaler/_private/commands.py,sha256=bah5gRe6v_gjCb6uEmlWYWQahClQBMaVmoWNQQTHHdI,60119
+ray/autoscaler/_private/constants.py,sha256=XGEg5ZckkXm19XeU6gx-ISOxTPcfES3JgIRY4CAduic,5766
+ray/autoscaler/_private/docker.py,sha256=g4FPTRY6wrt_fI4Dnk0MubZgRW9roLfwo8steo2T3Bc,3789
+ray/autoscaler/_private/event_summarizer.py,sha256=xdshB1iBm196bnGW02BsUDClYXEmUuU62wnAK0T0xw4,2881
+ray/autoscaler/_private/event_system.py,sha256=rd7wj33LgCSKcveGaqpSJ4RN3pwsqTlXCyTwAQGzVTQ,3870
+ray/autoscaler/_private/fake_multi_node/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/fake_multi_node/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/fake_multi_node/__pycache__/command_runner.cpython-312.pyc,,
+ray/autoscaler/_private/fake_multi_node/__pycache__/docker_monitor.cpython-312.pyc,,
+ray/autoscaler/_private/fake_multi_node/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/fake_multi_node/__pycache__/test_utils.cpython-312.pyc,,
+ray/autoscaler/_private/fake_multi_node/command_runner.py,sha256=pgeMrUV4GAYZakvDBuMliSbddZ_ASlG8LYFZV6HPx0Q,3222
+ray/autoscaler/_private/fake_multi_node/docker_monitor.py,sha256=Xl--oS_J6zpx8WhSoAKN4FXi0Sa70wCtDnOIKEpq2h4,7372
+ray/autoscaler/_private/fake_multi_node/node_provider.py,sha256=jqqKW9q8z7XbN15NZTRZgG4KkA4l7MBQc3OERN2awJo,25641
+ray/autoscaler/_private/fake_multi_node/test_utils.py,sha256=nFqRAIrfFIJVr9slRoCaCFaOrKLhd8CjMMCnSdEYfiw,12508
+ray/autoscaler/_private/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/gcp/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/gcp/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/gcp/__pycache__/node.cpython-312.pyc,,
+ray/autoscaler/_private/gcp/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/gcp/__pycache__/tpu_command_runner.cpython-312.pyc,,
+ray/autoscaler/_private/gcp/config.py,sha256=AiqZpBU33WPTdTffa71dTdOp4cueazhPSkq7E8qmGe8,26277
+ray/autoscaler/_private/gcp/node.py,sha256=V9J1TAlebhg-JIb7h_m3IlvyrvUoSZF5ZEKhgnR10uc,27082
+ray/autoscaler/_private/gcp/node_provider.py,sha256=MA8WYUf5jrEGKyK8CEbuFVrbFt6V3dqgSR0sbxtYqRU,12623
+ray/autoscaler/_private/gcp/tpu_command_runner.py,sha256=oJ7WO4aLUIrInS0j2wX0t2jg4s2i316I7s0G6H2nePY,12021
+ray/autoscaler/_private/kuberay/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/kuberay/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/kuberay/__pycache__/autoscaling_config.cpython-312.pyc,,
+ray/autoscaler/_private/kuberay/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/kuberay/__pycache__/run_autoscaler.cpython-312.pyc,,
+ray/autoscaler/_private/kuberay/__pycache__/utils.cpython-312.pyc,,
+ray/autoscaler/_private/kuberay/autoscaling_config.py,sha256=tVMUu7An_3XaYEX_If6e9zYdXgXgKDmZihvr3m_-yac,20847
+ray/autoscaler/_private/kuberay/node_provider.py,sha256=H10PoMnGDQsPFcowH23XBuGbcQS8PsX_-M0tmJKWlpY,21205
+ray/autoscaler/_private/kuberay/run_autoscaler.py,sha256=euXj0sX9z4X-TWIkNjBvZhCn1kC85EhuMTmYl4-C2rk,4548
+ray/autoscaler/_private/kuberay/utils.py,sha256=jBfQKs9gZcEgWY4mueyhnNahOOMokDOkClFQefWqUR8,3477
+ray/autoscaler/_private/legacy_info_string.py,sha256=Z2geRjdUbWaUFBiFsz137O5pu_oHXVb28foPBgis2ek,1225
+ray/autoscaler/_private/load_metrics.py,sha256=6WUVSAdFqdgbS3kGWemUpm-WTkfOIjCSOmlymu738mc,14657
+ray/autoscaler/_private/loader.py,sha256=qUmuP9226LpEwIoDc__q2n0kWuS0p9-p6Ghr0k-unuw,495
+ray/autoscaler/_private/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/local/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/local/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/local/__pycache__/coordinator_node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/local/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/local/config.py,sha256=mT-X9sbPT_s5v8n1oKJbpEUx2dkukONHKlzu5sZFkOk,4525
+ray/autoscaler/_private/local/coordinator_node_provider.py,sha256=8GagwaC3hTRo7fQXrff96V5Oa3YafqFXPxIV8XsLsEg,4142
+ray/autoscaler/_private/local/node_provider.py,sha256=QZPYjrWQw-jlLSXzJXLnC-uzibnzuIqVZ2fpvh5qpUg,11806
+ray/autoscaler/_private/log_timer.py,sha256=lhEtdlAAGhFbxGngzSmo5e3u9dE6fIl8cLdU3p10AL8,876
+ray/autoscaler/_private/monitor.py,sha256=K6UWS6ra5inQWDa8KilrcVQ_cO1vL84vUtcZ-S0anVM,28935
+ray/autoscaler/_private/node_launcher.py,sha256=LvaU6ZPROAGtOiIsMHCzK47AGACbZdqS-gKzmQumirA,8230
+ray/autoscaler/_private/node_provider_availability_tracker.py,sha256=QVn9Usn2uS0hnee9mKmb3YUBXXpFN_zrAOaMunV5BPQ,5862
+ray/autoscaler/_private/node_tracker.py,sha256=_rqXfHoZvLIXATduhIL-2x5V0zBZjiuiv6dHSYHoHhM,2744
+ray/autoscaler/_private/prom_metrics.py,sha256=SA_PWOIr7Ved6bbHx0DxpNMHDE_F2TSPF1qX8ws6CPY,11817
+ray/autoscaler/_private/providers.py,sha256=Fx9k0rPxl1lAemxCPRo5fzKtIu_4e2ndDaj_uOiaWyI,8779
+ray/autoscaler/_private/readonly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/readonly/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/readonly/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/readonly/defaults.yaml,sha256=qumkmEJl35ftXlQP50f6QtrtURhDy5GpzrZ_UFHnldI,752
+ray/autoscaler/_private/readonly/node_provider.py,sha256=usZIRxb3rzTxtJrYeHo5tUUguhmBofCqHnArY5pV3IA,2545
+ray/autoscaler/_private/resource_demand_scheduler.py,sha256=S7itPzqax9IgnfZwe8lqvgkovseDl6wZKnsXq9a8Rcs,41312
+ray/autoscaler/_private/spark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/spark/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/spark/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/spark/__pycache__/spark_job_server.cpython-312.pyc,,
+ray/autoscaler/_private/spark/node_provider.py,sha256=-bjrrlhAG5EIVt_LIDf_KMvOt8q5cu_Sefr5KYq0N6Q,9074
+ray/autoscaler/_private/spark/spark_job_server.py,sha256=cnh0QlwSZL5ileKrc74awGsG_sEay8X4oSd-RI7d7gQ,11344
+ray/autoscaler/_private/subprocess_output_util.py,sha256=VwJTMnAo7u_HbJKeYTeCXIMWot8CBhkShN1YVpeB2KE,15003
+ray/autoscaler/_private/updater.py,sha256=f5PFXjAd_AxWMS3Un5FehUaagXmh4tsM157JCw_hkIE,24887
+ray/autoscaler/_private/util.py,sha256=H_n3QSPdMHiiXNJF5xnhYOF19rzugFlMK8nZyRg1eMQ,38174
+ray/autoscaler/_private/vsphere/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/_private/vsphere/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/_private/vsphere/__pycache__/cluster_operator_client.cpython-312.pyc,,
+ray/autoscaler/_private/vsphere/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/_private/vsphere/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/_private/vsphere/cluster_operator_client.py,sha256=uiEGX81DVCxgP8ijNSm-4Qk4Ab0icr1kAQTVhre94aU,27472
+ray/autoscaler/_private/vsphere/config.py,sha256=tpjlj_ZFjd62QL2NHxkihG-RWyON4hNlBvRgB2nKCd8,5326
+ray/autoscaler/_private/vsphere/node_provider.py,sha256=oBDDXs_zgo5jjTV17YlbCN9EYZZJvy6Npo9k-HenhKo,4696
+ray/autoscaler/aliyun/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/aliyun/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/aws/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/aws/cloudwatch/prometheus.yml,sha256=AbNQEhaJsc1ohI7uxrhaj9QlGysVd9-q8yOSGfZJROc,334
+ray/autoscaler/aws/cloudwatch/ray_prometheus_waiter.sh,sha256=CHggfSNMYdXjlPGDFIY_we12VcWdXIgxwEet_RklXbs,1142
+ray/autoscaler/aws/defaults.yaml,sha256=p6LYLDlLDXm09_6symscp9T-Df5PUd_sRJTngeiiy5U,6911
+ray/autoscaler/azure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/azure/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/azure/defaults.yaml,sha256=UsYrV2tIm1R0AFs09ku0aPMl2keqOmOidDI3xXL7yHY,7847
+ray/autoscaler/batching_node_provider.py,sha256=xV4B_AWUyvbNMuh638jJ0f1uqCNyH52nGpttu8wGY_o,10437
+ray/autoscaler/command_runner.py,sha256=hJ2_O2EPmyavYzmf17r2pkM5WfoUe14lCzWO1AzTg6Y,3464
+ray/autoscaler/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/gcp/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/gcp/defaults.yaml,sha256=6AF5PhG5g-QSnLtczbdq77ohai7bROJGhzXyi5X139g,7296
+ray/autoscaler/launch_and_verify_cluster.py,sha256=tyhLqM0uU7VWCUDvuItPP1oCoZgl8gxdPtcFHBx-39U,17657
+ray/autoscaler/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/local/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/local/__pycache__/coordinator_server.cpython-312.pyc,,
+ray/autoscaler/local/coordinator_server.py,sha256=svex9PhyefQ8krqDK3cDujYKLv11ePBavx_0wOSvldw,4471
+ray/autoscaler/local/defaults.yaml,sha256=Pk5bSGGTK0UOHt8AT4TOEP8GVJMW8UFm8F9LOa6sHKU,893
+ray/autoscaler/node_launch_exception.py,sha256=EWnbG283fRiNah8-mUaYfFjnkk4p_0-yRnyvFYg0-RA,1238
+ray/autoscaler/node_provider.py,sha256=C2FOTTF3jZAEkMyLwqemdjfoM6Mpens_kyqJrvXS89U,10616
+ray/autoscaler/ray-schema.json,sha256=br14oaeRWEy0q3vzG57sbBqo4etuju9tlfGaxNmPTbQ,18165
+ray/autoscaler/sdk/__init__.py,sha256=gHjOa9a1uT-Q6hbb3AbA6tTvqkUGGrzvb-9B16JP4C0,652
+ray/autoscaler/sdk/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/sdk/__pycache__/sdk.cpython-312.pyc,,
+ray/autoscaler/sdk/sdk.py,sha256=7A-HKQ_yi0MW5mcbRkWt3cVXO0Ocm2GFiSzhM-XeE5w,14732
+ray/autoscaler/spark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/spark/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/spark/defaults.yaml,sha256=fujd8XkhAEV5aO_hETPKYPAe_KlS5r1t4YMV8mtiFxs,1513
+ray/autoscaler/tags.py,sha256=XhIcZN3-3oaePfdqWwWsDgrh0yLbTMADqmlp8Wh6GDE,1946
+ray/autoscaler/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/autoscaler.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/event_logger.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/metrics_reporter.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/monitor.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/scheduler.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/schema.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/sdk.cpython-312.pyc,,
+ray/autoscaler/v2/__pycache__/utils.cpython-312.pyc,,
+ray/autoscaler/v2/autoscaler.py,sha256=4AgKLkjkTFUG789jv7fl8rr-cSKPy_XgVyQcHB8XCjQ,8574
+ray/autoscaler/v2/event_logger.py,sha256=77cKmY_VzH8GVCyVNNL8NpfhccKPvmCw3SSL58J5ujA,8141
+ray/autoscaler/v2/instance_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/instance_manager/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/common.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/config.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/instance_manager.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/instance_storage.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/node_provider.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/ray_installer.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/reconciler.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/__pycache__/storage.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/instance_manager/cloud_providers/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/kuberay/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/instance_manager/cloud_providers/kuberay/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/kuberay/__pycache__/cloud_provider.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/kuberay/cloud_provider.py,sha256=iyvZbXYeTeCqIqfmlpviXIjvMFX6PiBFO5hOn53vC0A,23378
+ray/autoscaler/v2/instance_manager/cloud_providers/read_only/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/instance_manager/cloud_providers/read_only/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/read_only/__pycache__/cloud_provider.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/cloud_providers/read_only/cloud_provider.py,sha256=ZYHhOoJ8V_nUgMLjkw4O3swenqrTKJqLPZITQQG7xnc,2680
+ray/autoscaler/v2/instance_manager/common.py,sha256=A0FsMRGLUugd8pH3id6RPRucdtEH0U1N_xZCZQJCTn4,19207
+ray/autoscaler/v2/instance_manager/config.py,sha256=dGgBfGSz7eBoUPcnSy7DQkgnQBYJ1dqDeAgCm7maN8w,21169
+ray/autoscaler/v2/instance_manager/instance_manager.py,sha256=IieLYCi9Px6W1ChNKkDlLv4hWKQxg-vrxdtMoAGoay8,9731
+ray/autoscaler/v2/instance_manager/instance_storage.py,sha256=j3ecbqBvAB0zo1dRMlIOId_UL8KPW7wnYW2j3yFd5p4,5679
+ray/autoscaler/v2/instance_manager/node_provider.py,sha256=f3sd-4t8iJxlQyEz_D75Dcqu0C8pPymiNCIGaxa-O3o,18610
+ray/autoscaler/v2/instance_manager/ray_installer.py,sha256=pvdsAi56XorGUijHfJ29RLi-qv8WhnS1iuSz2Ck1Lkc,4143
+ray/autoscaler/v2/instance_manager/reconciler.py,sha256=RwQ48xmyjcWOUrS-BVSTNT77mhxmAHGmGN6MW-tlgFc,65606
+ray/autoscaler/v2/instance_manager/storage.py,sha256=sonkaNbQLA_KT7sTO09RI-fjpIKgKT6C5ao0Z14MRLw,6755
+ray/autoscaler/v2/instance_manager/subscribers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/v2/instance_manager/subscribers/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/subscribers/__pycache__/cloud_instance_updater.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/subscribers/__pycache__/ray_stopper.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/subscribers/__pycache__/threaded_ray_installer.cpython-312.pyc,,
+ray/autoscaler/v2/instance_manager/subscribers/cloud_instance_updater.py,sha256=MHDvHSPuw0IB196psHjcrIqv8zb4G9qJGVZMFk8lbGI,3207
+ray/autoscaler/v2/instance_manager/subscribers/ray_stopper.py,sha256=SSGxPr-XIiZ64LQ5vzDqc9QAVBv-QGQ_56ZWn-6Wrgs,5263
+ray/autoscaler/v2/instance_manager/subscribers/threaded_ray_installer.py,sha256=FM4cCgaUlNjuCQX7Qv3liiiTGQRG25a9GcNJGXCwMvU,3307
+ray/autoscaler/v2/metrics_reporter.py,sha256=JQ1pojYgdoQQyIpC7P5PTLzfhmNV1K3K06nXfX2c7u0,4423
+ray/autoscaler/v2/monitor.py,sha256=YhSQbVPutRD9D1mJF6oMc3NBSetyJEWHUCBgHH594jQ,11193
+ray/autoscaler/v2/scheduler.py,sha256=CSzqRHz0vbBvUumTfP9IhzQP1xPxs_gcpj9LL-u_5Jg,70463
+ray/autoscaler/v2/schema.py,sha256=pKyT7Ax4XKThBTDNqV66N3ZmCjB-PJp36DfbfCde9U8,12681
+ray/autoscaler/v2/sdk.py,sha256=JEx9XmMymqJ-asmg3rJk9P4pYDE7SRoW-_8h9MPNdtk,3928
+ray/autoscaler/v2/utils.py,sha256=L4qYaGSlCSjFcB7BOVSFbZiza--mG_H9MYCwL0b0erA,38858
+ray/autoscaler/vsphere/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/autoscaler/vsphere/__pycache__/__init__.cpython-312.pyc,,
+ray/autoscaler/vsphere/defaults.yaml,sha256=J6wBtj7QaZe8vZMCiC83XRk-SLDegiLcU7ilvaQV-EI,5557
+ray/client_builder.py,sha256=eHlfZUnIudi_rcfkdVbAFbXh1MQmhD5ateWzKH1akTw,14589
+ray/cloudpickle/__init__.py,sha256=lIwB8SqsfVuzH7agZGFPJfL_lum77ED2F6FF_nI9nE8,1510
+ray/cloudpickle/__pycache__/__init__.cpython-312.pyc,,
+ray/cloudpickle/__pycache__/cloudpickle.cpython-312.pyc,,
+ray/cloudpickle/__pycache__/cloudpickle_fast.cpython-312.pyc,,
+ray/cloudpickle/__pycache__/compat.cpython-312.pyc,,
+ray/cloudpickle/__pycache__/py_pickle.cpython-312.pyc,,
+ray/cloudpickle/cloudpickle.py,sha256=ejxhXSXd0dq7yZQ0nawKqH0PhGT1Qj6H5Kb16zHICMs,55282
+ray/cloudpickle/cloudpickle_fast.py,sha256=1GqUD4nLKsv0vv9ty2La3eVLyeWNrPFlhUCN-aNI-30,322
+ray/cloudpickle/compat.py,sha256=UMzLRwgoQjqrL7x9ar71hQXQ4oOm-kHO9rzhg0E9Cj4,664
+ray/cloudpickle/py_pickle.py,sha256=ATzQ96P3YbYhf3Z1aZfzzKMmbiQBdp2J94CjUQ3-fx8,676
+ray/cluster_utils.py,sha256=fXCtc2WdEQBeT-p_3m0TP89S3hDSnlyov8MYC3e6Fbc,15512
+ray/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/core/__pycache__/__init__.cpython-312.pyc,,
+ray/core/generated/__pycache__/autoscaler_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/autoscaler_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/common_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/common_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/core_worker_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/core_worker_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/dependency_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/dependency_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_definition_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_definition_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_lifecycle_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_lifecycle_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_task_definition_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_actor_task_definition_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_base_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_base_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_driver_job_definition_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_driver_job_definition_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_driver_job_lifecycle_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_driver_job_lifecycle_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_event_aggregator_service_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_event_aggregator_service_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_node_definition_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_node_definition_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_node_lifecycle_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_node_lifecycle_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_definition_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_definition_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_lifecycle_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_lifecycle_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_profile_events_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/events_task_profile_events_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_actor_data_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_actor_data_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_dataset_metadata_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_dataset_metadata_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_dataset_operator_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_dataset_operator_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_driver_job_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_driver_job_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_node_data_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_node_data_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_runtime_env_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_runtime_env_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_submission_job_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_submission_job_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_task_event_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_task_event_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_train_state_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/export_train_state_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/gcs_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/gcs_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/gcs_service_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/gcs_service_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/instance_manager_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/instance_manager_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/logging_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/logging_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/metrics_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/metrics_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/node_manager_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/node_manager_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/profile_events_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/profile_events_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/pubsub_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/pubsub_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/ray_client_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/ray_client_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/reporter_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/reporter_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/resource_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/resource_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_env_agent_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_env_agent_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_env_common_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_env_common_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_environment_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/runtime_environment_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/__pycache__/usage_pb2.cpython-312.pyc,,
+ray/core/generated/__pycache__/usage_pb2_grpc.cpython-312.pyc,,
+ray/core/generated/autoscaler_pb2.py,sha256=KOS-KiEgXn47Z3TXK07LFMJwSViOLjQMJQ-rqIxbhs8,32086
+ray/core/generated/autoscaler_pb2_grpc.py,sha256=ni6xPqbxf8FrgQIBLRe1UUbX3rdxArr9GOeotlJ-NEg,12304
+ray/core/generated/common_pb2.py,sha256=ew8XV0oi_yllbQ9mojwg_dk4xdHb2jf5J3GufWlc7R4,72430
+ray/core/generated/common_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/core_worker_pb2.py,sha256=81LVl2NHpwBxCH0fJenZ9kjmavfL3fx3o-OBvaS6fpg,40293
+ray/core/generated/core_worker_pb2_grpc.py,sha256=hNBfAFBJT-B9sazo01Y9vY0hR_Z_TOUqXmNPcwxIXIs,44581
+ray/core/generated/dependency_pb2.py,sha256=aBlRlo3lu7F6HVRsVe32l2Gi-nk6t9A7E2afujBlMa8,1277
+ray/core/generated/dependency_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/event_pb2.py,sha256=kMIk-7gD5oCLjCnbrCHNwM5Tpiq_vWQ0rc_zfzXfn_I,3167
+ray/core/generated/event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_actor_definition_event_pb2.py,sha256=2MnnJYd08zN9k7bNYXrxyf6h9xqV-8ejkBbFIqJxutM,4017
+ray/core/generated/events_actor_definition_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_actor_lifecycle_event_pb2.py,sha256=8U1TYM-j4KYkbQv56So1actqnS5OIMO70R-sopAjqjI,3219
+ray/core/generated/events_actor_lifecycle_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_actor_task_definition_event_pb2.py,sha256=qCvcGVY2wWyY0h4B6MeXuEZs1OZBf2l7O51pmb1WksA,4556
+ray/core/generated/events_actor_task_definition_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_base_event_pb2.py,sha256=5lKDdgP5-ofgf6_xNRA7C7m2xcwM6GwpwE5bNQEKyQk,6532
+ray/core/generated/events_base_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_driver_job_definition_event_pb2.py,sha256=A5P5vbzKo1Ck1sknRUJIX9ywmW3P7wRA6RZ8K8TGXpQ,3561
+ray/core/generated/events_driver_job_definition_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_driver_job_lifecycle_event_pb2.py,sha256=0FtcjNuCs7_cFRKjrec08wdi6pWRuxx7mvMMhlKjriQ,2928
+ray/core/generated/events_driver_job_lifecycle_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_event_aggregator_service_pb2.py,sha256=skyu_uG3LOrnxC4M5xKTjJjXBOpuip9yfJCC2U9VZIg,4535
+ray/core/generated/events_event_aggregator_service_pb2_grpc.py,sha256=wpCp6RNwq75Oh9EILZmtk4jLKhZCHSzNTdYNOgWnLIQ,2945
+ray/core/generated/events_node_definition_event_pb2.py,sha256=QC-W30kz1v6hfE-6APWwUEaIlxS208wi1pdu1KhWuD4,2646
+ray/core/generated/events_node_definition_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_node_lifecycle_event_pb2.py,sha256=H2iZGuznos5pp83xfpSTHRcbKpq30wSTubT1ixFRQ74,5613
+ray/core/generated/events_node_lifecycle_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_task_definition_event_pb2.py,sha256=ld4cXb3fBiCINfTrRdzCd0qbJb3G0oJ19cntJEcCZKg,4364
+ray/core/generated/events_task_definition_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_task_lifecycle_event_pb2.py,sha256=giO4u1IhCmKHmxEDIjCFCqIkr4JBvAQepejeXet_RZI,2922
+ray/core/generated/events_task_lifecycle_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/events_task_profile_events_pb2.py,sha256=7W9keNG7BOR-VEB5zYLf4w6K_eMkACjAC6rxqDw1mIA,1711
+ray/core/generated/events_task_profile_events_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_actor_data_pb2.py,sha256=X1wzSuCOW_pS4MUNvi8iiGlYddjt2YOySI4CWR2BVlU,5479
+ray/core/generated/export_actor_data_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_dataset_metadata_pb2.py,sha256=m0vv6UmePZ3dBV6dRygNX-Pvk5Cd0KQyV-xfiFRyYoU,5784
+ray/core/generated/export_dataset_metadata_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_dataset_operator_event_pb2.py,sha256=ZDLcZ_5RJU-Og7Y06oMOZzuzEMzzDWPBjwmS-0VytWY,2377
+ray/core/generated/export_dataset_operator_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_driver_job_event_pb2.py,sha256=OM9OGEEyWDXcad0DJJUZ3wKaQBKIp-x6zH8swzsVuyQ,3823
+ray/core/generated/export_driver_job_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_event_pb2.py,sha256=oHmMRk6MAZd_PTmWicRNuKjETrG2u_ArHWiz-SyqWjM,4319
+ray/core/generated/export_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_node_data_pb2.py,sha256=9wRPb_mf1A20Z0Hv6bcRC10xs0QQQciEW0GfxiM08XM,5067
+ray/core/generated/export_node_data_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_runtime_env_pb2.py,sha256=vy2YSsHQK1CnbNajh_rMDjOie0XyWn0LWvdz41sAp7U,3203
+ray/core/generated/export_runtime_env_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_submission_job_event_pb2.py,sha256=MK-5-x63yBYqSALYkq_hoq1F0_3iDvQYEXtbB2E6Y0Y,3882
+ray/core/generated/export_submission_job_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_task_event_pb2.py,sha256=XrzvH7gTYKpRFxeuRD-xm27iSj95aoqm2kIGpllmT2Q,11253
+ray/core/generated/export_task_event_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/export_train_state_pb2.py,sha256=yz_nsMD8rVtwnmphPsi07oYZnSGOdigJXRXXMCvFq_8,9531
+ray/core/generated/export_train_state_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/gcs_pb2.py,sha256=FeepmzTHIqD62vYP9Aiiz8y3LO0IAGO4bPxoutycUTI,45126
+ray/core/generated/gcs_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/gcs_service_pb2.py,sha256=fva680lUw7puHQybDdJtKk6GQA8JlkdkUeKJ0jEtSp8,89788
+ray/core/generated/gcs_service_pb2_grpc.py,sha256=oTgm3chP-ew7q72TBbkqqcTCJ79vymL4tplT25XWpPE,101874
+ray/core/generated/instance_manager_pb2.py,sha256=koFjEv9cAg5AMCcYnLDy9h1q94-KItAoM-666rH3IS8,14183
+ray/core/generated/instance_manager_pb2_grpc.py,sha256=jP2XnPAaMxdiWwYKaTqVvutS11CPZGqNNwNkSnAXNAo,5016
+ray/core/generated/logging_pb2.py,sha256=LtBFvE-49KMXyBnLuncN2SuVHMsmiWXOK4jIs-Cp_YI,1498
+ray/core/generated/logging_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/metrics_pb2.py,sha256=C0yB-61QWP8wd6BPEt_YrvdKjoRoZZ_6o-aWtd8VcaY,13773
+ray/core/generated/metrics_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/node_manager_pb2.py,sha256=ODicSSGGAVgYXWKjQHVS4atvELuAjYsM2Bdv0oYJB-0,49236
+ray/core/generated/node_manager_pb2_grpc.py,sha256=Tv6bxfrTlcmjonHUEu1U8TxJw-naoSpmlQ7ehKwiWUM,52356
+ray/core/generated/profile_events_pb2.py,sha256=1uTFQ248n0eMxam1mKn2Z9erOrMYBctGSZdpuV9w0-A,2366
+ray/core/generated/profile_events_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/pubsub_pb2.py,sha256=MeNinKmfvloTcs9VSLt532hU1sHMkNbXmSmWzM_4T20,14921
+ray/core/generated/pubsub_pb2_grpc.py,sha256=MFYtBqHwAumqX2OKBFocu2tnyIsWkhWhNV1vFJ8OObQ,4566
+ray/core/generated/ray_client_pb2.py,sha256=6v-nrcayZDKcC98AKpp_gwn_hKm507yGMdbMFXQwUXI,38306
+ray/core/generated/ray_client_pb2_grpc.py,sha256=kNp7RJIYJWyo61W_1aBCDMxsnFThVr8uXvehD9FEJrc,31474
+ray/core/generated/reporter_pb2.py,sha256=mqjVMNd8gGZEKUkLoUhkcNS5jPNJh7WCIebD5YeHljM,12371
+ray/core/generated/reporter_pb2_grpc.py,sha256=buOstCVjnd0b2AsGuooEQZyD2METh2-pMIAxX7BssAk,15462
+ray/core/generated/resource_pb2.py,sha256=ZnF3b1DBIuH_46s4J4KUAs1VEuNv9FBMOCcrqmh6UwA,2522
+ray/core/generated/resource_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/runtime_env_agent_pb2.py,sha256=IL1ys5eux9IjDsOeERbi7JeQUu1gBwe4MDvZ_zZp5Tc,6218
+ray/core/generated/runtime_env_agent_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/runtime_env_common_pb2.py,sha256=5KhwV6YZBCveGdz07K8LgJ4hzO1p-7-wmgjn_96i6nk,1771
+ray/core/generated/runtime_env_common_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/runtime_environment_pb2.py,sha256=kSTRpAIAeL8J-F_NP8U0T1Y4YtMfJYAL6-EJOvrGEnU,2939
+ray/core/generated/runtime_environment_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/generated/usage_pb2.py,sha256=ncMLO83D1yIWpolHNHz1A4FUKRcMQUcGSxMKnPqmyg8,9843
+ray/core/generated/usage_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
+ray/core/libjemalloc.so,sha256=AoSRnbI_leaSAmA5g4rvibWWS1z-xKiKy5s6n0oib9U,885296
+ray/core/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/core/src/__pycache__/__init__.cpython-312.pyc,,
+ray/core/src/plasma/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/core/src/plasma/__pycache__/__init__.cpython-312.pyc,,
+ray/core/src/ray/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/core/src/ray/__pycache__/__init__.cpython-312.pyc,,
+ray/core/src/ray/gcs/gcs_server,sha256=Xww5cu33ZVEDuqn_wVXjNb2eYRKuXE5M2O0g60uIhu8,30020208
+ray/core/src/ray/raylet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/core/src/ray/raylet/__pycache__/__init__.cpython-312.pyc,,
+ray/core/src/ray/raylet/raylet,sha256=Vz4AgE4D5EudAR8pkeAyB9f-oME0H3pUyQ7bkfV7Y_g,33168328
+ray/cross_language.py,sha256=zLBhiTmHpQN1_46-8HzC-3payWYMZ3t1yRw5YuoIsNg,3777
+ray/dag/__init__.py,sha256=h637YhI7QhEgHgLYzdnkiEJ73PtkrcZujYVOXdwYoQo,1146
+ray/dag/__pycache__/__init__.cpython-312.pyc,,
+ray/dag/__pycache__/base.cpython-312.pyc,,
+ray/dag/__pycache__/class_node.cpython-312.pyc,,
+ray/dag/__pycache__/collective_node.cpython-312.pyc,,
+ray/dag/__pycache__/compiled_dag_node.cpython-312.pyc,,
+ray/dag/__pycache__/conftest.cpython-312.pyc,,
+ray/dag/__pycache__/constants.cpython-312.pyc,,
+ray/dag/__pycache__/context.cpython-312.pyc,,
+ray/dag/__pycache__/dag_node.cpython-312.pyc,,
+ray/dag/__pycache__/dag_node_operation.cpython-312.pyc,,
+ray/dag/__pycache__/dag_operation_future.cpython-312.pyc,,
+ray/dag/__pycache__/format_utils.cpython-312.pyc,,
+ray/dag/__pycache__/function_node.cpython-312.pyc,,
+ray/dag/__pycache__/input_node.cpython-312.pyc,,
+ray/dag/__pycache__/output_node.cpython-312.pyc,,
+ray/dag/__pycache__/py_obj_scanner.cpython-312.pyc,,
+ray/dag/__pycache__/utils.cpython-312.pyc,,
+ray/dag/__pycache__/vis_utils.cpython-312.pyc,,
+ray/dag/base.py,sha256=YtWL9E3ufvwgrIEAlrktYiDTSYslL3236q34V8yKHfk,236
+ray/dag/class_node.py,sha256=LVDn6TQgAuY769MNs00iwyYrUk2Oe-6MXrZTMf9rVeI,11265
+ray/dag/collective_node.py,sha256=ZHgymCMYZ9aQH8gRFm4CMA-IqmOJeWCWlejly5syGHI,11877
+ray/dag/compiled_dag_node.py,sha256=y_l2WF1nLg3tbAKjoy2jKSzXN8CTJAkTFg5GUB2NFik,143800
+ray/dag/conftest.py,sha256=GseP4k3QkeKz7ptsqU33Lv3VLiaFTyCBLPv3SUv60EA,451
+ray/dag/constants.py,sha256=sSJCymlPXEZuZjd65FEAP-vr5sR0mKOlCMo8hqZMO7M,1567
+ray/dag/context.py,sha256=Mv9wuC29RPMYe_yGwuXVOqvYmH2fffr-L9jIhTCf41U,4500
+ray/dag/dag_node.py,sha256=m1K8A6z22I8vgD9E5ebYyglYWxnvdDAH85qgbYs4CXk,29422
+ray/dag/dag_node_operation.py,sha256=n9UzDSqJTrs_IsCQEgrGlSZn_hVYS8H0rNjjTNBDUPE,36988
+ray/dag/dag_operation_future.py,sha256=SDpDNK4oI7EsqOFyfcG5jeO-LmS-UgwzscMdAMMH8P0,4961
+ray/dag/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dag/experimental/__pycache__/__init__.cpython-312.pyc,,
+ray/dag/format_utils.py,sha256=z5PhuyA9F_zQe8dhiRJF_ICtP5q3IKjKzshMHCF91Ik,5432
+ray/dag/function_node.py,sha256=cfYj4_oqjEK1o2p3tlOj6gDHlpWlxFW1kadqHq3m730,1657
+ray/dag/input_node.py,sha256=qA26BlPCR10Ckc0XDGT5YRdmNGxVpUs6CTOA8ZOHX9w,11231
+ray/dag/output_node.py,sha256=J3M9hzW1EgvGewFKNVpguNcJAAfYJGThTBgzZ1HEc6Y,1363
+ray/dag/py_obj_scanner.py,sha256=IgSbQ8mrWQEsnspYoyqg_MyIMcmdfkoCxKjZ4i6bhKY,3676
+ray/dag/utils.py,sha256=7IGAyeKCcdcvKiK5lTLQ5C9LIsN1Lwaw1h-k1piTIN0,2244
+ray/dag/vis_utils.py,sha256=ghRvGDCjPOZ4vtZC2_rBC_0CcW5IisnH7Whm1qjXYf0,3017
+ray/dashboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/__pycache__/agent.cpython-312.pyc,,
+ray/dashboard/__pycache__/consts.cpython-312.pyc,,
+ray/dashboard/__pycache__/dashboard.cpython-312.pyc,,
+ray/dashboard/__pycache__/dashboard_metrics.cpython-312.pyc,,
+ray/dashboard/__pycache__/head.cpython-312.pyc,,
+ray/dashboard/__pycache__/http_server_agent.cpython-312.pyc,,
+ray/dashboard/__pycache__/http_server_head.cpython-312.pyc,,
+ray/dashboard/__pycache__/k8s_utils.cpython-312.pyc,,
+ray/dashboard/__pycache__/memory_utils.cpython-312.pyc,,
+ray/dashboard/__pycache__/optional_deps.cpython-312.pyc,,
+ray/dashboard/__pycache__/optional_utils.cpython-312.pyc,,
+ray/dashboard/__pycache__/routes.cpython-312.pyc,,
+ray/dashboard/__pycache__/state_aggregator.cpython-312.pyc,,
+ray/dashboard/__pycache__/state_api_utils.cpython-312.pyc,,
+ray/dashboard/__pycache__/timezone_utils.cpython-312.pyc,,
+ray/dashboard/__pycache__/utils.cpython-312.pyc,,
+ray/dashboard/agent.py,sha256=jxZLwOWCoD5ZxP87dZa76fZl1N-2xnuSoTjkV2gK1UE,16318
+ray/dashboard/client/build/asset-manifest.json,sha256=gII6T_nHcqeHUzYA7jUlVGgVH7TWVDFvhyg5TxN2xbQ,3403
+ray/dashboard/client/build/favicon.ico,sha256=rQx7hQCsoElXbbBeXhh0MRzi9zBoOEBFUWSOGhyUdlE,4286
+ray/dashboard/client/build/index.html,sha256=eeRCeAmn2cW78mnrzHFTEl7FyziQM6LrvYtjFpl90As,446
+ray/dashboard/client/build/speedscope-1.5.3/LICENSE,sha256=YkSxPpvyXP1C2ByTnWxTXZMllujDIAUCNPLCrFpvNqA,1067
+ray/dashboard/client/build/speedscope-1.5.3/README,sha256=-PRL6Fdn8PLdpgP1AVgD1Xl0JSblPENMwecjXerTccQ,124
+ray/dashboard/client/build/speedscope-1.5.3/demangle-cpp.8a387750.js,sha256=mv-Npn65w_JY38YBee4t9RzfyKOF-bXZ9NjnS_3CW74,168983
+ray/dashboard/client/build/speedscope-1.5.3/favicon-16x16.361d2b26.png,sha256=piptp3QrQ-55ixBrcJqoLq_RtQDvBQQnz-wF5NQdrKw,679
+ray/dashboard/client/build/speedscope-1.5.3/favicon-32x32.1165a94e.png,sha256=nqbm0Tt7gGQYeGbeuh6GdNi6JQcSebp7FfNLVN8lCNA,1585
+ray/dashboard/client/build/speedscope-1.5.3/file-format-schema.json,sha256=w9yvzZSfe6sSZt1MtB8yleqeqhPOuwoGVtQ1Pz3qkfs,9517
+ray/dashboard/client/build/speedscope-1.5.3/import.a03c2bef.js,sha256=Qe4Z_L5Rr7ULfRICH3SdWiqtBggNhFq2bu-x_vwjlDo,184993
+ray/dashboard/client/build/speedscope-1.5.3/index.html,sha256=nAQjhLPU6jkZXzxm5475aPI7-FXX-wjBz_P7Jo6JyjQ,611
+ray/dashboard/client/build/speedscope-1.5.3/perf-vertx-stacks-01-collapsed-all.3e0a632c.txt,sha256=3tqhyAVSZ2OAMO-iUIERYQX9DjV5RM78CXTnkt_Kvt0,263949
+ray/dashboard/client/build/speedscope-1.5.3/release.txt,sha256=osQRt7-ph11D7a46UXabHDHPoXrIkMjpH5f5cT9hLGM,87
+ray/dashboard/client/build/speedscope-1.5.3/reset.7ae984ff.css,sha256=jaknxOGdqznF5pYqR9NyjmKVx3nwhW_D3wAm8deN2nQ,835
+ray/dashboard/client/build/speedscope-1.5.3/speedscope.75eb7d8e.js,sha256=YFtVhhIYS0LG3C5nA0L637BvXxKjj0-5QoE4EZdz_eA,209212
+ray/dashboard/client/build/static/css/main.388a904b.css,sha256=BXM07M3GnaxZqhtnB1Jskw2rjD9chAgDM1vjal-h3Rs,5995
+ray/dashboard/client/build/static/css/main.388a904b.css.map,sha256=GyKVBvNhPKjtPFd9oOV2Q4H3FjCv-zi7ndYK4U7NCjU,9323
+ray/dashboard/client/build/static/js/495.01ff0983.chunk.js,sha256=sYE2hzD-JL8zgXIC03Vyvd3q_HQRPzpzMNaFl97i-5w,354
+ray/dashboard/client/build/static/js/495.01ff0983.chunk.js.map,sha256=xw0DE1CLbOUuFmzAlzhMMC5gpdyg1mIx6NPJnMX2oU4,685
+ray/dashboard/client/build/static/js/591.36c340d9.chunk.js,sha256=IvN5hpq3s7AgBqyn7m8MSghNLg9A7MpMzQSIvaZlC0c,1801
+ray/dashboard/client/build/static/js/591.36c340d9.chunk.js.map,sha256=I-77z7ZeS0_WQA6AiQM8mZM1xaMqKgkGd2OPlVfypR0,6625
+ray/dashboard/client/build/static/js/main.32399d4c.js,sha256=n3XQkfk1N-p3zMunNAtRJsrb7gdx815-LPOJD49PcrA,1066958
+ray/dashboard/client/build/static/js/main.32399d4c.js.LICENSE.txt,sha256=Snsan2qc7LP5d4xF0YKf3KYSJYPmFM5KONhSCevnlsc,2804
+ray/dashboard/client/build/static/js/main.32399d4c.js.map,sha256=qzTcwDFVQZ8Ix74C5rdLw1BqodVph17bUhvzHdibMz4,6409584
+ray/dashboard/client/build/static/media/logo.3704c1bbca650bb72a64b5d4c3fa5ced.svg,sha256=253qk71FS2ttzmMB_BbxrMj7DGBee1P7TRKe5vPYtNo,2625
+ray/dashboard/client/build/static/media/roboto-latin-100.a45108d3b34af91f9113.woff,sha256=xOrU3p96_yN9BrUw6thBPRNXQn9qkllENCu04rHc5tA,20368
+ray/dashboard/client/build/static/media/roboto-latin-100.c2aa4ab115bf9c6057cb.woff2,sha256=EoI9WFYFI4EhVUr_i7BgojXcNvN-_Z-x5-bqGpYivDU,15808
+ray/dashboard/client/build/static/media/roboto-latin-100italic.451d4e559d6f57cdf6a1.woff,sha256=WjqYQEFHaPouyYizPJ6Wb9_-LbflYKJws6nGugHxdxg,21704
+ray/dashboard/client/build/static/media/roboto-latin-100italic.7f839a8652da29745ce4.woff2,sha256=JskepDt5sdRWaV3kaPUD4BQenrdn_hZNr4vz86EBJW8,17008
+ray/dashboard/client/build/static/media/roboto-latin-300.37a7069dc30fc663c878.woff2,sha256=KfbaCowhxWgVEbubCGY9P9LF0Jyb2AVOw1TFY7jIt8E,15784
+ray/dashboard/client/build/static/media/roboto-latin-300.865f928cbabcc9f8f2b5.woff,sha256=drBUAP_52ltDhi43EwmeORORamKVYCZe0ksZ0DEifL8,20348
+ray/dashboard/client/build/static/media/roboto-latin-300italic.bd5b7a13f2c52b531a2a.woff,sha256=C-Cubv2FKzaVy3p2KGCW9g6Tt9McFuC3HKNeztf96PY,22204
+ray/dashboard/client/build/static/media/roboto-latin-300italic.c64e7e354c88e613c77c.woff2,sha256=ngJSTr7NgT_EvLQDNrsrAzhxsf3L0jQine5BidxEhQ0,17448
+ray/dashboard/client/build/static/media/roboto-latin-400.176f8f5bd5f02b3abfcf.woff2,sha256=SMP6b4bFTx2btRkiBxPUsKH4zRpYmjwDufqC6Y7LE-M,15736
+ray/dashboard/client/build/static/media/roboto-latin-400.49ae34d4cc6b98c00c69.woff,sha256=wdyH-Zx_8iiAYRfVjwhcbFcwV_ojcigIGAK32NPPdoQ,20268
+ray/dashboard/client/build/static/media/roboto-latin-400italic.b1d9d9904bfca8802a63.woff,sha256=gIFe_jvZMXxmbfDy5tcBM14XiVT2TrHpkQP-qBwqoTc,21952
+ray/dashboard/client/build/static/media/roboto-latin-400italic.d022bc70dc1bf7b3425d.woff2,sha256=QB5sJYAbotWXldBabdlz-VVmtBBw05ObqTB9ZYYK5Q4,17324
+ray/dashboard/client/build/static/media/roboto-latin-500.cea99d3e3e13a3a599a0.woff,sha256=upj5kdACxr-q97h0ZS_9zekmGoaSXbh98-0oYeoICt8,20464
+ray/dashboard/client/build/static/media/roboto-latin-500.f5b74d7ffcdf85b9dd60.woff2,sha256=JDaeGyRhr53O_sr5zJPWTPIqTFusMlBhALniEBRQe88,15872
+ray/dashboard/client/build/static/media/roboto-latin-500italic.0d8bb5b3ee5f5dac9e44.woff2,sha256=hoi2IEJzjro56Lwu34augykF6O4yQbVYNVJkZdnrjhs,17316
+ray/dashboard/client/build/static/media/roboto-latin-500italic.18d00f739ff1e1c52db1.woff,sha256=byl0o5bcBpXQcehCVR56-ccvDvjS0Hb-c6UjsaPC0Oc,22020
+ray/dashboard/client/build/static/media/roboto-latin-700.2267169ee7270a22a963.woff,sha256=gG6kbEJq-Pwk5c9CohAihzlpaTPTYpnrKK7mT2n8cfE,20356
+ray/dashboard/client/build/static/media/roboto-latin-700.c18ee39fb002ad58b6dc.woff2,sha256=tNB4ks3nFdULtpwZgt9JY4XR39j50YZ8MfGaPIY0z64,15816
+ray/dashboard/client/build/static/media/roboto-latin-700italic.7d8125ff7f707231fd89.woff2,sha256=XMLkdwHufcnguhYwPhcNsPyy3ymJt3Y6xwWJPTe04jc,17020
+ray/dashboard/client/build/static/media/roboto-latin-700italic.9360531f9bb817f917f0.woff,sha256=7sFCYI6LQX4qy25TAadQBHoE4sWmVjIjyq5JnhnqCO4,21588
+ray/dashboard/client/build/static/media/roboto-latin-900.870c8c1486f76054301a.woff2,sha256=7c3z9gJSpZh77cnIa1Qi2XK6UJu75g1YklMQx0SjPig,15712
+ray/dashboard/client/build/static/media/roboto-latin-900.bac8362e7a6ea60b6983.woff,sha256=6FhvnbfAUDqYTJRK0vH3g79gUa6ioGa8If3tyP5_poo,20392
+ray/dashboard/client/build/static/media/roboto-latin-900italic.c20d916c1a1b094c1cec.woff,sha256=aoDZy09JtZUbQH-JBc-oh_Hj8uLsQ2m_WOrGM7LgWUg,22304
+ray/dashboard/client/build/static/media/roboto-latin-900italic.cb5ad999740e9d8a8bd1.woff2,sha256=leYLk5GTHcTVzJs7DCiw6ydRuQZgN_dZS6dsR3OcGHs,17520
+ray/dashboard/consts.py,sha256=Tz7F-VjCYlsPuvkAhusP_KB6ZJ1mGbNIqi_25dQW2S8,4571
+ray/dashboard/dashboard.py,sha256=_pe0bJHRvdLH6i3e1kMScWXFK21mw7kZ7IEBxA6r5gE,10755
+ray/dashboard/dashboard_metrics.py,sha256=ZW162Ylx1W98i6NtykfnVE1D2TlB_rZQCQS9H92E0XI,4047
+ray/dashboard/head.py,sha256=lnmFxNEq0pGUyxW_u92pFDZmg2nO-yF9RDJYjAh15Wk,18787
+ray/dashboard/http_server_agent.py,sha256=ZFfaomMRzmuGkNpsYVeOWtB-1_utoPETAUyI9zsaWDo,4887
+ray/dashboard/http_server_head.py,sha256=AQeG8LlaAmc7qUuLElb2gQAkaB0LeBpnxaJ51y-DVOk,17715
+ray/dashboard/k8s_utils.py,sha256=iWQ_Y_KDM1iP-e6dmRaCR8Vf8qm_ct0C_ashuEhIR68,3928
+ray/dashboard/memory_utils.py,sha256=ZdpmVVs7WME0-_U6N3FNmIbkyGn71-qanrGu87jClYo,18614
+ray/dashboard/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/__pycache__/dashboard_sdk.cpython-312.pyc,,
+ray/dashboard/modules/__pycache__/version.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/aggregator/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/__pycache__/aggregator_agent.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/__pycache__/constants.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/__pycache__/multi_consumer_event_buffer.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/aggregator_agent.py,sha256=lwQN4YtbariujH_fqQvMl7Hvj96cjPzZn1B1skEZt-w,8878
+ray/dashboard/modules/aggregator/constants.py,sha256=Bv5m9d_c5FQ36v0tHrXImyoWPFYt1Pwb2iTw5_YvWOw,82
+ray/dashboard/modules/aggregator/multi_consumer_event_buffer.py,sha256=6YfsISbn3T1iYdfVhqeZXsY2XO5qznXWM1nrpqANk_E,7796
+ray/dashboard/modules/aggregator/publisher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/aggregator/publisher/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/publisher/__pycache__/async_publisher_client.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/publisher/__pycache__/configs.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/publisher/__pycache__/metrics.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/publisher/__pycache__/ray_event_publisher.cpython-312.pyc,,
+ray/dashboard/modules/aggregator/publisher/async_publisher_client.py,sha256=sPXaAkWotaHvZLdz9EtgyTPqrZG6YCvy0AtEBWC7L4Q,4820
+ray/dashboard/modules/aggregator/publisher/configs.py,sha256=tLUhKQ0vB9X13YzAsb0881YRwcIrf8UaWE8UzGGNBJQ,1287
+ray/dashboard/modules/aggregator/publisher/metrics.py,sha256=czbvpIs6uogbnwinW0IauhO6k3rjjL8DzFNSZIZHOgM,1838
+ray/dashboard/modules/aggregator/publisher/ray_event_publisher.py,sha256=ISw-x6rNoOlMhF7UrJcvXhQwar-2AB2vBnm_1bUwx0w,10506
+ray/dashboard/modules/dashboard_sdk.py,sha256=LWdpmkhOC7PNgBBwNeExG8nXAjePdH_d9DKhsHP__E4,15009
+ray/dashboard/modules/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/data/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/data/__pycache__/data_head.cpython-312.pyc,,
+ray/dashboard/modules/data/data_head.py,sha256=LzDsvoOSCKbRnN4R4k_tGgYaFltYBZ2rGDboP0qJtnI,6633
+ray/dashboard/modules/event/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/event/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/event/__pycache__/event_agent.cpython-312.pyc,,
+ray/dashboard/modules/event/__pycache__/event_consts.cpython-312.pyc,,
+ray/dashboard/modules/event/__pycache__/event_head.cpython-312.pyc,,
+ray/dashboard/modules/event/__pycache__/event_utils.cpython-312.pyc,,
+ray/dashboard/modules/event/event_agent.py,sha256=RZ6AO4nmYvJe3VXUrQEYCZnjZvpaLIN0xh0PppoabRU,5042
+ray/dashboard/modules/event/event_consts.py,sha256=1EV8DXG78W10rjUYUqik8Kj4STj0VeZs-IvVGnwouEc,700
+ray/dashboard/modules/event/event_head.py,sha256=XA4akOivGR2L62pee65ctY4rknOS5dKvDE4XDxsapcQ,8767
+ray/dashboard/modules/event/event_utils.py,sha256=M0gT8evdvabUkeZavv0NMvELtYbA8eR5Ka2ly6njJbM,7500
+ray/dashboard/modules/job/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/job/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/cli.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/cli_utils.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/common.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/job_agent.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/job_head.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/job_log_storage_client.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/job_manager.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/job_supervisor.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/pydantic_models.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/sdk.cpython-312.pyc,,
+ray/dashboard/modules/job/__pycache__/utils.cpython-312.pyc,,
+ray/dashboard/modules/job/cli.py,sha256=rhMqfgep1KAb941sib-3C0EmyOdR0MnZgcaiw3aqz1w,15959
+ray/dashboard/modules/job/cli_utils.py,sha256=nqq_R36FiOCX_2guPGlDEo29C2SR6ZRjrU8hVF81Wn0,1462
+ray/dashboard/modules/job/common.py,sha256=SHtKAEVSmC09jK6ajCQ_UrqXHDW9mmI5qsWtsMJW3Uo,22518
+ray/dashboard/modules/job/job_agent.py,sha256=9dgbfM2lr_yvcNKbH-AOEH0gzVO5C3qMzADzOgIqn98,7832
+ray/dashboard/modules/job/job_head.py,sha256=v593v5HAVmsxl2kgGJIuupiDok-yqLnLWfYvtLTFQsA,28729
+ray/dashboard/modules/job/job_log_storage_client.py,sha256=gyyLtWNgYcXpYXBuf6KphEQU2s1Xp2YPSITn-ts5B_E,2079
+ray/dashboard/modules/job/job_manager.py,sha256=5OKCkiPOCNxItkQb66ti0Q2arYmy-vOgVIhSnORBBnA,29416
+ray/dashboard/modules/job/job_supervisor.py,sha256=PJlLFou4LI3ax4kkzYo7CTwxxMVMyqfHpUaFnQhH1w0,20874
+ray/dashboard/modules/job/pydantic_models.py,sha256=DvJGALl8d3wio4cIRNCQYkB1S6XykiWvok5ElVuvO90,4325
+ray/dashboard/modules/job/sdk.py,sha256=_LOuos6WbnrQUS_xSz-5x_4QJk05ux3lfeubAJH3-lM,21366
+ray/dashboard/modules/job/utils.py,sha256=mD-aFL6O85949QyCJcuRheiG_aWIndBFYsIIgH_RGrs,10051
+ray/dashboard/modules/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/log/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/log/__pycache__/log_agent.cpython-312.pyc,,
+ray/dashboard/modules/log/__pycache__/log_consts.cpython-312.pyc,,
+ray/dashboard/modules/log/__pycache__/log_manager.cpython-312.pyc,,
+ray/dashboard/modules/log/__pycache__/log_utils.cpython-312.pyc,,
+ray/dashboard/modules/log/log_agent.py,sha256=2glBjff-jrGZx-0hMlw10oLmuLYLFgJUqKS73w9iLeM,14040
+ray/dashboard/modules/log/log_consts.py,sha256=ppK99FPwkRHMMj0Ss2dhI_527lH4yoVo81M60R2rZSY,129
+ray/dashboard/modules/log/log_manager.py,sha256=Gda8jXFUNJSiqCZqk8wsQ9hh8uS3JL-uql8d6J0ccQ8,17162
+ray/dashboard/modules/log/log_utils.py,sha256=IRpef6B8H86jBPaGieD8hTN8_YWW6xKFe-NjJOGss6o,238
+ray/dashboard/modules/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/metrics/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/metrics/__pycache__/grafana_dashboard_factory.cpython-312.pyc,,
+ray/dashboard/modules/metrics/__pycache__/install_and_start_prometheus.cpython-312.pyc,,
+ray/dashboard/modules/metrics/__pycache__/metrics_head.cpython-312.pyc,,
+ray/dashboard/modules/metrics/__pycache__/templates.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/metrics/dashboards/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/common.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/data_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/default_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/serve_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/serve_deployment_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/serve_llm_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/__pycache__/train_dashboard_panels.cpython-312.pyc,,
+ray/dashboard/modules/metrics/dashboards/common.py,sha256=ZWid_YBLzEx-QItW6J4o0aCUfzrPP6CB30muZ2WoVfA,14091
+ray/dashboard/modules/metrics/dashboards/data_dashboard_panels.py,sha256=_7fnEn-VF7eI7AaJuiE-9v_BATrmaCVTAMIWFgXCR8M,36787
+ray/dashboard/modules/metrics/dashboards/data_grafana_dashboard_base.json,sha256=63xRCEGtgSzWkPc8rDwV-H5d9WokxcL88T995ab2x_E,5890
+ray/dashboard/modules/metrics/dashboards/default_dashboard_panels.py,sha256=lTdHZ8iuTqkJivQI0SycV4yngK-zcvdM513wFG6lPMI,31761
+ray/dashboard/modules/metrics/dashboards/default_grafana_dashboard_base.json,sha256=nvOlT7SJKOEpup5JOslfaOirORD3g5MFbHSIjEXvJWM,4855
+ray/dashboard/modules/metrics/dashboards/serve_dashboard_panels.py,sha256=F3D76g8kMolBkBEvvhQTgooAz8C_Dvp_JLQq0W4wa9Y,16653
+ray/dashboard/modules/metrics/dashboards/serve_deployment_dashboard_panels.py,sha256=zW_5QVryJcFOmXK6TgI_JICxnaeHboK0fnL0_ltqFuo,9388
+ray/dashboard/modules/metrics/dashboards/serve_deployment_grafana_dashboard_base.json,sha256=Mynl8dIpN8KlHeym0sq4zcGWWpn57zyqbomU9LIn7SI,6084
+ray/dashboard/modules/metrics/dashboards/serve_grafana_dashboard_base.json,sha256=Zfh3aSexcq5C3f9Es1Q0PQfecev9t_YHxAERchkdz_k,4940
+ray/dashboard/modules/metrics/dashboards/serve_llm_dashboard_panels.py,sha256=q3bAIwRMTPyvuntHIzOC8N56ITbBEKFfDmBiW1g7qkY,24135
+ray/dashboard/modules/metrics/dashboards/serve_llm_grafana_dashboard_base.json,sha256=nPXhi-YV5d4UF9bNafbCQWtCv3OO3DEk_gzazVdfV7k,4140
+ray/dashboard/modules/metrics/dashboards/train_dashboard_panels.py,sha256=Ffn0Zp4wxs2y089ltCVihbWhe1RbfxHiTPI5WTNHZO4,11548
+ray/dashboard/modules/metrics/dashboards/train_grafana_dashboard_base.json,sha256=nQAX42XTqukDqoDgFNK7P-2Rs46Bgo6HNLiO3CPphYI,7590
+ray/dashboard/modules/metrics/export/prometheus/prometheus.yml,sha256=OHUFwiB6QQGgsEukeC0WYEVeCvtqNDNrM-whkgmnzJM,476
+ray/dashboard/modules/metrics/grafana_dashboard_factory.py,sha256=H_4aDtFFWKEHGMluVCEwBLEKao9__MDt_q-ek8IupGQ,11107
+ray/dashboard/modules/metrics/install_and_start_prometheus.py,sha256=dA-PRXV4H3--wEZEgWBELVK1-0zWQI7V0_kGfUZspWU,6400
+ray/dashboard/modules/metrics/metrics_head.py,sha256=dbn-9CmWg3exitoo8l5IKF1q09wC4nJejniUuaTyfNA,16221
+ray/dashboard/modules/metrics/templates.py,sha256=2cLRGIynKXTsXflXzV3N9rEBlYZ6Q4WYZRRUOIneEoY,1494
+ray/dashboard/modules/node/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/node/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/node/__pycache__/actor_consts.cpython-312.pyc,,
+ray/dashboard/modules/node/__pycache__/datacenter.cpython-312.pyc,,
+ray/dashboard/modules/node/__pycache__/node_consts.cpython-312.pyc,,
+ray/dashboard/modules/node/__pycache__/node_head.cpython-312.pyc,,
+ray/dashboard/modules/node/actor_consts.py,sha256=-bkzQjaKE_nt1_sN9CpzgHN_wT-f5nmRGMHFcmfO0Mg,95
+ray/dashboard/modules/node/datacenter.py,sha256=HLreSUaUTI-qbuUafFeE9cmTlxgef1XPrxocjByv6mQ,9137
+ray/dashboard/modules/node/node_consts.py,sha256=wVB6RpZXdLONV0cNeAkvk90vdo8JHYTqfTda5Qfh9DU,636
+ray/dashboard/modules/node/node_head.py,sha256=UfQ23FKyT18C5wmd7fKOKnbUH7RazlNRwZMsSzC7yeU,30986
+ray/dashboard/modules/reporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/reporter/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/gpu_profile_manager.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/gpu_providers.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/healthz_agent.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/profile_manager.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/reporter_agent.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/reporter_consts.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/reporter_head.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/reporter_models.cpython-312.pyc,,
+ray/dashboard/modules/reporter/__pycache__/utils.cpython-312.pyc,,
+ray/dashboard/modules/reporter/gpu_profile_manager.py,sha256=PydpGXGrrCgQbwK484Q15EbYISgeVBAI51vu6QUCgWM,11956
+ray/dashboard/modules/reporter/gpu_providers.py,sha256=V57NGiLfP9QVgvl4tI2RhAG3heNiD9q5o_A3TifGi-o,20265
+ray/dashboard/modules/reporter/healthz_agent.py,sha256=ZNg3RChINdMJqJF4vOqrJ9g6bUZNZI_TvMLPpd-AwzU,2069
+ray/dashboard/modules/reporter/profile_manager.py,sha256=xm6ixWjImVGAwdNmwsSR2N5tF-OqSLVNV3oH_huvmJw,12578
+ray/dashboard/modules/reporter/reporter_agent.py,sha256=Xr2tArEtXfMfF-AuI5wIRH4s3IVWrYlcAFlMPUXWTMM,66535
+ray/dashboard/modules/reporter/reporter_consts.py,sha256=8E9gYcn59Pga7D2TO_wZKEfk4W8-o5uSDKQVJ8cywTM,254
+ray/dashboard/modules/reporter/reporter_head.py,sha256=L4Rlr78zO0I6PMp7Z8RsOKuUTW98VnZdwHVCZfQB6Ag,37350
+ray/dashboard/modules/reporter/reporter_models.py,sha256=lnCR45sSYoX-5PgP-D5cA9FHLhseXMsCn1r7obtXzts,7456
+ray/dashboard/modules/reporter/utils.py,sha256=vzORvld2-zlEqGpS59NBLKxPlDijzYrI4HJkssFlves,628
+ray/dashboard/modules/serve/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/serve/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/serve/__pycache__/sdk.cpython-312.pyc,,
+ray/dashboard/modules/serve/__pycache__/serve_head.cpython-312.pyc,,
+ray/dashboard/modules/serve/sdk.py,sha256=PObjPjxiA98VRymiErkcDeZgQNuidzGtOH7LosSXaUU,2916
+ray/dashboard/modules/serve/serve_head.py,sha256=OD_NQX7mKJ9W7MyKM6ovubapWehCkDIzB21m3OIO_4g,12582
+ray/dashboard/modules/state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/state/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/state/__pycache__/state_head.cpython-312.pyc,,
+ray/dashboard/modules/state/state_head.py,sha256=VCyUDB3bV1GWgQhBX00dTMiz4mjYEoVrSl7d-J42tac,14729
+ray/dashboard/modules/train/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/train/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/train/__pycache__/train_head.cpython-312.pyc,,
+ray/dashboard/modules/train/train_head.py,sha256=2U0Nwf6h-w_Uu6uHmpeLnSeF4qAm59Ibd08Bxdj4Ldg,19055
+ray/dashboard/modules/usage_stats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/modules/usage_stats/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/modules/usage_stats/__pycache__/usage_stats_head.cpython-312.pyc,,
+ray/dashboard/modules/usage_stats/usage_stats_head.py,sha256=w5arE6jjasys2GR9cUuoEC49YjKv_yFyOtzXEYUTvo8,7996
+ray/dashboard/modules/version.py,sha256=McYMc3HBcNn5HSuq-uuVZHWuxxGZtq2eX7B_UFHONhI,630
+ray/dashboard/optional_deps.py,sha256=4JFlg6csFAZKGNlNFkSFeeXDCrRsEdldWPDM4jrm3t8,1014
+ray/dashboard/optional_utils.py,sha256=hu5LmXnVT1ofeBg3x6yE77-fzfUoogP5TVlWTjjXxYI,8187
+ray/dashboard/routes.py,sha256=Hrx2MakTfEG1Hdy_cMU1DFph0ApumMrqK_ajcA0Nv4Y,6670
+ray/dashboard/state_aggregator.py,sha256=gNZUR-GV1Q3UeQM4OXoz-BHQtcI8YI0MaIZircc-7JQ,26468
+ray/dashboard/state_api_utils.py,sha256=ANRPJ_-ELc5kdHTM6EhXdU8ythCUMJzmSUJFx_ZIOq8,9758
+ray/dashboard/subprocesses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/dashboard/subprocesses/__pycache__/__init__.cpython-312.pyc,,
+ray/dashboard/subprocesses/__pycache__/handle.cpython-312.pyc,,
+ray/dashboard/subprocesses/__pycache__/module.cpython-312.pyc,,
+ray/dashboard/subprocesses/__pycache__/routes.cpython-312.pyc,,
+ray/dashboard/subprocesses/__pycache__/utils.cpython-312.pyc,,
+ray/dashboard/subprocesses/handle.py,sha256=ncYBhzh-7y-hQuyYadvL1-bDV7AokeDPJdUOK8VN_VQ,12535
+ray/dashboard/subprocesses/module.py,sha256=6JuJw8qSsaNFYZ81mV3SxesoKh0-eOdPZ1z3MND2Cro,9187
+ray/dashboard/subprocesses/routes.py,sha256=jd67wPCEBK8uFX8lVb97SwTHaGEMwrGaaXgKZP6G-6A,3758
+ray/dashboard/subprocesses/utils.py,sha256=1bSDXhq1QfW0_q1o5wW72PUDN2xw8L7Bhw8RNcVwaDQ,1843
+ray/dashboard/timezone_utils.py,sha256=Y-JaOJUvwmQxPsRBeQunSDDClUN5_koGJlwwwP4WVk0,2352
+ray/dashboard/utils.py,sha256=VOcpZFwNdo3lt8O2qB9GQLs51V0p1dRKMcn29YoJWG4,23645
+ray/data/__init__.py,sha256=gPd8E7kYs0Ia5_QMM4e6FvVNNVdNczs70EZVCuWmySI,4587
+ray/data/__pycache__/__init__.cpython-312.pyc,,
+ray/data/__pycache__/aggregate.cpython-312.pyc,,
+ray/data/__pycache__/block.cpython-312.pyc,,
+ray/data/__pycache__/collate_fn.cpython-312.pyc,,
+ray/data/__pycache__/context.cpython-312.pyc,,
+ray/data/__pycache__/dataset.cpython-312.pyc,,
+ray/data/__pycache__/datatype.cpython-312.pyc,,
+ray/data/__pycache__/exceptions.cpython-312.pyc,,
+ray/data/__pycache__/expressions.cpython-312.pyc,,
+ray/data/__pycache__/grouped_data.cpython-312.pyc,,
+ray/data/__pycache__/iterator.cpython-312.pyc,,
+ray/data/__pycache__/llm.cpython-312.pyc,,
+ray/data/__pycache__/preprocessor.cpython-312.pyc,,
+ray/data/__pycache__/random_access_dataset.cpython-312.pyc,,
+ray/data/__pycache__/read_api.cpython-312.pyc,,
+ray/data/__pycache__/stats.cpython-312.pyc,,
+ray/data/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/__pycache__/aggregate.cpython-312.pyc,,
+ray/data/_internal/__pycache__/arrow_block.cpython-312.pyc,,
+ray/data/_internal/__pycache__/batcher.cpython-312.pyc,,
+ray/data/_internal/__pycache__/block_builder.cpython-312.pyc,,
+ray/data/_internal/__pycache__/block_list.cpython-312.pyc,,
+ray/data/_internal/__pycache__/collections.cpython-312.pyc,,
+ray/data/_internal/__pycache__/compute.cpython-312.pyc,,
+ray/data/_internal/__pycache__/delegating_block_builder.cpython-312.pyc,,
+ray/data/_internal/__pycache__/equalize.cpython-312.pyc,,
+ray/data/_internal/__pycache__/logging.cpython-312.pyc,,
+ray/data/_internal/__pycache__/memory_tracing.cpython-312.pyc,,
+ray/data/_internal/__pycache__/metadata_exporter.cpython-312.pyc,,
+ray/data/_internal/__pycache__/numpy_support.cpython-312.pyc,,
+ray/data/_internal/__pycache__/operator_event_exporter.cpython-312.pyc,,
+ray/data/_internal/__pycache__/output_buffer.cpython-312.pyc,,
+ray/data/_internal/__pycache__/pandas_block.cpython-312.pyc,,
+ray/data/_internal/__pycache__/plan.cpython-312.pyc,,
+ray/data/_internal/__pycache__/progress_bar.cpython-312.pyc,,
+ray/data/_internal/__pycache__/remote_fn.cpython-312.pyc,,
+ray/data/_internal/__pycache__/row.cpython-312.pyc,,
+ray/data/_internal/__pycache__/savemode.cpython-312.pyc,,
+ray/data/_internal/__pycache__/size_estimator.cpython-312.pyc,,
+ray/data/_internal/__pycache__/split.cpython-312.pyc,,
+ray/data/_internal/__pycache__/stats.cpython-312.pyc,,
+ray/data/_internal/__pycache__/streaming_repartition.cpython-312.pyc,,
+ray/data/_internal/__pycache__/table_block.cpython-312.pyc,,
+ray/data/_internal/__pycache__/torch_iterable_dataset.cpython-312.pyc,,
+ray/data/_internal/__pycache__/util.cpython-312.pyc,,
+ray/data/_internal/actor_autoscaler/__init__.py,sha256=WJ5XqGK1AWQL_Pir8_jenJm8VGBOPY1MWvJx6eSDf54,845
+ray/data/_internal/actor_autoscaler/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/actor_autoscaler/__pycache__/autoscaling_actor_pool.cpython-312.pyc,,
+ray/data/_internal/actor_autoscaler/__pycache__/base_actor_autoscaler.cpython-312.pyc,,
+ray/data/_internal/actor_autoscaler/__pycache__/default_actor_autoscaler.cpython-312.pyc,,
+ray/data/_internal/actor_autoscaler/autoscaling_actor_pool.py,sha256=pHeG7KRaoqb56jdO6oudpzdUhR1G1nNWx8nJw3k3aR4,3305
+ray/data/_internal/actor_autoscaler/base_actor_autoscaler.py,sha256=RwMVmBgW7d5daoA9-xKye09uRILPc9vvHM4djADAk_I,918
+ray/data/_internal/actor_autoscaler/default_actor_autoscaler.py,sha256=qEsfUQa7kPR5cAM6t-kWq1ur8RdsQZQg9EVoN7bEExw,7773
+ray/data/_internal/aggregate.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/arrow_block.py,sha256=4HaCVk5NmOyexshyNxOOTnSZq1dSMSSw4OhA2WRJV1w,19996
+ray/data/_internal/arrow_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/arrow_ops/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/arrow_ops/__pycache__/transform_polars.cpython-312.pyc,,
+ray/data/_internal/arrow_ops/__pycache__/transform_pyarrow.cpython-312.pyc,,
+ray/data/_internal/arrow_ops/transform_polars.py,sha256=7gdAVU_0j_xKWoqR7YDos8p_XwaaFuymVkTJifscd88,1819
+ray/data/_internal/arrow_ops/transform_pyarrow.py,sha256=vOgdZiBI2W_3zOCJdyqG7yJJui604Om1QvwDN3O7onk,38045
+ray/data/_internal/batcher.py,sha256=XbDJArxSI6mdwdkZ_PauC3aF7QmZnclfnVWAWtuEyUI,15391
+ray/data/_internal/block_batching/__init__.py,sha256=yh48Q33rYuRur6LwWmnEwHdr_86mbP3li_LLSE6zNGM,102
+ray/data/_internal/block_batching/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/block_batching/__pycache__/block_batching.cpython-312.pyc,,
+ray/data/_internal/block_batching/__pycache__/interfaces.cpython-312.pyc,,
+ray/data/_internal/block_batching/__pycache__/iter_batches.cpython-312.pyc,,
+ray/data/_internal/block_batching/__pycache__/util.cpython-312.pyc,,
+ray/data/_internal/block_batching/block_batching.py,sha256=tH6EEOzhvlBJ6rywC6w6QRfSKFHgph6IuhLSwDycIa8,1914
+ray/data/_internal/block_batching/interfaces.py,sha256=RT11Sw3O-kRJsUn7e81KRmxAlPxca7GUB5bl9SSMC-Y,1178
+ray/data/_internal/block_batching/iter_batches.py,sha256=UOwL4YAZDCxVnHC9rpcsDgPHJT2OA5HqJxcvM-H09eY,17239
+ray/data/_internal/block_batching/util.py,sha256=hEvV0eOzyGdZZuiKv6dNj1dKjlFlyLh7EaOlHtIOfkM,10633
+ray/data/_internal/block_builder.py,sha256=5R8yM1VjfBxY4f7MJhowPOSO-QEKiIAOfD9SZsyFyzI,1197
+ray/data/_internal/block_list.py,sha256=PfIB7yPzclU1UUcsrdwr6y1JOT2B-lxtGtztu38Aueg,4015
+ray/data/_internal/cluster_autoscaler/__init__.py,sha256=Ry99lBA88SyBi-mBBZsCJOtHcPgxyi73dRQCbyArHek,610
+ray/data/_internal/cluster_autoscaler/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/cluster_autoscaler/__pycache__/base_cluster_autoscaler.cpython-312.pyc,,
+ray/data/_internal/cluster_autoscaler/__pycache__/default_cluster_autoscaler.cpython-312.pyc,,
+ray/data/_internal/cluster_autoscaler/base_cluster_autoscaler.py,sha256=eS-hnimw7oOwuEVG7FKXJgNlTpiHHRF-e3ywElSqu-s,1399
+ray/data/_internal/cluster_autoscaler/default_cluster_autoscaler.py,sha256=q0DgO1qUJzHcoBn3nbY6x533DpTu1BIqcuwFR7_KlaM,4179
+ray/data/_internal/collections.py,sha256=XI1dx_x-jBZDPeuZx2-vRjY1mqsqjDZubvI53-BKVm0,1445
+ray/data/_internal/compute.py,sha256=BzFHFjr4ORruAEys3DgvEHNPE6Bfu15VK07yvVY2vuE,7306
+ray/data/_internal/datasource/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/datasource/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/audio_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/avro_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/bigquery_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/bigquery_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/binary_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/clickhouse_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/clickhouse_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/csv_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/csv_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/databricks_uc_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/delta_sharing_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/hudi_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/huggingface_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/iceberg_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/iceberg_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/image_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/image_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/json_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/json_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/lance_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/lance_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/mcap_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/mongo_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/mongo_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/numpy_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/numpy_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/parquet_bulk_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/parquet_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/parquet_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/range_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/sql_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/sql_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/text_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/tfrecords_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/tfrecords_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/torch_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/uc_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/video_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/webdataset_datasink.cpython-312.pyc,,
+ray/data/_internal/datasource/__pycache__/webdataset_datasource.cpython-312.pyc,,
+ray/data/_internal/datasource/audio_datasource.py,sha256=6a5sdqJZi1XZcyRxTAuB5reJejp0pRYIFQPqZMKPq-E,1574
+ray/data/_internal/datasource/avro_datasource.py,sha256=6uejfzw2xRqsily8N_acJx9eKvxYgK1DEZKgRSMYpM8,1484
+ray/data/_internal/datasource/bigquery_datasink.py,sha256=c7rq5R8_GUf9ty4PTlOZOZ4x9mDWSak7AqERPp6EgEQ,5113
+ray/data/_internal/datasource/bigquery_datasource.py,sha256=HVashOXTQ3-0kCHCJmnBpXMSXVDBYUpEJu63r1CpzDI,5024
+ray/data/_internal/datasource/binary_datasource.py,sha256=vFwIi_j9QQRseiYDcCHSfKOc_37qlMbKnnWcNwDUHK0,625
+ray/data/_internal/datasource/clickhouse_datasink.py,sha256=UrhFyGH1tanaU_ZjiwRAS231oBqk-WBO5srQF3VGEqs,17442
+ray/data/_internal/datasource/clickhouse_datasource.py,sha256=H7YShknbciQ9HkWm1P0I7Oz82Fb6PHvd2rHsvlP9LKQ,15045
+ray/data/_internal/datasource/csv_datasink.py,sha256=x2iHSSfIOm49wsZ1MtzRP35QMPEdu7x4qOscSZWD_3E,1234
+ray/data/_internal/datasource/csv_datasource.py,sha256=LPbmQk9E_NhnqQ4FYmSh3_aUe5Hdw1zOz_y8oQLERUc,2778
+ray/data/_internal/datasource/databricks_uc_datasource.py,sha256=7LG-AMksEwG8-uZjMcoH7-XGc92pp2zBDAec9g_z4VQ,7347
+ray/data/_internal/datasource/delta_sharing_datasource.py,sha256=Fh5kClDWuS20vsRxY9w8gabSqlj1FwpbMgWe8UYHKDA,4692
+ray/data/_internal/datasource/hudi_datasource.py,sha256=e6a_l1xwbMZB3DjGwHhFuNztC6_3Adebgo6gUZnqpuk,5730
+ray/data/_internal/datasource/huggingface_datasource.py,sha256=49k4JJBg6gFjnAm6QAfzBqSO9Aj9SD-QFThd_EBpxpU,8474
+ray/data/_internal/datasource/iceberg_datasink.py,sha256=pkD0V_ENj_2IYGu0GpLmFWgFnm90Bj-7vhZwGXUBNg8,19467
+ray/data/_internal/datasource/iceberg_datasource.py,sha256=4zC58Jnt7FYYuzJVk90FstOcmMOGcuDlxp5r34kybWI,18522
+ray/data/_internal/datasource/image_datasink.py,sha256=9RR3qBnEQEd_uwKmLWYTjGh5-H25LP4gTRxEspL-5KY,705
+ray/data/_internal/datasource/image_datasource.py,sha256=LB2V6LI1Hx-Kf0LZpGc8sS_gQjtxOXPftSVWmAmOaXg,6598
+ray/data/_internal/datasource/json_datasink.py,sha256=FhVN9is4Um-hjqnARGKOuy5rqOBYVxhqveHsZazLerM,1280
+ray/data/_internal/datasource/json_datasource.py,sha256=BLWZ5edNM8bWGT5N37vOc54_A0ggylEfRwP7kAgGWVM,10899
+ray/data/_internal/datasource/lance_datasink.py,sha256=uMRpy7UTO9000wuCpFLmGuy5KmaVzyyHJipjJj-Ms7Y,7305
+ray/data/_internal/datasource/lance_datasource.py,sha256=0Kjphw6Cs04UmvU6UbNLBQPmKkNUJWYjQezzmGz6GUU,4480
+ray/data/_internal/datasource/mcap_datasource.py,sha256=Cf3ArVoOhVKlYKdTn41hKVxfW_KKQPPH1WAfKfQG_dE,9923
+ray/data/_internal/datasource/mongo_datasink.py,sha256=yoejPDlM97KY__8cp0AQpA0Sfq0qHScxjhtC4gI74QM,1586
+ray/data/_internal/datasource/mongo_datasource.py,sha256=2lu_nxGwjr2x_y1WexFbkHL4aciAfXU8MG5fNMCfyb0,4780
+ray/data/_internal/datasource/numpy_datasink.py,sha256=RICLpCrT0secP-szSPSYuBry7hkTDVG055ybsHD2K8I,617
+ray/data/_internal/datasource/numpy_datasource.py,sha256=Jznq_rFfN56e4xjBeZNmZmP_X2aw87cygjkiOFpCfwA,1251
+ray/data/_internal/datasource/parquet_bulk_datasource.py,sha256=JCfE_u1gVZuSIp1zwaVrSs9zX6qRfSA5fYwUShkUL-g,1549
+ray/data/_internal/datasource/parquet_datasink.py,sha256=wovJ_I_nvHlnPjOdWVyjgBrALvvCUbdd4ZJFhAVV2fE,11563
+ray/data/_internal/datasource/parquet_datasource.py,sha256=6iIOv_ijL6X-FZLhLAU_SlB2NzmJkf_3YuziaX4XauQ,35316
+ray/data/_internal/datasource/range_datasource.py,sha256=8-NZf9K7LhLbgCm1oB2hIYIsbVZBUBL8qbAB9_LG7EQ,5124
+ray/data/_internal/datasource/sql_datasink.py,sha256=ZH9A4_cqgUKPANh-GQ6R5t-dU3Xp9O7pncJTwrXjSNA,1251
+ray/data/_internal/datasource/sql_datasource.py,sha256=qQta5fWIUKUDitfRNYqWZfSHO4b5w7mNL8wq7Ew1R2Y,7187
+ray/data/_internal/datasource/text_datasource.py,sha256=xiht9OJRukt8yLNxqyymEyoSdBexgAJzhLpK7RLtJc8,1198
+ray/data/_internal/datasource/tfrecords_datasink.py,sha256=ITi8Oh21E2hlaE4hMWWhwqF9kCm0ejaktsjH7hBi26s,7860
+ray/data/_internal/datasource/tfrecords_datasource.py,sha256=CtzAy6fuoOQGb8i7XgxnCx-r687sKMnzImT6peIUi2c,16653
+ray/data/_internal/datasource/torch_datasource.py,sha256=0bZmYXgziQD40u67QT5t_MUVetzkGOAR-yQORSw9lhA,2199
+ray/data/_internal/datasource/uc_datasource.py,sha256=ndRMgP-D_xe6bGsVvmXoviPQKFIJT5jrWDKZEwvynTU,7082
+ray/data/_internal/datasource/video_datasource.py,sha256=HDDG6aOim-9Bn13Xh2UmT3Q_Xa3s8frEqkyS4wRM2CU,1856
+ray/data/_internal/datasource/webdataset_datasink.py,sha256=8cqVTLedeKBCPi6bNFXBDwTeWkuBvyGJTPSXmXBtl9c,1793
+ray/data/_internal/datasource/webdataset_datasource.py,sha256=7o76rnTNkYbd9aEhSjldldWFaHnczFiaONoCOPExb_k,12562
+ray/data/_internal/delegating_block_builder.py,sha256=-S-YEiwzGYdLDQmIBaN4BDN9PkE1PsZZfdgUNdDhE70,2696
+ray/data/_internal/equalize.py,sha256=8hMB3F8XnId5Lk2b6TvBBbJqvavoCx04KApqv_fbxyk,5492
+ray/data/_internal/execution/__init__.py,sha256=8fmWNoC53V87-Mggnrg5wi9L4Oh8dSqojDB877oJGRA,177
+ray/data/_internal/execution/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/autoscaling_requester.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/dataset_state.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/execution_callback.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/legacy_compat.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/progress_manager.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/ranker.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/resource_manager.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/streaming_executor.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/streaming_executor_state.cpython-312.pyc,,
+ray/data/_internal/execution/__pycache__/util.cpython-312.pyc,,
+ray/data/_internal/execution/autoscaling_requester.py,sha256=4ouvfcU5epMVWpgWAZErsj8k_-DBj55D2TCmK7dotSM,5152
+ray/data/_internal/execution/backpressure_policy/__init__.py,sha256=8lGpZ0YzuGoY8XexjK7M4WpLNHl0CMf55j2H67yksMM,1488
+ray/data/_internal/execution/backpressure_policy/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/backpressure_policy/__pycache__/backpressure_policy.cpython-312.pyc,,
+ray/data/_internal/execution/backpressure_policy/__pycache__/concurrency_cap_backpressure_policy.cpython-312.pyc,,
+ray/data/_internal/execution/backpressure_policy/__pycache__/downstream_capacity_backpressure_policy.cpython-312.pyc,,
+ray/data/_internal/execution/backpressure_policy/__pycache__/resource_budget_backpressure_policy.cpython-312.pyc,,
+ray/data/_internal/execution/backpressure_policy/backpressure_policy.py,sha256=9oCHgZi8iOW6iQTtEQ5WIyuvWA9aIuDRrEv6ZgPIwog,2190
+ray/data/_internal/execution/backpressure_policy/concurrency_cap_backpressure_policy.py,sha256=Z0-58przhAtgOVqGww_fTQGaVOslKRof1rybIOeqDyE,9906
+ray/data/_internal/execution/backpressure_policy/downstream_capacity_backpressure_policy.py,sha256=uCK7ToQ4XY8EBwbkoU4mVP-GHwKYdusf-wmt7icbdkE,3620
+ray/data/_internal/execution/backpressure_policy/resource_budget_backpressure_policy.py,sha256=CITiZUILklsa8wKPXOfp2yaVPU6laeFr02jHNKpGDYQ,1056
+ray/data/_internal/execution/bundle_queue/__init__.py,sha256=-5iP8h7pXGVIj56-Vpm7fGkKd5zFl1AhFX8YfuaNIdM,209
+ray/data/_internal/execution/bundle_queue/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/bundle_queue/__pycache__/bundle_queue.cpython-312.pyc,,
+ray/data/_internal/execution/bundle_queue/__pycache__/fifo_bundle_queue.cpython-312.pyc,,
+ray/data/_internal/execution/bundle_queue/bundle_queue.py,sha256=4M2hzSItkc7whXGzSOTLjelsLVHWMWTky9wjA-KwCN0,1883
+ray/data/_internal/execution/bundle_queue/fifo_bundle_queue.py,sha256=3M0HaLiI98_-rDdmkR0H8YCKkF7rwXpucbx0sfVfPZg,4610
+ray/data/_internal/execution/callbacks/__init__.py,sha256=DnNfyZUYFpMGqO3Cd1Z0-uALKT9McSjmZNt1f02h0-U,169
+ray/data/_internal/execution/callbacks/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/callbacks/__pycache__/insert_issue_detectors.cpython-312.pyc,,
+ray/data/_internal/execution/callbacks/insert_issue_detectors.py,sha256=dYAJY8ny5D0DWaN4fQgH3VU7yM5MXqcEguDFQ9bAkS4,804
+ray/data/_internal/execution/dataset_state.py,sha256=XYs0yxhAngiClQq2YwcJr_lVHfJfuKD9VgjeXSOEaPU,478
+ray/data/_internal/execution/execution_callback.py,sha256=PAxfXziL9_1q8XG96G97VZNtZ5rGz4o39KQgixPKA9I,3198
+ray/data/_internal/execution/interfaces/__init__.py,sha256=l9Bz87qiHfmwYFijOqz1Bi8HnlxzczJWiU26icEUMjQ,608
+ray/data/_internal/execution/interfaces/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/common.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/execution_options.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/executor.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/op_runtime_metrics.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/physical_operator.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/ref_bundle.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/task_context.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/__pycache__/transform_fn.cpython-312.pyc,,
+ray/data/_internal/execution/interfaces/common.py,sha256=Y823QKako8kA06diBbqOxP_X3KXLAx3XQYgJWPdKBjM,5582
+ray/data/_internal/execution/interfaces/execution_options.py,sha256=E9SG58vPnh-EwSxfDF-4xtvN9KhezHt2kLwxisDr2Ks,13587
+ray/data/_internal/execution/interfaces/executor.py,sha256=YbMGuizvRttiM_gqA_FB4emZhrfZOuY97n0FZL5nbFY,3373
+ray/data/_internal/execution/interfaces/op_runtime_metrics.py,sha256=IAFqgFWZ0UVyBHu9fi0Xg22bf-J4dnLNZtQJpsVdjZ4,37327
+ray/data/_internal/execution/interfaces/physical_operator.py,sha256=laHAmxSH2JByqpdQMw6CoPvb6HUbZTU7j8rvkIcBUwY,34141
+ray/data/_internal/execution/interfaces/ref_bundle.py,sha256=n2ml0OKl7zHaaGQBZkb2agfExbHZpVvszLYGhTSuYAU,15968
+ray/data/_internal/execution/interfaces/task_context.py,sha256=KlDfIrkIodQfHlrXEpcHvnp-E6fpDr86_XWTXq2gaIw,2782
+ray/data/_internal/execution/interfaces/transform_fn.py,sha256=Zv1-SKz_Lw_0EC7GPYmbzq8Lswhxr2WV-P5f5xYPc9g,420
+ray/data/_internal/execution/legacy_compat.py,sha256=qseHsuYGRtgnuW_nry09Pb-zeKDrVYUhscbACVCZ5dM,6938
+ray/data/_internal/execution/node_trackers/__init__.py,sha256=u80V6vt1yxwh97wfdzwO5AW6aIlUZnBmW9R_nbkWNF0,163
+ray/data/_internal/execution/node_trackers/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/node_trackers/__pycache__/actor_location.cpython-312.pyc,,
+ray/data/_internal/execution/node_trackers/actor_location.py,sha256=KSfmSSFUBc1ypEVIRwrhMnCVFkChzgd3TNUVCFn_-jE,1384
+ray/data/_internal/execution/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/execution/operators/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/actor_pool_map_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/aggregate_num_rows.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/base_physical_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/hash_aggregate.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/hash_shuffle.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/input_data_buffer.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/join.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/limit_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/map_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/map_transformer.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/output_splitter.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/sub_progress.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/task_pool_map_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/union_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/__pycache__/zip_operator.cpython-312.pyc,,
+ray/data/_internal/execution/operators/actor_pool_map_operator.py,sha256=E2oASKeDfQOfdNEnXof-UdW6u-68oSm_AuVnJQBDTcE,48171
+ray/data/_internal/execution/operators/aggregate_num_rows.py,sha256=MECnD10AbFzpN1bK02rBXIZIONdC6X6h_2oYepmey_M,1971
+ray/data/_internal/execution/operators/base_physical_operator.py,sha256=Wp73Td8lfrcbPG7_F8gEoeNEufJFP9WIQ9kxn_0hHUc,9817
+ray/data/_internal/execution/operators/hash_aggregate.py,sha256=JOwhBEeJZoKSKwkgVpJtOwplozNYpOPAZ8lQlMw7HwU,8579
+ray/data/_internal/execution/operators/hash_shuffle.py,sha256=-Zlj80CQ3YkJDCgIRw1neU8c7XK35522xzG5WKFMQqQ,61844
+ray/data/_internal/execution/operators/input_data_buffer.py,sha256=koS-gNcR8XDZPkZNrhnDgVysk-dqlUN1G2ctrzZA8MU,3671
+ray/data/_internal/execution/operators/join.py,sha256=87gXsnDAccFOeWOO5kHDIddmhuGY2BqAUbInyn7y5Pw,19362
+ray/data/_internal/execution/operators/limit_operator.py,sha256=syqwX-X_tnd84i9yCpSoaRqjcWxl9P0u4HdBZPLJmO0,5332
+ray/data/_internal/execution/operators/map_operator.py,sha256=5F2sYDmVLywJ6Nj2B2OvVjOv3P6XwZ1otRvJ18VV5NQ,38690
+ray/data/_internal/execution/operators/map_transformer.py,sha256=KWufb6exWwsGsKY3Q8zgRE17Wv99OM90y8-NIOfTPpc,14580
+ray/data/_internal/execution/operators/output_splitter.py,sha256=X_3xGaI3MXryJSMYzJ02VU-irkJd5LL2Iwit9VJ_J8g,13991
+ray/data/_internal/execution/operators/sub_progress.py,sha256=QTte6yWJ4HqUYSRYXNchnxJdI9OQ9glQSqdRZZMUEcU,806
+ray/data/_internal/execution/operators/task_pool_map_operator.py,sha256=4td4dQpS5M8mQ8l4sP8HV4xF_FA38kfygfwLBqkydU4,7428
+ray/data/_internal/execution/operators/union_operator.py,sha256=_1dOp_ykNJ2NJgqG56XwIMqbx4Fr-EyvcoymYEzlY3Q,5105
+ray/data/_internal/execution/operators/zip_operator.py,sha256=vwkZc6TXNiq7J0H0WEoKNgyOMuAWq5LXc63sAA1cdv0,12753
+ray/data/_internal/execution/progress_manager.py,sha256=r6PSFtN65sJLyp1i2bps3JBy0NyfwI7euMKCAqqKyzM,17447
+ray/data/_internal/execution/ranker.py,sha256=uaRn7cyI6b7hL80TqXvihn3g9yLgS8wd_5o76C8cHXY,2853
+ray/data/_internal/execution/resource_manager.py,sha256=9LZ-fu1gS6KeHDfY_vaIX9vOZVWHu9dt_DmWAHjHzGY,40358
+ray/data/_internal/execution/streaming_executor.py,sha256=VMCCT8M4zH_nlrSL0w2p0ymphGrxeDAYKNkUpKH4dm4,32396
+ray/data/_internal/execution/streaming_executor_state.py,sha256=2SrqIRUM3P9ayFi3sgcgjmFX1ymkzNR0MRzwCXoOeBQ,32646
+ray/data/_internal/execution/util.py,sha256=XzL4yfxTkNE7qQpbaqGL-YgU-SKp1DFeNNrCtRRxBf0,2610
+ray/data/_internal/issue_detection/__init__.py,sha256=XQCCbWgKcRU4hD8ZP1pzwdUyIbbwQuEsoECgiKXxwZQ,642
+ray/data/_internal/issue_detection/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/issue_detection/__pycache__/issue_detector.cpython-312.pyc,,
+ray/data/_internal/issue_detection/__pycache__/issue_detector_configuration.cpython-312.pyc,,
+ray/data/_internal/issue_detection/__pycache__/issue_detector_manager.cpython-312.pyc,,
+ray/data/_internal/issue_detection/detectors/__init__.py,sha256=gVk6RqbUK-9RtLCFMjpjj5lXdgrKjM4lmlWP8Ouj_MQ,628
+ray/data/_internal/issue_detection/detectors/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/issue_detection/detectors/__pycache__/hanging_detector.cpython-312.pyc,,
+ray/data/_internal/issue_detection/detectors/__pycache__/hash_shuffle_detector.cpython-312.pyc,,
+ray/data/_internal/issue_detection/detectors/__pycache__/high_memory_detector.cpython-312.pyc,,
+ray/data/_internal/issue_detection/detectors/hanging_detector.py,sha256=mri39-zuv-5T7HtnqPIOuznJkc2K6zb1WZNTh850AC4,7110
+ray/data/_internal/issue_detection/detectors/hash_shuffle_detector.py,sha256=EGsbtu5TeJtnCoSlhvu65bLyPons1j1zIFlzZRPcUoA,4240
+ray/data/_internal/issue_detection/detectors/high_memory_detector.py,sha256=YGTmqllAi0CQL3Jv6gJma-Ehr_G8YqF9siYsuMeMpDY,3941
+ray/data/_internal/issue_detection/issue_detector.py,sha256=n-veqNPiskmPCh6ftak-bMWv1Jk-5OmN5vSNeq-KHe8,861
+ray/data/_internal/issue_detection/issue_detector_configuration.py,sha256=lXAPXKCYYNOA4KjL8GXCJqBnilbMQSAHz3mkNYANmYc,814
+ray/data/_internal/issue_detection/issue_detector_manager.py,sha256=TKiBpBqv2WmkNYuXOi8vACacXr17q8mDaBLYNIKZwjQ,3758
+ray/data/_internal/iterator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/iterator/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/iterator/__pycache__/iterator_impl.cpython-312.pyc,,
+ray/data/_internal/iterator/__pycache__/stream_split_iterator.cpython-312.pyc,,
+ray/data/_internal/iterator/iterator_impl.py,sha256=GtBYE8ZbH4bMHSS9y-32uZtyOlelZ_QnqEeYB9ZsIQI,1182
+ray/data/_internal/iterator/stream_split_iterator.py,sha256=Fm8BWqKhlK6JLcLoWOpSt3Mkxl_wWGPKqxI2ARtzikg,10630
+ray/data/_internal/logging.py,sha256=jrxzXmOMtQzqHSnuZ-vRrX14t9dCZ2C1Q2pCb1SPh5w,14811
+ray/data/_internal/logical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/logical/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/logical/__pycache__/optimizers.cpython-312.pyc,,
+ray/data/_internal/logical/__pycache__/ruleset.cpython-312.pyc,,
+ray/data/_internal/logical/__pycache__/util.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__init__.py,sha256=FTUFNYnb3QT2DLVbyIknh81CzHNRoBmT_DWU2pYy9AI,786
+ray/data/_internal/logical/interfaces/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/logical_operator.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/logical_plan.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/operator.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/optimizer.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/physical_plan.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/plan.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/__pycache__/source_operator.cpython-312.pyc,,
+ray/data/_internal/logical/interfaces/logical_operator.py,sha256=lyY9zWSyCCOxlkmhBqdjUnhcZxfPUw0eVJCJ2VeQv3c,5898
+ray/data/_internal/logical/interfaces/logical_plan.py,sha256=FmKIiKzStzYlB9PJKXOc5PQd4tDQA6NH3-CNXR6zxFc,936
+ray/data/_internal/logical/interfaces/operator.py,sha256=4PjqiOgZwb9ZGGN8ALbGl_Ema33IF4IG71SodIoWTLk,3264
+ray/data/_internal/logical/interfaces/optimizer.py,sha256=0JMJPhJuMZvT5lIfVIK6s_vhtg5uJMbOok1SSjzk8qc,1386
+ray/data/_internal/logical/interfaces/physical_plan.py,sha256=8QpVWKXKcvStShoy8a9rkJed8dmn5WjbgGKxXsTezng,930
+ray/data/_internal/logical/interfaces/plan.py,sha256=TJov8hvgA7zkPHAhh2DPv9h16gTvAKF8lqA072YJZNY,598
+ray/data/_internal/logical/interfaces/source_operator.py,sha256=Vz397oMwDMFM0_vbneY6bNUWgx8yzKvD5iYeIbyggH4,471
+ray/data/_internal/logical/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/logical/operators/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/all_to_all_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/count_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/from_operators.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/input_data_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/join_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/map_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/n_ary_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/one_to_one_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/read_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/streaming_split_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/__pycache__/write_operator.cpython-312.pyc,,
+ray/data/_internal/logical/operators/all_to_all_operator.py,sha256=7fJ6iks1fi3qR1eKKFA_Bdg2B0O64Y05Aexe8nKhz-s,8027
+ray/data/_internal/logical/operators/count_operator.py,sha256=ad8H0LftaZAQzElXXN_m2C5GiRvTscUtwHD9-lYNiU0,583
+ray/data/_internal/logical/operators/from_operators.py,sha256=JwugXZ6CxLr3hu8YQAdTF93pm8JXSmODhd4G_4N3GJg,3098
+ray/data/_internal/logical/operators/input_data_operator.py,sha256=OedXrtRU1ptA5W32wyStWWQBYQ_Ed9O5-uk5l7jd250,1826
+ray/data/_internal/logical/operators/join_operator.py,sha256=c75mM-ZArIjR35lq4tCpNH8Ije1r7nlgo9uFPoaoVEo,8300
+ray/data/_internal/logical/operators/map_operator.py,sha256=EGHNILy_IqqirA7XfQhK3KlSjjZN5GKwo7eb8k_vttg,15560
+ray/data/_internal/logical/operators/n_ary_operator.py,sha256=teXb8ZSILt7VCilHZtLMyxXehFDEjpo232u0KAT2C7Q,1812
+ray/data/_internal/logical/operators/one_to_one_operator.py,sha256=T5uiCRFQsuBRVoD-Z3OOk89_nywNDm82rmFzf09yiL4,4385
+ray/data/_internal/logical/operators/read_operator.py,sha256=v71b2vr9WLdBl2w5DxSBoZSCdgwlExEDJ2u1JL9oI_k,7413
+ray/data/_internal/logical/operators/streaming_split_operator.py,sha256=aKXLEEjDp5cwbR_88wq6ZK-lOwFHHsy_ffUHVtd5XRg,673
+ray/data/_internal/logical/operators/write_operator.py,sha256=l0aMhQqKSsKxqSLiAM3A_QMXDwYJpx7D5J9CuzTSHHU,1215
+ray/data/_internal/logical/optimizers.py,sha256=OK5Qw9VvxVV5OiZjEtJUDqdW13NDF18FmdpXbFZtzEs,3390
+ray/data/_internal/logical/rules/__init__.py,sha256=2RvYumHx-e3eFVtvPqOA5CZGI0XkoJglgqAWbHnob98,104
+ray/data/_internal/logical/rules/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/configure_map_task_memory.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/inherit_batch_format.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/inherit_target_max_block_size.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/limit_pushdown.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/operator_fusion.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/predicate_pushdown.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/projection_pushdown.cpython-312.pyc,,
+ray/data/_internal/logical/rules/__pycache__/set_read_parallelism.cpython-312.pyc,,
+ray/data/_internal/logical/rules/configure_map_task_memory.py,sha256=Vyb1uIntBaSP907VTZpPkslyDcbJNySF-0azrv3563c,3709
+ray/data/_internal/logical/rules/inherit_batch_format.py,sha256=3liaenM0jJCx_3jhzZE6ET6rrTbRShMjSJhYSv00E2g,1684
+ray/data/_internal/logical/rules/inherit_target_max_block_size.py,sha256=-JXK4PzfjdBYVLbu3XJosfhKmt_3ZX1kf2IC_0_WC0Q,1250
+ray/data/_internal/logical/rules/limit_pushdown.py,sha256=2ktbb90snMvDkT5EIAQpFVzrmvbneyNUqh1RzNGA_Pw,8132
+ray/data/_internal/logical/rules/operator_fusion.py,sha256=cZlIbksQKaTyIOXLrPGwKnr7f3ZzLv573jbfqopMsh4,21214
+ray/data/_internal/logical/rules/predicate_pushdown.py,sha256=ZyFpzv2dBJpa5pNgiuYJWux9IsrJ29wM7k1aO9hb-T8,9454
+ray/data/_internal/logical/rules/projection_pushdown.py,sha256=9GkOxnTo5uuDGCO9GMwnbf2w89VTcV_Txt5Un49H3OI,16095
+ray/data/_internal/logical/rules/set_read_parallelism.py,sha256=1p8ql8f_VmThfc4riYaimH6et6yPHc9iPqyR_9s0sVQ,5748
+ray/data/_internal/logical/ruleset.py,sha256=M2gc_B4aV5CmFbagjKxTDpL5BihWJxVXzlz0lfUfn-w,3149
+ray/data/_internal/logical/util.py,sha256=q9o47Jyyb1-j79Es0dgOaXiwW3OlLsGOPhfqbMsai9Q,3078
+ray/data/_internal/memory_tracing.py,sha256=HC_9pDdGuOrDtxO5aKG98tMKySbesMLaD7ImjZJzmcc,5861
+ray/data/_internal/metadata_exporter.py,sha256=navGcs1I5HfBWIHgAx3uxJ6XWVzXXRywvwfSv1lTM9o,13199
+ray/data/_internal/numpy_support.py,sha256=ZWz4jGldEdrDfrBKD8mIc_NBYo3CLRQm-KwSB_ONXUo,8432
+ray/data/_internal/operator_event_exporter.py,sha256=3ykVXH286bagoveVIOTHlWWlrikhcxL7Sg4C9vYYHbw,5231
+ray/data/_internal/output_buffer.py,sha256=3gev4guTa1LWVvwA6-uIw9mIMI6131pZFEwP8QuoiL4,7385
+ray/data/_internal/pandas_block.py,sha256=565jCiGVOGTRFxVLfLP1jSJzXAHbiHz86hVxhVtvdwc,24933
+ray/data/_internal/plan.py,sha256=_fgaWlHxZi9-1XtB8GjvdeVfQsfr3w-bzXGUFclHP5Y,25618
+ray/data/_internal/planner/__init__.py,sha256=JBFixax2Wh-qQz1h_xbn4zCrgZxgcXZoihpgqWjH5YM,304
+ray/data/_internal/planner/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/aggregate.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/plan_all_to_all_op.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/plan_download_op.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/plan_read_op.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/plan_udf_map_op.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/plan_write_op.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/planner.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/random_shuffle.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/randomize_blocks.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/repartition.cpython-312.pyc,,
+ray/data/_internal/planner/__pycache__/sort.cpython-312.pyc,,
+ray/data/_internal/planner/aggregate.py,sha256=T0ab1mzaahlmq7cFShsCRUWvuR1K7lsmA_mlIWSNYi0,3334
+ray/data/_internal/planner/exchange/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/planner/exchange/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/aggregate_task_spec.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/interfaces.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/pull_based_shuffle_task_scheduler.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/push_based_shuffle_task_scheduler.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/shuffle_task_spec.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/sort_task_spec.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/__pycache__/split_repartition_task_scheduler.cpython-312.pyc,,
+ray/data/_internal/planner/exchange/aggregate_task_spec.py,sha256=IwvFeP52QWYY0KLCTf6GXysHLODqJpXbLwgYTI5GCO0,4129
+ray/data/_internal/planner/exchange/interfaces.py,sha256=gjkD550QdUWI84iEc-WfN4bUOXeMfNyJ4qDEWi885js,5320
+ray/data/_internal/planner/exchange/pull_based_shuffle_task_scheduler.py,sha256=nLeYj1r0zbfZAhQSnqDfyPqxIz6ZNlhuR0sHavAzFX4,6276
+ray/data/_internal/planner/exchange/push_based_shuffle_task_scheduler.py,sha256=Wvn_BoYfJyRUFYF3YKBztYzkF9rnsMg61Px-XMUBDX0,33537
+ray/data/_internal/planner/exchange/shuffle_task_spec.py,sha256=B5YqiLYMOgR9x2vp9p9KMREdXq1dbzQba-oqhlbvqpo,5532
+ray/data/_internal/planner/exchange/sort_task_spec.py,sha256=_8qqeRVbtcX9d1N8RVOwjeUOwY52fbLcw3WFrm0uqRw,9034
+ray/data/_internal/planner/exchange/split_repartition_task_scheduler.py,sha256=pMCE1RKhWZtlLo3nMdIsKvf-KFqEvBCG8VrnfZ0BWVk,6640
+ray/data/_internal/planner/plan_all_to_all_op.py,sha256=dN8IzWmmSxrTX1hD6vzZ1vKvmndMFJxa3se-vvwfQ1s,5801
+ray/data/_internal/planner/plan_download_op.py,sha256=tEvjXTRxleGOh8IrYy1oDiWSPgmPxYYuZOFTXoSyKfM,13726
+ray/data/_internal/planner/plan_expression/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/_internal/planner/plan_expression/__pycache__/__init__.cpython-312.pyc,,
+ray/data/_internal/planner/plan_expression/__pycache__/expression_evaluator.cpython-312.pyc,,
+ray/data/_internal/planner/plan_expression/__pycache__/expression_visitors.cpython-312.pyc,,
+ray/data/_internal/planner/plan_expression/expression_evaluator.py,sha256=-t_amBmrU2BIhYjc7zPLjpncJpDgAl7bYolwc9EhJ_w,27519
+ray/data/_internal/planner/plan_expression/expression_visitors.py,sha256=pxy0mGeWKv_BRMpNnPoKj34Fh7m6BGQ0LLyRkBtdcHc,11333
+ray/data/_internal/planner/plan_read_op.py,sha256=LvrDLhXXEPwZeYuMx0X36bqtrvXnTzZx77tuD1-EXTs,4781
+ray/data/_internal/planner/plan_udf_map_op.py,sha256=51KmUPOZk_X1Jh1pe6nPPAECQH9SBX2DN1xJ4lCsfpo,27737
+ray/data/_internal/planner/plan_write_op.py,sha256=jM5kwTqBjQZ2rkRkLCjOhR0YSGpFiRY2j-A5Q9Xx4YM,4339
+ray/data/_internal/planner/planner.py,sha256=pQZjPTTkNva4nMJYDm9V9ds2YpHwyig3pPRwIOegG1g,9283
+ray/data/_internal/planner/random_shuffle.py,sha256=GYOQOCkXFHgfZzuQ8acs4ipAL2knEouPA5n7T4bqIdo,3475
+ray/data/_internal/planner/randomize_blocks.py,sha256=uq5_Ia3wiqPCjXOU9ji8fOOCsALcnzZHEdLfflDPTm4,1819
+ray/data/_internal/planner/repartition.py,sha256=mcWVrXUJ9bcKocvw5ntvSrdI0cuR15QSe-KyiF8_QQk,3248
+ray/data/_internal/planner/sort.py,sha256=eNRzzRgFUA5-1kdTlYmEw0Q3Q7n1ya_dfz-KKehnif0,3051
+ray/data/_internal/progress_bar.py,sha256=oNa_3XMeb3LP5tlLyeqq63MG9dDRTlASs0uzzeG6b2M,7881
+ray/data/_internal/remote_fn.py,sha256=NWhWFnzKhkPyERyQOgpjMiDpT5cqEDiPJDbUUQlhRPk,3127
+ray/data/_internal/row.py,sha256=ZCuvnmVYHsGaHwOpxBi1rFIPgtlUbs1crwkUf96mzvc,1299
+ray/data/_internal/savemode.py,sha256=JK5AWT9L1UEiAVtll_aRlC-FJIqAR5U0mx8_cK8JULo,638
+ray/data/_internal/size_estimator.py,sha256=ysmwWsXLZHvikQWAd8cUC1awnW-nz727yv6ICXkeODI,2869
+ray/data/_internal/split.py,sha256=0J-10zVv7dpGQhIFsKq-AC3bzJi80gpIP-vw00GwPBs,11139
+ray/data/_internal/stats.py,sha256=VrQ4jz70AYEF_kSL59hbftuiCrVP1hap2gOHcJFVurQ,74847
+ray/data/_internal/streaming_repartition.py,sha256=msZvFbo8DjFvsjgeyOg5IASndXDazZtCkRLyW4jvS44,3963
+ray/data/_internal/table_block.py,sha256=1IjlJi4kA2QYMA0tYPShHBFVCJRAD-K7TKH5zZgxHFA,21584
+ray/data/_internal/torch_iterable_dataset.py,sha256=WYYir0pFH5-xd-1M0-Hpr6wQ5-SRjJditEA-QaCDWTE,259
+ray/data/_internal/util.py,sha256=fepYNuQ69WyfYP8UUZIYOGOBhD_P7zx1paHbT2YahbY,62441
+ray/data/aggregate.py,sha256=HfKuw2v9-7m_QmW9MyShm8y-lMC3uRAEyBS1OrIuuD4,57604
+ray/data/block.py,sha256=5OJTzQhBkVAopX47RSFIiEG107lF-5ovO7xAAnaBXOQ,28749
+ray/data/collate_fn.py,sha256=aEsYwIXvf_IJM7gTnuMdtsdhoiQ5BCCrhfgsPSuP9gY,8227
+ray/data/context.py,sha256=RK4UX7dqTk4WGyfgV10fD2z7p-I8MBOFsrymIF-4E1c,34536
+ray/data/dataset.py,sha256=zHk6lK44ZNSUrVQVfYrosn3alWJbOzz_sOJIX7Zhb6Q,284769
+ray/data/datasource/__init__.py,sha256=z2GTP3wAn17VRmpfC2lHY9yPDS3RofWQo9mFaoKfO8I,2037
+ray/data/datasource/__pycache__/__init__.cpython-312.pyc,,
+ray/data/datasource/__pycache__/datasink.cpython-312.pyc,,
+ray/data/datasource/__pycache__/datasource.cpython-312.pyc,,
+ray/data/datasource/__pycache__/file_based_datasource.cpython-312.pyc,,
+ray/data/datasource/__pycache__/file_datasink.cpython-312.pyc,,
+ray/data/datasource/__pycache__/file_meta_provider.cpython-312.pyc,,
+ray/data/datasource/__pycache__/filename_provider.cpython-312.pyc,,
+ray/data/datasource/__pycache__/partitioning.cpython-312.pyc,,
+ray/data/datasource/__pycache__/path_util.cpython-312.pyc,,
+ray/data/datasource/__pycache__/util.cpython-312.pyc,,
+ray/data/datasource/datasink.py,sha256=LQYPHE2ZXVP6JBcFvHRC_bLDpJO7BUFZK6sUs3MCx3g,6023
+ray/data/datasource/datasource.py,sha256=zb1onvjJ9pcPR6mjML7wOTNf4ZiZc-uI3VY2yeI0o_c,17842
+ray/data/datasource/file_based_datasource.py,sha256=G6mLQm5UByk3nP-FtNaybIG_1IJ3UEJZkQ3ggPse8L8,19836
+ray/data/datasource/file_datasink.py,sha256=7DVoSaKDwvyK1smwWNoYzsvmCHDFrSCWYuQm9Afo2Vw,11042
+ray/data/datasource/file_meta_provider.py,sha256=rzH2czE1BnVjVWeBh6PozFCJnIIdALY_go6QyRXDdWI,18659
+ray/data/datasource/filename_provider.py,sha256=CiBU1k6n2FCP9bV4iO5L4C2PAoYMfOqpUn7gYyCulO8,4986
+ray/data/datasource/partitioning.py,sha256=9ChXsbY8l1eK7zzlX9SdDPRsovZuKcVMfk6gaNUEwjc,19650
+ray/data/datasource/path_util.py,sha256=3d_1JGiGBP_nsIpvSiR95XKnEgD7j6Fi5DTaoEalX98,8540
+ray/data/datasource/util.py,sha256=I6IhXLzPqaAWsipD_ES3aDrd2sa7VNpmkWVCRq6k_fU,859
+ray/data/datatype.py,sha256=SMy4prCwVEbF_x6cokvYyZQ_p_yhYmIS3agZQ5ajCJs,36840
+ray/data/exceptions.py,sha256=l6U837Uwrfgs6rvdwaHVf8P7zaTHikW4vtJFz5bT-Gs,3926
+ray/data/expressions.py,sha256=HYgcfIctmGtol_nGwYW6ZWqCWResRiu9ReqxTW2Wclg,36639
+ray/data/extensions/__init__.py,sha256=wfJu5w7Offu__LsrBGAR41UlKlZ1exB9TRLWjbuTgWg,1187
+ray/data/extensions/__pycache__/__init__.cpython-312.pyc,,
+ray/data/extensions/__pycache__/object_extension.cpython-312.pyc,,
+ray/data/extensions/__pycache__/tensor_extension.cpython-312.pyc,,
+ray/data/extensions/object_extension.py,sha256=ZUSj0p2sfBBShI3mi3on2S7Z66H_Fj-4AM9i42t9-Gs,301
+ray/data/extensions/tensor_extension.py,sha256=xeEYpd5EgkEbCe1ufWiuKvinnZ5L7Q_1S_7re6s7V00,476
+ray/data/grouped_data.py,sha256=WwrmuhuSwUYEs3XGbBPvfZyhzeduqWLoxczn2arXtlc,25312
+ray/data/iterator.py,sha256=CJbelmeaOlM0LQIxOdEJyKeS0vtbd9kG9zBNI6mAcPg,47004
+ray/data/llm.py,sha256=c4bz6H4LxpBuJJwztjCG1tHYTIpFS0p9XZwEurY-KaE,22403
+ray/data/namespace_expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/data/namespace_expressions/__pycache__/__init__.cpython-312.pyc,,
+ray/data/namespace_expressions/__pycache__/list_namespace.cpython-312.pyc,,
+ray/data/namespace_expressions/__pycache__/string_namespace.cpython-312.pyc,,
+ray/data/namespace_expressions/__pycache__/struct_namespace.cpython-312.pyc,,
+ray/data/namespace_expressions/list_namespace.py,sha256=siQI-ZZZQro0gIpPWixXXFUykcvPBdjydyb2Hp1zj2Y,4049
+ray/data/namespace_expressions/string_namespace.py,sha256=P3C9Wwnify1c4v38tLw-OLDCzC4fEeysj9jUt8w0Mw4,13361
+ray/data/namespace_expressions/struct_namespace.py,sha256=3u3H95OkgTzB_ky_7goHBbXDbVR8hIdMQFM5JScTvhg,2535
+ray/data/preprocessor.py,sha256=DflMvYFitIEyXTANUjyOXrymgWG8tpIWuTw790jOvNQ,27311
+ray/data/preprocessors/__init__.py,sha256=cmLZN7WN66y806Cgjid9gdoG8bd_aWyhJQFJQliBiSo,1389
+ray/data/preprocessors/__pycache__/__init__.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/chain.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/concatenator.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/discretizer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/encoder.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/hasher.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/imputer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/normalizer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/scaler.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/serialization_handlers.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/tokenizer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/torch.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/transformer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/utils.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/vectorizer.cpython-312.pyc,,
+ray/data/preprocessors/__pycache__/version_support.cpython-312.pyc,,
+ray/data/preprocessors/chain.py,sha256=FvTFX1N51S7F72EzvNgqr1kzB7_mVnlL5QI2lWW-kYk,4159
+ray/data/preprocessors/concatenator.py,sha256=fv89EFbZD_kbqgx4iGsJnSX2Dd8bQmcdbJRL_ihwcdo,6174
+ray/data/preprocessors/discretizer.py,sha256=q4-0MsIKcVkPO1ub4I2IllHoIBcRB2lqsN2B3rgTHxc,16408
+ray/data/preprocessors/encoder.py,sha256=ib-DzflpeTfbq9C1itdwbXxz4_awIvnpbF_deISwhxc,36495
+ray/data/preprocessors/hasher.py,sha256=B9DYSH_Gkr7igtfe7071FXqdNfETJkVXfaw-xBODAac,4708
+ray/data/preprocessors/imputer.py,sha256=CakkDBwu2NGDUSuLYV623YTIcFN8YqtydfZdVHvhvZE,9174
+ray/data/preprocessors/normalizer.py,sha256=i-IgwLu_zyweWWEoJmtyGbr0MLmppMPE8k74jHFpsdM,4530
+ray/data/preprocessors/scaler.py,sha256=CNRcrhrDWYkG9ds2AhAjPaUgm17q3EwDT3DrBVsjDXA,18740
+ray/data/preprocessors/serialization_handlers.py,sha256=nw8OZRrTGI4_slsSPzTiBSMd97hXmFDjLS-bU18NrdE,6524
+ray/data/preprocessors/tokenizer.py,sha256=PWSUw4ihicIhyQO3fBHqKiuLZBHgL2goqzOAXU0KKQ0,3523
+ray/data/preprocessors/torch.py,sha256=XH-nrMHwf3xOjZ3pfWyiJZTIpsqkAc1Y6Ze65-MIyy4,5745
+ray/data/preprocessors/transformer.py,sha256=LkJC5yOspBT-oRw8qLi0BmcKvi33uoyKYzV0npGNG-0,3816
+ray/data/preprocessors/utils.py,sha256=GY-_AwSvXejAzr68vlGxvx-q2PlyVaIBkuGZNfpVlbM,7397
+ray/data/preprocessors/vectorizer.py,sha256=MXwTo9-Tipj42pr68FIpe3HzPg8ujp4UeYM2KJC2ofA,13475
+ray/data/preprocessors/version_support.py,sha256=9U4NooYUt1VsShusLkrbnaZGGvYLp4V6Ft_VTwnPI9g,3170
+ray/data/random_access_dataset.py,sha256=1CANv48tZfvYFjV9Ln1XAtFblDa3sftmFkttyKDqPiA,10209
+ray/data/read_api.py,sha256=3VUyZqGxBfvukOGGNVo3Le5t-EKkyr_dklEwvBd5PnE,194884
+ray/data/stats.py,sha256=-YU2AhBScgCrJDUKfuvIsterUILcp1Z9TPbF0oKtIzg,4960
+ray/exceptions.py,sha256=t3ioTlBjgjoEOOS6oGmFXdUxfEUi2o1GNeN8c1eLCW8,31738
+ray/experimental/__init__.py,sha256=cz9cDOzRUzyRq0xKhnx5ZpfnbvWN4bv6qLDKqEuAafs,378
+ray/experimental/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/__pycache__/compiled_dag_ref.cpython-312.pyc,,
+ray/experimental/__pycache__/dynamic_resources.cpython-312.pyc,,
+ray/experimental/__pycache__/gradio_utils.cpython-312.pyc,,
+ray/experimental/__pycache__/internal_kv.cpython-312.pyc,,
+ray/experimental/__pycache__/locations.cpython-312.pyc,,
+ray/experimental/__pycache__/queue.cpython-312.pyc,,
+ray/experimental/__pycache__/shuffle.cpython-312.pyc,,
+ray/experimental/__pycache__/tf_utils.cpython-312.pyc,,
+ray/experimental/__pycache__/tqdm_ray.cpython-312.pyc,,
+ray/experimental/channel/__init__.py,sha256=L-msl2PLqIoO0kekIWSu-cJ93ZhefUS82LKYb7j-pl0,1240
+ray/experimental/channel/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/accelerator_context.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/auto_transport_type.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/cached_channel.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/common.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/communicator.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/communicator_handle.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/conftest.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/cpu_communicator.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/intra_process_channel.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/nccl_group.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/serialization_context.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/shared_memory_channel.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/torch_tensor_accelerator_channel.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/torch_tensor_type.cpython-312.pyc,,
+ray/experimental/channel/__pycache__/utils.cpython-312.pyc,,
+ray/experimental/channel/accelerator_context.py,sha256=ySycmztjv45NTq7PThsiQFnhyTLJiiBwVCMgeCkrmAI,8603
+ray/experimental/channel/auto_transport_type.py,sha256=KKlANsUXsBFdA2uQyZlT9Ro9nvILH9RV6bp0W-FToLA,6743
+ray/experimental/channel/cached_channel.py,sha256=MH9h0FVYmDxk4yY2NQiKzDQ5NQNKLaLlIriD4QyEWDw,4672
+ray/experimental/channel/common.py,sha256=nMgnyPsFTJm-amm-kwwRxiWizck_nqD7lGRdoG2HB9I,24600
+ray/experimental/channel/communicator.py,sha256=CmSmVbY-e_jrECzu9wKQIgBFuwvM_w7P-f5El_OIjas,5821
+ray/experimental/channel/communicator_handle.py,sha256=-u0j2R54JqL60D3SFYSQ6xbmbClB2yL5_Rj5B0ofTjg,689
+ray/experimental/channel/conftest.py,sha256=neF8StM_tt8hLCAvDe7GIncFq4ulitc0AivVSK1gQRE,6060
+ray/experimental/channel/cpu_communicator.py,sha256=qeHKXPQWWpc1wWNZba73eWhP39FHOPyWe96zr-XyImE,6791
+ray/experimental/channel/intra_process_channel.py,sha256=rwTORm7iXTCtBTq3k9gKBFGyFskOAUADX_JPBa90XqQ,2749
+ray/experimental/channel/nccl_group.py,sha256=AXfqECrQGxjcv5xntzjraYCOrUgYdkODGkSof5li5I0,13562
+ray/experimental/channel/serialization_context.py,sha256=r9Wg-H20Ze2wPiBM2ploFPdz9xXK5xzhlmIE9k5Xs9w,10055
+ray/experimental/channel/shared_memory_channel.py,sha256=wRQo_nOj8IsyAR14YMl9p6KEEPFe8mkDsIXslw2WBlg,31961
+ray/experimental/channel/torch_tensor_accelerator_channel.py,sha256=LquXqQ-Ma96VCb303qUNkATEN-Rvaqif4NxSTsq2sDM,34457
+ray/experimental/channel/torch_tensor_type.py,sha256=EUoNou61hxlPNXmXLZDYxDJUGpMao8IfE2ChKGzF6r8,7424
+ray/experimental/channel/utils.py,sha256=hvaySGkhrCF0a1SRH5HLyHeHHaso5dkdelipB45TS74,2788
+ray/experimental/collective/__init__.py,sha256=SrmAdbs3ujACvNNws_sh6m8bSLQV03sgGE3OKFJ7zpk,589
+ray/experimental/collective/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/collective.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/collective_tensor_transport.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/communicator.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/conftest.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/nixl_tensor_transport.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/operations.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/tensor_transport_manager.cpython-312.pyc,,
+ray/experimental/collective/__pycache__/util.cpython-312.pyc,,
+ray/experimental/collective/collective.py,sha256=kQFKcYzNoQOZjIbpHGffsLq-K4SzD6O8XsIgvbl-ugQ,8228
+ray/experimental/collective/collective_tensor_transport.py,sha256=NfczoIPij0TyRuf3hWNP8ukX_O6zgVcdX0I5Op9Og20,7237
+ray/experimental/collective/communicator.py,sha256=-MdyO8aSs4sIWouvL23fJPlbDEMMyR0UX3jkPyujza0,1643
+ray/experimental/collective/conftest.py,sha256=X1onN8UFtFglEH4zriOHsYPQM5HQ3MO2grreBYojZeE,6940
+ray/experimental/collective/nixl_tensor_transport.py,sha256=BIMsVXKx3EwPVf800EoNgxWY3-chGuzXerFWgc_jYVA,7153
+ray/experimental/collective/operations.py,sha256=VFlH4Sq2WBAdWBOvu4g-1D6IJ0Np-rRYnQ76giW_SzE,6968
+ray/experimental/collective/tensor_transport_manager.py,sha256=BUT9RaDHMsCsED_H_W5gStBUpjFpEJh0kEp9CtVKJJ8,5136
+ray/experimental/collective/util.py,sha256=Oo2TJWrJeVVCv6Dp-X_QKZW4M6tcXND9_fV7n9eS-FY,2713
+ray/experimental/compiled_dag_ref.py,sha256=5k93qI6mkcBvsYcpepB0L10TtFUq9iFRZuMjP7SzOSg,8628
+ray/experimental/dynamic_resources.py,sha256=xLcUITwjRz3VborMa-3irftI_kcfZIXy1akR2T6MIyA,366
+ray/experimental/gpu_object_manager/__init__.py,sha256=W_u6FQt66mqkUiNno5bO38AQBUGYrzGyCwbt1ZAXmg8,169
+ray/experimental/gpu_object_manager/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/gpu_object_manager/__pycache__/gpu_object_manager.cpython-312.pyc,,
+ray/experimental/gpu_object_manager/__pycache__/gpu_object_store.cpython-312.pyc,,
+ray/experimental/gpu_object_manager/gpu_object_manager.py,sha256=4Q6f0c7OtpQosuYMYCtUN4-7zI4CN20BKbkUdXUjLMQ,28308
+ray/experimental/gpu_object_manager/gpu_object_store.py,sha256=V4GWN9O5OT_Njqc1vBWSO3KiGfEKKU7ZZoO-7yP-Gn8,16867
+ray/experimental/gradio_utils.py,sha256=yeAKHd5mSocJ_Kuvii43sndbwJFbZkibGn6NBO5Fhbo,426
+ray/experimental/internal_kv.py,sha256=70sXf6AyN4F2lDeW68Hbm2QWbZ2kxovc-XG9q7NyaD8,3369
+ray/experimental/job/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/experimental/job/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/job/example_job/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/experimental/job/example_job/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/job/example_job/__pycache__/demo_script.cpython-312.pyc,,
+ray/experimental/job/example_job/demo_script.py,sha256=L9s61byeJoxKrEIxLY2bDqCMxNSWGh_ubuK8-bTHHyE,2258
+ray/experimental/locations.py,sha256=0kurQ-lHu1RHT7usojiVPoDHqPjq_hcCo-YcTbr90l0,2829
+ray/experimental/multiprocessing/__init__.py,sha256=hLGdYmPBcC5PUMJkdzHQtmkpoxB4zyFV78jtyM5G35A,101
+ray/experimental/multiprocessing/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/multiprocessing/__pycache__/pool.cpython-312.pyc,,
+ray/experimental/multiprocessing/pool.py,sha256=yg38ubcXojfwJfhHh3LyXDWTZgQ0cECr-U4fm3NjVbM,107
+ray/experimental/queue.py,sha256=PtN0yekRZRd6mPfLV9UZg7AUNOVDjpD1Vmy3k42cA0c,278
+ray/experimental/shuffle.py,sha256=bGL9biDRlMJBUwIik9BRHIZ9sEv0JTJRIS_89Iw1fwE,11888
+ray/experimental/state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/experimental/state/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/state/__pycache__/api.cpython-312.pyc,,
+ray/experimental/state/__pycache__/common.cpython-312.pyc,,
+ray/experimental/state/__pycache__/custom_types.cpython-312.pyc,,
+ray/experimental/state/__pycache__/exception.cpython-312.pyc,,
+ray/experimental/state/__pycache__/state_cli.cpython-312.pyc,,
+ray/experimental/state/__pycache__/state_manager.cpython-312.pyc,,
+ray/experimental/state/__pycache__/util.cpython-312.pyc,,
+ray/experimental/state/api.py,sha256=lTSJNmYiuJH7H6cl3KWBJJEJipQLahY5h4MUAd1qEjM,153
+ray/experimental/state/common.py,sha256=z0wOGro4JJknd9w8v4jT47rDL_vAAgAPBDhjzeXONH8,160
+ray/experimental/state/custom_types.py,sha256=sZThzLiKVyqpdBDYevFd3iZYs4vUHIU-5-TgzdXgrDA,164
+ray/experimental/state/exception.py,sha256=I3QCtFf7vzLryGd0EywtcQtWeWxDgiNWsvK0gjD7z7Y,163
+ray/experimental/state/state_cli.py,sha256=XbcE3Pf2LUYgYsZzIyMSNXeeKdJKKi99MNM0BBzoYP8,163
+ray/experimental/state/state_manager.py,sha256=J8L5kVU-z9JFDpO3dGU2kWft_YVCAzHzdY_xbK1Y6yI,167
+ray/experimental/state/util.py,sha256=elplAt5gVGD8C1pYaJveHZq-rPiSAVfJ5NT6upvjcGI,158
+ray/experimental/tf_utils.py,sha256=xlweVsthvDkiMpnjq-B50_Mo9_uiS3sTrBfHMexMmOY,121
+ray/experimental/tqdm_ray.py,sha256=DHgKwCpOicGacvgA3e9kikAsjiLr4Bz5vb-XVCNfUnc,13609
+ray/experimental/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/experimental/util/__pycache__/__init__.cpython-312.pyc,,
+ray/experimental/util/__pycache__/types.cpython-312.pyc,,
+ray/experimental/util/types.py,sha256=lLTv8ybRrhpSZQ3In7Gc02Ljfhr67H0WiH1Nvs0aX9Q,670
+ray/includes/__init__.pxd,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/includes/array.pxd,sha256=EBJP_Qw1M0_qXjWvsEVmON8T_hHpt_EVp5iDAB4ql30,218
+ray/includes/common.pxd,sha256=zg9f6NvE8_jEsAg7_8XKBcL_dSMPdeZe2UgyL3879xA,31881
+ray/includes/function_descriptor.pxd,sha256=Vy4rxapEoeNsSgtlHICzwBD48HEdWt1_e9fA65mEK2Y,2954
+ray/includes/global_state_accessor.pxd,sha256=cingMfL7-dxEplcHOS2LDfnkqZitvKF-_JHiG5D-Snk,5793
+ray/includes/libcoreworker.pxd,sha256=u4bDBZiY0dFiFqbxsLZ7aPhwcevZSTDgrnv-A3S2Ptk,19419
+ray/includes/metric.pxd,sha256=PBHPVEZ31z7xbkjWxH-cYfobYqoUs5NssLDDMZJ7Ess,1503
+ray/includes/network_util.pxd,sha256=0k2vJhRgIhLjNxP6k_TEcwvUTMSujMin6xaFcTmAWCA,538
+ray/includes/object_ref.pyi,sha256=RrgdjmOnviD45Ff3AmnqwVZ1-FWxiYx9jfC222fvQMQ,2187
+ray/includes/optional.pxd,sha256=oU2g1EOi9-fnxfZ6Y2Oj9LookffOAJd6Yk4n8Sd_E9g,1083
+ray/includes/ray_config.pxd,sha256=q2m5xHq6VCTh9xS5M4BSLDX-cTC7KsaUVutBeQgla8A,2416
+ray/includes/rpc_token_authentication.pxd,sha256=E3cdhab7lyMBvVyCfKT8NGSZvAL895miXqYPUP2XJpA,1709
+ray/includes/setproctitle.pxd,sha256=0JO7miSyizRhsrDH8STDu-3tYYlqz-vw9LXJGUcWk2U,355
+ray/includes/stream_redirection.pxd,sha256=r3lcrUN7--0adpTTw-yeOrtpMBQo7khliotuRq8M2wU,678
+ray/includes/unique_ids.pxd,sha256=bA7VEE4LUi-QksPyj4WqAI8UlOnrXSvpyFgyB2yIpks,4779
+ray/includes/unique_ids.pyi,sha256=kyIcG8JcHXISAuKIhAUI5d4zkr2X6FlAbAFl0pWKiPg,3374
+ray/internal/__init__.py,sha256=LYqgMsS5qemO2omV9My10UZ8pMcm_Ik_PwCBX5VwkNQ,63
+ray/internal/__pycache__/__init__.cpython-312.pyc,,
+ray/jars/ray_dist.jar,sha256=MkXkj3oz8bIB9lkozWXubR-Wn6jlQMRGZWtNzoJfm3A,33546378
+ray/job_config.py,sha256=KDUNXzNrL8gXcde4gLk71TKJvUPXhiM8yOMMlcWckXg,9742
+ray/job_submission/__init__.py,sha256=VoyceKIY3jL0b9_YP2fd2y_n-H9YAEjCtSNN3T8K8MQ,371
+ray/job_submission/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/__init__.py,sha256=eFk3XkNKygF3kj7OYW7bGsiVi-Y_6hLeSBrEKZC-yus,253
+ray/llm/_internal/batch/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/__pycache__/utils.cpython-312.pyc,,
+ray/llm/_internal/batch/benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/batch/benchmark/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/benchmark/__pycache__/benchmark_processor.cpython-312.pyc,,
+ray/llm/_internal/batch/benchmark/__pycache__/dataset.cpython-312.pyc,,
+ray/llm/_internal/batch/benchmark/benchmark_processor.py,sha256=L_dqdJtX0uWJdBpXDyWlcQFLl9xywIN2bQegxMSVFa0,16048
+ray/llm/_internal/batch/benchmark/dataset.py,sha256=LIDgPJ9mqcHOUsdJ__FOvLhRfTZPgVzB1AM3DrY7cYI,5459
+ray/llm/_internal/batch/observability/__init__.py,sha256=_7VxkhMfGEH12Hy2Im0IDtUTKe_ansvqOhSsFYnkxCY,597
+ray/llm/_internal/batch/observability/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/observability/logging/__init__.py,sha256=iUidR4k4lYW4lTPy6dWBLIm_2pOXYw--lhgvr_O_eE4,1278
+ray/llm/_internal/batch/observability/logging/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/observability/logging/__pycache__/setup.cpython-312.pyc,,
+ray/llm/_internal/batch/observability/logging/setup.py,sha256=qa9P-tMLg09Fb4p0gK_dENIqwopy7-VQnz-wcPpqqEk,789
+ray/llm/_internal/batch/observability/usage_telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc,,
+ray/llm/_internal/batch/observability/usage_telemetry/usage.py,sha256=-w76UamvaOc6xKzSmg-A4ZAGjXsC9iiLAJouvO14Isk,5255
+ray/llm/_internal/batch/processor/__init__.py,sha256=9I8dpJoj1hz9zbELoMCF0FMPBrc18OZ8tLTgb6XznUY,522
+ray/llm/_internal/batch/processor/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/__pycache__/base.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/__pycache__/http_request_proc.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/__pycache__/serve_deployment_proc.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/__pycache__/sglang_engine_proc.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/__pycache__/vllm_engine_proc.cpython-312.pyc,,
+ray/llm/_internal/batch/processor/base.py,sha256=5Nedhn3xDxhZ20gNdN9LMw7_D6qWgOFkYOuRzTDVO4Y,16028
+ray/llm/_internal/batch/processor/http_request_proc.py,sha256=BkJk0P5D3-n9LGnV4vTB9tSUV5bIEjUvnK6m9ify8Kc,4158
+ray/llm/_internal/batch/processor/serve_deployment_proc.py,sha256=KVLXWGo3jqH7W2VV9e3V2ZPUpsDKd1CDsvxFzvuSMaw,2921
+ray/llm/_internal/batch/processor/sglang_engine_proc.py,sha256=2Hr8ctVmMfL-b7EKiILRpaMOA2rpsaxS6AqmBrixcJ8,7395
+ray/llm/_internal/batch/processor/vllm_engine_proc.py,sha256=2QNUr6qr0DaFyBhCH50gu3hGEWDpNNZMVEuhmwe2oME,10934
+ray/llm/_internal/batch/stages/__init__.py,sha256=NzRbURJlo25skxqbPL-NfoCNxwd16XKYZSSm5MoaeXQ,966
+ray/llm/_internal/batch/stages/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/base.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/chat_template_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/common.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/http_request_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/prepare_image_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/serve_deployment_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/sglang_engine_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/tokenize_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/__pycache__/vllm_engine_stage.cpython-312.pyc,,
+ray/llm/_internal/batch/stages/base.py,sha256=9i3y_og_SuGsETqLIZSGiPEnMo7Vli5SkktOAWPhVQw,10543
+ray/llm/_internal/batch/stages/chat_template_stage.py,sha256=cJXdDJplKrDQk5kcWklbQjpWZBP8Z637T1_DB30HahA,4966
+ray/llm/_internal/batch/stages/common.py,sha256=Jc-VvDHTnDsdru8wPUKyo8t8aKzu7AvyPi2-jviycvc,822
+ray/llm/_internal/batch/stages/http_request_stage.py,sha256=cXJFE6DRYCskamxGTHrut8YFiPUndp39lCYZ9KQlJOY,7103
+ray/llm/_internal/batch/stages/prepare_image_stage.py,sha256=zzUdh1aAC1uPPfAnXPmpk6adPqLta2nDmUOGU-5GBjU,13412
+ray/llm/_internal/batch/stages/serve_deployment_stage.py,sha256=00c5b7fZ0cf3G3PDfYPVm0KQfeOuKcHpKCK5UlYJEo4,5206
+ray/llm/_internal/batch/stages/sglang_engine_stage.py,sha256=EZX3DhqU9NH_qBU_XIXXqzAnnisoHfoSR3qi4OvGVNc,13870
+ray/llm/_internal/batch/stages/tokenize_stage.py,sha256=HVcP8xKAWSZAHyYSIK1clnH3fE1JtAG66rVKh43u0mM,4171
+ray/llm/_internal/batch/stages/vllm_engine_stage.py,sha256=6s_veT0rr8jUQ5u86ri1etADnTPGxZz4tqdZhYa53Oc,25968
+ray/llm/_internal/batch/utils.py,sha256=d18j-shp38m7X7dF350ntmpobVct44hW4lsIve5hf3o,2295
+ray/llm/_internal/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/common/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/common/__pycache__/base_pydantic.cpython-312.pyc,,
+ray/llm/_internal/common/__pycache__/constants.cpython-312.pyc,,
+ray/llm/_internal/common/__pycache__/dict_utils.cpython-312.pyc,,
+ray/llm/_internal/common/__pycache__/models.cpython-312.pyc,,
+ray/llm/_internal/common/base_pydantic.py,sha256=kmHLid-ia3MLf0g-Hd156d_V_6sELsSOlnyUYDm1djc,1162
+ray/llm/_internal/common/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/common/callbacks/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/common/callbacks/__pycache__/base.cpython-312.pyc,,
+ray/llm/_internal/common/callbacks/__pycache__/cloud_downloader.cpython-312.pyc,,
+ray/llm/_internal/common/callbacks/base.py,sha256=29MQxgeuY363i3C5HUwYYo6HlbcJGzIAiGK0WIlxtGA,5094
+ray/llm/_internal/common/callbacks/cloud_downloader.py,sha256=i3maHRCXZDg7jsrTy3c9cKqGVPHFeB_Ju5w7QXJUl3E,3105
+ray/llm/_internal/common/constants.py,sha256=q-V34_CrtVnD96jA2U1EPnEAExg5S6iOICbQRxxSe8k,391
+ray/llm/_internal/common/dict_utils.py,sha256=LMjB0CUtcowAM9EfkdhGjf9OYDUzvDwkVUCGA_ppSwU,1180
+ray/llm/_internal/common/models.py,sha256=6TPrnddXV-72Q2CKe2dpJjVFJhtow456H_aWXPdI6dk,1303
+ray/llm/_internal/common/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/common/observability/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/common/observability/__pycache__/logging_utils.cpython-312.pyc,,
+ray/llm/_internal/common/observability/__pycache__/telemetry_utils.cpython-312.pyc,,
+ray/llm/_internal/common/observability/logging/__init__.py,sha256=0Q7iViwmVoSAVfs0Ho9JFTne5As-PZTyq5Gr6O6BGVk,1271
+ray/llm/_internal/common/observability/logging/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/common/observability/logging/__pycache__/setup.cpython-312.pyc,,
+ray/llm/_internal/common/observability/logging/setup.py,sha256=mtFF6PDOcB9t_NuKam8f7NcxK1Dmf9-W2lvdsarewjM,815
+ray/llm/_internal/common/observability/logging_utils.py,sha256=SXfhzy_FkVKYbJeMGX-SjdVWv6ulHMuyyMumWiEMHIc,1746
+ray/llm/_internal/common/observability/telemetry_utils.py,sha256=6JEYpc0788SULFFOQ_PzyvKyR1HuupEXqYAbs5MzDEc,1156
+ray/llm/_internal/common/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/common/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/common/utils/__pycache__/cloud_utils.cpython-312.pyc,,
+ray/llm/_internal/common/utils/__pycache__/download_utils.cpython-312.pyc,,
+ray/llm/_internal/common/utils/__pycache__/import_utils.cpython-312.pyc,,
+ray/llm/_internal/common/utils/__pycache__/lora_utils.cpython-312.pyc,,
+ray/llm/_internal/common/utils/__pycache__/upload_utils.cpython-312.pyc,,
+ray/llm/_internal/common/utils/cloud_utils.py,sha256=GCXSQaKsxFLOUnlFxLjQU16K07Lq4XpwHuX61uc7Z6U,30599
+ray/llm/_internal/common/utils/download_utils.py,sha256=zM9zutIDaUSBgnkMiLpxXYbBUo6cVbwkDuIogydrcfA,11657
+ray/llm/_internal/common/utils/import_utils.py,sha256=i9QClr-9xoIpij4bBI1ZkjHEbFXXBkf1-DDID-L6NQ0,1219
+ray/llm/_internal/common/utils/lora_utils.py,sha256=sFpvR1CzInT2T4b1f5HZ6_xbwqKft1Fu5q9is7njO_Q,7406
+ray/llm/_internal/common/utils/upload_utils.py,sha256=iT5UzaYt0jaaHfGdxhd_9d5SHlYqVV5jGrT4AD_Gcsk,4513
+ray/llm/_internal/serve/__init__.py,sha256=AwPHQA5unCSlY7irtG3KT4RW0S4rkovQqDlXvxwIxFM,456
+ray/llm/_internal/serve/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/__pycache__/constants.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/config_generator/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/__pycache__/generator.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/__pycache__/inputs.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/__pycache__/start.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/base_configs/templates/base_serve_config.yaml,sha256=5pGx3azg40wWHRL5wh8qZVJB_ABDftYFtUla6w-RJes,137
+ray/llm/_internal/serve/config_generator/base_configs/templates/default_deployment_configs.yaml,sha256=XNdzSsuIO7lA1hn38Ox8AB4l_oMwDehCtjrER49IbZI,3186
+ray/llm/_internal/serve/config_generator/generator.py,sha256=fg7qEA8ZEf5puE-xWQbY64aMo7iFb33Bya9Uczw6JuM,4183
+ray/llm/_internal/serve/config_generator/inputs.py,sha256=3TlVFuJ51CmxpRy_X-VEiAmxAppfCwdlEMIox4w-4u0,7190
+ray/llm/_internal/serve/config_generator/start.py,sha256=uMuue6naMHaHNGo_B2kKPv-36aykRgDZUSnsOlr920M,7591
+ray/llm/_internal/serve/config_generator/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/config_generator/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/constants.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/files.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/gpu.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/input_converter.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/models.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/overrides.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/prompt.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/__pycache__/text_completion.cpython-312.pyc,,
+ray/llm/_internal/serve/config_generator/utils/constants.py,sha256=DRk8qYwWaiSKBM5265_UPCSKp98231jithTzSu707Zc,516
+ray/llm/_internal/serve/config_generator/utils/files.py,sha256=hHdhrBvxSUjLYh6XuGVt81AXAAsjr2f0tuBhGK6Fxfo,1353
+ray/llm/_internal/serve/config_generator/utils/gpu.py,sha256=lBi6dqIvx1dCI_-SMFmOcSw0vYfuAq3uQ99LFl5PeTY,1294
+ray/llm/_internal/serve/config_generator/utils/input_converter.py,sha256=Da0BHsP6dsg8SWbp8gO_7j0G1IX5__m508ZfHZuIYQc,879
+ray/llm/_internal/serve/config_generator/utils/models.py,sha256=v4HZah_HrgjvtVOFfe7Vj8fMPqu6UQ3HZD1vdlsHOog,1014
+ray/llm/_internal/serve/config_generator/utils/overrides.py,sha256=jWN9SUtXMRwNgQydZRaAEWtHt1m6WQhBC2AQwYVVD1w,3592
+ray/llm/_internal/serve/config_generator/utils/prompt.py,sha256=kaoWrAn6KzOAYN06tMNrEJU4HXWqNy9l1uTKwfgHs4E,465
+ray/llm/_internal/serve/config_generator/utils/text_completion.py,sha256=IbKRpSI8f5p9WAzqIGhFrTVF-ZXcfPu7VqU416THkUk,3626
+ray/llm/_internal/serve/constants.py,sha256=6ehZ37hABkXyg9zulSkZSbGryghqFKw8ZOSPrfi-BJg,3464
+ray/llm/_internal/serve/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/core/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/core/__pycache__/protocol.cpython-312.pyc,,
+ray/llm/_internal/serve/core/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/core/configs/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/core/configs/__pycache__/llm_config.cpython-312.pyc,,
+ray/llm/_internal/serve/core/configs/__pycache__/openai_api_models.cpython-312.pyc,,
+ray/llm/_internal/serve/core/configs/llm_config.py,sha256=WGEWyxrCdmeuUOmyr4cW8GeI94_D3abNIidjMJd3thM,19554
+ray/llm/_internal/serve/core/configs/openai_api_models.py,sha256=dC8kJ5BQfUWsjlIdgLF51xQ_ukPzKOppXV4PI23Dihs,7004
+ray/llm/_internal/serve/core/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/core/engine/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/core/engine/__pycache__/protocol.cpython-312.pyc,,
+ray/llm/_internal/serve/core/engine/protocol.py,sha256=bMuJunPAQGY42dyteSbTLBLrHy6p5_xbmXwoSeRv8Ks,6445
+ray/llm/_internal/serve/core/ingress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/core/ingress/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/core/ingress/__pycache__/builder.cpython-312.pyc,,
+ray/llm/_internal/serve/core/ingress/__pycache__/ingress.cpython-312.pyc,,
+ray/llm/_internal/serve/core/ingress/__pycache__/middleware.cpython-312.pyc,,
+ray/llm/_internal/serve/core/ingress/builder.py,sha256=V7w_aCm0AV_FMS6RKKjM9lNjrncxKGP-LtgWjIuYAiQ,5364
+ray/llm/_internal/serve/core/ingress/ingress.py,sha256=MaDGbeI484i3y7s1H5UpSbfNFa3OZXTpMkZFEtHVSo4,24443
+ray/llm/_internal/serve/core/ingress/middleware.py,sha256=8c9FWfjHdHNqCM-hZi2ZaeWYuFyXslNoyHuSD1Pa9BU,6362
+ray/llm/_internal/serve/core/protocol.py,sha256=MIIa4FFxBGTH4MOOFHJ0S7NK8BCBwwzNhndiTyGWmXk,2265
+ray/llm/_internal/serve/core/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/core/server/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/core/server/__pycache__/builder.cpython-312.pyc,,
+ray/llm/_internal/serve/core/server/__pycache__/llm_server.cpython-312.pyc,,
+ray/llm/_internal/serve/core/server/builder.py,sha256=Mers5DQPXPubyysnLV-p21dJJSvypR4EsRxHR0QzZCY,2657
+ray/llm/_internal/serve/core/server/llm_server.py,sha256=fBdmQulSDrDR37i6CHW7I92pr90ZipjKAISx9BhXWZE,19298
+ray/llm/_internal/serve/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/engines/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/engines/vllm/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_engine.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_models.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__init__.py,sha256=_1Os5mOVrkhW0HQdIMl-ViWOa6No_mD42-2091izsYU,208
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/base.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/factory.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/lmcache.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/multi_connector.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/nixl.cpython-312.pyc,,
+ray/llm/_internal/serve/engines/vllm/kv_transfer/base.py,sha256=Br15uT0EsUquxto4RTM4oQdY5Mka9uT6H1-Q-byZQGg,2914
+ray/llm/_internal/serve/engines/vllm/kv_transfer/factory.py,sha256=sk9C7ngxJPc0fhqmKVsYRi2gwbqjx_X-ECqnM3DU-Nw,4723
+ray/llm/_internal/serve/engines/vllm/kv_transfer/lmcache.py,sha256=-Ay4yDQcWXqWqIOdDASFcqio069QCEcwnOO6wFxxbO8,2020
+ray/llm/_internal/serve/engines/vllm/kv_transfer/multi_connector.py,sha256=RAQfkNywW1o8ZSmLD-usgO1QNbzjh_u4bJsy103n0IM,1824
+ray/llm/_internal/serve/engines/vllm/kv_transfer/nixl.py,sha256=Zuvxo0S0A71q9JF4Qs0TluAwWLHkVMaEeDnVhMefzJU,2540
+ray/llm/_internal/serve/engines/vllm/vllm_engine.py,sha256=7ttyOzO5yiI6K80rSFW-i-mL6chXwM9rKgZsUVSPLOU,21011
+ray/llm/_internal/serve/engines/vllm/vllm_models.py,sha256=u385LWA9SXTjfjIbcTOIr3zVfIJjnKPCHemq9wlaW4Q,12325
+ray/llm/_internal/serve/observability/__init__.py,sha256=Lfd_5T5dAf3Zh0xeRYyiuzA1ypQ89icxr55tbHRBFmU,597
+ray/llm/_internal/serve/observability/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/logging/__init__.py,sha256=ftp87fUcHpLwFFwp2IHD-JVyLqhrtMwoTocS0wiha2o,1334
+ray/llm/_internal/serve/observability/logging/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/logging/__pycache__/setup.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/logging/setup.py,sha256=4MZorCJzVm52I5tcXIsu3fylYIM-mDF-mVL_mjonK3k,897
+ray/llm/_internal/serve/observability/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/observability/metrics/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/__pycache__/event_loop_monitoring.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/__pycache__/fast_api_metrics.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/__pycache__/fastapi_utils.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/__pycache__/middleware.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/__pycache__/utils.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/metrics/event_loop_monitoring.py,sha256=z75K-iPmh6jImqbEyh5UVZSmVwN8SbbY5VvJzNBHEl0,1853
+ray/llm/_internal/serve/observability/metrics/fast_api_metrics.py,sha256=IllS8z2iDZJo56BWmZPVsEBVG8XVCgq65KSjGy_2vTw,4103
+ray/llm/_internal/serve/observability/metrics/fastapi_utils.py,sha256=zPmnLFhnbf_sBG6pnyI2bDCPyTfYiXg59njnGBH8QN4,773
+ray/llm/_internal/serve/observability/metrics/middleware.py,sha256=zJFfDPKyywSjGLHEHYpqKv6doUz6yyh3UhEWSlOZAbE,5169
+ray/llm/_internal/serve/observability/metrics/utils.py,sha256=V1RUJoMgZFhX8I1aLcT1kZyDNi0isbVHf8GlZ4zPVBE,3948
+ray/llm/_internal/serve/observability/usage_telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc,,
+ray/llm/_internal/serve/observability/usage_telemetry/usage.py,sha256=HdR2nvDiUl466xPfe1MTJCukrlNnKNsDPhFGm1qR2YQ,12664
+ray/llm/_internal/serve/routing_policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/routing_policies/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/routing_policies/prefix_aware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/routing_policies/prefix_aware/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/routing_policies/prefix_aware/__pycache__/prefix_aware_router.cpython-312.pyc,,
+ray/llm/_internal/serve/routing_policies/prefix_aware/__pycache__/prefix_tree.cpython-312.pyc,,
+ray/llm/_internal/serve/routing_policies/prefix_aware/prefix_aware_router.py,sha256=WcJy7aasUeWIQMzJUe2DSihu4ESxIcYMBHYz2yv5PTI,16827
+ray/llm/_internal/serve/routing_policies/prefix_aware/prefix_tree.py,sha256=Wwi8jdBZpPTvOpX6cmCP2yKuGa3c5fyqb9sFXEzFY80,26397
+ray/llm/_internal/serve/serving_patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/serving_patterns/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/data_parallel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/serving_patterns/data_parallel/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/data_parallel/__pycache__/builder.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/data_parallel/__pycache__/dp_rank_assigner.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/data_parallel/__pycache__/dp_server.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/data_parallel/builder.py,sha256=11hSji5S9fP5Jsz5bs4uMaFVJPFBl9y3oOucZc57JoM,4979
+ray/llm/_internal/serve/serving_patterns/data_parallel/dp_rank_assigner.py,sha256=RkYHVctZ1kobxBtETFV7W6ThGD7YXpJF5GqOrz2FTPA,4768
+ray/llm/_internal/serve/serving_patterns/data_parallel/dp_server.py,sha256=aUqXmtiwtRBDTk2SQI12Z7d1GcIaT7oCgOVOV8zcpPs,3803
+ray/llm/_internal/serve/serving_patterns/prefill_decode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/serving_patterns/prefill_decode/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/prefill_decode/__pycache__/builder.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/prefill_decode/__pycache__/pd_server.cpython-312.pyc,,
+ray/llm/_internal/serve/serving_patterns/prefill_decode/builder.py,sha256=pTYPM80TgRw0Lr25qEaEAnmoBKVl0scTha6sV_uKfaQ,5908
+ray/llm/_internal/serve/serving_patterns/prefill_decode/pd_server.py,sha256=L6OrAGj_kKK33MvjZ8EoqimtlLP7f6u5dby_SG0SLrc,6416
+ray/llm/_internal/serve/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/_internal/serve/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/__pycache__/batcher.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/__pycache__/lora_serve_utils.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/__pycache__/node_initialization_utils.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/__pycache__/registry.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/__pycache__/server_utils.cpython-312.pyc,,
+ray/llm/_internal/serve/utils/batcher.py,sha256=3jC6YyWY6_9j-f_RU0ADvAmss2GIvnlU39aBvjI_6rk,3662
+ray/llm/_internal/serve/utils/lora_serve_utils.py,sha256=nma6wbLVDfxPd8r8pKDHamvqbp-5OKyupEI3Yd5ETbs,8425
+ray/llm/_internal/serve/utils/node_initialization_utils.py,sha256=uP1wxikNT9rp_cubCJKPjJ9-aOcfr2WSIs_iQ8lrlXc,2485
+ray/llm/_internal/serve/utils/registry.py,sha256=rJUmUFhwNYjN3-3hcwMagR3k-EgDHh6rFqFJ9GNXlYQ,11148
+ray/llm/_internal/serve/utils/server_utils.py,sha256=gJLcor7Z2qYVNsb9G3nypQztp6b7PpZgktud5DQxqzE,4593
+ray/llm/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/llm/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/llm/utils/__pycache__/upload_model.cpython-312.pyc,,
+ray/llm/utils/upload_model.py,sha256=ivD9WDKZFvM70KoIO6CXekySpT1ZaYt0CyQ90AZ0Qxg,429
+ray/nightly-wheels.yaml,sha256=39nLCMEmHDM-lPcov1Ea3Hokor2LA76wttE6AlfSUQA,674
+ray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/remote_function.py,sha256=fYGTjks0u6PCmmMudZnrwjEKObq2PszM95sVwMCbdI8,24448
+ray/rllib/__init__.py,sha256=ATjt_l2VJxJp4AfDj5LgtsWrX6mk_GqgQqnAmLgLeWQ,1638
+ray/rllib/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/__init__.py,sha256=bvTdM5fxujli-X7GLEVX_S6_wtC3Z-khZu96u5ab5fU,959
+ray/rllib/algorithms/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/algorithm.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/algorithm_config.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/callbacks.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/mock.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/registry.cpython-312.pyc,,
+ray/rllib/algorithms/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/algorithms/algorithm.py,sha256=LcZZWF0VEEsN708jhnVdupapPTftsQRdDvU-OazOmyw,211499
+ray/rllib/algorithms/algorithm_config.py,sha256=5EjuwEzPb1ArovNVp5tB3L8aAdlo2ME1FEHh8ysY2vo,320090
+ray/rllib/algorithms/appo/__init__.py,sha256=cRAMgsk4tf2GdjrmkiZIFNMBzyvFrpaaYRG_fTlLpd0,343
+ray/rllib/algorithms/appo/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/appo.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/appo_learner.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/appo_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/appo_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/appo_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/default_appo_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/appo/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/algorithms/appo/appo.py,sha256=WbHUSvjW4cAizUY2fn2f5dhWbc91kZcwUUMH1QoxHwk,18156
+ray/rllib/algorithms/appo/appo_learner.py,sha256=HERC2jMwGMIwsNB3T-xpwBmGrgUf6LNJwZaMt3BoPM8,5920
+ray/rllib/algorithms/appo/appo_rl_module.py,sha256=LBeVORs6uprwTLWMDPNSJ_5tAevu4-fJ90zNZnNTjVw,382
+ray/rllib/algorithms/appo/appo_tf_policy.py,sha256=YEz05dtFJ6kLPkz9whmJCrU4TxPanj6v-zVJEvp-yPo,15323
+ray/rllib/algorithms/appo/appo_torch_policy.py,sha256=savsNQs1Ju6iCqFv-E3sUvsrtvpoKTK_yX3Wuv6qI3o,15697
+ray/rllib/algorithms/appo/default_appo_rl_module.py,sha256=p48WSK07YkUkaAfzVkwcQJ4W6SK50uMzHJm7FDx78c4,2253
+ray/rllib/algorithms/appo/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/appo/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/appo/torch/__pycache__/default_appo_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/appo/torch/appo_torch_learner.py,sha256=ULqM4fHo-FJ9_Hxd4hDV2rQw0JKYRUC6EXifxoqXuaw,9029
+ray/rllib/algorithms/appo/torch/appo_torch_rl_module.py,sha256=abuZ2e98LGJDEtfgnujy_ei9PN72mFpbr8Ep6w8h7YI,446
+ray/rllib/algorithms/appo/torch/default_appo_torch_rl_module.py,sha256=6WFlKnJ0dHVza2j09F7CHIAkpww7gZTIHVQokRkOXDs,334
+ray/rllib/algorithms/appo/utils.py,sha256=AhyloQ6jjATStPn-sMf7snEfhfLwygHssx3k07pKcPs,6206
+ray/rllib/algorithms/bc/__init__.py,sha256=LF3kh1ZhSwu0p8BNsj4hKfr-fRgqiqb4w3PMgnGz2KA,93
+ray/rllib/algorithms/bc/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/bc/__pycache__/bc.cpython-312.pyc,,
+ray/rllib/algorithms/bc/__pycache__/bc_catalog.cpython-312.pyc,,
+ray/rllib/algorithms/bc/bc.py,sha256=GJFKa17FaaDnwJs6U2ZJw_I8WQvT_UDlL029h9331kc,3929
+ray/rllib/algorithms/bc/bc_catalog.py,sha256=4fY4g5CZTkbfLP2a8-VAerht7ivvPWF-uXtK9v6firw,4478
+ray/rllib/algorithms/bc/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/bc/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/bc/torch/__pycache__/default_bc_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/bc/torch/default_bc_torch_rl_module.py,sha256=CwERN0owB4q7zZrBOC9kT4p_YX8dz7D6xaXXJh50Hrs,1712
+ray/rllib/algorithms/callbacks.py,sha256=sPg8YDYZL-E-Wg7egRFsQafM9chH7f9feMCSlSpDT1o,235
+ray/rllib/algorithms/cql/__init__.py,sha256=3PzQXLnbdxMQsmVrqdl-RXAVqkLTiOyqkt85ooIk2Zg,209
+ray/rllib/algorithms/cql/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/cql/__pycache__/cql.cpython-312.pyc,,
+ray/rllib/algorithms/cql/__pycache__/cql_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/cql/__pycache__/cql_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/cql/cql.py,sha256=HN20SUyD6VQ4Qecw490Lguie3qTdElgUg4cfK0s-IoM,14430
+ray/rllib/algorithms/cql/cql_tf_policy.py,sha256=Y7VdiREK9CkIxCtFZp0jjM_OaJlYzHgy4RX71y2O1U0,15740
+ray/rllib/algorithms/cql/cql_torch_policy.py,sha256=xtpzlr4fqEzXBiXp8NDPm6rvxGMuVFtfokwxYf_KLH8,14688
+ray/rllib/algorithms/cql/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/cql/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/cql/torch/__pycache__/cql_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/cql/torch/__pycache__/default_cql_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/cql/torch/cql_torch_learner.py,sha256=usbYmT-sZ7PMo0QrTNYkG31-kAPjVih4gx97-T22FC0,11774
+ray/rllib/algorithms/cql/torch/default_cql_torch_rl_module.py,sha256=NdvBhGBgvcOz2ppcIHxNp7uaf-W1duYcqO8KtM8T5H8,8421
+ray/rllib/algorithms/dqn/__init__.py,sha256=u7YA5eq0n3QsV_3-SUXbXHNkDCX2seBWA3vnvLlfiU8,272
+ray/rllib/algorithms/dqn/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/default_dqn_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/distributional_q_tf_model.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn_catalog.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn_learner.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn_torch_model.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/__pycache__/dqn_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/default_dqn_rl_module.py,sha256=G0VYxyDB-kXau85cgEoqejQkUiCHSCBwe0WHC8LKyRo,6736
+ray/rllib/algorithms/dqn/distributional_q_tf_model.py,sha256=x8HWfXoKkMBeG_F9CPY138Hnm_CynSKnjmpRV0IgJs0,8020
+ray/rllib/algorithms/dqn/dqn.py,sha256=ZV-nxI1per_c_juTNnWH4dBxLFoKI0fXvJVfRPzVvz4,37035
+ray/rllib/algorithms/dqn/dqn_catalog.py,sha256=UnWiw70_v202vzvF3ZiaOyGZH3VJnEvfzRwK-s3TGIg,7427
+ray/rllib/algorithms/dqn/dqn_learner.py,sha256=Css9_UoogpIAVZOr8JI97njIwKwkUWJEPqhPRky6vgQ,4654
+ray/rllib/algorithms/dqn/dqn_tf_policy.py,sha256=k6vRboksOU_7RR1pGS29b3aN2cF6UHClvHn3aPAH2Gg,17643
+ray/rllib/algorithms/dqn/dqn_torch_model.py,sha256=nVQwevU5rFJH_je-HxBee6dsQzGSpoKoAs6Np0jeRWs,6865
+ray/rllib/algorithms/dqn/dqn_torch_policy.py,sha256=3_Q8zxR74KgA2kn-ehJhsPrPEGA7s0seoHFcN4VfCWI,17271
+ray/rllib/algorithms/dqn/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/dqn/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/torch/__pycache__/default_dqn_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/torch/__pycache__/dqn_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/dqn/torch/default_dqn_torch_rl_module.py,sha256=VB7j-nxpBQrH0VS2LLfQyduCRafZBervryEvDmss4Y8,13850
+ray/rllib/algorithms/dqn/torch/dqn_torch_learner.py,sha256=XMYF0mGzeFVjZPJm3ER4-ATzjz-mfnJKGMixPpt6DvA,12435
+ray/rllib/algorithms/dreamerv3/__init__.py,sha256=k6k1cCGIdnzCUkAJ85FKlvfZXh1S90XrQy2pdgEfE1A,420
+ray/rllib/algorithms/dreamerv3/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/__pycache__/dreamerv3.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/__pycache__/dreamerv3_catalog.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/__pycache__/dreamerv3_learner.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/__pycache__/dreamerv3_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/dreamerv3.py,sha256=f-1IPmMPMz4E4PBXMY5GBbhFRbFF9N8Gudqp0FXVmqw,33531
+ray/rllib/algorithms/dreamerv3/dreamerv3_catalog.py,sha256=uH2IQgMi-Nhx4ZStOlRx-ebqQ486JZuevzpZq7unu3s,6576
+ray/rllib/algorithms/dreamerv3/dreamerv3_learner.py,sha256=nPld68nZgyGqtVvVzRtx_cCol_aMSZV5M9MEnTElbKE,1051
+ray/rllib/algorithms/dreamerv3/dreamerv3_rl_module.py,sha256=GaMoUonzQpsy5DIm6vhXjJfgScjHHrlZzikpebnThSc,2958
+ray/rllib/algorithms/dreamerv3/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/dreamerv3/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/__pycache__/dreamerv3_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/__pycache__/dreamerv3_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/dreamerv3_torch_learner.py,sha256=wP8peJktV1bV4rUNDXm8SkeU55qOsRWEQ-_EdQxXZRs,39210
+ray/rllib/algorithms/dreamerv3/torch/dreamerv3_torch_rl_module.py,sha256=ldtd_kDcpzvpG1Xc4aYVC6NIedVbYPuOi17SxwZESRA,2982
+ray/rllib/algorithms/dreamerv3/torch/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/dreamerv3/torch/models/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/__pycache__/actor_network.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/__pycache__/critic_network.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/__pycache__/dreamer_model.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/__pycache__/world_model.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/actor_network.py,sha256=mUR0y_zgxS8f_nfHWTnHpfirSyaxcqtgxEe11RtTWQ0,7435
+ray/rllib/algorithms/dreamerv3/torch/models/components/__init__.py,sha256=kA_74iODH1dVV0C_QpFJsvC9HpeIrxStC_gGtydVbMM,1121
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/cnn_atari.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/continue_predictor.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/conv_transpose_atari.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/dynamics_predictor.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/mlp.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/representation_layer.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/reward_predictor.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/reward_predictor_layer.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/sequence_model.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/__pycache__/vector_decoder.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/torch/models/components/cnn_atari.py,sha256=I1zib7rApl7l9ekLLZ27AEDQ_02-ojT8K88JOcSHf4g,2583
+ray/rllib/algorithms/dreamerv3/torch/models/components/continue_predictor.py,sha256=8TENyO7fbhKq7jxopyJwKgsV4j6AJRzPSTj-ti5d2DE,2383
+ray/rllib/algorithms/dreamerv3/torch/models/components/conv_transpose_atari.py,sha256=R-W08eKPgsVNE_dEC0kEYAAA9G4DMm5zdh2ib-5NUOE,3884
+ray/rllib/algorithms/dreamerv3/torch/models/components/dynamics_predictor.py,sha256=VQNyLd-qiLKHn6JwZsgX-74eQIG6Lx9NUM95R7MaTsQ,2918
+ray/rllib/algorithms/dreamerv3/torch/models/components/mlp.py,sha256=BIx8xduF2qcyuaG0JTDh3GdiF6Bq86RK8fXmPmyEnug,3337
+ray/rllib/algorithms/dreamerv3/torch/models/components/representation_layer.py,sha256=zJTpRUliGahUwsJVVG8Tg9WiG1QxUjf5SCO21Cqj7g0,5821
+ray/rllib/algorithms/dreamerv3/torch/models/components/reward_predictor.py,sha256=vxF-6bPKmmh755XQ0_VqxHngnmPuwcQ4sjalTKcBZEE,3559
+ray/rllib/algorithms/dreamerv3/torch/models/components/reward_predictor_layer.py,sha256=HFLyY6eSQssuFRzSfi79GLAR3spxcMjoxcBFITdU1xs,4482
+ray/rllib/algorithms/dreamerv3/torch/models/components/sequence_model.py,sha256=Sb5Mg_i-JWl8vG9mdfDoW-a7Vh-xamv-PDfN0kwn3wM,5203
+ray/rllib/algorithms/dreamerv3/torch/models/components/vector_decoder.py,sha256=FzTWj3Jhw6G3yCUJXbLKGNEUMKPAiREKLuCQ4Xl8pxQ,2245
+ray/rllib/algorithms/dreamerv3/torch/models/critic_network.py,sha256=wJboupiHNIgypx-cBPeW5UQcO2nquj9PbQJW0mRtQuQ,7376
+ray/rllib/algorithms/dreamerv3/torch/models/dreamer_model.py,sha256=GvC3iI-HKrRX2nj71bHpXpxOzZkm6CnNVKwIz9ow6XE,23272
+ray/rllib/algorithms/dreamerv3/torch/models/world_model.py,sha256=4kd_RrxpDH1Q2z4c9OTID8B7Tqwi-S5h6khVkHjUL0A,19349
+ray/rllib/algorithms/dreamerv3/utils/__init__.py,sha256=Xk4WbtFriNYnQDYpcl3M84NBuq0jribHjdo0lcV4cVU,3574
+ray/rllib/algorithms/dreamerv3/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/utils/__pycache__/add_is_firsts_to_batch.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/utils/__pycache__/debugging.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/utils/__pycache__/summaries.cpython-312.pyc,,
+ray/rllib/algorithms/dreamerv3/utils/add_is_firsts_to_batch.py,sha256=LTEJLsJPSQzcI_yrj_snZiskQzjBhkv5RYlhoX0ale4,1112
+ray/rllib/algorithms/dreamerv3/utils/debugging.py,sha256=HC95oVrsVSvR5IQg449ZApXy2CVT8nrjNGvHNXsbgrs,5970
+ray/rllib/algorithms/dreamerv3/utils/summaries.py,sha256=Y_v2OYZXuKAOHDAucBTpfghZ1zPfzdX04eqXzrh8HZc,14781
+ray/rllib/algorithms/impala/__init__.py,sha256=JEKyK70m7GHOMfR6oJnEhsY4vySY5odTj3PihQEtpWk,500
+ray/rllib/algorithms/impala/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/impala.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/impala_learner.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/impala_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/impala_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/vtrace_tf.cpython-312.pyc,,
+ray/rllib/algorithms/impala/__pycache__/vtrace_torch.cpython-312.pyc,,
+ray/rllib/algorithms/impala/impala.py,sha256=EpYmHGCcV-GXk2c16uvftyXZoOmUwcVJfNJkt9qK3K4,61773
+ray/rllib/algorithms/impala/impala_learner.py,sha256=L3Ijxf5cLQ8WRd3M_EyNZJexzeBVJWgVHH_unGqNRpM,21534
+ray/rllib/algorithms/impala/impala_tf_policy.py,sha256=gDs1VPtmhNr9JN_wFWUkEBcMHwsbTiy2DTMboTk_ZXo,17546
+ray/rllib/algorithms/impala/impala_torch_policy.py,sha256=LnrBKmDmhGo1b82FyhzKsvtcdDVp0l_StUNwVOLWFI4,16340
+ray/rllib/algorithms/impala/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/impala/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/impala/torch/__pycache__/impala_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/impala/torch/__pycache__/vtrace_torch_v2.cpython-312.pyc,,
+ray/rllib/algorithms/impala/torch/impala_torch_learner.py,sha256=7rpYI54O-PCatj64t3kQwuybOkEJPrLvtZYlqxLiQpo,6534
+ray/rllib/algorithms/impala/torch/vtrace_torch_v2.py,sha256=nKqRfOM18L_UKZ_3lBsiNyYc5APFkGWubTFRZ6p2sHY,7017
+ray/rllib/algorithms/impala/utils.py,sha256=D5-KOiw5o2e4yNO0u07jmgL1Df-9jnIONtNb9HEDqGs,3216
+ray/rllib/algorithms/impala/vtrace_tf.py,sha256=5Pf02sazw11lsQOx1Bw44qOlOnh7mKkT9qySO3u3NTw,16182
+ray/rllib/algorithms/impala/vtrace_torch.py,sha256=UhGszIEBNTFt1oFVoTfY7NtmqQQTIS5TecgDM9O5itQ,14496
+ray/rllib/algorithms/iql/__init__.py,sha256=SvJ1KKwUF3jJ5aOa9AoeQx6nTxqPRk2Vx4xFNfCCKTo,99
+ray/rllib/algorithms/iql/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/iql/__pycache__/default_iql_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/iql/__pycache__/iql.cpython-312.pyc,,
+ray/rllib/algorithms/iql/__pycache__/iql_learner.cpython-312.pyc,,
+ray/rllib/algorithms/iql/default_iql_rl_module.py,sha256=hjHC6sPJ8hzR_HkXc78Z6pe6AViFMkLos9rchnsW-dg,1579
+ray/rllib/algorithms/iql/iql.py,sha256=PZ-4E7gSwYa4sqdyEyz9_-OPIvNVV6zmIa0-J7FL8pk,8433
+ray/rllib/algorithms/iql/iql_learner.py,sha256=czimy9S3Y7tv5VS0XuXnU_YDwFfGRuvHiXDep1DGOjg,3194
+ray/rllib/algorithms/iql/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/iql/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/iql/torch/__pycache__/default_iql_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/iql/torch/__pycache__/iql_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/iql/torch/default_iql_torch_rl_module.py,sha256=F-0WjEwRu4_Y8PrEb3M-E8V4PFdjatcAPr5O8CBkyO0,3050
+ray/rllib/algorithms/iql/torch/iql_torch_learner.py,sha256=ex-_W-IwW2g95JEb_KX2RbpN4dblVO9so_xAQZkk7UE,9270
+ray/rllib/algorithms/marwil/__init__.py,sha256=KosRDwnLVcvYDUpxC0x6gzZyz-renwL6XNHFlIlK2hI,401
+ray/rllib/algorithms/marwil/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/__pycache__/marwil.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/__pycache__/marwil_learner.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/__pycache__/marwil_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/__pycache__/marwil_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/marwil.py,sha256=Bklb1vnACpbtR4it6n2hMlS4VPoBYyQKud-aDVB8NbE,20778
+ray/rllib/algorithms/marwil/marwil_learner.py,sha256=0C1MwOzJvKFctGF6nhm7b7yt9oZb342m_syLIQYE0a0,1806
+ray/rllib/algorithms/marwil/marwil_tf_policy.py,sha256=qNRJSbhOsrgP81RhvvVjh7EZKlCtWpNCnCmPACse2WQ,9211
+ray/rllib/algorithms/marwil/marwil_torch_policy.py,sha256=W7xJmMUyuSyBTgPDtLT_f1VgZjD-c88G2CVY8HtMlnU,5350
+ray/rllib/algorithms/marwil/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/marwil/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/torch/__pycache__/marwil_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/marwil/torch/marwil_torch_learner.py,sha256=hwqxIz05nXVqPTEF2i14Xt09U2REHYpScBOPgg8JRE8,5555
+ray/rllib/algorithms/mock.py,sha256=fXS0U2qd4Np0xvipMh5NA1gGJ88Sll5TN1vuUhvvCfc,4429
+ray/rllib/algorithms/ppo/__init__.py,sha256=o658U_1tQrYKqAXRxBLSF9rEOmO4larYJnyRGxfHGCo,327
+ray/rllib/algorithms/ppo/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/default_ppo_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo_catalog.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo_learner.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/__pycache__/ppo_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/default_ppo_rl_module.py,sha256=ysMtQUgrJ5FdNVcZsGPMjkBoP5ZzQrPRbda4bnAYQCE,2543
+ray/rllib/algorithms/ppo/ppo.py,sha256=Z6WRmUgNkKDL1LHIuQ8kQuquY7RQsybHWUVekRqfQOY,23590
+ray/rllib/algorithms/ppo/ppo_catalog.py,sha256=CPRPU7-3ikzEBe2DdUmfiUYBGH2n9DwkOhsRlax1fJE,7752
+ray/rllib/algorithms/ppo/ppo_learner.py,sha256=CYBiZSdIIYkwSLmGsKACjaskVYrTn8Q7g4ZPaIp7kMg,5779
+ray/rllib/algorithms/ppo/ppo_rl_module.py,sha256=cnn9B8z6K78Euik1OaautvyAofuV32VL2kLefgnaYdg,372
+ray/rllib/algorithms/ppo/ppo_tf_policy.py,sha256=KvvGAB2r1eMoMCWcgqAS6pb8lJoKPT18ZAlXhiDgt_g,8775
+ray/rllib/algorithms/ppo/ppo_torch_policy.py,sha256=nRKzIgsUXAQiP4V64JuyXyDcEF5yESSs5EHocCv_JzA,7784
+ray/rllib/algorithms/ppo/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/ppo/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/torch/__pycache__/default_ppo_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/torch/__pycache__/ppo_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/torch/__pycache__/ppo_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/ppo/torch/default_ppo_torch_rl_module.py,sha256=fb0t_Ve96y15Tgqpe4DcKWEk_qZjxRb9TY5BxrW27XY,3126
+ray/rllib/algorithms/ppo/torch/ppo_torch_learner.py,sha256=7djuhSUn3yLExLjYErnV5qQ2ncANSBwXR5iBwd14tXY,6745
+ray/rllib/algorithms/ppo/torch/ppo_torch_rl_module.py,sha256=cKtR3RbbZ0XxizovEB_TMQwAiYRfJoLCfgDSNuGiHMk,436
+ray/rllib/algorithms/registry.py,sha256=WIjVkVM8gREj5U40Y3I0TS17DlTXgcpnarxOK3px5TQ,4995
+ray/rllib/algorithms/sac/__init__.py,sha256=sMmd9l6UmU6OCWgIFS6RHUdHfs_x4zHPDAL-4H5glBo,272
+ray/rllib/algorithms/sac/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/default_sac_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_catalog.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_learner.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_tf_model.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_tf_policy.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_torch_model.cpython-312.pyc,,
+ray/rllib/algorithms/sac/__pycache__/sac_torch_policy.cpython-312.pyc,,
+ray/rllib/algorithms/sac/default_sac_rl_module.py,sha256=kahKJStYgj1zMM-nLWAfiGdNPexDbEyem3sXzPKvOKs,5618
+ray/rllib/algorithms/sac/sac.py,sha256=JXmWSt0MQLHbLPFXc3juOlaYX8h8whdaIx9-iezCt4Q,27799
+ray/rllib/algorithms/sac/sac_catalog.py,sha256=gmszXCJ2fPov6XSWWqnNHIdYquKNrE0Q580r5VRda7Q,12378
+ray/rllib/algorithms/sac/sac_learner.py,sha256=JSgc8SuizgZPW1CZuBBd2f5NWSbJMqqri18op5s6TPY,3983
+ray/rllib/algorithms/sac/sac_tf_model.py,sha256=xXElCRcYXKnFs1E-cS5XAu1d2baOqM8uhUVqj1PC5EM,12619
+ray/rllib/algorithms/sac/sac_tf_policy.py,sha256=wwrH0qq2-m_mmYJ5BHqS2bZW-AQ12WOjiuab71-3t8U,30599
+ray/rllib/algorithms/sac/sac_torch_model.py,sha256=LVqjLpPjvTKl2JGVHqDHyfbKa6LN7uDLJY4WpHYfFTw,13077
+ray/rllib/algorithms/sac/sac_torch_policy.py,sha256=ARZ2mb_N4I40ttNGwMl2z1-815RALLXgHObhDP_xmWo,20098
+ray/rllib/algorithms/sac/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/algorithms/sac/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/algorithms/sac/torch/__pycache__/default_sac_torch_rl_module.cpython-312.pyc,,
+ray/rllib/algorithms/sac/torch/__pycache__/sac_torch_learner.cpython-312.pyc,,
+ray/rllib/algorithms/sac/torch/default_sac_torch_rl_module.py,sha256=BE39px-zR2pQlnrSe96hK3p4opI9yC2jvo--U9JysI4,11509
+ray/rllib/algorithms/sac/torch/sac_torch_learner.py,sha256=AdOKMJheweuCEfmW3xpTj3cps0YKtO4keUAK-6kWpv8,17621
+ray/rllib/algorithms/utils.py,sha256=TqLbmWtXnU08Qq_sCeX5QdRpzT8Kd2q22B5IbR2895w,8731
+ray/rllib/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/callbacks/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/callbacks/__pycache__/callbacks.cpython-312.pyc,,
+ray/rllib/callbacks/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/callbacks/callbacks.py,sha256=gAJMt3Gm38z-PlqhR_0JhDEm1c5HdUukQdY_6C5UT4s,30337
+ray/rllib/callbacks/utils.py,sha256=uJN53McKoPdK0-ihJrI_w0-OXwccb7ACKy-8IEgU4UI,5839
+ray/rllib/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/connectors/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/__pycache__/connector.cpython-312.pyc,,
+ray/rllib/connectors/__pycache__/connector_pipeline_v2.cpython-312.pyc,,
+ray/rllib/connectors/__pycache__/connector_v2.cpython-312.pyc,,
+ray/rllib/connectors/__pycache__/registry.cpython-312.pyc,,
+ray/rllib/connectors/__pycache__/util.cpython-312.pyc,,
+ray/rllib/connectors/action/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/connectors/action/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/action/__pycache__/clip.cpython-312.pyc,,
+ray/rllib/connectors/action/__pycache__/immutable.cpython-312.pyc,,
+ray/rllib/connectors/action/__pycache__/lambdas.cpython-312.pyc,,
+ray/rllib/connectors/action/__pycache__/normalize.cpython-312.pyc,,
+ray/rllib/connectors/action/__pycache__/pipeline.cpython-312.pyc,,
+ray/rllib/connectors/action/clip.py,sha256=reg4vQsOqX6yCUYUROUzkG1k9naw-Sk5qMm492OKt6g,1339
+ray/rllib/connectors/action/immutable.py,sha256=riy8vj5tVllyv_7U0VMXDk8mQh8UDUaKzR6_YGXIEUg,1240
+ray/rllib/connectors/action/lambdas.py,sha256=InePeRbpv0q0n56qcd1ddu5zBoCto4HiapZ10um9O8A,2317
+ray/rllib/connectors/action/normalize.py,sha256=QLyBWQaDcLFp-AW7JjuhhGpCpzTNW6_xabPr-jetmww,1385
+ray/rllib/connectors/action/pipeline.py,sha256=qizQHXFVWlEWjAq34UUc_PHxyTmD1oZEqXjcSbzjNfU,2086
+ray/rllib/connectors/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/connectors/agent/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/clip_reward.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/env_sampling.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/lambdas.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/mean_std_filter.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/obs_preproc.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/pipeline.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/state_buffer.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/synced_filter.cpython-312.pyc,,
+ray/rllib/connectors/agent/__pycache__/view_requirement.cpython-312.pyc,,
+ray/rllib/connectors/agent/clip_reward.py,sha256=B4Pjvu3gKSe4lbtAHz6VOx02VhXiEqDdYxeAfZNbTyA,1723
+ray/rllib/connectors/agent/env_sampling.py,sha256=tlvwEMpcXdOtFfKpu0jXfNEbDG7hC9dIgQ8EuM369C0,964
+ray/rllib/connectors/agent/lambdas.py,sha256=yNqe7RA5ing6lAoD7h3WC1w9wKS_tOLxJOycG2Wqeuc,2607
+ray/rllib/connectors/agent/mean_std_filter.py,sha256=Ut9i1R-CrQOM38xj_oArKFMDOY-yeVSv_GuZCx8Ki0Y,6909
+ray/rllib/connectors/agent/obs_preproc.py,sha256=J5QM4BRqCV8yXTz7orY4dff6LLHc1OjYJRQxlF2lhl4,2520
+ray/rllib/connectors/agent/pipeline.py,sha256=yHMg_pAFf0vN4cdvHg5n2mBT7X0GhnPuuLKXD8bMZM0,2380
+ray/rllib/connectors/agent/state_buffer.py,sha256=JY8CocA4ntC3eZepGo1sVov9zW9Kat2xw_6-CPWq7MQ,4361
+ray/rllib/connectors/agent/synced_filter.py,sha256=QKnxWF5dGQ8e0YyLXc-oYuzhcbCVfO6iHAvOz1VdMJg,1938
+ray/rllib/connectors/agent/view_requirement.py,sha256=WEV6J9P8JLl99iaCVLVDW4rNybGVwZT7HO5fsGYozos,5288
+ray/rllib/connectors/common/__init__.py,sha256=0HY939bzZG3sBg-bwTLkdzF4C1nxJXrpZ1i88y5Qs2o,883
+ray/rllib/connectors/common/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/add_observations_from_episodes_to_batch.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/add_states_from_episodes_to_batch.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/add_time_dim_to_batch_and_zero_pad.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/agent_to_module_mapping.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/batch_individual_items.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/flatten_observations.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/frame_stacking.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/module_to_agent_unmapping.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/numpy_to_tensor.cpython-312.pyc,,
+ray/rllib/connectors/common/__pycache__/tensor_to_numpy.cpython-312.pyc,,
+ray/rllib/connectors/common/add_observations_from_episodes_to_batch.py,sha256=EyN4p7P025Kp4f5D3rp_Pm8Kpa0fy-hcSu-ZgDABGJA,7027
+ray/rllib/connectors/common/add_states_from_episodes_to_batch.py,sha256=buXP9-wC69G_0MMtxdaCqwlV9cEKLM2TGam8RCNWNFE,14630
+ray/rllib/connectors/common/add_time_dim_to_batch_and_zero_pad.py,sha256=f3ah_VoGmZMcOJFADZCsmXpJOWzaSfG-gaQpMPbtlUg,12363
+ray/rllib/connectors/common/agent_to_module_mapping.py,sha256=yw6G2UBglf7JxDmwVjtTO9MrK_CXaFr7PnsTqwbhBCg,12048
+ray/rllib/connectors/common/batch_individual_items.py,sha256=fm2pRVb0wO75dDjEYTSuTnsoMFKTj9y6dX3PcUKnYdc,8197
+ray/rllib/connectors/common/flatten_observations.py,sha256=F4tWaWho6f85u2Pt7wUyQydguk6G4gVH61RY5mUAC40,17378
+ray/rllib/connectors/common/frame_stacking.py,sha256=f8mIejjrRYndmfiM_DrpLAoUju5O2pKZ0VHmb8BL1S0,5971
+ray/rllib/connectors/common/module_to_agent_unmapping.py,sha256=A7cWhhH9gyN4YENugL_6t1fgQ_ZBq_fWFjtromJPx18,1636
+ray/rllib/connectors/common/numpy_to_tensor.py,sha256=UVAW_NX5CuHyFb63bpMpeP2TgmXfvSjXFvadIbdS7QQ,4632
+ray/rllib/connectors/common/tensor_to_numpy.py,sha256=3q0OGwkDp0bujobTa-4heEYmJTWZOWYf9VyfTJ4kuVc,826
+ray/rllib/connectors/connector.py,sha256=BgI-Q723i2_jsBP3YZ0CtDqMFMVnFXRb_wwOMb16qTk,16043
+ray/rllib/connectors/connector_pipeline_v2.py,sha256=t0We67aS050McCT4XfMwlG3jPPX30e-BQ-55tEsLXlM,15598
+ray/rllib/connectors/connector_v2.py,sha256=8SCFomroZ3i2XbYw42PXyXNtl2-oXrZZOMeWUXp2uHk,45905
+ray/rllib/connectors/env_to_module/__init__.py,sha256=zzF7_WPqAMN6VzaG1x_a18hUc3SfQNZQ1Gd1ZHDje0c,1425
+ray/rllib/connectors/env_to_module/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/env_to_module_pipeline.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/flatten_observations.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/frame_stacking.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/mean_std_filter.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/observation_preprocessor.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/prev_actions_prev_rewards.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/__pycache__/write_observations_to_episodes.cpython-312.pyc,,
+ray/rllib/connectors/env_to_module/env_to_module_pipeline.py,sha256=5c-JWYyRej1N-NThkPgrxU-eq0cVUzsyO3eWi0McY54,1844
+ray/rllib/connectors/env_to_module/flatten_observations.py,sha256=rZ88HvttrIw3Tlk3NyeNNPuq-35e6-qbnvDYSX9-YWo,192
+ray/rllib/connectors/env_to_module/frame_stacking.py,sha256=alN05Z5HNNJIIWicEtWstDVJHgZaw5nXQOyJOm9QI0Q,179
+ray/rllib/connectors/env_to_module/mean_std_filter.py,sha256=UyfmadpoT4vSxx-klkAhrwJVbRxpHI6qUmwr-K3U23Q,10297
+ray/rllib/connectors/env_to_module/observation_preprocessor.py,sha256=W5iWB5mXUqElFJv8tVD9tnOhGShXmMdBP5vWPdIW6w8,7548
+ray/rllib/connectors/env_to_module/prev_actions_prev_rewards.py,sha256=k3BPsH8mjAxvI4G-QlS5xgMaLcnHa0tqHzi_1X9k9Xo,6813
+ray/rllib/connectors/env_to_module/write_observations_to_episodes.py,sha256=5ul-8hjJXWJndOqTscoRAzCcsE_ideYFhh3m3uy1PAw,5510
+ray/rllib/connectors/learner/__init__.py,sha256=MYzhZztMafxYE6kUDXGS4C8za6IecNn2b5MemnKlT8g,1861
+ray/rllib/connectors/learner/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/add_columns_from_episodes_to_train_batch.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/add_infos_from_episodes_to_train_batch.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/add_next_observations_from_episodes_to_train_batch.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/add_one_ts_to_episodes_and_truncate.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/compute_returns_to_go.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/frame_stacking.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/general_advantage_estimation.cpython-312.pyc,,
+ray/rllib/connectors/learner/__pycache__/learner_connector_pipeline.cpython-312.pyc,,
+ray/rllib/connectors/learner/add_columns_from_episodes_to_train_batch.py,sha256=7jA3scEICDsKGCBLsU6OPIUcHHks5Z6sWoMDjFzpnWk,6617
+ray/rllib/connectors/learner/add_infos_from_episodes_to_train_batch.py,sha256=Gebvv7mbYMlEvel0svUkFsdUDDdD2fZR1-gSDAFjAeg,1850
+ray/rllib/connectors/learner/add_next_observations_from_episodes_to_train_batch.py,sha256=xEP5cQA1F3ryqxACfMz-cPxL0vpv2Bw7fKDXbL7NhNw,3880
+ray/rllib/connectors/learner/add_one_ts_to_episodes_and_truncate.py,sha256=nJ4u4dmILqgSgN29QuiaPWJgEYdPR01vPmP--5K779Y,6858
+ray/rllib/connectors/learner/compute_returns_to_go.py,sha256=y6Vhid0ypllVNzAFTy6CBaIOfVGpapVKRF_3zwalqJc,2337
+ray/rllib/connectors/learner/frame_stacking.py,sha256=jcBQbWYlOc_v3mbML2HvraM4CPdjUfPM3usLiDLewes,174
+ray/rllib/connectors/learner/general_advantage_estimation.py,sha256=P8IBRgEK6L2cY3A5XSGL4KvL1lKzNZRrk7--qUYAyCY,8785
+ray/rllib/connectors/learner/learner_connector_pipeline.py,sha256=ikS_aaHgRF0bs8xwoYtExe-v-mv35Jk01pUK8QzC4dA,2055
+ray/rllib/connectors/module_to_env/__init__.py,sha256=6Ypf4CYTqOl-jCUJuFgVTbi4oNGao1NzVkhYrdxqIWM,1022
+ray/rllib/connectors/module_to_env/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/get_actions.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/listify_data_for_vector_env.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/module_to_env_pipeline.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/normalize_and_clip_actions.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/remove_single_ts_time_rank_from_batch.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/__pycache__/unbatch_to_individual_items.cpython-312.pyc,,
+ray/rllib/connectors/module_to_env/get_actions.py,sha256=OWntRZ2w-cHAsrrA9KdXJ5xqllTCZUvLKssJ4cn9jZ8,3493
+ray/rllib/connectors/module_to_env/listify_data_for_vector_env.py,sha256=WdmZYbE7LnobeUTvqgpTdH8nmB7-HsIB_u0rsbEzwGQ,3427
+ray/rllib/connectors/module_to_env/module_to_env_pipeline.py,sha256=nWuf4DqMm9aqG5yokvcieC5m4zMMyTL6pKi-d--pooM,207
+ray/rllib/connectors/module_to_env/normalize_and_clip_actions.py,sha256=zYlvwtaJBdwVLyP7fst1FqLd8IjESCtmAgi7g2eYSng,5983
+ray/rllib/connectors/module_to_env/remove_single_ts_time_rank_from_batch.py,sha256=a5zDgP2HNNG_YALlOuCNmr9epnQFifPlm7OmgPqxZgo,2295
+ray/rllib/connectors/module_to_env/unbatch_to_individual_items.py,sha256=4mI2jID2eTs8x-GiL-x0DImlFKZxqXbLp-RNJ8Zb0yE,4829
+ray/rllib/connectors/registry.py,sha256=ENyUmgtqEQ8pbDRuM06LqGZZ6UAdez75TTvguK35xLg,1330
+ray/rllib/connectors/util.py,sha256=ngvSGhghrgjIWunCRe2IgH4tXgki3fMd44x28-hCTFU,6196
+ray/rllib/core/__init__.py,sha256=CycW41iYQHOEmWYtsk5fz3Qf0v-Yt5f_WFl5E4_-ep8,1014
+ray/rllib/core/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/__pycache__/columns.cpython-312.pyc,,
+ray/rllib/core/columns.py,sha256=8NatwbdDuECeq4WtTkUJnHrxFRE8ZjerRQSRrTPG8FU,2560
+ray/rllib/core/distribution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/distribution/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/distribution/__pycache__/distribution.cpython-312.pyc,,
+ray/rllib/core/distribution/distribution.py,sha256=lXWFQJ-MscG_FQlf1WjvBSPVhEGS8UErLoV7osiXGa4,8472
+ray/rllib/core/distribution/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/distribution/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/distribution/torch/__pycache__/torch_distribution.cpython-312.pyc,,
+ray/rllib/core/distribution/torch/torch_distribution.py,sha256=p-6WYHmXyDeRGMJzk0utAcqWkKd6JKqB0_w-Tb72T34,25074
+ray/rllib/core/learner/__init__.py,sha256=ejJlUBwLQVS_N-sdSQE8QL30Dp7v_lwYvfXEOfsGPjw,163
+ray/rllib/core/learner/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/differentiable_learner.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/differentiable_learner_config.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/learner.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/learner_group.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/training_data.cpython-312.pyc,,
+ray/rllib/core/learner/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/core/learner/differentiable_learner.py,sha256=uF4c188VLVRRrBc5481I-k0ucSHXVPaMksdkYaLp0Hk,33046
+ray/rllib/core/learner/differentiable_learner_config.py,sha256=EDcfu2FP9qnv4tcZ6lw6FYvAfY81PvBG9fu4NyWwLao,6196
+ray/rllib/core/learner/learner.py,sha256=_Pp3YNH_ebhVf2GwtJO755F_0rs6Q--4igsp3qLMw0I,73635
+ray/rllib/core/learner/learner_group.py,sha256=m2FCce4KaC6pvXNaDD8c67t9fqlCFxd_hkBVNT11gIE,33314
+ray/rllib/core/learner/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/learner/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/learner/torch/__pycache__/torch_differentiable_learner.cpython-312.pyc,,
+ray/rllib/core/learner/torch/__pycache__/torch_learner.cpython-312.pyc,,
+ray/rllib/core/learner/torch/__pycache__/torch_meta_learner.cpython-312.pyc,,
+ray/rllib/core/learner/torch/torch_differentiable_learner.py,sha256=K73D-BvLiTLSoMfRZCOiN0zhRk6mpzrMzpGUJvt96i0,16838
+ray/rllib/core/learner/torch/torch_learner.py,sha256=GCxBwYHjI9Ow8HT8paq9omeEGptFVxpbJS7Vh1LhsjA,28175
+ray/rllib/core/learner/torch/torch_meta_learner.py,sha256=-ki2My3pEM7xDuWHbv5swSShWMm-w3cQHDU8mowX6v4,19597
+ray/rllib/core/learner/training_data.py,sha256=Vd0Qh8Jze6lOK5bTcTD4u9qCQ4b4nLCWV83UhtCZdmU,5666
+ray/rllib/core/learner/utils.py,sha256=3bMHLA19b34GXLhaK2ylrZqk5b5UD4Ikbg8qucOdlUY,1925
+ray/rllib/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/models/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/models/__pycache__/base.cpython-312.pyc,,
+ray/rllib/core/models/__pycache__/catalog.cpython-312.pyc,,
+ray/rllib/core/models/__pycache__/configs.cpython-312.pyc,,
+ray/rllib/core/models/base.py,sha256=Joum7y4lkLTruKhHMoH7ohs75CWPicR7ESfmCzL_Hmw,15770
+ray/rllib/core/models/catalog.py,sha256=3ezSQgmGoJmJk2EVs4hJG9eo7AIC3QeyHK6NxDqH9LU,27260
+ray/rllib/core/models/configs.py,sha256=5VPZZ1cnvvymJ9Rn8RtGDf4wpg5MCJQXl2o6um8NSuA,44201
+ray/rllib/core/models/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/models/specs/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/models/specs/__pycache__/specs_base.cpython-312.pyc,,
+ray/rllib/core/models/specs/__pycache__/specs_dict.cpython-312.pyc,,
+ray/rllib/core/models/specs/specs_base.py,sha256=Mi8o1EQSA2qVLiwgr5iOpSZMIcTj0P_t3WO3OzfArpw,513
+ray/rllib/core/models/specs/specs_dict.py,sha256=-UmstaoPV8tXv8pDLwDqnaj1XwGW6SVxLPhwaWZ9g3w,192
+ray/rllib/core/models/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/models/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/models/torch/__pycache__/base.cpython-312.pyc,,
+ray/rllib/core/models/torch/__pycache__/encoder.cpython-312.pyc,,
+ray/rllib/core/models/torch/__pycache__/heads.cpython-312.pyc,,
+ray/rllib/core/models/torch/__pycache__/primitives.cpython-312.pyc,,
+ray/rllib/core/models/torch/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/core/models/torch/base.py,sha256=weQnNJnJSyXcEBysl2Wwrv8MaJJVLPxluj403bWxn2U,3076
+ray/rllib/core/models/torch/encoder.py,sha256=rvh_ojKeAVu7M6yn1rcZcWJRDa8qOhNfTyf6cdvBlaU,10095
+ray/rllib/core/models/torch/heads.py,sha256=NnlQ4eY5zgWpIbKYmz3MKXGNzAeC2Wzyx2c8h_DvyNQ,8905
+ray/rllib/core/models/torch/primitives.py,sha256=E-IeH4NDbAqzjSmAE15Rfq4mSIWbhbblMx7nXBF60is,23303
+ray/rllib/core/models/torch/utils.py,sha256=DgVUlmSoFImdyFcIHTeYNLIoRXbWrDEJyxjCCIF2Bio,2945
+ray/rllib/core/rl_module/__init__.py,sha256=nzYrjbTZa8LszAGTX3Wm3cavEvbH7I4kOfQg9r5dVMs,1622
+ray/rllib/core/rl_module/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/rl_module/__pycache__/default_model_config.cpython-312.pyc,,
+ray/rllib/core/rl_module/__pycache__/multi_rl_module.cpython-312.pyc,,
+ray/rllib/core/rl_module/__pycache__/rl_module.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__init__.py,sha256=E1q3XJj4BxUuCpDuGUBuLHjmLc87WpkJMoCC04C9xk0,603
+ray/rllib/core/rl_module/apis/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__pycache__/inference_only_api.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__pycache__/q_net_api.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__pycache__/self_supervised_loss_api.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__pycache__/target_network_api.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/__pycache__/value_function_api.cpython-312.pyc,,
+ray/rllib/core/rl_module/apis/inference_only_api.py,sha256=Qs0wI2675Ee9_TLUCOFXZqEQG_ddksIYsrJ6iqwaYF4,2610
+ray/rllib/core/rl_module/apis/q_net_api.py,sha256=-oSCciHQFLl5XTU0b4tQnZ7QuGw7jaf7KoFBcTVL7Nc,2045
+ray/rllib/core/rl_module/apis/self_supervised_loss_api.py,sha256=ypnFcqHYCd7fVx0jAmR-vfyoCY45z_t-LHqsTVV5feY,2321
+ray/rllib/core/rl_module/apis/target_network_api.py,sha256=uNheo4LC-Gx2fIR-DzctvxQbARGUrk7EvMPpisDGb98,2075
+ray/rllib/core/rl_module/apis/value_function_api.py,sha256=uEPU8bY3LpCs_YTM9KypthGi14Ab354cxrHJiBLEUSE,1286
+ray/rllib/core/rl_module/default_model_config.py,sha256=NbB8DKxNaq0l8oO1dLOWY22N0te1rS0dVvSA57p1c9A,11231
+ray/rllib/core/rl_module/multi_rl_module.py,sha256=QkZnJQZNKpWdiepYz8l-1IMMwtB2Yp9nU5FjsTVSfdM,32691
+ray/rllib/core/rl_module/rl_module.py,sha256=7rr4Jr1sWTs9NJhtgYaKBDf286C76hahpSAYB-jdRiI,33864
+ray/rllib/core/rl_module/torch/__init__.py,sha256=F_S_bYNHu1gUzXdAZ-C8Yjy0uf57CawBuSWT30VOrbs,72
+ray/rllib/core/rl_module/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/rl_module/torch/__pycache__/torch_compile_config.cpython-312.pyc,,
+ray/rllib/core/rl_module/torch/__pycache__/torch_rl_module.cpython-312.pyc,,
+ray/rllib/core/rl_module/torch/torch_compile_config.py,sha256=_QEt38oVKr9H3mNeY2HuPcMioGQIuwDCNKYu2A1iNPA,1601
+ray/rllib/core/rl_module/torch/torch_rl_module.py,sha256=aqxQpEdfXkUKtCl1TL-12BvPhneQVxY_50Xutxavurk,12126
+ray/rllib/core/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/testing/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/testing/__pycache__/bc_algorithm.cpython-312.pyc,,
+ray/rllib/core/testing/__pycache__/testing_learner.cpython-312.pyc,,
+ray/rllib/core/testing/bc_algorithm.py,sha256=0B8iOQA6h1-bidMS9OAYmvFkZ0noKyvZE4fVMi_HAr0,1437
+ray/rllib/core/testing/testing_learner.py,sha256=bqufAp-PdGJGOynMLWDoVf2B1kU3FqtL7M0psmlRGrg,2464
+ray/rllib/core/testing/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/core/testing/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/core/testing/torch/__pycache__/bc_learner.cpython-312.pyc,,
+ray/rllib/core/testing/torch/__pycache__/bc_module.cpython-312.pyc,,
+ray/rllib/core/testing/torch/bc_learner.py,sha256=uoD0aDwiUA69JMWO1oH3KO81K1GKBkEqji0VBzJTnvM,1158
+ray/rllib/core/testing/torch/bc_module.py,sha256=NX8mx4g1FBQQ0rFsgsbi7VXOzDfpMuX0OVCu4-B0JqU,5092
+ray/rllib/env/__init__.py,sha256=G-VtTYSTc4mem4K2UkTIPouG_FTxmUIPnyAVvUl2xCE,1190
+ray/rllib/env/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/env/__pycache__/base_env.cpython-312.pyc,,
+ray/rllib/env/__pycache__/env_context.cpython-312.pyc,,
+ray/rllib/env/__pycache__/env_errors.cpython-312.pyc,,
+ray/rllib/env/__pycache__/env_runner.cpython-312.pyc,,
+ray/rllib/env/__pycache__/env_runner_group.cpython-312.pyc,,
+ray/rllib/env/__pycache__/external_env.cpython-312.pyc,,
+ray/rllib/env/__pycache__/external_multi_agent_env.cpython-312.pyc,,
+ray/rllib/env/__pycache__/multi_agent_env.cpython-312.pyc,,
+ray/rllib/env/__pycache__/multi_agent_env_runner.cpython-312.pyc,,
+ray/rllib/env/__pycache__/multi_agent_episode.cpython-312.pyc,,
+ray/rllib/env/__pycache__/policy_client.cpython-312.pyc,,
+ray/rllib/env/__pycache__/remote_base_env.cpython-312.pyc,,
+ray/rllib/env/__pycache__/single_agent_env_runner.cpython-312.pyc,,
+ray/rllib/env/__pycache__/single_agent_episode.cpython-312.pyc,,
+ray/rllib/env/__pycache__/tcp_client_inference_env_runner.cpython-312.pyc,,
+ray/rllib/env/__pycache__/vector_env.cpython-312.pyc,,
+ray/rllib/env/base_env.py,sha256=7rMnXOo5yTxa0s1qost83p7XSLw73xbvf3Dy7G9RKGQ,15999
+ray/rllib/env/env_context.py,sha256=tQ41rRw9foahTDEtlbT4z6O9ydKupuIRxyoysnEwB48,5195
+ray/rllib/env/env_errors.py,sha256=SNHx_ht8G9iouGyPvHmGk6sGY25SZnYc7NJ0AtbszLA,810
+ray/rllib/env/env_runner.py,sha256=2T5MccMUaWf869IAj0uMI5UKRtxRgQL4x3THsC8p7mI,12065
+ray/rllib/env/env_runner_group.py,sha256=mMs0DDJEWKr54UAeKMcgWooHovur1jqybqzpIUMrG4M,55240
+ray/rllib/env/external/__init__.py,sha256=hpeSihAJU7O0ywP65gEi9NYtGmqesXmn5jBujb-WPxU,189
+ray/rllib/env/external/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/env/external/__pycache__/env_runner_server_for_external_inference.cpython-312.pyc,,
+ray/rllib/env/external/__pycache__/rllink.cpython-312.pyc,,
+ray/rllib/env/external/env_runner_server_for_external_inference.py,sha256=8p50rth-31oeiSAm2eJ40NSvb_JtmALVRH_lFBRhlv0,14138
+ray/rllib/env/external/rllink.py,sha256=TxiOGI6oM9fwvPqmKv04IBu8kQQaYnt8sDWx2YQi3BE,3514
+ray/rllib/env/external_env.py,sha256=qq8Kt-vIPKaQ10ke9exkqr5eaWz6c66J69Fg9MOOL5E,17118
+ray/rllib/env/external_multi_agent_env.py,sha256=6UxU6zYgD9r3unVzkp3aLsdLa9OCyw46s-i4MTC3mFM,5487
+ray/rllib/env/multi_agent_env.py,sha256=g3R5fB0l8dpgNCrsPe0Mi7qt3u6tm_tRyfyp7m592L0,30904
+ray/rllib/env/multi_agent_env_runner.py,sha256=EDBP341ZN43Gmsb1tgSrt0Yra8pnsj0fLduDB1f25ZI,45001
+ray/rllib/env/multi_agent_episode.py,sha256=dT-RVZNjKFrx1DvbpYFeiwXAPSpw24fDABtca0grAg8,135039
+ray/rllib/env/policy_client.py,sha256=Gj3QokHMo4q9dHtQSwcL-SPVtzBRdwAR2U6RxyHoX7g,10722
+ray/rllib/env/remote_base_env.py,sha256=XCRrSc-OCoFN2BRZjwO6PGVZ9dYUhnfIs0UmplUpm1w,19249
+ray/rllib/env/single_agent_env_runner.py,sha256=xD7ieTBjcjTSnCnNM-yZF-PGEUTTDKxwom1Y6UcmPuw,37311
+ray/rllib/env/single_agent_episode.py,sha256=_-36nmx8VBnGp3Z7F3-H2XkIIeQTQPg5nEpyTvibU-g,86900
+ray/rllib/env/tcp_client_inference_env_runner.py,sha256=Oa_KTIiyk5d5SZfdHDjudbs7LCxbtLRZQh6YbwGhY08,202
+ray/rllib/env/utils/__init__.py,sha256=dxUIiYZhKoOtvUd_VGaraLlDK8T5tOsGcWBiigY8Ixw,3452
+ray/rllib/env/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/env/utils/__pycache__/external_env_protocol.cpython-312.pyc,,
+ray/rllib/env/utils/__pycache__/infinite_lookback_buffer.cpython-312.pyc,,
+ray/rllib/env/utils/external_env_protocol.py,sha256=74ELTKX8s736Szte99cthyzL05CtCy99t1G3vk8GNQ8,248
+ray/rllib/env/utils/infinite_lookback_buffer.py,sha256=GBR1FDYtUnXie9dmmVAX0UT_52WX8tROCmvZkXbbKEc,28798
+ray/rllib/env/vector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/env/vector/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/env/vector/__pycache__/registration.cpython-312.pyc,,
+ray/rllib/env/vector/__pycache__/sync_vector_multi_agent_env.cpython-312.pyc,,
+ray/rllib/env/vector/__pycache__/vector_multi_agent_env.cpython-312.pyc,,
+ray/rllib/env/vector/registration.py,sha256=h10vKANqte3ZpO8bl9QMtXqJ3UtmvboFpgNGaGBJLsk,3130
+ray/rllib/env/vector/sync_vector_multi_agent_env.py,sha256=cOq2WMJ2u8ju3XTGQ4FjWUChrC4uHgPMR3cJi3TWgAU,8329
+ray/rllib/env/vector/vector_multi_agent_env.py,sha256=CEO_sTt4f7rXoDycsnT5n9ql0dSbgVsOidfX7jIBmpc,2910
+ray/rllib/env/vector_env.py,sha256=78bemt8rnmYg7FQbrKgvq7E37PzvnRZiUm5vpSchuoE,20231
+ray/rllib/env/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/env/wrappers/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/atari_wrappers.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/dm_control_wrapper.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/dm_env_wrapper.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/group_agents_wrapper.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/multi_agent_env_compatibility.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/open_spiel.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/pettingzoo_env.cpython-312.pyc,,
+ray/rllib/env/wrappers/__pycache__/unity3d_env.cpython-312.pyc,,
+ray/rllib/env/wrappers/atari_wrappers.py,sha256=weOM-tmhsPIdRj5ou2b3H9_EmLxVn5OG-4Ffp92RBho,14215
+ray/rllib/env/wrappers/dm_control_wrapper.py,sha256=pG3f1JIB3osp8WJ37gYg8EJ5S8OGPjugnDR8sLtIfxo,8025
+ray/rllib/env/wrappers/dm_env_wrapper.py,sha256=gNOp_LbC8LLetIVOlYkkxssKYoIxhs5mtPFX0T5ubss,2792
+ray/rllib/env/wrappers/group_agents_wrapper.py,sha256=jtcPFpnHvmLa7AQdUq2x3L7nIbxNQaODl5tgRC_IiRI,5905
+ray/rllib/env/wrappers/multi_agent_env_compatibility.py,sha256=QbE2eYjEMW3ilX-TGA853Lxplu5sIRrYCcaZxCsJPXw,2574
+ray/rllib/env/wrappers/open_spiel.py,sha256=A_IHeSgo8s54j2wR_qrIp-ECv4teVqygRcFmUE9w-EI,4645
+ray/rllib/env/wrappers/pettingzoo_env.py,sha256=44p-MLpOMA17TffXee_Fubsx2Y0FBk0OcD5RdJKieuE,6645
+ray/rllib/env/wrappers/unity3d_env.py,sha256=c3mRF1p75cknZObVUkCq4iwkASjpPkjrQxl9cfF-FeY,12475
+ray/rllib/evaluation/__init__.py,sha256=-kNFm8XxIj28AVeKThXQZzTQ8fIXUkITq5Q8xIXtcjA,634
+ray/rllib/evaluation/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/env_runner_v2.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/episode_v2.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/metrics.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/observation_function.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/postprocessing.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/rollout_worker.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/sample_batch_builder.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/sampler.cpython-312.pyc,,
+ray/rllib/evaluation/__pycache__/worker_set.cpython-312.pyc,,
+ray/rllib/evaluation/collectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/evaluation/collectors/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/evaluation/collectors/__pycache__/agent_collector.cpython-312.pyc,,
+ray/rllib/evaluation/collectors/__pycache__/sample_collector.cpython-312.pyc,,
+ray/rllib/evaluation/collectors/__pycache__/simple_list_collector.cpython-312.pyc,,
+ray/rllib/evaluation/collectors/agent_collector.py,sha256=7MB4-8aRaWtndNZtFtPSf3aX8scKB8tg7408kWsEB_0,30961
+ray/rllib/evaluation/collectors/sample_collector.py,sha256=AUO0APgrL1Tgik2xSv-HHoDCizJ-iTY3eXgov5sSE3c,12275
+ray/rllib/evaluation/collectors/simple_list_collector.py,sha256=F5KmCE33jAORZvTCdiGFUHck4GocMdGfGozcLilHY_8,28704
+ray/rllib/evaluation/env_runner_v2.py,sha256=tPlWViNuo-TfOUQNwyChVTGBww2ocE6jrCEt6ovCroA,51737
+ray/rllib/evaluation/episode_v2.py,sha256=-OiPspi_Pw-6B5JQp8SmajaZDwrpeRvItmiqrlMRxWU,14949
+ray/rllib/evaluation/metrics.py,sha256=J4J_7FtGNvlQEV-c_wjb-6GNfcobq9c8lwkG3sFSV-A,9160
+ray/rllib/evaluation/observation_function.py,sha256=i4ekjaVFsGpTvDLlgmrPhz7Qgp89vHlzUwr4gvojX50,3182
+ray/rllib/evaluation/postprocessing.py,sha256=LlXqo-ExksaR6FrG0MOi8puy6jTpQmDXf90Ukol_k5k,12131
+ray/rllib/evaluation/rollout_worker.py,sha256=EX0e_uf07Z6gXgQoG-ACvDtYq8dsC3PUPPABfmHWfw0,80405
+ray/rllib/evaluation/sample_batch_builder.py,sha256=SX3EMwCMOsho5QM72E-TzWDeB38YgpZmJHDZTwwK-OA,10039
+ray/rllib/evaluation/sampler.py,sha256=3M-FwgRguI09bSFpjKvbRccY0l6YreSgLvmfR1eNEdc,9718
+ray/rllib/evaluation/worker_set.py,sha256=5CXIP_HJR_R1thYSWNBgP_G4PfTGyVpTNMXSOmAd1cM,239
+ray/rllib/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/__pycache__/centralized_critic.cpython-312.pyc,,
+ray/rllib/examples/__pycache__/compute_adapted_gae_on_postprocess_trajectory.cpython-312.pyc,,
+ray/rllib/examples/__pycache__/quadx_waypoints.cpython-312.pyc,,
+ray/rllib/examples/__pycache__/replay_buffer_api.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/_old_api_stack/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/__pycache__/attention_net_supervised.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/__pycache__/parametric_actions_cartpole_embeddings_learnt_by_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/attention_net_supervised.py,sha256=9fSZRjDflHFtBinKRbeZZD3t_ux_q-yDLpEqx9Z8zjs,2380
+ray/rllib/examples/_old_api_stack/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/_old_api_stack/models/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/action_mask_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_dist.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/autoregressive_action_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/centralized_critic_models.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/custom_loss_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/fast_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_encoder.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/mobilenet_v2_with_lstm_models.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/neural_computer.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/parametric_actions_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/shared_weights_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/__pycache__/simple_rpg_model.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/models/action_mask_model.py,sha256=zxjWk-VI7yMeB1SPSFevJoxGj8Oy6E3AK9YTJMkrsuE,4292
+ray/rllib/examples/_old_api_stack/models/autoregressive_action_dist.py,sha256=E9PQmUCzS5WDwIDShr_jF8HvMf51j4gUx3DCRro1x54,4994
+ray/rllib/examples/_old_api_stack/models/autoregressive_action_model.py,sha256=1UvpXHaw9N3ZNioNtwHx7f5crKjZL39IRgrB9muYjQ8,5697
+ray/rllib/examples/_old_api_stack/models/centralized_critic_models.py,sha256=nNzuY1l3_VYhJlSeJQbK5Pq6rNL8Ya5s5Z1DiVSKoKg,6916
+ray/rllib/examples/_old_api_stack/models/custom_loss_model.py,sha256=EU2xUr15XoRocFBAzqy_tAmQM1HXUdUF45_SHw-6TkI,5362
+ray/rllib/examples/_old_api_stack/models/fast_model.py,sha256=MPHPdJCawlICl-sL4Ux59BNjMNyzLuMib3KAvCzFzSI,2840
+ray/rllib/examples/_old_api_stack/models/mobilenet_v2_encoder.py,sha256=IBRFeN04mh4BUujFL6r_rHD6PtZfDllkSv-J6CmHOe4,1659
+ray/rllib/examples/_old_api_stack/models/mobilenet_v2_with_lstm_models.py,sha256=XD8NYd1UktRwDGWBgPqOgoAmzGqP8LJ-OGQoTU_zyqs,5790
+ray/rllib/examples/_old_api_stack/models/neural_computer.py,sha256=WxvhRtPFpu8IHvdsjbfMPaX11c6XFsxINxiqPvZYzGc,8596
+ray/rllib/examples/_old_api_stack/models/parametric_actions_model.py,sha256=DvU4aYxHJexBF9DHXxKLEFGw_oM6sXqvMhUV0CTgH3A,7332
+ray/rllib/examples/_old_api_stack/models/shared_weights_model.py,sha256=xH4R44E1T-rL2sJ6bpAfMwXuEcnDSQu2yD0UatDWO5k,6969
+ray/rllib/examples/_old_api_stack/models/simple_rpg_model.py,sha256=X_dewCnA_RzTi0TjlLwEe6k243MdndjgvRGwwFNuo3M,2632
+ray/rllib/examples/_old_api_stack/parametric_actions_cartpole.py,sha256=gq3NXEkKEVYjTVZQLNMuHEXu7uNtPBAADww2ttvUznk,3817
+ray/rllib/examples/_old_api_stack/parametric_actions_cartpole_embeddings_learnt_by_model.py,sha256=dHqdNw5VPWfVm52G8vAhcO-wRq3TkJDY6KycPNcC-SE,3530
+ray/rllib/examples/_old_api_stack/policy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/_old_api_stack/policy/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/policy/__pycache__/cliff_walking_wall_policy.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/policy/__pycache__/random_policy.cpython-312.pyc,,
+ray/rllib/examples/_old_api_stack/policy/cliff_walking_wall_policy.py,sha256=et8-PQDcjX1iGrM-yQeCOKujFMZXdz0apJh6tskxcag,4181
+ray/rllib/examples/_old_api_stack/policy/random_policy.py,sha256=sI5pNR7ODv1qOs-Nsx4YUFUtIRjMueuMqIopnIh1NZc,3214
+ray/rllib/examples/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/actions/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/actions/__pycache__/autoregressive_actions.cpython-312.pyc,,
+ray/rllib/examples/actions/__pycache__/custom_action_distribution.cpython-312.pyc,,
+ray/rllib/examples/actions/__pycache__/nested_action_spaces.cpython-312.pyc,,
+ray/rllib/examples/actions/autoregressive_actions.py,sha256=O8gVVJjh5UNsNX-p3GdOou7KmiAQQTMLvBvbfIRT90M,4196
+ray/rllib/examples/actions/custom_action_distribution.py,sha256=7M4EJuIYpEt65oZi2jG5rOQbKut6DeMiq5xjVJFVQtk,4631
+ray/rllib/examples/actions/nested_action_spaces.py,sha256=JAmVbZz0H0_tmKki9PgMqkGuZnZ5y3xC1B_jCthbWP4,2696
+ray/rllib/examples/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/algorithms/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/algorithms/__pycache__/appo_custom_algorithm_w_shared_data_actor.cpython-312.pyc,,
+ray/rllib/examples/algorithms/__pycache__/maml_lr_supervised_learning.cpython-312.pyc,,
+ray/rllib/examples/algorithms/__pycache__/vpg_custom_algorithm.cpython-312.pyc,,
+ray/rllib/examples/algorithms/appo_custom_algorithm_w_shared_data_actor.py,sha256=Y__8Ygl4nMySl7GRLz6pWSAAscjZbGlOXld9DXrxW3k,7729
+ray/rllib/examples/algorithms/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/algorithms/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/__pycache__/appo_w_shared_data_actor.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/__pycache__/maml_lr_differentiable_learner.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/__pycache__/maml_lr_differentiable_rlm.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/__pycache__/maml_lr_meta_learner.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/__pycache__/vpg.cpython-312.pyc,,
+ray/rllib/examples/algorithms/classes/appo_w_shared_data_actor.py,sha256=6PEiDVoCZdrBFYZRr_IlpIZ9nvH_paxFomT1PFYyrEs,3076
+ray/rllib/examples/algorithms/classes/maml_lr_differentiable_learner.py,sha256=pux1WGky2moUlpdJKwCHVPybEe_KHLUNWMDjzFBwBLo,1100
+ray/rllib/examples/algorithms/classes/maml_lr_differentiable_rlm.py,sha256=-Ex5zdGX5MtAKa4AaKk2iF7OS6IR6DUlWgI1c_M7YiE,1566
+ray/rllib/examples/algorithms/classes/maml_lr_meta_learner.py,sha256=Gc8Rzr4U-d_wTNJ6EKN--IsQjZz_j0bk5eNkR5ZQgQo,1357
+ray/rllib/examples/algorithms/classes/vpg.py,sha256=MNY4HgB7u1CXKgLYO0kZ5_xeSQ0EN9MRxSz1F6h1UGQ,6842
+ray/rllib/examples/algorithms/maml_lr_supervised_learning.py,sha256=2ZgMRNe5X-xxm506MXI01w3Z2B0atPVs0VSZZNWLMq8,16320
+ray/rllib/examples/algorithms/vpg_custom_algorithm.py,sha256=q2LPRqbbAS1hxKDUvrtxrFy4kBnCr0wm4M8KpjSD2IQ,5078
+ray/rllib/examples/centralized_critic.py,sha256=Mf-vA8Bo12QgQa2RJdGjNM_CmJQeslzFLQ-MHQdJ4GE,11236
+ray/rllib/examples/checkpoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/checkpoints/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/cartpole_dqn_export.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/change_config_during_training.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/checkpoint_by_custom_criteria.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/continue_training_from_checkpoint.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/onnx_torch_lstm.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/__pycache__/restore_1_of_n_agents_from_checkpoint.cpython-312.pyc,,
+ray/rllib/examples/checkpoints/cartpole_dqn_export.py,sha256=wFnCGffhCAXttUJl8YxpODTVuhrfO4802iIFc5GJ_Gw,2612
+ray/rllib/examples/checkpoints/change_config_during_training.py,sha256=_6Z88Q3w6uJH1kNaiC5w2qv_jw_VWo3lbeT0cMGc3R0,11867
+ray/rllib/examples/checkpoints/checkpoint_by_custom_criteria.py,sha256=fbq67nuF8UrypXFMFPYeN8zVK7e8X92j0m0P229hOtA,6381
+ray/rllib/examples/checkpoints/continue_training_from_checkpoint.py,sha256=iPPndR-0otj1d3fUDd3dnVloRyFXEgS4sD_SvfmvL5s,12512
+ray/rllib/examples/checkpoints/onnx_torch_lstm.py,sha256=wc5nQWQo7x7it9kTTO6zqTvpj4NJf5a7n-N_l_XZ9OA,3994
+ray/rllib/examples/checkpoints/restore_1_of_n_agents_from_checkpoint.py,sha256=4vMQIZ3wgPKMDwQTPYzAppUMK_E0AlW2lWGbipQaHXU,6147
+ray/rllib/examples/compute_adapted_gae_on_postprocess_trajectory.py,sha256=DId710piai4aqRNEwuJAIUJVNYOMkcY7QesZROV2XYo,5695
+ray/rllib/examples/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/connectors/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/count_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/euclidian_distance_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/flatten_observations_dict_space.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/frame_stacking.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/mean_std_filtering.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/multi_agent_observation_preprocessor.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/prev_actions_prev_rewards.cpython-312.pyc,,
+ray/rllib/examples/connectors/__pycache__/single_agent_observation_preprocessor.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/connectors/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/__pycache__/add_other_agents_row_index_to_xy_pos.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/__pycache__/count_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/__pycache__/euclidian_distance_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/__pycache__/protobuf_cartpole_observation_decoder.cpython-312.pyc,,
+ray/rllib/examples/connectors/classes/add_other_agents_row_index_to_xy_pos.py,sha256=VDIucsfy3jdgZypS-v_rDZsFzhbvVRrId6DeJIJzYHo,4829
+ray/rllib/examples/connectors/classes/count_based_curiosity.py,sha256=NUZByloofJqEBF-vvK7dpAWkNVh6ohuR5f6YzH_yR_8,3622
+ray/rllib/examples/connectors/classes/euclidian_distance_based_curiosity.py,sha256=6DCmXP4Uq2eAh3jYQCXR-Gxf2Zl_Ak6KytwWh8ytGEo,4959
+ray/rllib/examples/connectors/classes/protobuf_cartpole_observation_decoder.py,sha256=tbB8gKbWcMKNIbl7aULubi3F4cfya_qfcotDfQgGwq4,2930
+ray/rllib/examples/connectors/count_based_curiosity.py,sha256=8OgGt6d7QegkHNueG9XtyP290KQcrmXxplj_ju-TMok,424
+ray/rllib/examples/connectors/euclidian_distance_based_curiosity.py,sha256=Eur41PMN8HgpWnSOx7wiQKoWPwNKbaCFz7QJ6o1kVH8,437
+ray/rllib/examples/connectors/flatten_observations_dict_space.py,sha256=qYkHIEl5J_R0fWmKDh7pvEhaUyn9d16bFE1y9LXO410,6202
+ray/rllib/examples/connectors/frame_stacking.py,sha256=i2nvgDvU2G0VaxhdC2F1gYc0i2j6cMaiAcpVBVdMw7k,9494
+ray/rllib/examples/connectors/mean_std_filtering.py,sha256=GaKS9-AceUcWeqLGGI0wSJNW1GD0Q8FUd9SFvmRt94g,8179
+ray/rllib/examples/connectors/multi_agent_observation_preprocessor.py,sha256=djcRYMAXRbCB_OctM57gNvUtBwTcFwKHLjLOcRePbV8,6076
+ray/rllib/examples/connectors/prev_actions_prev_rewards.py,sha256=WzQp_jyKytG_QHN34GRIc7ZO_G7oFE2JiwB-rZxKxNM,7182
+ray/rllib/examples/connectors/single_agent_observation_preprocessor.py,sha256=1kr0_-kBOIfIAxflYlzKl_2N1ZHvoEQmRSEXCC0Z6dc,6480
+ray/rllib/examples/curiosity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/curiosity/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/curiosity/__pycache__/count_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/curiosity/__pycache__/euclidian_distance_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/curiosity/__pycache__/intrinsic_curiosity_model_based_curiosity.cpython-312.pyc,,
+ray/rllib/examples/curiosity/count_based_curiosity.py,sha256=1LqDMpjstYAxAaRBhK9EAs_BfpFpQ40eNfDXIU61r_Y,5505
+ray/rllib/examples/curiosity/euclidian_distance_based_curiosity.py,sha256=8YI_HmEx1Mlu11NTpBIxbWoOxxmP-j7B3F90rGCU2Mc,5278
+ray/rllib/examples/curiosity/intrinsic_curiosity_model_based_curiosity.py,sha256=TROfAg0Ty1F0mJA290KmtYQvQxqWsvX-8TtssZs5p3M,11160
+ray/rllib/examples/curriculum/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/curriculum/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/curriculum/__pycache__/curriculum_learning.cpython-312.pyc,,
+ray/rllib/examples/curriculum/__pycache__/pong_curriculum_learning.cpython-312.pyc,,
+ray/rllib/examples/curriculum/curriculum_learning.py,sha256=F8hDSorp5y6zx1OCdGQsVt2AnRjZfO3vijIHB3pgBZw,9246
+ray/rllib/examples/curriculum/pong_curriculum_learning.py,sha256=39KVVT87dcpyBhYg9gd8JtayoqhVrVuM_ipAUhy4hyY,10629
+ray/rllib/examples/debugging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/debugging/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/debugging/__pycache__/deterministic_sampling_and_training.cpython-312.pyc,,
+ray/rllib/examples/debugging/deterministic_sampling_and_training.py,sha256=5ffzEbz_xQ0JUlmGJwD5qWMEThhECSbtfCZjiSEpXqM,5743
+ray/rllib/examples/envs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/envs/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/agents_act_in_sequence.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/agents_act_simultaneously.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/async_gym_env_vectorization.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/custom_env_render_method.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/custom_gym_env.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/env_connecting_to_rllib_w_tcp_client.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/env_rendering_and_recording.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/env_w_protobuf_observations.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/greyscale_env.cpython-312.pyc,,
+ray/rllib/examples/envs/__pycache__/unity3d_env_local.cpython-312.pyc,,
+ray/rllib/examples/envs/agents_act_in_sequence.py,sha256=qurqZlxZUhCeqe7xbHpDmwBTMltH0jVieQXFhr2noWg,3385
+ray/rllib/examples/envs/agents_act_simultaneously.py,sha256=Ty0tihYuWxTpcheORbXL837pzFI31XEyY9SNqm9quaY,4262
+ray/rllib/examples/envs/async_gym_env_vectorization.py,sha256=nPv_JWL3DGAvFE7hcwthmLv5jqf1oMPDA9atSXNuLNg,5170
+ray/rllib/examples/envs/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/envs/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/action_mask_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cartpole_crashing.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cartpole_sparse_rewards.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cartpole_with_dict_observation_space.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cartpole_with_large_observation_space.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cartpole_with_protobuf_observation_space.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/cliff_walking_wall_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/correlated_actions_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/d4rl_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/debug_counter_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/deterministic_envs.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/dm_control_suite.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/env_using_remote_actor.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/env_with_subprocess.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/fast_image_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/gpu_requiring_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/look_and_push.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/memory_leaking_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/mock_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/nested_space_repeat_after_me_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/parametric_actions_cartpole.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/random_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/recommender_system_envs_with_recsim.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/repeat_after_me_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/repeat_initial_obs_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/simple_corridor.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/simple_rpg.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/six_room_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/stateless_cartpole.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/stateless_pendulum.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/transformed_action_space_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/__pycache__/windy_maze_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/action_mask_env.py,sha256=rZE2878ZeBs6XrOpAUGVlnvIp60nLyZCe2pmLgC9xVE,1490
+ray/rllib/examples/envs/classes/cartpole_crashing.py,sha256=7HJjHJgWZNDQgkdLE8gacmIMzLpuJ_rs4o3bN6PSkrg,6977
+ray/rllib/examples/envs/classes/cartpole_sparse_rewards.py,sha256=xmtI3owJVNFNtb1aHS_7FpQ_fqO-BnK-WQ2BPucLQYE,1597
+ray/rllib/examples/envs/classes/cartpole_with_dict_observation_space.py,sha256=KpoW8wJaEX8hcrIemlB2wAwGWhf4wsy7ESB4_brIE9k,2923
+ray/rllib/examples/envs/classes/cartpole_with_large_observation_space.py,sha256=4-5BTDLSJvKrevSOKm3Dvo3R4dpY8Vyvvfz8E0GWvDc,2483
+ray/rllib/examples/envs/classes/cartpole_with_protobuf_observation_space.py,sha256=bf8ahgwgTNyzJkrKTKJMIFa5RFctISQPhew9AH3gskc,2973
+ray/rllib/examples/envs/classes/cliff_walking_wall_env.py,sha256=BwNZqzH5DoMON2sDw22NbMSYeAc-fz6htPN5y7QatPI,2251
+ray/rllib/examples/envs/classes/correlated_actions_env.py,sha256=wgBYAcrGNJePJpbPhnp6hWmz5yn9M7IvAanjSfYk2rc,3236
+ray/rllib/examples/envs/classes/d4rl_env.py,sha256=6SZrXLhOwa_nY0aya4PP_c1agrVzCmUpRxxQ7zlEsxk,863
+ray/rllib/examples/envs/classes/debug_counter_env.py,sha256=0OVRScn2PsO1Ah6Bsj2_goQOq67faSP7abZTgLMDGHM,3091
+ray/rllib/examples/envs/classes/deterministic_envs.py,sha256=K9O5MbtVvZhw_3V2iNACPpBVRhDpvvpjiax3Z7EDrVo,296
+ray/rllib/examples/envs/classes/dm_control_suite.py,sha256=aBzx4GRrm84h5_UXmjb1r0OqdjTE8vDK3jAs43YzBWo,2888
+ray/rllib/examples/envs/classes/env_using_remote_actor.py,sha256=IZb3e57yQM-D2SmKDlyY0w28tjD0DXLD1l6qOBKmysc,2159
+ray/rllib/examples/envs/classes/env_with_subprocess.py,sha256=nix-R5-SFpy23E0nM8zfcn6pH49WCMsKUeVxnnbmKVo,1326
+ray/rllib/examples/envs/classes/fast_image_env.py,sha256=-Bt-OLU2v_hRiRNUdoGSy_QWkQTba8BJQFgWinNS-ec,574
+ray/rllib/examples/envs/classes/gpu_requiring_env.py,sha256=E-fEKBeMgCraCZ0L_oTR2pvleHuSThulFiJRZeKvB-w,1447
+ray/rllib/examples/envs/classes/look_and_push.py,sha256=QE3gFWKJ5nQs69nYTyaSOrB8o9TUL4VH4ItVz7IGnak,2121
+ray/rllib/examples/envs/classes/memory_leaking_env.py,sha256=vcPpsMDp7DJqBzaCaaokw9M3wFVCxuMRfRmgjKk6W50,915
+ray/rllib/examples/envs/classes/mock_env.py,sha256=hG9xMdxRRUvwffTQ7stN6C6ii4iVVhILJ6ZTeg1rq7k,7675
+ray/rllib/examples/envs/classes/multi_agent/__init__.py,sha256=2arIETeJ_Z8DFPEazmtgM810VRVIaGhe3cYcfgImdQk,1236
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/bandit_envs_discrete.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/bandit_envs_recommender_system.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/double_row_corridor_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/guess_the_number_game.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/pettingzoo_chess.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/pettingzoo_connect4.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/rock_paper_scissors.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/tic_tac_toe.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/__pycache__/two_step_game.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/bandit_envs_discrete.py,sha256=_tbzLtFQzCgNfQtyNQCE8k1-JAvwh7k8OCwrtjOrEi0,6413
+ray/rllib/examples/envs/classes/multi_agent/bandit_envs_recommender_system.py,sha256=AKCZK9bojawxFVAt3Ckd-xgYRx66nScHJ_VHWpRyGbg,8857
+ray/rllib/examples/envs/classes/multi_agent/double_row_corridor_env.py,sha256=rmIdrEkB-YlTp2J40V--6uzO0895FKAxyAPzs_mlHgE,4930
+ray/rllib/examples/envs/classes/multi_agent/footsies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/envs/classes/multi_agent/footsies/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/__pycache__/encoder.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/__pycache__/fixed_rlmodules.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/__pycache__/footsies_env.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/encoder.py,sha256=NZ31aS0JmQmyPYMh2CNRgAy-LK7uwdC2Jy83qyBt7ys,8564
+ray/rllib/examples/envs/classes/multi_agent/footsies/fixed_rlmodules.py,sha256=JafeRxlecMEWV_2Xv6RD87lUX15EbPzGLbpVSCh91E8,1675
+ray/rllib/examples/envs/classes/multi_agent/footsies/footsies_env.py,sha256=kCntk4GZNP9eIfv9QcLfUVH0Z9Y38z-W2wi_dpI0ev0,10329
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/__pycache__/constants.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/__pycache__/footsies_binary.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/__pycache__/footsies_game.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/constants.py,sha256=CwG4hAVlqemFlGA0YF6cz5WeIMMU6MoBb9e8UyNT21U,5220
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/footsies_binary.py,sha256=d10mne_vBkuOSGrO-_beNwtXPN4mWKT7Xq_W_H5YW6c,8071
+ray/rllib/examples/envs/classes/multi_agent/footsies/game/footsies_game.py,sha256=dU_f1wX23pnVQmNY8crRuMEQKGGFicVE2UgMNw4THxI,4780
+ray/rllib/examples/envs/classes/multi_agent/footsies/utils.py,sha256=PW__bqNMsw43VMOWvGYmgg33bjGbTV-eZaE4L93jl6A,12711
+ray/rllib/examples/envs/classes/multi_agent/guess_the_number_game.py,sha256=b_rmhpkHvkhiadtBT12vkKtwAlBd1KNsEpINwoZH6Bs,3536
+ray/rllib/examples/envs/classes/multi_agent/pettingzoo_chess.py,sha256=2jEqGp84huv72uruHMW7-_pUv6gVl6PUpcSTrE7o84E,6917
+ray/rllib/examples/envs/classes/multi_agent/pettingzoo_connect4.py,sha256=c_mOfPOLwqDwMz4mPbNjEmkXIuxNy42tPnBlIybTw1Y,6333
+ray/rllib/examples/envs/classes/multi_agent/rock_paper_scissors.py,sha256=maKcKnebqam01cvHqJ1nbWDICZw3EmnTWg8UUS1cuzk,4473
+ray/rllib/examples/envs/classes/multi_agent/tic_tac_toe.py,sha256=FbcvCE99N-1jQZMV2nQHCfOpXNwjxjYLFeLP6QKFtEQ,4977
+ray/rllib/examples/envs/classes/multi_agent/two_step_game.py,sha256=W_PO8CgOetNhMR-G2e7GvWlaTowZQSfGbvmg7DptyG4,4558
+ray/rllib/examples/envs/classes/nested_space_repeat_after_me_env.py,sha256=655ozyfhzwCH0I4CYvM5hmlqRay0wwvFEMzv9m0lWQc,1866
+ray/rllib/examples/envs/classes/parametric_actions_cartpole.py,sha256=SPt7neS5SjZzFHLNePINmYqkIIcnwM7bPgFXAU4QHMA,5484
+ray/rllib/examples/envs/classes/random_env.py,sha256=DBMzN_jOaGLpAgqwR7BXxRpGsFLJ0XuzISX5xlsJBZg,4630
+ray/rllib/examples/envs/classes/recommender_system_envs_with_recsim.py,sha256=luoRsLqqAO4aFyl5Q0qpB_JSKCebQQ5JS0_b0Oz4-3M,3386
+ray/rllib/examples/envs/classes/repeat_after_me_env.py,sha256=jc8rqSB42wpt3pquGLrbJ1DWvuDjY_-XHE4eQunmfAw,1591
+ray/rllib/examples/envs/classes/repeat_initial_obs_env.py,sha256=vLk2k_rRSW6PUVpCb40WtYCVMMvwph0dRu2CePsmGkk,905
+ray/rllib/examples/envs/classes/simple_corridor.py,sha256=Qhs5jE_9rark-fldueAnq90FSI3NOqpw3ajxocROKHk,1414
+ray/rllib/examples/envs/classes/simple_rpg.py,sha256=rdqxPtq8m-WecNeAkJPkv5b9pPXmHk8LBveiidySI_E,1564
+ray/rllib/examples/envs/classes/six_room_env.py,sha256=TnjvJTCspLjn0lqO3VBOZDLRXwTNHxtjHBpjPikQ6Qw,11253
+ray/rllib/examples/envs/classes/stateless_cartpole.py,sha256=RpGWjICUQHbiejxIrnTAsZN_GyKas3aAUnKr8OWfZTM,1332
+ray/rllib/examples/envs/classes/stateless_pendulum.py,sha256=yZPE4LtFRY5-Kb3_A8P0mQZ59j-KJbVFTCGjG9r3SQs,1279
+ray/rllib/examples/envs/classes/transformed_action_space_env.py,sha256=L0-21X4ZepivYn3s0Lp143JohzyuK20OgdlWkg3X8HY,2044
+ray/rllib/examples/envs/classes/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/envs/classes/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/utils/__pycache__/cartpole_observations_proto.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/utils/__pycache__/dummy_external_client.cpython-312.pyc,,
+ray/rllib/examples/envs/classes/utils/cartpole_observations_proto.py,sha256=AETSgAf9oAjtaCibpmMebm3P6CmeTuqftBFj7Bk1iYg,1213
+ray/rllib/examples/envs/classes/utils/dummy_external_client.py,sha256=y0JOgJpPxc9BbG7fWDH3v19Ww4qwxloKB3UcsrQbKbo,4081
+ray/rllib/examples/envs/classes/windy_maze_env.py,sha256=kiYjWo60mOc1Cw8N7D_617PoxdYY_OAmdHkI6FcGpdQ,5762
+ray/rllib/examples/envs/custom_env_render_method.py,sha256=9YoGhEV7FhUN0iP6m9nnYA21YziaJhWNlNT9GTgKR9M,7876
+ray/rllib/examples/envs/custom_gym_env.py,sha256=BoDgb4TE8MNE3hJsdrL1B4m-R64NsH8V-TwhC0m637g,5894
+ray/rllib/examples/envs/env_connecting_to_rllib_w_tcp_client.py,sha256=QVum1vPJ6rsb-az10LISHK_b1onKW2J9u4V4WSsFQWA,5358
+ray/rllib/examples/envs/env_rendering_and_recording.py,sha256=W18qR0bYA3vAiAAC8PIHjLuatDaLIUvDibu9_-z7-rY,12980
+ray/rllib/examples/envs/env_w_protobuf_observations.py,sha256=p4cymf56BR-8fn54IJUGGAJrhnpcACk-G8KnF_DNjZQ,3542
+ray/rllib/examples/envs/greyscale_env.py,sha256=XrMpD4Mxot0IR8s8KLuXydOmH93xolbUXW7sZPyxMjY,3831
+ray/rllib/examples/envs/unity3d_env_local.py,sha256=s4REX41URVkePZ4WhgPh73-95OIq0n6lUcBWKDgYFYQ,6636
+ray/rllib/examples/evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/evaluation/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/evaluation/__pycache__/custom_evaluation.cpython-312.pyc,,
+ray/rllib/examples/evaluation/__pycache__/evaluation_parallel_to_training.cpython-312.pyc,,
+ray/rllib/examples/evaluation/custom_evaluation.py,sha256=rVcSHtQkfC5EkpUW3i_QGJ9ZM9ti6cyOB3zmme69PKg,9843
+ray/rllib/examples/evaluation/evaluation_parallel_to_training.py,sha256=XmALgDQDr1WdlnKxlE0gH0yqcqRHPIGtaRdyl93UC9U,11661
+ray/rllib/examples/fault_tolerance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/fault_tolerance/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/fault_tolerance/__pycache__/crashing_and_stalling_env.cpython-312.pyc,,
+ray/rllib/examples/fault_tolerance/crashing_and_stalling_env.py,sha256=1XUA6ax1h0eD5RR1hDgvOQbIGTWsVEnzNMsmGsuGUDw,7678
+ray/rllib/examples/gpus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/gpus/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/gpus/__pycache__/float16_training_and_inference.cpython-312.pyc,,
+ray/rllib/examples/gpus/__pycache__/fractional_gpus_per_learner.cpython-312.pyc,,
+ray/rllib/examples/gpus/__pycache__/gpus_on_env_runners.cpython-312.pyc,,
+ray/rllib/examples/gpus/__pycache__/mixed_precision_training_float16_inference.cpython-312.pyc,,
+ray/rllib/examples/gpus/float16_training_and_inference.py,sha256=sEZPJFC7CkpIlEtkCWkS6qFwOd0H8qGie1f2mBLPueQ,10239
+ray/rllib/examples/gpus/fractional_gpus_per_learner.py,sha256=m4_YSpj-lWkJcHLOkfpumkP4TlRvo0B9pbYvmh_sV_Y,5253
+ray/rllib/examples/gpus/gpus_on_env_runners.py,sha256=66lhLK2wTWrKOxeBMPJ1bPXXbkV5nOwEE9L8oCrFrhI,3282
+ray/rllib/examples/gpus/mixed_precision_training_float16_inference.py,sha256=5nQcLqod2FJ0nagqlLbcvx6fVu5AVGm8B1zd8CteUlg,6733
+ray/rllib/examples/hierarchical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/hierarchical/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/hierarchical/__pycache__/hierarchical_training.cpython-312.pyc,,
+ray/rllib/examples/hierarchical/hierarchical_training.py,sha256=uCYc3o3ljiagFtbm-oTRpf7OnAeuNefF3989blJCKDQ,7296
+ray/rllib/examples/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/inference/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/inference/__pycache__/policy_inference_after_training.cpython-312.pyc,,
+ray/rllib/examples/inference/__pycache__/policy_inference_after_training_w_connector.cpython-312.pyc,,
+ray/rllib/examples/inference/__pycache__/policy_inference_after_training_with_attention.cpython-312.pyc,,
+ray/rllib/examples/inference/__pycache__/policy_inference_after_training_with_lstm.cpython-312.pyc,,
+ray/rllib/examples/inference/policy_inference_after_training.py,sha256=E1M_mWPNcWALE0WlhzmFRxWie5QvB30n3QmdaVJ5KvA,9357
+ray/rllib/examples/inference/policy_inference_after_training_w_connector.py,sha256=Z5SFCGW4vfQ9Gz_C2p_m9YLRzExabtCoYRWwzkEslf4,12539
+ray/rllib/examples/inference/policy_inference_after_training_with_attention.py,sha256=ksCInBUVZGHM_Nmq8mlteRv5H9jphzP_8EZMC54qfNY,6276
+ray/rllib/examples/inference/policy_inference_after_training_with_lstm.py,sha256=GdLsSL-mNAvvxRDBxVF5rt7YTNHE0Sp5LDNJJgeMh4Y,5660
+ray/rllib/examples/learners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/learners/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/learners/__pycache__/ppo_with_custom_loss_fn.cpython-312.pyc,,
+ray/rllib/examples/learners/__pycache__/ppo_with_torch_lr_schedulers.cpython-312.pyc,,
+ray/rllib/examples/learners/__pycache__/separate_vf_lr_and_optimizer.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/learners/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__pycache__/custom_ppo_loss_fn_learner.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__pycache__/intrinsic_curiosity_learners.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__pycache__/separate_vf_lr_and_optimizer_learner.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__pycache__/vpg_torch_learner.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/__pycache__/vpg_torch_learner_shared_optimizer.cpython-312.pyc,,
+ray/rllib/examples/learners/classes/custom_ppo_loss_fn_learner.py,sha256=Ag_y2yPhGuuMflRvX0Bj42bgqHXhaWb433OEyv_V9_0,1774
+ray/rllib/examples/learners/classes/intrinsic_curiosity_learners.py,sha256=JERqD3eh387wCf31IpdytJC-VyI6kXGfDzm1wPwaN5E,6613
+ray/rllib/examples/learners/classes/separate_vf_lr_and_optimizer_learner.py,sha256=-4F9EZY4g_gf0LnBk9pFv29zAgehbqKFJc0jmxWCtB0,3772
+ray/rllib/examples/learners/classes/vpg_torch_learner.py,sha256=3CD5GX_4vsVnyZy9fjONMd9OhEvpzOuUSBKMqE6ubis,2835
+ray/rllib/examples/learners/classes/vpg_torch_learner_shared_optimizer.py,sha256=mkxTTPdYaZCFLh5Ao-lWxYD1Id7rz-vYk7zxm4mNBHs,1331
+ray/rllib/examples/learners/ppo_with_custom_loss_fn.py,sha256=kkaPLQd3DqHG2oH0UFaN9TxmAjcY8y_wyi5s7PF0gKk,5693
+ray/rllib/examples/learners/ppo_with_torch_lr_schedulers.py,sha256=tcT7snWb5hq0BqJT-lBIsDYGNJPE0eBJvOpJCm-Vac0,7846
+ray/rllib/examples/learners/separate_vf_lr_and_optimizer.py,sha256=00akyzLIr4-zYZiFfMYCG8DhSiKVl9iQ4UsIa0bMJMU,5788
+ray/rllib/examples/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/metrics/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/metrics/__pycache__/custom_metrics_in_algorithm_training_step.cpython-312.pyc,,
+ray/rllib/examples/metrics/__pycache__/custom_metrics_in_env_runners.cpython-312.pyc,,
+ray/rllib/examples/metrics/custom_metrics_in_algorithm_training_step.py,sha256=GnaTtanRRaauE3Ltr16u7XGcm4n29isemUOlisF-QMo,4157
+ray/rllib/examples/metrics/custom_metrics_in_env_runners.py,sha256=S_C7TOD4ek7d4HveOBaIGB42CLDMkUoK6BEh1alryRg,14344
+ray/rllib/examples/multi_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/multi_agent/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/custom_heuristic_policy.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/different_spaces_for_agents.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/multi_agent_cartpole.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/multi_agent_pendulum.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/pettingzoo_independent_learning.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/pettingzoo_parameter_sharing.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/pettingzoo_shared_value_function.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/rock_paper_scissors_heuristic_vs_learned.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/rock_paper_scissors_learned_vs_learned.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/self_play_footsies.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/self_play_league_based_with_open_spiel.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/self_play_with_open_spiel.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/shared_encoder_cartpole.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/__pycache__/two_step_game_with_grouped_agents.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/custom_heuristic_policy.py,sha256=mPuCFy3t-jjnlQjJ2Cz1iJ0XwRFcycQStCiP_NaB6QA,3813
+ray/rllib/examples/multi_agent/different_spaces_for_agents.py,sha256=3D4l8oKDgu2ApWpijNFch6jnjJv_IjDA30r59ddRd-8,4009
+ray/rllib/examples/multi_agent/multi_agent_cartpole.py,sha256=8dd8QktdgQUQVUU883QGIdZUQo0TCIuYGhi54Drm82w,1986
+ray/rllib/examples/multi_agent/multi_agent_pendulum.py,sha256=0NuurT_8s_iz8qOPFdrLU0ffV39ZTWKw76efIN1CxU0,2364
+ray/rllib/examples/multi_agent/pettingzoo_independent_learning.py,sha256=vS9vUVFP0cYQPi_pR9HbMSPTjQJNV47fBZE32iHrfvU,4122
+ray/rllib/examples/multi_agent/pettingzoo_parameter_sharing.py,sha256=SQp2Z0BM0nabObM9Dc4wHAyRpbO_MxoyF0vSbePE_lg,3775
+ray/rllib/examples/multi_agent/pettingzoo_shared_value_function.py,sha256=ty_VEsbyNoDSYlb0gAJVou1Qt8KLgHvOpAsZuHnMhQ4,241
+ray/rllib/examples/multi_agent/rock_paper_scissors_heuristic_vs_learned.py,sha256=nb_6Ry4JL6G1n6_lkjhzYI--50eT1-i3EHtlPPIH4Os,5431
+ray/rllib/examples/multi_agent/rock_paper_scissors_learned_vs_learned.py,sha256=GQaPgVuSADPJJpA1xFJBBOhwD9dhP0DBI5nQhdndE4Y,3079
+ray/rllib/examples/multi_agent/self_play_footsies.py,sha256=TMJYnMg0t7-5F_CCCIJ1h4iNRGlwLWucnxhgyWmLBqw,4067
+ray/rllib/examples/multi_agent/self_play_league_based_with_open_spiel.py,sha256=apS2hpSvzBEogjrxu8RMIUJhJgSG1vcYigB8nX_XeH4,10913
+ray/rllib/examples/multi_agent/self_play_with_open_spiel.py,sha256=sOo6X8fTGAWFqtmSI7j6K48ysMHDkd6JIyppdKTVKLA,9753
+ray/rllib/examples/multi_agent/shared_encoder_cartpole.py,sha256=1YbcljK23yap-yT4_qUseFSWlV5BMeJOweAEOf3LBg4,6664
+ray/rllib/examples/multi_agent/two_step_game_with_grouped_agents.py,sha256=UWcGZkCMJ_s10Awg22eMMyQiwGEHnkc0Rk3v6bRPXOo,3343
+ray/rllib/examples/multi_agent/utils/__init__.py,sha256=PsTJ13qS4t0xAMOHOlQNqSR5LQOMCrRDbTPoLIP4aFg,1321
+ray/rllib/examples/multi_agent/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/utils/__pycache__/self_play_callback.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/utils/__pycache__/self_play_callback_old_api_stack.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/utils/__pycache__/self_play_league_based_callback.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/utils/__pycache__/self_play_league_based_callback_old_api_stack.cpython-312.pyc,,
+ray/rllib/examples/multi_agent/utils/self_play_callback.py,sha256=FKk2u8UiP_9OToCleFT5dYm3KzH_m0A3kR4SIp66PrY,3722
+ray/rllib/examples/multi_agent/utils/self_play_callback_old_api_stack.py,sha256=b0Qo1iJeEZ9cc0YXmbgHhUgTwV1r-3v3ywt-fDAKRnI,3553
+ray/rllib/examples/multi_agent/utils/self_play_league_based_callback.py,sha256=p6-k6dNwM_iH2A-GKSsB9euYMZvNx9brNWED62Xp5TM,13494
+ray/rllib/examples/multi_agent/utils/self_play_league_based_callback_old_api_stack.py,sha256=WWqmygCQv6LUG8fmH5UOkLfXBmVcigUK4EAJxuAssjQ,9232
+ray/rllib/examples/offline_rl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/offline_rl/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/cartpole_recording.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/custom_input_api.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/offline_rl.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/offline_rl_with_image_data.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/pretrain_bc_single_agent_evaluate_as_multi_agent.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/saving_experiences.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/__pycache__/train_w_bc_finetune_w_ppo.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/cartpole_recording.py,sha256=Gs0e-HbENgt-RlHwSSXUBRnAk9mw5KQZ6IQBHpkGqA4,5925
+ray/rllib/examples/offline_rl/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/offline_rl/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/classes/__pycache__/image_offline_data.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/classes/__pycache__/image_offline_prelearner.cpython-312.pyc,,
+ray/rllib/examples/offline_rl/classes/image_offline_data.py,sha256=AkpyED_jn9IAcvZbR8c5H-pM4kG4EHGoi4wYUU5l2EA,2756
+ray/rllib/examples/offline_rl/classes/image_offline_prelearner.py,sha256=pBHW31PfIpp7yTycXrTWpXYz867TpUD-pAstpeXgzdw,3734
+ray/rllib/examples/offline_rl/custom_input_api.py,sha256=5qOU3h9QU_fhrrsYcXYosdhjNv8jdORlVYk3175V0GQ,4258
+ray/rllib/examples/offline_rl/offline_rl.py,sha256=UWZpeegrd9OHHpEFEAFvEYe97KsqJQiaBCUWL8gWiWQ,6219
+ray/rllib/examples/offline_rl/offline_rl_with_image_data.py,sha256=ApZHOmij_KPqvjzj1QTJOTQCTlnwBdabRMZtRybpcpU,4784
+ray/rllib/examples/offline_rl/pretrain_bc_single_agent_evaluate_as_multi_agent.py,sha256=jeT2pbqDwHcZINtRZc92QV6bXyTH_AW0x28vqH2DnU0,7218
+ray/rllib/examples/offline_rl/saving_experiences.py,sha256=VBi7qQ-3GZG2VDGhVJDcAiWtjvGkTamouByFVtB7szI,2239
+ray/rllib/examples/offline_rl/train_w_bc_finetune_w_ppo.py,sha256=YKHFYPTJIoq9Elh8ojvDDm7Gv2_97Hk3htcFTY2D-0E,12561
+ray/rllib/examples/quadx_waypoints.py,sha256=alLLNmqdJ44xYe6amnbC2Gq8wIR2JY5S0givK3HoFhs,4013
+ray/rllib/examples/ray_serve/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/ray_serve/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/ray_serve/__pycache__/ray_serve_with_rllib.cpython-312.pyc,,
+ray/rllib/examples/ray_serve/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/ray_serve/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/ray_serve/classes/__pycache__/cartpole_deployment.cpython-312.pyc,,
+ray/rllib/examples/ray_serve/classes/cartpole_deployment.py,sha256=587tUWemZnL32ZCLWdzo0p1rrIcKYTsLL3PgjDH0NmM,1832
+ray/rllib/examples/ray_serve/ray_serve_with_rllib.py,sha256=7uQ08IvVJi_qCnds7v8SB1RNoz3kLtUHQ1ps0nd1wN8,6712
+ray/rllib/examples/ray_tune/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/ray_tune/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/ray_tune/__pycache__/custom_experiment.cpython-312.pyc,,
+ray/rllib/examples/ray_tune/__pycache__/custom_logger.cpython-312.pyc,,
+ray/rllib/examples/ray_tune/__pycache__/custom_progress_reporter.cpython-312.pyc,,
+ray/rllib/examples/ray_tune/custom_experiment.py,sha256=_jNZsuQnAKk_FIViIJSl0w_bZoDRvy-Joyeebk6bveE,8339
+ray/rllib/examples/ray_tune/custom_logger.py,sha256=vnjADBma3eg30wxjyntQ3p-bbmFD7rzYPfYYo8RdeCY,4762
+ray/rllib/examples/ray_tune/custom_progress_reporter.py,sha256=Axl5d_i19ltriCyuwnFOydGmdgTb_0U8q7Ggv32g8NA,4783
+ray/rllib/examples/replay_buffer_api.py,sha256=BNPQlKflh_hlXTPbrU4QMDIP_BQvh3bm2Zh3XeRHbbY,2545
+ray/rllib/examples/rl_modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/examples/rl_modules/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/action_masking_rl_module.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/custom_cnn_rl_module.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/custom_lstm_rl_module.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/migrate_modelv2_to_new_api_stack_by_config.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/migrate_modelv2_to_new_api_stack_by_policy_checkpoint.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/__pycache__/pretraining_single_agent_training_multi_agent.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/action_masking_rl_module.py,sha256=m2r3zKJOviJy7_1mC5CfoL3VL1qXu-wFcyA04-aWWDg,5313
+ray/rllib/examples/rl_modules/classes/__init__.py,sha256=Gjh1N_dKvUt0-pDRyrdWx_1z7PADABnMUFYOYX8EskU,215
+ray/rllib/examples/rl_modules/classes/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/action_masking_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/autoregressive_actions_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/custom_action_distribution_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/intrinsic_curiosity_model_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/lstm_containing_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/mobilenet_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/modelv2_to_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/random_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/rock_paper_scissors_heuristic_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/tiny_atari_cnn_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/vpg_torch_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/__pycache__/vpg_using_shared_encoder_rlm.cpython-312.pyc,,
+ray/rllib/examples/rl_modules/classes/action_masking_rlm.py,sha256=F7KYAnzJMq3aXOUCg4wOf2ZtTy6oTMErqZINzYm_RQQ,9351
+ray/rllib/examples/rl_modules/classes/autoregressive_actions_rlm.py,sha256=Mjw_Pl08nJm2XYgBPRpuPEp7lGqEZ9BCp3QbXaqarPY,4697
+ray/rllib/examples/rl_modules/classes/custom_action_distribution_rlm.py,sha256=BxHvHVdDWHfxmXcMWxkUCfIZf1mhnhGfJRvntW_RanU,6716
+ray/rllib/examples/rl_modules/classes/intrinsic_curiosity_model_rlm.py,sha256=C0kcpdnR0FcBrOOyzYYTvk2XPrKxcnfwle5dXUy6_EI,9483
+ray/rllib/examples/rl_modules/classes/lstm_containing_rlm.py,sha256=iGMjGS_WuNhIuug8YQab_nio8aFjSHq6EEcnzPQjOJI,6039
+ray/rllib/examples/rl_modules/classes/mobilenet_rlm.py,sha256=qpXEWxzPnDhRVqB6BsttxZrlifvPC9zAHnHMto5IAOY,2895
+ray/rllib/examples/rl_modules/classes/modelv2_to_rlm.py,sha256=0ndciU5NoRwFHLQD-wpAFZB9Y6xstspXwY7HxnPdvgI,9244
+ray/rllib/examples/rl_modules/classes/random_rlm.py,sha256=l2SvAbV3b6qKT9vRCUFwhJDE65c5vH0fEo_Ly6B5nl8,2248
+ray/rllib/examples/rl_modules/classes/rock_paper_scissors_heuristic_rlm.py,sha256=9r2I_xGnVBHxugee181gGLD-cqZTnzarmmU3N4YMtV8,3264
+ray/rllib/examples/rl_modules/classes/tiny_atari_cnn_rlm.py,sha256=9qja87BlegAmbpx8_opeUlwb8-V4a3NXB2pkHjPpa4g,7812
+ray/rllib/examples/rl_modules/classes/vpg_torch_rlm.py,sha256=OlT03SZLfpSw1kfAhyJYTaupnqcRKlKEIffSDxgwJDI,2746
+ray/rllib/examples/rl_modules/classes/vpg_using_shared_encoder_rlm.py,sha256=R0DdSQnYWVsVssk5vQzKZHisxdw0AJqtll0FhOgZH58,9562
+ray/rllib/examples/rl_modules/custom_cnn_rl_module.py,sha256=gtmeQQ8HUmO8OAfQ61LakJ0X4ghSety7JmHzsvc4zX4,4760
+ray/rllib/examples/rl_modules/custom_lstm_rl_module.py,sha256=JcZRhEHdp1UmWmO5xQbqmUr4SvSoZurKZOuMV_rM33E,3906
+ray/rllib/examples/rl_modules/migrate_modelv2_to_new_api_stack_by_config.py,sha256=ml5cjnPx6KDqDE6YVSy8C9KRnrf904dK6onkHdE7ddo,2429
+ray/rllib/examples/rl_modules/migrate_modelv2_to_new_api_stack_by_policy_checkpoint.py,sha256=U4K-V5-3MYOCW6vbsZGkZqd7XuYVF4UBhoYqS-z1P0o,3962
+ray/rllib/examples/rl_modules/pretraining_single_agent_training_multi_agent.py,sha256=7QoPO_pH63i8ew9xOY7k4lohBVIAuTU0rlrGiOpbxcg,7575
+ray/rllib/execution/__init__.py,sha256=4dBf66RZBAEwKSSI8V2ZzhORdoa2a96pkorYV3Z1WWc,697
+ray/rllib/execution/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/learner_thread.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/minibatch_buffer.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/multi_gpu_learner_thread.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/replay_ops.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/rollout_ops.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/segment_tree.cpython-312.pyc,,
+ray/rllib/execution/__pycache__/train_ops.cpython-312.pyc,,
+ray/rllib/execution/buffers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/execution/buffers/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/execution/buffers/__pycache__/mixin_replay_buffer.cpython-312.pyc,,
+ray/rllib/execution/buffers/mixin_replay_buffer.py,sha256=1Q-L51bUm-27eLOkeWCaSXD6wHXMLWSgQfRpo3riNVA,6842
+ray/rllib/execution/learner_thread.py,sha256=J1gnTTobfOutcFYhsobV-oBjrydJMzLqa31SN6R7tO4,5733
+ray/rllib/execution/minibatch_buffer.py,sha256=3i9VqRqzLb4BXs1aHq9q7Qw_ekYw9fUaaGL_sS3iDo0,1952
+ray/rllib/execution/multi_gpu_learner_thread.py,sha256=X2VmceUzEwpEGwgSx0zV9S92-9nhE2rAJY4TeTwVTro,9662
+ray/rllib/execution/replay_ops.py,sha256=4R-LXLuQMaZQEFd58CA1_cQOAUPLHuB52Q-kEjre7fA,1255
+ray/rllib/execution/rollout_ops.py,sha256=PN8O1O53rJtEUQwcGMPSiH-CzkklnzvbB2XSWYtPRuo,8646
+ray/rllib/execution/segment_tree.py,sha256=Whdo3jl1E0GD-bsojYfsxpGIyxZfMuf8ZEQGNNRE5Pg,8081
+ray/rllib/execution/train_ops.py,sha256=VfjkpVcIkcDXyPVrg2uE9j7UvMUdfzBDbXybnNyfOTo,8127
+ray/rllib/models/__init__.py,sha256=F9n_z6iLEDa0RMte_293yjHlv3lJX44ZXbjRKXUNb8Y,345
+ray/rllib/models/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/models/__pycache__/action_dist.cpython-312.pyc,,
+ray/rllib/models/__pycache__/catalog.cpython-312.pyc,,
+ray/rllib/models/__pycache__/distributions.cpython-312.pyc,,
+ray/rllib/models/__pycache__/modelv2.cpython-312.pyc,,
+ray/rllib/models/__pycache__/preprocessors.cpython-312.pyc,,
+ray/rllib/models/__pycache__/repeated_values.cpython-312.pyc,,
+ray/rllib/models/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/models/action_dist.py,sha256=EzVJRjjwCP5wPP123lZlcpr44RQ0UzXx1WwavE_8QfE,3425
+ray/rllib/models/catalog.py,sha256=FFiQPXQhtp3Z4vXvvldx7oYvyt3Wembr3BimbstXFOc,36145
+ray/rllib/models/distributions.py,sha256=knkywbQV-gcOGdMgxLfiNS2NkBgtl_1M75BcSIhkABc,291
+ray/rllib/models/modelv2.py,sha256=_nb8_xpL6GH4Z_2HWbnXN14u7F2PZDYFfGuqRWtUG9Y,17924
+ray/rllib/models/preprocessors.py,sha256=7jIIqerBzIv5yaepI_9hOy2JHrJnqe8cJLAVOYyzmO4,16616
+ray/rllib/models/repeated_values.py,sha256=w63AevQf_lyFCGcTjYMfe_0l25hwM5WHMtbU3FDa1kQ,6791
+ray/rllib/models/tf/__init__.py,sha256=2Vp28M6xRc0h7fp4RmAUUlefjdorYjj15f5DuQUNBmw,338
+ray/rllib/models/tf/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/attention_net.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/complex_input_net.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/fcnet.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/misc.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/recurrent_net.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/tf_action_dist.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/tf_modelv2.cpython-312.pyc,,
+ray/rllib/models/tf/__pycache__/visionnet.cpython-312.pyc,,
+ray/rllib/models/tf/attention_net.py,sha256=u9QpnPHKZfdhzLkSWBO0MQpwK9veDr5g_FiIW9E549Y,23011
+ray/rllib/models/tf/complex_input_net.py,sha256=qkaDtgY4lA7x5-Cg60oMbT9H9zLi79TCZQ-5n_qJClA,8352
+ray/rllib/models/tf/fcnet.py,sha256=N1vh4TZypqYiDhutjwU3LtWVr_S0DDJ4LGVr1ZGx46g,5564
+ray/rllib/models/tf/layers/__init__.py,sha256=0FKA_4lLjHLQ_ucnr7LhC7uR3r0Sjxp7Q67X0vsJnrM,554
+ray/rllib/models/tf/layers/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/models/tf/layers/__pycache__/gru_gate.cpython-312.pyc,,
+ray/rllib/models/tf/layers/__pycache__/multi_head_attention.cpython-312.pyc,,
+ray/rllib/models/tf/layers/__pycache__/noisy_layer.cpython-312.pyc,,
+ray/rllib/models/tf/layers/__pycache__/relative_multi_head_attention.cpython-312.pyc,,
+ray/rllib/models/tf/layers/__pycache__/skip_connection.cpython-312.pyc,,
+ray/rllib/models/tf/layers/gru_gate.py,sha256=Ku4-9_xAvKfc_eOucrIjjT2PulpjiJzl2zRIpcy-_Cg,1968
+ray/rllib/models/tf/layers/multi_head_attention.py,sha256=K2gzjsw32AaYVhx7xER_jA9cDkKYd_PiG-aQwt499C0,2257
+ray/rllib/models/tf/layers/noisy_layer.py,sha256=EeBTYold_zbB2aqBTVe_a_hQHc6DzhU0vQZ3QmwwBug,3961
+ray/rllib/models/tf/layers/relative_multi_head_attention.py,sha256=vYmfDXGW90KQvWHVjCNizApH7x8jlJ92iHmRt0qvQpA,5757
+ray/rllib/models/tf/layers/skip_connection.py,sha256=-huaQt3Wz2_YPtbBDJDVsXUEgof1IA1cwASwNh5u94c,1653
+ray/rllib/models/tf/misc.py,sha256=4cAeBl2MgJP-2trUZeHxI3GFxUSaXyWwyUqpJ1iWwOU,2658
+ray/rllib/models/tf/recurrent_net.py,sha256=VU1NsZACDeJaUsIGX4hLGK_PGPGja9IXmELllRBl3lA,11569
+ray/rllib/models/tf/tf_action_dist.py,sha256=t28g0NBcJTA5zZ2gS_0uG09h_ybhxqanOpr6GdkK07A,27265
+ray/rllib/models/tf/tf_modelv2.py,sha256=pKThbofRegftxQm6zH7E3_whJIlmaTCBtvAU_pCcork,5125
+ray/rllib/models/tf/visionnet.py,sha256=Lr3ln7VvI-Nn7dTlnf32NTm-PZ81Hqz3J6egOl1WiOI,10646
+ray/rllib/models/torch/__init__.py,sha256=zM2oOUgiqMKv7qQW2pBM6XyIqqjddHvTfnVoSEbjoos,387
+ray/rllib/models/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/attention_net.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/complex_input_net.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/fcnet.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/mingpt.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/misc.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/recurrent_net.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/torch_action_dist.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/torch_distributions.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/torch_modelv2.cpython-312.pyc,,
+ray/rllib/models/torch/__pycache__/visionnet.cpython-312.pyc,,
+ray/rllib/models/torch/attention_net.py,sha256=LH-vPOKv_4PhSnYvWBD5YbVOQ0gQbvA-pEuZ2-X6yas,18225
+ray/rllib/models/torch/complex_input_net.py,sha256=k7xLVNbHtvVxp8mGU873zXJQs-ZaoyhItQq-xYXjMeg,9287
+ray/rllib/models/torch/fcnet.py,sha256=N8JRcEYSJmKvp2LkjhgiLWxCJc3qDrz_orkfbZKXKfM,5903
+ray/rllib/models/torch/mingpt.py,sha256=mHhJZ9sEPwmFwuRH5IsLHcS4bD8kEhCcZszP_uKndw8,11144
+ray/rllib/models/torch/misc.py,sha256=lWFkF6zcbyRPNSYHIZbMwtk0WeM1-TzW58er0eB1aSA,11632
+ray/rllib/models/torch/modules/__init__.py,sha256=HcEs6_p32uWqTvqexeyYWLAt707FcyNQZkTHKGZ7zLw,438
+ray/rllib/models/torch/modules/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/models/torch/modules/__pycache__/gru_gate.cpython-312.pyc,,
+ray/rllib/models/torch/modules/__pycache__/multi_head_attention.cpython-312.pyc,,
+ray/rllib/models/torch/modules/__pycache__/noisy_layer.cpython-312.pyc,,
+ray/rllib/models/torch/modules/__pycache__/relative_multi_head_attention.cpython-312.pyc,,
+ray/rllib/models/torch/modules/__pycache__/skip_connection.cpython-312.pyc,,
+ray/rllib/models/torch/modules/gru_gate.py,sha256=JdVCD8KywnSzghNMwsjOrRUeFVP8C2fC8NDBVafo6yo,2334
+ray/rllib/models/torch/modules/multi_head_attention.py,sha256=hqxlkr7d77SmaCbAdguHFucXok2nmbg9KEnPCcjn--s,2467
+ray/rllib/models/torch/modules/noisy_layer.py,sha256=2s0RFCnmvPoMFJhlVzTyFFet4GGY_JPa9mHLvjBtcJc,3406
+ray/rllib/models/torch/modules/relative_multi_head_attention.py,sha256=f7L38tc2SwrbqUBJ6a6BFSPGGTEV08zaIEQE72K71C0,6336
+ray/rllib/models/torch/modules/skip_connection.py,sha256=8dHNpQv_nZmvNvYXHZWSqwg1mb7CEuHnMchM4Cges1E,1455
+ray/rllib/models/torch/recurrent_net.py,sha256=hbmKaRJULcVy0EroowFXFP7oc5vdZADw43qITx_zMWI,12224
+ray/rllib/models/torch/torch_action_dist.py,sha256=KQGtDeOYu_-fo5r0c0A6Uh7u-zfZmCWeqfhX5hV1qfw,24345
+ray/rllib/models/torch/torch_distributions.py,sha256=n_HO28J_IDRIhnrd_CpSeHHccNzy2dNCEdDeO1ZpFaI,502
+ray/rllib/models/torch/torch_modelv2.py,sha256=5HPalgNJ6qi5VfT3nvibHledwG-m_Zqdi45m7AKCO3w,2710
+ray/rllib/models/torch/visionnet.py,sha256=9L9B2tZ04ywmPrrLp6ZEabkjJCcC29ZE8XZ9TJ0xbzo,10804
+ray/rllib/models/utils.py,sha256=TTuHVZDKkhk08v09Vn0QToHX9DDQ6lNoO91ACbikNJ4,10459
+ray/rllib/offline/__init__.py,sha256=ZELTOibdcQJE0X5ToAr6vv18ZKZ9EDJ6wH-V6rfp0-0,1044
+ray/rllib/offline/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/d4rl_reader.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/dataset_reader.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/dataset_writer.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/feature_importance.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/input_reader.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/io_context.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/is_estimator.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/json_reader.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/json_writer.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/mixed_input.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/off_policy_estimator.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_data.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_env_runner.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_evaluation_runner.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_evaluation_runner_group.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_evaluation_utils.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_evaluator.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_policy_evaluation_runner.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/offline_prelearner.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/output_writer.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/resource.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/shuffled_input.cpython-312.pyc,,
+ray/rllib/offline/__pycache__/wis_estimator.cpython-312.pyc,,
+ray/rllib/offline/d4rl_reader.py,sha256=2WOVVLb3cD1ZhRwLnuo7VwXjomHAZLpzLr00SeRyBOg,1596
+ray/rllib/offline/dataset_reader.py,sha256=qAptsLhKP6Ps-aYOOORwz47pQPgTbECFN38IN23ksYo,11710
+ray/rllib/offline/dataset_writer.py,sha256=nq0LC7kXmodFf0_4GHGvYqWlcN6ZryiyOSNAQTuA-TI,2801
+ray/rllib/offline/estimators/__init__.py,sha256=EVEvrL1iEmIlfAu9NZLVrBMKl1BPgIgQqMmxY62N5qI,544
+ray/rllib/offline/estimators/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/direct_method.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/doubly_robust.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/feature_importance.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/fqe_torch_model.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/importance_sampling.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/off_policy_estimator.cpython-312.pyc,,
+ray/rllib/offline/estimators/__pycache__/weighted_importance_sampling.cpython-312.pyc,,
+ray/rllib/offline/estimators/direct_method.py,sha256=mj0PQN_TBIt-05m_OvyLRQn--wYPyhdWEjh0I3OArws,6705
+ray/rllib/offline/estimators/doubly_robust.py,sha256=XsUz6K7CeJQ26MraXC60769VSiRQU0eaoOk3iWCFK94,9619
+ray/rllib/offline/estimators/feature_importance.py,sha256=LZoIH7OyoC3AFBJqKh0HbPpcatFKHT0w_-xaSdoL6Tc,332
+ray/rllib/offline/estimators/fqe_torch_model.py,sha256=X3LuXZFUhGrpjV8dBj6ow16HBRyoUzAFmVFou7Dfou4,11812
+ray/rllib/offline/estimators/importance_sampling.py,sha256=tf9XFASaecjzhIUmQ5oq5C89L5A0LKoSHdwIzMao8dc,4601
+ray/rllib/offline/estimators/off_policy_estimator.py,sha256=5u5kZKhn3lXF3XYAtJUG1XTI5BaONlXj_0OoImd819A,10124
+ray/rllib/offline/estimators/weighted_importance_sampling.py,sha256=s9Cr_L6RktN5D3thpVZ_TiW32BrNL1sos9OafgZdCIw,7047
+ray/rllib/offline/feature_importance.py,sha256=3T-dGEbymroNHWdZeYGHexhile8epWAHCO45loC8JiI,10680
+ray/rllib/offline/input_reader.py,sha256=MTiSz12QRjGcO_D6irNHdQIlBqZHIeZeDBIx1WxuoPc,4855
+ray/rllib/offline/io_context.py,sha256=k8bnvtWsWe5z8ggVHfS63JIVwolrpFkRxGxcLmFKbQ4,2543
+ray/rllib/offline/is_estimator.py,sha256=X5DZEbuYVNZiK0hHwc988QLRgADakuhFt3L4nFBWjkU,304
+ray/rllib/offline/json_reader.py,sha256=zOzeHs6MuHIF1H1U5bn4UZ1V6r43-XPIJFgyWM0LQXg,16889
+ray/rllib/offline/json_writer.py,sha256=2PJiRIYkDUfQv4lg6YsJh315WQfLdEAMUxnrvy_osu4,4962
+ray/rllib/offline/mixed_input.py,sha256=0PSXj4MjqxGpRON6Dbbs_c_u3Vv-99-BmxPdTawRVFU,2030
+ray/rllib/offline/off_policy_estimator.py,sha256=Mf9YSwEtBzzAgBbDOsyRhxjqN77p3eHDkDvfuc8mzzI,311
+ray/rllib/offline/offline_data.py,sha256=Vbs6STB5vndY6VLHjx6j1X_ztCxRjbJYpB5P3yt0quo,13006
+ray/rllib/offline/offline_env_runner.py,sha256=B2hiogCMHCQy8s5FCNFWBzWeQcGlJtc8zBPgf45qb10,13101
+ray/rllib/offline/offline_evaluation_runner.py,sha256=6pfvyfbdsNO5z_walb1iajgeyrE_dLGAjRBSHrUqhww,16329
+ray/rllib/offline/offline_evaluation_runner_group.py,sha256=9oTM9LfH4BNuF-aowyZvmDgBNU-PVZJ9xZGvfBhSUXo,7444
+ray/rllib/offline/offline_evaluation_utils.py,sha256=M7i7lGW_RSZjezSfRcghYfsJv8QI2sHMupL9D2Y2r7M,4744
+ray/rllib/offline/offline_evaluator.py,sha256=oLyNyrJs1B8kTvwKWJ7T7e-qoppW0enBgoMGp5tcBOs,2285
+ray/rllib/offline/offline_policy_evaluation_runner.py,sha256=Fe_yR5wGoS3C3F8qaeaYySnhcwifzAii2umsYylna9c,23736
+ray/rllib/offline/offline_prelearner.py,sha256=041dJVky7OlvrQjlZhvunzB_w1VZsHljLPh6CAudP2U,27770
+ray/rllib/offline/output_writer.py,sha256=9MJ5XuO3TM-2ZUXyRKu6NR9W0aoTZ_8fbh83lO20f5k,660
+ray/rllib/offline/resource.py,sha256=-a8SrRWxlnJkV2O0cYQA_Ki_Qt853rq6B3Aox7G0Qcw,1265
+ray/rllib/offline/shuffled_input.py,sha256=M57IjbpEwN1tSjM6ER_nNaIKxY3aeciDI_N3eAuGWz0,1355
+ray/rllib/offline/wis_estimator.py,sha256=jeKJasiF6-WJyntBmbQWksgjIEwjEF-8_tSNPbcMZNI,370
+ray/rllib/policy/__init__.py,sha256=uxssoPxnXJGyLSci4xX5igSeweAHJbhqEjVahFKSG2M,386
+ray/rllib/policy/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/dynamic_tf_policy.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/dynamic_tf_policy_v2.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/eager_tf_policy.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/eager_tf_policy_v2.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/policy.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/policy_map.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/policy_template.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/rnn_sequencing.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/sample_batch.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/tf_mixins.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/tf_policy.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/tf_policy_template.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/torch_mixins.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/torch_policy.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/torch_policy_v2.cpython-312.pyc,,
+ray/rllib/policy/__pycache__/view_requirement.cpython-312.pyc,,
+ray/rllib/policy/dynamic_tf_policy.py,sha256=v9PdW_Zz_WXVYtNZDNH_qN4H6YjCY1vt1cBsDrsS--Q,57806
+ray/rllib/policy/dynamic_tf_policy_v2.py,sha256=aSiXPZZJRgWCQksZPOupPIY6dW3p79r8QW_ge4V9xNw,40728
+ray/rllib/policy/eager_tf_policy.py,sha256=6kYa4ZQvdmr6Aoc6sTKbIUnthOCckLbSuRr8Hbq_zjc,43457
+ray/rllib/policy/eager_tf_policy_v2.py,sha256=oJRnAehjjw8ShmN9ljuWERA9DBIgfALyoDUwsbRT_98,36066
+ray/rllib/policy/policy.py,sha256=aifAt_QAbR229psjb5JvNURQN3yWTr2NWfq8KyhTncI,69980
+ray/rllib/policy/policy_map.py,sha256=l7_b4rM2zXKkpbQBuK_ZKJYaRe_RsaQK4tXedx4jp3s,10247
+ray/rllib/policy/policy_template.py,sha256=l4PzcXJeUP7oJMaHz3vdqzEFMknO15tZMKCEcdmvMTA,20218
+ray/rllib/policy/rnn_sequencing.py,sha256=G91uYWvQZukMot4_ni8KXUjrPej3pJHJMeqK1YUTwGk,26832
+ray/rllib/policy/sample_batch.py,sha256=twj79blMHnbS_WeZlEU0hZQzIFFkT6ojwz1YWwBoRmM,68721
+ray/rllib/policy/tf_mixins.py,sha256=p5GS9h93l5C_s4rdwgaIlZvEOu1VlQfXhtnyKLYYugc,15277
+ray/rllib/policy/tf_policy.py,sha256=E5OHVE6D9HHa-B8-bR3tI2vSweA-4div6kUPFW7PWrs,48691
+ray/rllib/policy/tf_policy_template.py,sha256=ZM-VpLUmCLB2SV69c1a5dqd_fl5fqIxZX_Txf5f4wTI,16935
+ray/rllib/policy/torch_mixins.py,sha256=fF1me7JfH_gQg_p0lnOipe0zOYm_8nPQD0xz21kfkGk,8682
+ray/rllib/policy/torch_policy.py,sha256=cfo1ZT0vpHLM97AxOu_ydDDDmHGjW9IIja13AsuOP7E,49541
+ray/rllib/policy/torch_policy_v2.py,sha256=WGKI5hJgpRCM97_LsTFa0D4AQyMymxE5o7fYte8RvBA,49486
+ray/rllib/policy/view_requirement.py,sha256=GOR_xPwT_HW_KVCw8_08slvUsUY4_MpsAO1SKzBQS34,6294
+ray/rllib/tuned_examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/tuned_examples/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/tuned_examples/__pycache__/cleanup_experiment.cpython-312.pyc,,
+ray/rllib/tuned_examples/cleanup_experiment.py,sha256=cqS1csVQzMPCd9oGYayEmeb5y7CqxQwpvZBCcDYATq8,7539
+ray/rllib/tuned_examples/dreamerv3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/tuned_examples/dreamerv3/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/atari_100k_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/atari_200M_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/cartpole_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/dm_control_suite_vision_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/flappy_bird_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/frozenlake_2x2_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/frozenlake_4x4_deterministic_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/gymnasium_robotics_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/highway_env_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/__pycache__/pendulum_dreamerv3.cpython-312.pyc,,
+ray/rllib/tuned_examples/dreamerv3/atari_100k_dreamerv3.py,sha256=6ijgVqdO0-eIX1s7-nkx6BvETq-BKBmx1aFZBigTCaU,3543
+ray/rllib/tuned_examples/dreamerv3/atari_200M_dreamerv3.py,sha256=q81JrY5-_kg3g1s3RTO0or6Xq-xCP9n1QtHOUdKZ6ks,3202
+ray/rllib/tuned_examples/dreamerv3/cartpole_dreamerv3.py,sha256=VoEdju6ANXYPXNoqXqCUbssrJYpR_YSoBT5ZhFeTvjU,541
+ray/rllib/tuned_examples/dreamerv3/dm_control_suite_vision_dreamerv3.py,sha256=-T0D-vOj5ZicVho7-_LxU-s_TpRGa-9x607KgimVJ7s,2606
+ray/rllib/tuned_examples/dreamerv3/flappy_bird_dreamerv3.py,sha256=b1XNP2-f9fPrJs9a8OvrSS1GMatrwR-of2IV5amR5NE,2503
+ray/rllib/tuned_examples/dreamerv3/frozenlake_2x2_dreamerv3.py,sha256=8JuU3Ql-4wlZ0RVOD7vbuhxqmC2kvVGiIVNKWM3VCIY,776
+ray/rllib/tuned_examples/dreamerv3/frozenlake_4x4_deterministic_dreamerv3.py,sha256=STdrZbDxbEGSzCQWs5GU8DbRvElJlhI4GuRmfRMwtBc,728
+ray/rllib/tuned_examples/dreamerv3/gymnasium_robotics_dreamerv3.py,sha256=1lB5JWrGegIY5E5AZhB35OiXBuUaX6WEdTNZQLE1m4o,2263
+ray/rllib/tuned_examples/dreamerv3/highway_env_dreamerv3.py,sha256=XSp_kW1TQ4Xwf9gUvMjH7NL2GdNMhu0hbC2UxQgvPpg,2280
+ray/rllib/tuned_examples/dreamerv3/pendulum_dreamerv3.py,sha256=aAblg7fmHYIGrJ99O6WWyXvLDf4IWn18yn7VDiXZzaE,1996
+ray/rllib/utils/__init__.py,sha256=DONkfdGnS4EBXZrEQqwD1vfUnnwLX72Scfe0fam46I0,5180
+ray/rllib/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/actor_manager.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/actors.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/annotations.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/checkpoints.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/compression.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/error.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/filter.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/filter_manager.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/framework.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/from_config.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/images.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/lambda_defaultdict.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/memory.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/minibatch_utils.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/numpy.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/policy.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/serialization.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/sgd.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/tensor_dtype.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/test_utils.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/tf_run_builder.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/tf_utils.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/threading.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/torch_utils.cpython-312.pyc,,
+ray/rllib/utils/__pycache__/typing.cpython-312.pyc,,
+ray/rllib/utils/actor_manager.py,sha256=PcV8UHpjmDDpbDXsvZfh0KHd8Tq5MSV2vD1JRhoyNVo,46065
+ray/rllib/utils/actors.py,sha256=etkCaERyTQNMV2vYI5xjhzcn9pnPlmacZgmr3-TLAp0,9964
+ray/rllib/utils/annotations.py,sha256=z1QH4kUEhNFaLciDRnZRMbVVX3xQnDaqVEXsuf9sInA,6828
+ray/rllib/utils/checkpoints.py,sha256=IANrZ2FhPDxt81IuYzjbYUrbFia0a_fwBikIYa7QNBM,43455
+ray/rllib/utils/compression.py,sha256=FF0M-0g8N2Vb4IGr8_8iMlOTbQWLv-ooTVIBKaFFPAw,2163
+ray/rllib/utils/debug/__init__.py,sha256=H0tWfXLpJbwxh2YZPOivg8FqhxJV1wE2GoqRYk-WrPc,289
+ray/rllib/utils/debug/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/debug/__pycache__/deterministic.cpython-312.pyc,,
+ray/rllib/utils/debug/__pycache__/memory.cpython-312.pyc,,
+ray/rllib/utils/debug/__pycache__/summary.cpython-312.pyc,,
+ray/rllib/utils/debug/deterministic.py,sha256=i0u4-aa1_01avg0BYeFKs7T8FMbPfAMl0id30GDrDZI,1032
+ray/rllib/utils/debug/memory.py,sha256=xjoQRShi2-AlIjr5HsyPsOepNBJMjkx5dNLxIO1Fxk4,7792
+ray/rllib/utils/debug/summary.py,sha256=Xg2L-iA6xZyTZgWnXUJwWi4gR9fnBGf7tkZy3RU5n9I,2337
+ray/rllib/utils/error.py,sha256=LZQSDeBuKoCxUNyy2ZlvS0bekLIW6TYxW0XHsUelm2I,5245
+ray/rllib/utils/exploration/__init__.py,sha256=C6G1mWMyWnbbTz_n5VEAOY2HL9Sv97JscP51_vyLiuM,1668
+ray/rllib/utils/exploration/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/curiosity.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/epsilon_greedy.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/exploration.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/gaussian_noise.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/ornstein_uhlenbeck_noise.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/parameter_noise.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/per_worker_epsilon_greedy.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/per_worker_gaussian_noise.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/per_worker_ornstein_uhlenbeck_noise.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/random.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/random_encoder.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/slate_epsilon_greedy.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/slate_soft_q.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/soft_q.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/stochastic_sampling.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/thompson_sampling.cpython-312.pyc,,
+ray/rllib/utils/exploration/__pycache__/upper_confidence_bound.cpython-312.pyc,,
+ray/rllib/utils/exploration/curiosity.py,sha256=PJfvnCtZ_Vnbiq3n5EQ9yLd3nl7iWaMNcF1wUEJqJ9o,18662
+ray/rllib/utils/exploration/epsilon_greedy.py,sha256=bqrxJDDBMABqh8nZ5TwmAyeQaWvwCTisUSyVFut5YFY,9429
+ray/rllib/utils/exploration/exploration.py,sha256=2FBIMHB_Q6_THx4SO3ykYKFnkL8vhNnVhCBJIPTDcY8,7177
+ray/rllib/utils/exploration/gaussian_noise.py,sha256=zxnORAjWgAI4LS2pkntKOphHYF5v9evknvtgsTJMaDY,9197
+ray/rllib/utils/exploration/ornstein_uhlenbeck_noise.py,sha256=UP2oj795iVEMThSOT44vnVkw_yfHlerUzRTcpQolw9U,10453
+ray/rllib/utils/exploration/parameter_noise.py,sha256=ZCItU8dbCBK4jo3_wBxYABHKtKfC26Tp0dLpB4pxUfw,17226
+ray/rllib/utils/exploration/per_worker_epsilon_greedy.py,sha256=Be-L3YmXWVH_PH04EylrC-wx_4TCmPDYPl6cnwskpqQ,2146
+ray/rllib/utils/exploration/per_worker_gaussian_noise.py,sha256=yCSoATQPTFKKMfPYUioHxNhveb6QOLCmsLLSA4r12UY,1779
+ray/rllib/utils/exploration/per_worker_ornstein_uhlenbeck_noise.py,sha256=X4pxf36lXnTRLPfSpBRKZ0Gs2s92rQ8E5QWNSC39YCE,1947
+ray/rllib/utils/exploration/random.py,sha256=mpFe8kT1N3HvKf5SjGrMJqq-vCQ3bahbgJOWLD_csJo,6661
+ray/rllib/utils/exploration/random_encoder.py,sha256=wyIhJHCnXe7HHnhT4265IpbwTLI2XtO6-OxrYXrQUjE,10695
+ray/rllib/utils/exploration/slate_epsilon_greedy.py,sha256=mS-xxsFlIl_2qq-E74Q-KU0KMNM0chj4GncY3PrKdVE,3871
+ray/rllib/utils/exploration/slate_soft_q.py,sha256=RBOHsroxQQIPXS9gwFFR9XtpJOLj1L_j5qkGZIhazJA,1509
+ray/rllib/utils/exploration/soft_q.py,sha256=xwfIj73GjUKVpeCcV2Q6vjHkWwkbkJu_BRE25AWX7W4,2115
+ray/rllib/utils/exploration/stochastic_sampling.py,sha256=5DKneMLQRjPhLrscbRz5nlFmxedGOoVmf3hXwTglVHk,5427
+ray/rllib/utils/exploration/thompson_sampling.py,sha256=P9WGW-CyCRIgIWoTevqbVDyf2dYYzIq0rkgBW0ER49E,1490
+ray/rllib/utils/exploration/upper_confidence_bound.py,sha256=fmvRbfNkX45JJB_XbGq5N8mWWXMDBTmd7y8h53z-iXE,1405
+ray/rllib/utils/filter.py,sha256=2DdS0pjBcX1DZfnSFrPMZdmmFs2CkHR6jE6RHoMw60w,17209
+ray/rllib/utils/filter_manager.py,sha256=8xIe_spWnh9A6BaMkp8RsntXWYmzcrZl5XLG9arxDqM,3238
+ray/rllib/utils/framework.py,sha256=mGU7sk_thhIqjXrxFseQcXPPrewb9dVQ965KxgEoy3o,13193
+ray/rllib/utils/from_config.py,sha256=IdDRz4BoPPGFcpY6QmPDmz0_k7OB4CWttUwvVv1rb5M,11848
+ray/rllib/utils/images.py,sha256=F0eEkTYeCdDwvXvaLmKZgZg7Hl9H0QSs_erjqbABEW0,1557
+ray/rllib/utils/lambda_defaultdict.py,sha256=Z7qlHYxnUsqLELE_AF585VGdG6GtbT-_bnpNjjuViyw,1998
+ray/rllib/utils/memory.py,sha256=P6KChN2h5tjD3YbQM5A3bN7ShjKC0jjAqyi5-qcldOM,247
+ray/rllib/utils/metrics/__init__.py,sha256=0UvwWxLqwrdHEN33YHjczzoEbuZROIULDHOdSDsBBZg,9616
+ray/rllib/utils/metrics/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/learner_info.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/metrics_logger.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/ray_metrics.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/stats.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/utils/metrics/__pycache__/window_stat.cpython-312.pyc,,
+ray/rllib/utils/metrics/learner_info.py,sha256=MmI1DWuwOAIDdkvwsruSPeunkWOEETIwHmYCVIsB89k,4441
+ray/rllib/utils/metrics/metrics_logger.py,sha256=FOaD8DbPaRLJJRJnHIObDNVRpDsTtIYp7u5U_MyQbi8,65398
+ray/rllib/utils/metrics/ray_metrics.py,sha256=7gk0fqZSqoB37aHxsxboF6aICQ6LaP7NuEgHLamNxi8,1498
+ray/rllib/utils/metrics/stats.py,sha256=cZmKUMPT7os4fIdyW69e1_BkU7QxGN_OlMmp9EontKM,44884
+ray/rllib/utils/metrics/utils.py,sha256=j3C_-vS2G2OT6KwITc29Z5Enei6hOEgX9A67aH6sra8,641
+ray/rllib/utils/metrics/window_stat.py,sha256=6m5JtxatluH1qJrdZFYR9DT6b3P1IyPEMGHqsvkPDRI,2553
+ray/rllib/utils/minibatch_utils.py,sha256=T9zo1EIEdIIeo7zOC-bTeS3sieFWMntLnULiq9RKG0k,16208
+ray/rllib/utils/numpy.py,sha256=JDcnNOOcSwnU8MgLXxh9IbIxb5LuWAJz-OvNBqODcjM,20024
+ray/rllib/utils/policy.py,sha256=kIuuSTYDVQuF4FCCFvCfrkhMkAo2WbkjHaXMgpJXxAU,11239
+ray/rllib/utils/postprocessing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/utils/postprocessing/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/postprocessing/__pycache__/episodes.cpython-312.pyc,,
+ray/rllib/utils/postprocessing/__pycache__/value_predictions.cpython-312.pyc,,
+ray/rllib/utils/postprocessing/__pycache__/zero_padding.cpython-312.pyc,,
+ray/rllib/utils/postprocessing/episodes.py,sha256=lXW2IZJCwH-k8V8jGgqngcl_jIjrPLPBmdm3ogQUnVQ,5263
+ray/rllib/utils/postprocessing/value_predictions.py,sha256=ts899IEI25w-vnNnJaMoXNDhvhxZKRFNS5GUDhEgm9o,4313
+ray/rllib/utils/postprocessing/zero_padding.py,sha256=FGLvU44UnhC_pjIj_KMq9FsI_c_y1s3uhOI5TBxPcag,9571
+ray/rllib/utils/pre_checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/utils/pre_checks/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/pre_checks/__pycache__/env.cpython-312.pyc,,
+ray/rllib/utils/pre_checks/env.py,sha256=iotuhWJAtGDbN211XbdaWpv3ThjJ6zqIEXSDnjIzt7Y,10850
+ray/rllib/utils/replay_buffers/__init__.py,sha256=Gaaj2ZdHmIfTqNt9vNqA5332DPLPGoClnt5tpEkGWZc,1632
+ray/rllib/utils/replay_buffers/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/base.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/episode_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/fifo_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/multi_agent_episode_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/multi_agent_mixin_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/multi_agent_prioritized_episode_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/multi_agent_prioritized_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/multi_agent_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/prioritized_episode_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/prioritized_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/reservoir_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/simple_replay_buffer.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/__pycache__/utils.cpython-312.pyc,,
+ray/rllib/utils/replay_buffers/base.py,sha256=S4X19ys7b8xpgJX-rq5Nm-zRMGG_pMJWHPqBUWaB_LE,2271
+ray/rllib/utils/replay_buffers/episode_replay_buffer.py,sha256=QnBwGbJGNILwcNfjHab2SKavSTUprzhRsis8mFfePDg,66613
+ray/rllib/utils/replay_buffers/fifo_replay_buffer.py,sha256=it5NSwASkZDd5KCChpmqfo_uA-bD_c55HPlno3FgXjQ,3494
+ray/rllib/utils/replay_buffers/multi_agent_episode_buffer.py,sha256=Oepfi3GYYAw7we05sigDSalqpkbDCWuFPt7fNbOi3RA,54164
+ray/rllib/utils/replay_buffers/multi_agent_mixin_replay_buffer.py,sha256=ioNhKLlaF9YqFQLXY1btUl422z5ktwLymbQZqmDUb7A,16712
+ray/rllib/utils/replay_buffers/multi_agent_prioritized_episode_buffer.py,sha256=U4WSfyDsbVfEo9_gvzR8IlX1fvBz6xpshYFZ8fiSGa0,50957
+ray/rllib/utils/replay_buffers/multi_agent_prioritized_replay_buffer.py,sha256=C6AjxeggjpJ2tanUwlXxLkgAp42BVqhkGMkh0w30ruM,12279
+ray/rllib/utils/replay_buffers/multi_agent_replay_buffer.py,sha256=EHfZrkFXAxqdzECKGgeqC_mbVis-itBrMPQ_lQF5_Po,16374
+ray/rllib/utils/replay_buffers/prioritized_episode_buffer.py,sha256=aOM_MClXSTA-oho0Isf5GCpaKa0QfUIkUrsCjIj_2qA,33219
+ray/rllib/utils/replay_buffers/prioritized_replay_buffer.py,sha256=MNCvZm3h9k6nbMajVRIZc5ZsnG6kZLJhwbSK5mJFmuE,8803
+ray/rllib/utils/replay_buffers/replay_buffer.py,sha256=f706thKSB3FFJNd_FhLoQujggXcqiaBegiWWojyHSyI,14390
+ray/rllib/utils/replay_buffers/reservoir_replay_buffer.py,sha256=W_6tk7627x4_UyVeilGetA4f5LgmdwMa32Ox-TXAdAM,4533
+ray/rllib/utils/replay_buffers/simple_replay_buffer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/utils/replay_buffers/utils.py,sha256=pERN2vY3LjbgBYGZ15Ncbxz78x21iPU7DJb0oBjEZ5w,18038
+ray/rllib/utils/runners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/utils/runners/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/runners/__pycache__/runner.cpython-312.pyc,,
+ray/rllib/utils/runners/__pycache__/runner_group.cpython-312.pyc,,
+ray/rllib/utils/runners/runner.py,sha256=lZbQuJYy1byzfN5EGT_Iibq4kU7U-KGqvmn2nbQYEbU,3255
+ray/rllib/utils/runners/runner_group.py,sha256=A6-FE6jW6wV5QaS_oRQ8Elgj10QYPaBrG3gMWsD2S68,30324
+ray/rllib/utils/schedules/__init__.py,sha256=NH_Y_BjCwNgGQjIJ4ZItDAp7ff-dVmY9pgJKIutzfqo,584
+ray/rllib/utils/schedules/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/constant_schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/exponential_schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/linear_schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/piecewise_schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/polynomial_schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/schedule.cpython-312.pyc,,
+ray/rllib/utils/schedules/__pycache__/scheduler.cpython-312.pyc,,
+ray/rllib/utils/schedules/constant_schedule.py,sha256=SJhiuhFc6MbIikZbs6BNfZCiETedK9JF7vZ8vJa3VU0,1002
+ray/rllib/utils/schedules/exponential_schedule.py,sha256=nyZs9t8Ertg-QdakTH4bmKIIMNVTDX2afRvEHMZyZLc,1833
+ray/rllib/utils/schedules/linear_schedule.py,sha256=9Dn7AW7rmXNL4yeEYSKlQMymnLEiPlmEVshisb211_o,528
+ray/rllib/utils/schedules/piecewise_schedule.py,sha256=bQBq-nEVPfLv53A8EsxJsppI9E-Q2htzBzBPH1FdxB8,4172
+ray/rllib/utils/schedules/polynomial_schedule.py,sha256=7qamzs2z_cJ-aYiLl-yQjGx4qxPS_IQKMncX6Z5U3qk,2215
+ray/rllib/utils/schedules/schedule.py,sha256=27P7t8oIWDI4Op9KKurscfdSiCU0OUO5OC30ZPqZTzQ,2265
+ray/rllib/utils/schedules/scheduler.py,sha256=sOfErVaJGkOK6QP8A7jt30bk05HHNzGEZbwU02M77Z8,6825
+ray/rllib/utils/serialization.py,sha256=FRS4Z1ev7d2ANWZN7fCWtaR2bPOd61ICR45H3SPvWFo,13448
+ray/rllib/utils/sgd.py,sha256=SPkOOMynT7O8hZ2iZSqZVC9MceL7aSPRU6OshxN4xPk,4603
+ray/rllib/utils/spaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/rllib/utils/spaces/__pycache__/__init__.cpython-312.pyc,,
+ray/rllib/utils/spaces/__pycache__/flexdict.cpython-312.pyc,,
+ray/rllib/utils/spaces/__pycache__/repeated.cpython-312.pyc,,
+ray/rllib/utils/spaces/__pycache__/simplex.cpython-312.pyc,,
+ray/rllib/utils/spaces/__pycache__/space_utils.cpython-312.pyc,,
+ray/rllib/utils/spaces/flexdict.py,sha256=IEl0J84bS8DSlTByosF3WcsJP6cIefPJlKJzTK94UJo,1298
+ray/rllib/utils/spaces/repeated.py,sha256=tXyq2rSD1jH91WDpeNQHTC3yohfTyRs16sAL8vJ2PYQ,1110
+ray/rllib/utils/spaces/simplex.py,sha256=75oReN-TAsFew2YKq-HZDlB9Rnt0FcW0XlxfugP8tSI,1881
+ray/rllib/utils/spaces/space_utils.py,sha256=e-oLSLDP2pORrJsvx5VljEUrdgEyL_c6L2u74QBd1-s,17983
+ray/rllib/utils/tensor_dtype.py,sha256=iMP2MNkXN5NDo2areVouQZ7E8iOMTGeZP6T8nLm40Xw,1893
+ray/rllib/utils/test_utils.py,sha256=9gEyx-6R7COKmAb7gQ53xZwgZYvt5qHRHto_xz5grJ4,73037
+ray/rllib/utils/tf_run_builder.py,sha256=BkviB1rYBghKi2CE2-nZ2RZdvbGe6BdcyC12JzyAgfw,3879
+ray/rllib/utils/tf_utils.py,sha256=IQQJ7iQDfMEwHAAO_Obe_x5DX4gPaZsWnG5qVFrsDK0,36914
+ray/rllib/utils/threading.py,sha256=e91Bv1KdeXLbaa7xx7Rfb1CMpx-dq34vTuaos6XWOGo,1032
+ray/rllib/utils/torch_utils.py,sha256=7JLLK7Q7CAQjRkDUqMsSbQxyeiLhkak0EFBBKOztq0g,30945
+ray/rllib/utils/typing.py,sha256=Hg_LuN7WuWwBNWrI1M1m2KiR_zpHS20autTD7nHXzJo,11848
+ray/runtime_context.py,sha256=Dvy0NCbAzqMV2ZW1nQl65hZcCD6JZTOSbFs-DrkLRc0,19883
+ray/runtime_env/__init__.py,sha256=-hdkF3iZF7KGkZFt7Fw3yy2rWR_CQNh970SUI5D67d0,145
+ray/runtime_env/__pycache__/__init__.cpython-312.pyc,,
+ray/runtime_env/__pycache__/runtime_env.cpython-312.pyc,,
+ray/runtime_env/runtime_env.py,sha256=KyFRJkBN9bKwb2l5Xc9tvwrbaxl2Ek8a2S4zLWecpYY,26189
+ray/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/scripts/__pycache__/__init__.cpython-312.pyc,,
+ray/scripts/__pycache__/scripts.cpython-312.pyc,,
+ray/scripts/__pycache__/symmetric_run.cpython-312.pyc,,
+ray/scripts/scripts.py,sha256=D_wW8O8IBNiZ3cvXCX2XtrOcaJUnODWCGFx0jwxmh7s,92459
+ray/scripts/symmetric_run.py,sha256=QDtsIzRqWkP5gFcTlksExDMAlMJSjEPL3QTHxn7zi-Q,9055
+ray/serve/__init__.py,sha256=31_DECK9vsFFVa4V1egSDgPQjrwUyumnYCBMQruaAdw,1460
+ray/serve/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/__pycache__/api.cpython-312.pyc,,
+ray/serve/__pycache__/autoscaling_policy.cpython-312.pyc,,
+ray/serve/__pycache__/batching.cpython-312.pyc,,
+ray/serve/__pycache__/config.cpython-312.pyc,,
+ray/serve/__pycache__/context.cpython-312.pyc,,
+ray/serve/__pycache__/dag.cpython-312.pyc,,
+ray/serve/__pycache__/deployment.cpython-312.pyc,,
+ray/serve/__pycache__/exceptions.cpython-312.pyc,,
+ray/serve/__pycache__/gradio_integrations.cpython-312.pyc,,
+ray/serve/__pycache__/grpc_util.cpython-312.pyc,,
+ray/serve/__pycache__/handle.cpython-312.pyc,,
+ray/serve/__pycache__/metrics.cpython-312.pyc,,
+ray/serve/__pycache__/multiplex.cpython-312.pyc,,
+ray/serve/__pycache__/request_router.cpython-312.pyc,,
+ray/serve/__pycache__/schema.cpython-312.pyc,,
+ray/serve/__pycache__/scripts.cpython-312.pyc,,
+ray/serve/__pycache__/task_consumer.cpython-312.pyc,,
+ray/serve/__pycache__/task_processor.cpython-312.pyc,,
+ray/serve/_private/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/__pycache__/api.cpython-312.pyc,,
+ray/serve/_private/__pycache__/application_state.cpython-312.pyc,,
+ray/serve/_private/__pycache__/autoscaling_state.cpython-312.pyc,,
+ray/serve/_private/__pycache__/build_app.cpython-312.pyc,,
+ray/serve/_private/__pycache__/client.cpython-312.pyc,,
+ray/serve/_private/__pycache__/cluster_node_info_cache.cpython-312.pyc,,
+ray/serve/_private/__pycache__/common.cpython-312.pyc,,
+ray/serve/_private/__pycache__/config.cpython-312.pyc,,
+ray/serve/_private/__pycache__/constants.cpython-312.pyc,,
+ray/serve/_private/__pycache__/constants_utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/controller.cpython-312.pyc,,
+ray/serve/_private/__pycache__/controller_avatar.cpython-312.pyc,,
+ray/serve/_private/__pycache__/default_impl.cpython-312.pyc,,
+ray/serve/_private/__pycache__/deploy_utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/deployment_info.cpython-312.pyc,,
+ray/serve/_private/__pycache__/deployment_node.cpython-312.pyc,,
+ray/serve/_private/__pycache__/deployment_scheduler.cpython-312.pyc,,
+ray/serve/_private/__pycache__/deployment_state.cpython-312.pyc,,
+ray/serve/_private/__pycache__/endpoint_state.cpython-312.pyc,,
+ray/serve/_private/__pycache__/exceptions.cpython-312.pyc,,
+ray/serve/_private/__pycache__/grpc_util.cpython-312.pyc,,
+ray/serve/_private/__pycache__/handle_options.cpython-312.pyc,,
+ray/serve/_private/__pycache__/http_util.cpython-312.pyc,,
+ray/serve/_private/__pycache__/local_testing_mode.cpython-312.pyc,,
+ray/serve/_private/__pycache__/logging_utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/long_poll.cpython-312.pyc,,
+ray/serve/_private/__pycache__/metrics_utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/proxy.cpython-312.pyc,,
+ray/serve/_private/__pycache__/proxy_request_response.cpython-312.pyc,,
+ray/serve/_private/__pycache__/proxy_response_generator.cpython-312.pyc,,
+ray/serve/_private/__pycache__/proxy_router.cpython-312.pyc,,
+ray/serve/_private/__pycache__/proxy_state.cpython-312.pyc,,
+ray/serve/_private/__pycache__/replica.cpython-312.pyc,,
+ray/serve/_private/__pycache__/replica_result.cpython-312.pyc,,
+ray/serve/_private/__pycache__/router.cpython-312.pyc,,
+ray/serve/_private/__pycache__/task_consumer.cpython-312.pyc,,
+ray/serve/_private/__pycache__/test_utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/usage.cpython-312.pyc,,
+ray/serve/_private/__pycache__/utils.cpython-312.pyc,,
+ray/serve/_private/__pycache__/version.cpython-312.pyc,,
+ray/serve/_private/api.py,sha256=K49F6EtR0AhSjNeGHlEMv4Lsx9aR88N7lKFCf0feZ_A,10401
+ray/serve/_private/application_state.py,sha256=NMlaSCgGzZhovqM7oFEWYZM5lymgpEbWSA02Tf1aB9U,66558
+ray/serve/_private/autoscaling_state.py,sha256=ZLxHr9fTZijt8ztnQxhale-H6XiZxV3aOIaCw-n_SGI,41937
+ray/serve/_private/benchmarks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/benchmarks/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/common.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/handle_noop_latency.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/handle_throughput.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/http_noop_latency.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/locust_utils.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/microbenchmark.cpython-312.pyc,,
+ray/serve/_private/benchmarks/__pycache__/proxy_benchmark.cpython-312.pyc,,
+ray/serve/_private/benchmarks/common.py,sha256=DTGxmW42HS74UW-Y57fjU3SAloS72RpE9eRqtMpBvVo,8970
+ray/serve/_private/benchmarks/handle_noop_latency.py,sha256=8ujbO6wDskL3OYEpUHR85Rg5RKETg2THaxOxIAh3dD8,943
+ray/serve/_private/benchmarks/handle_throughput.py,sha256=jvGCdtFk35hL8fcXzg1wrtnev6EiEf9rJULYMfaZ600,1460
+ray/serve/_private/benchmarks/http_noop_latency.py,sha256=4txfJbA9s8RPUG69rOIu9NZM15wNRYrQgoDjVcApl8E,897
+ray/serve/_private/benchmarks/locust_utils.py,sha256=i5RJ4-r5aUyv-z3u-3Ky2rZIUwuj4aObu7v8KboUbtI,8707
+ray/serve/_private/benchmarks/microbenchmark.py,sha256=KzNS7CUr9qPWBbx_Grvoj9mGgDC8HJQaHWl0mwF7cig,5273
+ray/serve/_private/benchmarks/proxy_benchmark.py,sha256=RE98C15Ddmx23YbMwBdzQt45am2dprfWiI0bKyVgpR0,8997
+ray/serve/_private/benchmarks/serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/benchmarks/serialization/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/benchmarks/serialization/__pycache__/common.cpython-312.pyc,,
+ray/serve/_private/benchmarks/serialization/common.py,sha256=iu6KBTA1iUX0O9Uiz3T1qRcLHiSqhhMwwjO8reL_-T8,802
+ray/serve/_private/benchmarks/streaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/benchmarks/streaming/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/__pycache__/common.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/__pycache__/streaming_core_throughput.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/__pycache__/streaming_grpc_throughput.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/__pycache__/streaming_handle_throughput.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/__pycache__/streaming_http_throughput.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/_grpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/benchmarks/streaming/_grpc/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/_grpc/__pycache__/grpc_server.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/_grpc/__pycache__/test_server_pb2_grpc.cpython-312.pyc,,
+ray/serve/_private/benchmarks/streaming/_grpc/grpc_server.py,sha256=fgNQLAikGwUac2gWCot9IKPCaWzFO0BoDsxIOlTZcGc,1860
+ray/serve/_private/benchmarks/streaming/_grpc/test_server_pb2_grpc.py,sha256=jMH9tTMpZKGJ2fzoJtGyGVGzxZKseo81fd9FQ6rels8,8850
+ray/serve/_private/benchmarks/streaming/common.py,sha256=YBC2Uhk-quzsK2Ib0Mzz4bn6JD6GadrUvMteQ-sY-SA,3627
+ray/serve/_private/benchmarks/streaming/streaming_core_throughput.py,sha256=wtuA4VUjFCqs4tkjRk4ww3UKje5wFm4f10VCW6aD-kg,2350
+ray/serve/_private/benchmarks/streaming/streaming_grpc_throughput.py,sha256=OswRDb00-hcvwKvgGNYt825ETWc7EMvXI79ynIgDOTM,6015
+ray/serve/_private/benchmarks/streaming/streaming_handle_throughput.py,sha256=sR-N6tXV7yymHl0SG29c5gvXEqiEwuUGHfKblt8QqJw,2363
+ray/serve/_private/benchmarks/streaming/streaming_http_throughput.py,sha256=6sxqagqQpR48eJ0ha9q9OwT1aYm_0KbdCcHc7F6U0U8,3701
+ray/serve/_private/build_app.py,sha256=MWiIBKjIsTIPSete-9OPd2gckJGPeSUtFoZrEOfdZXY,8094
+ray/serve/_private/client.py,sha256=XEmboFsc1DvZoJBQKfeK46LTkEcAeZiC7ifoa1L1pPw,23351
+ray/serve/_private/cluster_node_info_cache.py,sha256=BFe4cv35R-SkrKg3nFpw1Hhf_0ul7Uwe9XLbY_R8Pwk,3721
+ray/serve/_private/common.py,sha256=OyWfqa7hk2CZSrs2dODBlqFjrnvrL6d-aGwM8p8TIe4,33355
+ray/serve/_private/config.py,sha256=1TO1_igA-vWgi3iH5oE3I0N-ckwgshga8DmAqFIO7m0,34922
+ray/serve/_private/constants.py,sha256=6Ih0WJyi6QOkEfh6L3nRx3sxhXxex3CweXg1ldi_L8Y,18797
+ray/serve/_private/constants_utils.py,sha256=V7WESPcWq3pU_J8ojI71lJiDPGoLpbF7XpkmHCZEJ7o,9681
+ray/serve/_private/controller.py,sha256=TVVFFFvNBXlyPjf6Bsf_jCsGlk5O4VRvb1N7V8pWABM,50331
+ray/serve/_private/controller_avatar.py,sha256=m5V41cV60w-X8FOCVE5jJT-jpdUdd9o4nkAQsh4Ojew,1442
+ray/serve/_private/default_impl.py,sha256=IDE7ozNGnp52_giRwBNWxiwol1mgET8bfFhl46ic0Us,8187
+ray/serve/_private/deploy_utils.py,sha256=JJfqSAgReFcFKO8V7K0uzH5Cm0cEgmI7l55zJqIv96w,4675
+ray/serve/_private/deployment_info.py,sha256=4JRsLF_P_G5iLN6TjTZndSCzmMGNMlDUMOCPl9rzF_E,6232
+ray/serve/_private/deployment_node.py,sha256=gv1qjTmG17li46BHpKqwroOfh6sx3L9b5mKPUMaVZ0Y,1770
+ray/serve/_private/deployment_scheduler.py,sha256=uqFQ-Bq_Yx8HUN9yQtPCIBanacEMRhiNWgd53LV99g0,31592
+ray/serve/_private/deployment_state.py,sha256=wUZpLf3YGnpxQI0Z_4v_wmdh3zsSgvsNC1MPrNpIq6I,150065
+ray/serve/_private/endpoint_state.py,sha256=snU37WHweQZN59NPxGEhu2G88z6RgrCV1r1lqmxYc7g,3806
+ray/serve/_private/exceptions.py,sha256=reotGz6jF0u3pc__T4fpga4Rjee_414kmwy85QdZI3c,145
+ray/serve/_private/grpc_util.py,sha256=QoVHTrtnFFbQ9fERm8sB-XTwytp2Q4lAm-sxUHe6QBY,6768
+ray/serve/_private/handle_options.py,sha256=3-9QWU7onmcgq2xeehX0s5JQXs9OR62-_DlPKGsHzYA,2263
+ray/serve/_private/http_util.py,sha256=EGEaCUpGx0BLbtp2fTkbn83DApuMLgtHSc8AJjyp2nU,30149
+ray/serve/_private/local_testing_mode.py,sha256=bXezRG2wxXvshuqIbEwsOWON6V6QCzuD15FzYPKO85Y,11922
+ray/serve/_private/logging_utils.py,sha256=3q-zc97WSe8zxqL9ZKACHlcQtQgXifLFnwZx3T6FogE,19041
+ray/serve/_private/long_poll.py,sha256=8lILtsGdNg3fqz_0Nbg1WdmczRfeG1vd3SmkWfAFKVM,20124
+ray/serve/_private/metrics_utils.py,sha256=uzsqeRkn-kDO46Kwem1RMRqjiUKb6FF7F6t-kXNDyCg,17070
+ray/serve/_private/proxy.py,sha256=IDaVV1qicnBAjKfoa-wEIAVNiUrsPvhxCNld4a7A68c,52588
+ray/serve/_private/proxy_request_response.py,sha256=tcyQv3wzoRdIrIY8jyBWoV9ITYoAX-qtEQ-jCmXsC2c,6058
+ray/serve/_private/proxy_response_generator.py,sha256=bBuvzu0ROZeGmYY6-JR8BxQ82VvElzDT9ddNGFCVnbo,6491
+ray/serve/_private/proxy_router.py,sha256=6G3o21e9E80LRtztKwrj46z76kWGzsh1fwueeQLh1HI,10213
+ray/serve/_private/proxy_state.py,sha256=Eq33B4GObVQF_t1thpIkiBysSh9MGT6TrfvI4fpXqec,30085
+ray/serve/_private/replica.py,sha256=w1mql_K7HXljIKOQdbK5wPMAwvPITeTlGzSwpR-96eo,90781
+ray/serve/_private/replica_result.py,sha256=X59SitUixmByv3y0xqW6cyjrF1MO6Hl0HNI7fjQDXEI,8817
+ray/serve/_private/request_router/__init__.py,sha256=imAkj50ssUFq1_2_5HMyElV-J8PZQl0zTW55qVu2WYA,393
+ray/serve/_private/request_router/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/request_router/__pycache__/common.cpython-312.pyc,,
+ray/serve/_private/request_router/__pycache__/pow_2_router.cpython-312.pyc,,
+ray/serve/_private/request_router/__pycache__/replica_wrapper.cpython-312.pyc,,
+ray/serve/_private/request_router/__pycache__/request_router.cpython-312.pyc,,
+ray/serve/_private/request_router/common.py,sha256=4rpkOGxyCNUG7WlJuhUzxBKg9riPLXhTkRh-Z2ui49E,3602
+ray/serve/_private/request_router/pow_2_router.py,sha256=ceDWz25bSWe2P9797FTHvw7iPLeXL-mVTBvwXODG7UY,3129
+ray/serve/_private/request_router/replica_wrapper.py,sha256=p_to8gSaY7HJN5lZKqU6GJXsyQhbuGJyIm-pmxkVdPg,6898
+ray/serve/_private/request_router/request_router.py,sha256=5GDDcsuJC6yEXLUpL1hysZLv9RdIcWWC5Cl2JC4AeUk,49492
+ray/serve/_private/router.py,sha256=EdVhwQJsXMtYsJnKBf9M4xnckPmHQhQVm7K-3x-ZUcA,49950
+ray/serve/_private/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/storage/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/storage/__pycache__/kv_store.cpython-312.pyc,,
+ray/serve/_private/storage/__pycache__/kv_store_base.cpython-312.pyc,,
+ray/serve/_private/storage/kv_store.py,sha256=cxvTn7CTCKwp8asu1Ol3E5evy0vuVXWdbU-Bfsc5H5g,3597
+ray/serve/_private/storage/kv_store_base.py,sha256=UwhSwo1fYAWDB77qJ2ksHpDIdSA5r7T41o9dESUUVOA,1532
+ray/serve/_private/task_consumer.py,sha256=cWUbpl1xc3uZFRQ0P-tBHOHAahiJ1_5DzuVuBTdqYrM,221
+ray/serve/_private/test_utils.py,sha256=k7wJfmM0vDGniDTakC5-OEna34m8YM0ueTRF08PIPK0,27147
+ray/serve/_private/thirdparty/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/serve/_private/thirdparty/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/_private/thirdparty/__pycache__/get_asgi_route_name.cpython-312.pyc,,
+ray/serve/_private/thirdparty/get_asgi_route_name.py,sha256=woqYXpQm3ce2pdkjWphTgm2fJm7zsLf9NrFgxVCJAFQ,5629
+ray/serve/_private/usage.py,sha256=5ZeTG4_xyzdnlYPkMdH52zZcHDs-nh3lTLgfez4jHCw,2580
+ray/serve/_private/utils.py,sha256=o9rmk_9teNnjidjrCdcZ0kL2Gq-Nebr_UPuU2XU_R0A,23017
+ray/serve/_private/version.py,sha256=cxKQJdO6BEveId94JDvuOjZF7n-I5mHQLyCxd-C7Hpg,9283
+ray/serve/api.py,sha256=fAawVbxCzmV6ikBw6TwU1yzCOWsNyw-pSqU3JT6d7-A,39718
+ray/serve/autoscaling_policy.py,sha256=EmfKvLgjpq08caEtdByAeVXmr_iwHIuc3aZLO-x9caE,7498
+ray/serve/batching.py,sha256=BhB1GXoA23JDe23e679w8Jji7Kn8z4lYIC8Qow9bhAw,28662
+ray/serve/config.py,sha256=kyfpy24HjiYMCpt9HwCiNeEbCk35faUQloPrc8ZQ98E,26951
+ray/serve/context.py,sha256=fFjcMAZFI-MFkSOLWXk-64CqaIaJaJbLFHl_cGhZQxQ,11300
+ray/serve/dag.py,sha256=6rXZdW60pdKgF0FltQvqduwR9NCLkwfHJ6rIBbe8aQg,73
+ray/serve/deployment.py,sha256=BtmG0NNKoN9gmw0f2WcPOqDRjQRZWepFQRqYUNdcD4E,19169
+ray/serve/exceptions.py,sha256=XqlDd_KWWfFB7yvkPIqkuRCDWkQEfUQLSK1HD3Hscws,1786
+ray/serve/generated/__pycache__/serve_pb2.cpython-312.pyc,,
+ray/serve/generated/__pycache__/serve_pb2_grpc.cpython-312.pyc,,
+ray/serve/generated/serve_pb2.py,sha256=dii3eMy-sFohqunWFljgPIqR81DAdrLqwk-sadfM5yE,37806
+ray/serve/generated/serve_pb2_grpc.py,sha256=uwqOT_p3A5WEdTvbGJ1m9WeSLGVRYMn0zLh41ZFG9oA,18527
+ray/serve/gradio_integrations.py,sha256=1IJAUrR_tB7xwwfI5dVNTgrslb3jkfDEb1kVKOqAAu0,968
+ray/serve/grpc_util.py,sha256=FKpVvDuaSEAncAb3tNtKzBN3QcSoAyfzlZJEKqDC5MY,5939
+ray/serve/handle.py,sha256=XceCa3G5MZmp51Nc-06B3V91QELtv3A-aipqXc7zues,29338
+ray/serve/llm/__init__.py,sha256=xWw3F7wrWBhJwUsUj_9vPDv_o_pBTajQ-Wm00TsskKg,12794
+ray/serve/llm/__pycache__/__init__.cpython-312.pyc,,
+ray/serve/llm/__pycache__/deployment.cpython-312.pyc,,
+ray/serve/llm/__pycache__/gen_config.cpython-312.pyc,,
+ray/serve/llm/__pycache__/ingress.cpython-312.pyc,,
+ray/serve/llm/__pycache__/openai_api_models.cpython-312.pyc,,
+ray/serve/llm/__pycache__/request_router.cpython-312.pyc,,
+ray/serve/llm/deployment.py,sha256=VUiPz3Gi_mpjQRLg78SLhEAg8saT1BmIquRmeZ2QAjE,4145
+ray/serve/llm/gen_config.py,sha256=RclEGKfWyQ0ePdL3mIiB2fYG_-P6FrPlKh-CqWE0S-k,200
+ray/serve/llm/ingress.py,sha256=ImoqdrqinJlODLLQ8q5sJwPadkZxKff3ohWUzF8pBfs,2811
+ray/serve/llm/openai_api_models.py,sha256=qu9R5hOYvAmxGyHeL3zIAN4232hoR2Ic2otfE869Z8s,3320
+ray/serve/llm/request_router.py,sha256=arcbHN2RnefuB9CfM47HDLXQ04j1cGPypeuTS--z52s,1797
+ray/serve/metrics.py,sha256=0fd0A1-ODcv8JlcBNNRlceO38wVoMbdo-BcUig0i8AQ,9272
+ray/serve/multiplex.py,sha256=IZMAygFYW0k1DjbV-LKv4K1djFYAE20sL2rihy4yAO4,11015
+ray/serve/request_router.py,sha256=GQ7Crm-ga3gDOubaPFZ_vMYRESzzyhCprDDvsMdrzPo,479
+ray/serve/schema.py,sha256=FAN5lYZ3sw_U4Ac-hqNmacssAn-xs-LqL-Db9Nm0wv0,56564
+ray/serve/scripts.py,sha256=E3tV67VIFFPrBIjMidst8a8Gli4g0mdp8zBDq2TmRLg,29271
+ray/serve/task_consumer.py,sha256=nP2Z19N61tjluq6O7n8VHUbXaPptLK7l-8Ni2mRABSE,7220
+ray/serve/task_processor.py,sha256=qgSpdZ44AxOt2xJ-S6ig0-am27lnsNGHQIramxRz_ZU,12922
+ray/setup-dev.py,sha256=RLAPsyr6WEiGsKDuUn_o2Wi8xN-11mnlClCrmIZXu-o,6844
+ray/thirdparty_files/colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/thirdparty_files/colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158
+ray/thirdparty_files/colorama-0.4.6.dist-info/RECORD,sha256=seVTCEP4TJmavRMV14xcqS0IAZkfHqm2f0-q5ErSwk0,2263
+ray/thirdparty_files/colorama-0.4.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/thirdparty_files/colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105
+ray/thirdparty_files/colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491
+ray/thirdparty_files/colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266
+ray/thirdparty_files/colorama/__pycache__/__init__.cpython-312.pyc,,
+ray/thirdparty_files/colorama/__pycache__/ansi.cpython-312.pyc,,
+ray/thirdparty_files/colorama/__pycache__/ansitowin32.cpython-312.pyc,,
+ray/thirdparty_files/colorama/__pycache__/initialise.cpython-312.pyc,,
+ray/thirdparty_files/colorama/__pycache__/win32.cpython-312.pyc,,
+ray/thirdparty_files/colorama/__pycache__/winterm.cpython-312.pyc,,
+ray/thirdparty_files/colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522
+ray/thirdparty_files/colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128
+ray/thirdparty_files/colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325
+ray/thirdparty_files/colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181
+ray/thirdparty_files/colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134
+ray/thirdparty_files/psutil-7.1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ray/thirdparty_files/psutil-7.1.3.dist-info/LICENSE,sha256=uJwGOzeG4o4MCjjxkx22H-015p3SopZvvs_-4PRsjRA,1548
+ray/thirdparty_files/psutil-7.1.3.dist-info/METADATA,sha256=NXjcTuX0PlQtKYOXQbnj2HYMPA5djDiOwrC5gL2MZqE,23082
+ray/thirdparty_files/psutil-7.1.3.dist-info/RECORD,sha256=vO-p-4W4K8qN5OyFARlUxJQNQ2TyvAdR5_NPJhuxAJI,4587
+ray/thirdparty_files/psutil-7.1.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/thirdparty_files/psutil-7.1.3.dist-info/WHEEL,sha256=k0riQKE5_0GXActL4egeNSDuQ6nsFSKiWV91AD_j7xg,184
+ray/thirdparty_files/psutil-7.1.3.dist-info/top_level.txt,sha256=gCNhn57wzksDjSAISmgMJ0aiXzQulk0GJhb2-BAyYgw,7
+ray/thirdparty_files/psutil/__init__.py,sha256=7BTYRH1NsMvPBQyeRKqecd5d-JcxaeNFwnXtPYckDzo,87789
+ray/thirdparty_files/psutil/__pycache__/__init__.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_common.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_psaix.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_psbsd.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_pslinux.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_psosx.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_psposix.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_pssunos.cpython-312.pyc,,
+ray/thirdparty_files/psutil/__pycache__/_pswindows.cpython-312.pyc,,
+ray/thirdparty_files/psutil/_common.py,sha256=kDPOMktstdNqB0MbHgCvjR2wl8DQmwfIauBZXOsYLsI,28584
+ray/thirdparty_files/psutil/_psaix.py,sha256=Pcos7TWAKSoU-eNFQ-u8wpMKlOS5xnA1f1TMgtmpC2s,18179
+ray/thirdparty_files/psutil/_psbsd.py,sha256=K_Dn2NU6XsgW2ZTrVB5sH3AaQ6TGc3Q09a4rUcX-a6w,30701
+ray/thirdparty_files/psutil/_pslinux.py,sha256=FFjrlyH8_vZndau5DBLpXFxePZ0AozDIKtF0Tn0_BgM,86321
+ray/thirdparty_files/psutil/_psosx.py,sha256=ixb1t2ketUSzt8_AzVkixT8cly8qf7J5qet8KLZsZIo,16541
+ray/thirdparty_files/psutil/_psposix.py,sha256=5pBE2Mk8LCWLthOpgKM5_S6AJOSankF0dHrozwtDi-Y,7141
+ray/thirdparty_files/psutil/_pssunos.py,sha256=K99FU9-0k1W08ynXxX8LFaUaz4e59Iu-4vgp6gYSNCE,24848
+ray/thirdparty_files/psutil/_psutil_linux.abi3.so,sha256=EiNyuoEmue7geZAxz5owrp6iFwHUfWsGEjtrDtgLJ_E,146080
+ray/thirdparty_files/psutil/_pswindows.py,sha256=QInpXnEXWc_FyN0cHkgb53p-USMbWEgRqQG95iGnH64,36532
+ray/train/__init__.py,sha256=p66VE185n5cdZLbw5-sbB3OcO_PKjQ5YNwcj42HI6x0,3852
+ray/train/__pycache__/__init__.cpython-312.pyc,,
+ray/train/__pycache__/_checkpoint.cpython-312.pyc,,
+ray/train/__pycache__/backend.cpython-312.pyc,,
+ray/train/__pycache__/base_trainer.cpython-312.pyc,,
+ray/train/__pycache__/constants.cpython-312.pyc,,
+ray/train/__pycache__/context.cpython-312.pyc,,
+ray/train/__pycache__/data_parallel_trainer.cpython-312.pyc,,
+ray/train/__pycache__/error.cpython-312.pyc,,
+ray/train/__pycache__/predictor.cpython-312.pyc,,
+ray/train/__pycache__/session.cpython-312.pyc,,
+ray/train/__pycache__/trainer.cpython-312.pyc,,
+ray/train/__pycache__/utils.cpython-312.pyc,,
+ray/train/_checkpoint.py,sha256=YWOQiql-um_YqqlfgytVa8ABkxQU8lk7sxZE-QR4IAQ,17068
+ray/train/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/train/_internal/__pycache__/accelerator.cpython-312.pyc,,
+ray/train/_internal/__pycache__/backend_executor.cpython-312.pyc,,
+ray/train/_internal/__pycache__/base_worker_group.cpython-312.pyc,,
+ray/train/_internal/__pycache__/checkpoint_manager.cpython-312.pyc,,
+ray/train/_internal/__pycache__/data_config.cpython-312.pyc,,
+ray/train/_internal/__pycache__/dl_predictor.cpython-312.pyc,,
+ray/train/_internal/__pycache__/framework_checkpoint.cpython-312.pyc,,
+ray/train/_internal/__pycache__/session.cpython-312.pyc,,
+ray/train/_internal/__pycache__/storage.cpython-312.pyc,,
+ray/train/_internal/__pycache__/syncer.cpython-312.pyc,,
+ray/train/_internal/__pycache__/utils.cpython-312.pyc,,
+ray/train/_internal/__pycache__/worker_group.cpython-312.pyc,,
+ray/train/_internal/accelerator.py,sha256=Dko-pPYKMhpcmuSH6cKW-hJvqx8brgmuB0_aCLxcXX8,107
+ray/train/_internal/backend_executor.py,sha256=15W6LW4GgXu8leNH_DfBmZgubdoZrvjqgPDZOmduxg4,30697
+ray/train/_internal/base_worker_group.py,sha256=Lanu2N9VHtIVUEYL2R7LtbLalMF3me77T5QgNHmBrds,3406
+ray/train/_internal/checkpoint_manager.py,sha256=-CXCXymuVmMUwne45Kwdghg5If3SHdOfrtreqUmHHug,8074
+ray/train/_internal/data_config.py,sha256=zZT3TJL0IEHYN_NZjLcECNr27jlRdhohuJWCAv4OdVs,6059
+ray/train/_internal/dl_predictor.py,sha256=821wcP_AxBj0fDRCyhgJD0U9nI2qdCLd4eezmiuvRTk,3570
+ray/train/_internal/framework_checkpoint.py,sha256=v1Xb5wniN546Dde5jrKZDQ3lje6dPcQQ4Q-8tVEt1tE,1491
+ray/train/_internal/session.py,sha256=UupZ5LostwbnGNrZtKdLyoUQh3VgKRlXVIjFfUdx7Vs,41239
+ray/train/_internal/state/__init__.py,sha256=MmAOOS_XhXYiecXSy9TQmY_4WHYsXjmH7dBlevWKwpQ,315
+ray/train/_internal/state/__pycache__/__init__.cpython-312.pyc,,
+ray/train/_internal/state/__pycache__/export.cpython-312.pyc,,
+ray/train/_internal/state/__pycache__/schema.cpython-312.pyc,,
+ray/train/_internal/state/__pycache__/state_actor.cpython-312.pyc,,
+ray/train/_internal/state/__pycache__/state_manager.cpython-312.pyc,,
+ray/train/_internal/state/export.py,sha256=zrrY2Ubr8aZf8d5IyiCDLP7Q7sBL672iFWVFogxlbZU,3729
+ray/train/_internal/state/schema.py,sha256=gQ332m8CC7xVqngyRACrK2B5ycBMM7xQRGXOSxHNLXQ,5093
+ray/train/_internal/state/state_actor.py,sha256=KFv-QNzlG1sj9skx71Kh1JhyTkgAgwMMeYixwpaGy3c,5054
+ray/train/_internal/state/state_manager.py,sha256=7cv3wWQik94EYYLIjliVGhXMSlJ_-G3XAATZiiyYEwM,4323
+ray/train/_internal/storage.py,sha256=tRYorn49qaUmL-eqmCVc2zcISnr3YODZ1Pu6q2r5fm0,27646
+ray/train/_internal/syncer.py,sha256=aoyosq9OHT6U7vqqYYPOdLSgScVuVHhZnU0jMPH8-0c,14003
+ray/train/_internal/utils.py,sha256=TlwdOJtanRGRpzQ25ypvSNYYLoF1_PI8cC-mIn0bt-A,6851
+ray/train/_internal/worker_group.py,sha256=vwS6IbhzbVlXKdtjbuuW2MHPisJEZ9Z-rzG3iPtKe7c,15665
+ray/train/backend.py,sha256=R5jqM_64p631p2kqWrIA-2V65zeYudPMv-Epe7S4M1I,1761
+ray/train/base_trainer.py,sha256=ujIxHQStrWLQOEFVpCL9EGA6C5eeWd_KTpWePeq3phQ,37414
+ray/train/collective/__init__.py,sha256=Gj0iEoGatq-o2t6oIeOPehod1cEPA2zB-lLm_HN7yi4,560
+ray/train/collective/__pycache__/__init__.cpython-312.pyc,,
+ray/train/collective/__pycache__/collectives.cpython-312.pyc,,
+ray/train/collective/collectives.py,sha256=2aQ5Z1vjoyacMqLaMPJUdDtZYGqyJyPDq77Pir1nMxY,2405
+ray/train/constants.py,sha256=UtAlwkuytnkQeALr7p38Nrd1uxp9QXtS7_J3bxXiIaA,6261
+ray/train/context.py,sha256=Iv_2bQhsyAzUigbrw6maBngLjG9wvzk0rIarkEm4n2c,4745
+ray/train/data_parallel_trainer.py,sha256=Y-4op8jv6fctCQd-NyL-PQQAWGHUJzUljIAsyaXCsEQ,22551
+ray/train/error.py,sha256=euJNlXHzrvcM40f831icdRoX5zV4SjRTJNEC-G2BOo8,183
+ray/train/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/horovod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/horovod/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/horovod/__pycache__/horovod_example.cpython-312.pyc,,
+ray/train/examples/horovod/__pycache__/horovod_pytorch_example.cpython-312.pyc,,
+ray/train/examples/horovod/__pycache__/horovod_tune_example.cpython-312.pyc,,
+ray/train/examples/horovod/horovod_example.py,sha256=uhCeg40nFaOiilucOSBedHkjRSqk-3VmTvAo-23Z4Xg,8286
+ray/train/examples/horovod/horovod_pytorch_example.py,sha256=IcdBk7RVE38cHHP048ryxQlzQgqu4mHC1P7frW4jRuA,8268
+ray/train/examples/horovod/horovod_tune_example.py,sha256=L0cCd0DX55eX2PpaGrFaWR3u7CLxEIjdrMmhwHfn0a0,3946
+ray/train/examples/pytorch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/pytorch/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/pytorch/__pycache__/torch_fashion_mnist_example.cpython-312.pyc,,
+ray/train/examples/pytorch/__pycache__/torch_linear_example.cpython-312.pyc,,
+ray/train/examples/pytorch/__pycache__/torch_quick_start.cpython-312.pyc,,
+ray/train/examples/pytorch/__pycache__/torch_regression_example.cpython-312.pyc,,
+ray/train/examples/pytorch/torch_data_prefetch_benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/pytorch/torch_data_prefetch_benchmark/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/pytorch/torch_data_prefetch_benchmark/__pycache__/auto_pipeline_for_host_to_device_data_transfer.cpython-312.pyc,,
+ray/train/examples/pytorch/torch_data_prefetch_benchmark/auto_pipeline_for_host_to_device_data_transfer.py,sha256=WIwsCRd0Ry1RK7W0cCBFJcpye4B-FXcXxd7yL8zEkas,4444
+ray/train/examples/pytorch/torch_fashion_mnist_example.py,sha256=03VBsFQ1_GrD6nDh7mRA5qy5PGzH_JFpzw7iI9K1Q5k,4851
+ray/train/examples/pytorch/torch_linear_example.py,sha256=uwgchCs6CUWVz41ppWocEmJDyXEYMQifMEeo7qSNmfw,4264
+ray/train/examples/pytorch/torch_quick_start.py,sha256=vQ5E5SN5D4BlRnVUe2b28NWYqMHvYWFV_fifg_fCtc0,2882
+ray/train/examples/pytorch/torch_regression_example.py,sha256=ziv6tkmqbz5LTCZuvX3VLsoNFdEZkglmCwq7M3N9kbU,4657
+ray/train/examples/pytorch_geometric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/pytorch_geometric/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/pytorch_geometric/__pycache__/distributed_sage_example.cpython-312.pyc,,
+ray/train/examples/pytorch_geometric/distributed_sage_example.py,sha256=3Zv-gcOtb4eKmX-k8wJ1OI4R7GnzXku3agX5FkFUsC8,8004
+ray/train/examples/tf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/examples/tf/__pycache__/__init__.cpython-312.pyc,,
+ray/train/examples/tf/__pycache__/tensorflow_autoencoder_example.cpython-312.pyc,,
+ray/train/examples/tf/__pycache__/tensorflow_mnist_example.cpython-312.pyc,,
+ray/train/examples/tf/__pycache__/tensorflow_quick_start.cpython-312.pyc,,
+ray/train/examples/tf/__pycache__/tensorflow_regression_example.cpython-312.pyc,,
+ray/train/examples/tf/tensorflow_autoencoder_example.py,sha256=7ewtUQmk-43J5vB9Yo2ZBIPnji7q1rD1sqC2qSQrBE8,5553
+ray/train/examples/tf/tensorflow_mnist_example.py,sha256=K594Xnvg8tYe6vjwhjjA-t7Edktt4I00uCPwcuVMTk4,4302
+ray/train/examples/tf/tensorflow_quick_start.py,sha256=OWZ1LcqSuIzpkP_HyY6axDHYTBnvv-b_HBSCfVmUtrI,2789
+ray/train/examples/tf/tensorflow_regression_example.py,sha256=jj8zWD8GEih2OnomgxBTcKPdO7dhnmjgwZW5fUNTWRA,3507
+ray/train/horovod/__init__.py,sha256=tm8swrwj4TENah8D-Eqj4CGGu_-BRjnANP9gmty7XXw,712
+ray/train/horovod/__pycache__/__init__.cpython-312.pyc,,
+ray/train/horovod/__pycache__/config.cpython-312.pyc,,
+ray/train/horovod/__pycache__/horovod_trainer.cpython-312.pyc,,
+ray/train/horovod/config.py,sha256=7n6ze29mofmXM7_yeGyydE-trcqDoz4V-kEE5q9crr4,6086
+ray/train/horovod/horovod_trainer.py,sha256=WdtWokrMCNf2HtfdmwBeglUVTtfFjf7BnRJ9QHxPhRg,7968
+ray/train/huggingface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/huggingface/__pycache__/__init__.cpython-312.pyc,,
+ray/train/huggingface/transformers/__init__.py,sha256=Ib3o0MSFGA4LlFDVM6O3tWTSIqz1ylXHNcmIDUGMooQ,229
+ray/train/huggingface/transformers/__pycache__/__init__.cpython-312.pyc,,
+ray/train/huggingface/transformers/__pycache__/_transformers_utils.cpython-312.pyc,,
+ray/train/huggingface/transformers/_transformers_utils.py,sha256=9Ae7s4UliVuLg2qm_JNzsDuIqnPWHCV5SoiVCt4WsyM,5865
+ray/train/lightgbm/__init__.py,sha256=WGmAfVKrQlpDYdkKyS_a0ophi-lRoUVpIbRpAGGgRbw,718
+ray/train/lightgbm/__pycache__/__init__.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/_lightgbm_utils.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/config.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/lightgbm_checkpoint.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/lightgbm_predictor.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/lightgbm_trainer.cpython-312.pyc,,
+ray/train/lightgbm/__pycache__/v2.cpython-312.pyc,,
+ray/train/lightgbm/_lightgbm_utils.py,sha256=ekwPlcKWZ283XX8QpcpnkiGtWEIEl3tzMRafO3dHIZA,7543
+ray/train/lightgbm/config.py,sha256=Fbec0RjTzxjZX1vGn7gGl6uiHh6bvLQy-6zBbTvieB8,3091
+ray/train/lightgbm/lightgbm_checkpoint.py,sha256=dGaZGW9M_fL621cGswF80b4ZS0Rrm3wB7nV6SEqXg-8,2542
+ray/train/lightgbm/lightgbm_predictor.py,sha256=XUIJEE3QkxHZ-6bd6-j54jWcoVHBVgSvSa5T5pO5yis,5532
+ray/train/lightgbm/lightgbm_trainer.py,sha256=Y4MRk-KB02mxJjv6Zqcf8SQQ1k1p9dt3Jh38hWy8zlc,13540
+ray/train/lightgbm/v2.py,sha256=kKyLm3O0ZygQDpnoX6SrsAgTdJgs283RW8irv5ISYck,5714
+ray/train/lightning/__init__.py,sha256=Aehbb6TxsXA7uWMuvQJvk-vFyikW13N2FS_6y-L5aFc,767
+ray/train/lightning/__pycache__/__init__.cpython-312.pyc,,
+ray/train/lightning/__pycache__/_lightning_utils.cpython-312.pyc,,
+ray/train/lightning/_lightning_utils.py,sha256=QWj1z-qlwTAB6ULHNkK0Y2_aFk6xlJpmRu34ePjJoC8,10615
+ray/train/predictor.py,sha256=JCbutZP5SAcOZQ-HAYKfUkefh9MTLufvYaqI8IFHFJ8,9689
+ray/train/session.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/tensorflow/__init__.py,sha256=iTXFUv6FkmBiggPhqFXt-ZrE5P_mtdVmRqT0Ek4mflk,966
+ray/train/tensorflow/__pycache__/__init__.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/config.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/keras.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/tensorflow_checkpoint.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/tensorflow_predictor.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/tensorflow_trainer.cpython-312.pyc,,
+ray/train/tensorflow/__pycache__/train_loop_utils.cpython-312.pyc,,
+ray/train/tensorflow/config.py,sha256=CcKcTQb8ACnW96uJCBqn8zzq-jnd7Rob5fWOVjqEMTk,1845
+ray/train/tensorflow/keras.py,sha256=WdRY-g0brF_dHgb54P1g09PkJFM-N0YTDXVKgtF4nGo,7342
+ray/train/tensorflow/tensorflow_checkpoint.py,sha256=k_9YVg5Z3KpakG8WwjI8M5d8X3WNHFO4i3ehyiPSHyk,5524
+ray/train/tensorflow/tensorflow_predictor.py,sha256=_P7ncCJJ4YPuu-1es9RWvt04HevQPuQbP24GEpSifn0,9625
+ray/train/tensorflow/tensorflow_trainer.py,sha256=RVGsoSzoFJBF5jG-3kmSRTJf1IbTkkfIww7MP68w89o,7436
+ray/train/tensorflow/train_loop_utils.py,sha256=d6_L-_lvVp1Jgqm5fncHLKNAJvplkITt-7kVtIgVcN0,934
+ray/train/torch/__init__.py,sha256=0fxqMHEUTpXTZb-s5PNpjzijw5Sj-drFeVnoHY5ZCL0,1479
+ray/train/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/train/torch/__pycache__/config.cpython-312.pyc,,
+ray/train/torch/__pycache__/torch_checkpoint.cpython-312.pyc,,
+ray/train/torch/__pycache__/torch_detection_predictor.cpython-312.pyc,,
+ray/train/torch/__pycache__/torch_predictor.cpython-312.pyc,,
+ray/train/torch/__pycache__/torch_trainer.cpython-312.pyc,,
+ray/train/torch/__pycache__/train_loop_utils.cpython-312.pyc,,
+ray/train/torch/config.py,sha256=JgZz25FP3tAA56JikZbjQDCAH7lfNR6uo9SMtCQOPz8,8451
+ray/train/torch/torch_checkpoint.py,sha256=t8SS2WcFk7BgfGR7FrG4eMje1DSv7Bs1h-ToPlvbR1A,6254
+ray/train/torch/torch_detection_predictor.py,sha256=KqM3SA2BMoydM3_llTLtOJqzDAFsF-S9mNuv3zMtrHw,2931
+ray/train/torch/torch_predictor.py,sha256=XgS64FwseUrsv9Q4J4zcb9RLEraPArFGec9lCYyrkPE,9649
+ray/train/torch/torch_trainer.py,sha256=7uGgalrz2oypNOIIVNEe1KYnUGCG8i-ZEQBEQbIZFTM,8482
+ray/train/torch/train_loop_utils.py,sha256=n-dfzIwdrSXzUShveKSOmH8GWnmrf90yYKxP2HIIrpk,29137
+ray/train/torch/xla/__init__.py,sha256=HqATrA5R9ncndEv6qkWLkMrCp29Veq9w0cSm6eQpxtw,91
+ray/train/torch/xla/__pycache__/__init__.cpython-312.pyc,,
+ray/train/torch/xla/__pycache__/config.cpython-312.pyc,,
+ray/train/torch/xla/config.py,sha256=19qkZ5UcnhPl8HdHgreCmOO49YW8WYzV7FGHufFmgL4,6465
+ray/train/trainer.py,sha256=UJZBObNzL7TdMYFu37S4ocq72rc4T01Uhw1gK_FjIfQ,6812
+ray/train/utils.py,sha256=Lsut8Asu-cUtzKpzg9LYY6fDwE5ZsxLYUfLnhkRigmA,807
+ray/train/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/__pycache__/constants.cpython-312.pyc,,
+ray/train/v2/_internal/__pycache__/exceptions.cpython-312.pyc,,
+ray/train/v2/_internal/__pycache__/migration_utils.cpython-312.pyc,,
+ray/train/v2/_internal/__pycache__/util.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__init__.py,sha256=DmbYUPyMIf51qhqONTeuL_iMVvvYzM5VyzFiaMUnqbY,553
+ray/train/v2/_internal/callbacks/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/accelerators.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/backend_setup.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/datasets.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/env_callback.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/metrics.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/state_manager.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/tpu_reservation_callback.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/user_callback.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/__pycache__/working_dir_setup.cpython-312.pyc,,
+ray/train/v2/_internal/callbacks/accelerators.py,sha256=hZUl_-FskRSu8vjR4JQVe7DbvTCcspF9whAk-gxek20,5548
+ray/train/v2/_internal/callbacks/backend_setup.py,sha256=3eUrS42MELgFf_rCscIX4d1elT2jio1ATypVLIwp8iY,1056
+ray/train/v2/_internal/callbacks/datasets.py,sha256=0Av56bOVPmZoiRThvc7MUSfHEGbelQSBZfO-VgqCs6Y,4015
+ray/train/v2/_internal/callbacks/env_callback.py,sha256=8WpuPwZVGnjg-6rSYsj_bZg9Fb5qmZLpZfDw2jJtaZI,1410
+ray/train/v2/_internal/callbacks/metrics.py,sha256=kXYIvCH3fUYu3qhzmqXiyVIw8k1VzMt-VFlVHSgC8BU,3910
+ray/train/v2/_internal/callbacks/state_manager.py,sha256=Q3TSVGvajkMAeWhOZ0aS6ZLiUeWTXhL9AxMFtTNahAs,6382
+ray/train/v2/_internal/callbacks/tpu_reservation_callback.py,sha256=LVBU-znKQ8302LSfm1aaAJ99ALHeOcbPbaiwC2g-uaE,1662
+ray/train/v2/_internal/callbacks/user_callback.py,sha256=76VQtf8dBQhfZG7jHkp2OWhRkYE_EsdFU4ebwmascTk,1743
+ray/train/v2/_internal/callbacks/working_dir_setup.py,sha256=3hkjCiTRG7KlRTxgckwWNMC3Y_5D2d7Vq_XN_bNczl0,933
+ray/train/v2/_internal/constants.py,sha256=0A_NRcdTX6MxB_4zOday474xXl3tT2Svu4LeD_58pJI,5685
+ray/train/v2/_internal/data_integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/data_integration/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/data_integration/__pycache__/interfaces.cpython-312.pyc,,
+ray/train/v2/_internal/data_integration/interfaces.py,sha256=jSz5FnPNc4PD70iau-NZMf0OrgiN4gh2AIoNail9RGo,969
+ray/train/v2/_internal/exceptions.py,sha256=mP2XgFxrBKDrr17Lf8IzL7-a8WvOwY_jAB6aOcy-hn0,5935
+ray/train/v2/_internal/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/execution/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/callback.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/collective_impl.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/context.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/storage.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/train_fn_utils.cpython-312.pyc,,
+ray/train/v2/_internal/execution/__pycache__/training_report.cpython-312.pyc,,
+ray/train/v2/_internal/execution/callback.py,sha256=GHNhs38qKPowo5PeXA4Inv_Hu__oK-ltSfdrYOFWBzk,6430
+ray/train/v2/_internal/execution/checkpoint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/execution/checkpoint/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/checkpoint/__pycache__/checkpoint_manager.cpython-312.pyc,,
+ray/train/v2/_internal/execution/checkpoint/__pycache__/report_handler.cpython-312.pyc,,
+ray/train/v2/_internal/execution/checkpoint/__pycache__/sync_actor.cpython-312.pyc,,
+ray/train/v2/_internal/execution/checkpoint/__pycache__/validation_manager.cpython-312.pyc,,
+ray/train/v2/_internal/execution/checkpoint/checkpoint_manager.py,sha256=110D5WKCD7TZlLEcLXcoHYmQyz_xzvPM4ZaYOv7tdd8,15124
+ray/train/v2/_internal/execution/checkpoint/report_handler.py,sha256=IiY30_gum7Z0pvrEsIkW8PWONTi-mo6QDpMQzPJWP_Y,5329
+ray/train/v2/_internal/execution/checkpoint/sync_actor.py,sha256=5WZ3O0oeyVM74_HnntABMmVQ-3jy_sPH9IGKnvSzbeM,8732
+ray/train/v2/_internal/execution/checkpoint/validation_manager.py,sha256=r-VIkJOWRYgF79fds5Za5UbyHJBiEI5kHY3Tk2Wpqek,5627
+ray/train/v2/_internal/execution/collective_impl.py,sha256=CW83NLviqDK3nxrFm1UDFrF9kXwqLNAXTi1j5a1RY40,1880
+ray/train/v2/_internal/execution/context.py,sha256=4UlWP9TgbwS4yDT5QzaSrFJbjbXJEpfyljm1eT7LRSw,17346
+ray/train/v2/_internal/execution/controller/__init__.py,sha256=DFK8IXpD3lqtNhfaVhgPIy7erg45Gz9uwWdKqh6YC2Y,71
+ray/train/v2/_internal/execution/controller/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/controller/__pycache__/controller.cpython-312.pyc,,
+ray/train/v2/_internal/execution/controller/__pycache__/state.cpython-312.pyc,,
+ray/train/v2/_internal/execution/controller/controller.py,sha256=_CzFehDjUwzYLi7D2C4pLbEYOVZvKpChFMUjawgd-Pc,22919
+ray/train/v2/_internal/execution/controller/state.py,sha256=Ejsxq4KlswR-GFGPnjTyzkkffe1Jt2k6XxRnVDrfrmE,5682
+ray/train/v2/_internal/execution/failure_handling/__init__.py,sha256=38KZGMtyxQi79wQD6HN8sWbPnXXRFYGNmn90j3a4lgk,327
+ray/train/v2/_internal/execution/failure_handling/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/failure_handling/__pycache__/default.cpython-312.pyc,,
+ray/train/v2/_internal/execution/failure_handling/__pycache__/factory.cpython-312.pyc,,
+ray/train/v2/_internal/execution/failure_handling/__pycache__/failure_policy.cpython-312.pyc,,
+ray/train/v2/_internal/execution/failure_handling/default.py,sha256=ZgYUF9p4E5DxUhEIn-E358UbSyz1K7M043z-_PzbY04,3432
+ray/train/v2/_internal/execution/failure_handling/factory.py,sha256=lH2CoyIxNJVXVv2pCl8csLZccu88noxIQWeUrJx3-tw,417
+ray/train/v2/_internal/execution/failure_handling/failure_policy.py,sha256=tEil4omF6Yzf0u4dmD28AlOMYQsvNJnsturACryoGOE,779
+ray/train/v2/_internal/execution/local_mode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/execution/local_mode/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/local_mode/__pycache__/torch.cpython-312.pyc,,
+ray/train/v2/_internal/execution/local_mode/__pycache__/utils.cpython-312.pyc,,
+ray/train/v2/_internal/execution/local_mode/torch.py,sha256=neR-mXDSVmvACjeZM_iMfXoruAWy7e2JJoILSMeM-kw,3190
+ray/train/v2/_internal/execution/local_mode/utils.py,sha256=lCnj-7CDJ3LfJf60vrOMy4dj_IPa2ZAsa0EXBl4OMuk,1200
+ray/train/v2/_internal/execution/scaling_policy/__init__.py,sha256=ErmB335ru4RLODjGWtJz0YxlkUph-_fiePI8Qsly0XI,394
+ray/train/v2/_internal/execution/scaling_policy/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/scaling_policy/__pycache__/factory.cpython-312.pyc,,
+ray/train/v2/_internal/execution/scaling_policy/__pycache__/fixed.cpython-312.pyc,,
+ray/train/v2/_internal/execution/scaling_policy/__pycache__/scaling_policy.cpython-312.pyc,,
+ray/train/v2/_internal/execution/scaling_policy/factory.py,sha256=nxQKnFbjUuHbElXBVWdRYXzgp4KFEYbgPfWsKO5zQ9Q,423
+ray/train/v2/_internal/execution/scaling_policy/fixed.py,sha256=qZkjmPtPweqKfbLT9ry52mpcG8bQ35vkMyGNLanldhw,773
+ray/train/v2/_internal/execution/scaling_policy/scaling_policy.py,sha256=kjcX5EW2bsjqxM1oJgqV5t-zWcTvx3h0aG_qM0nQxsY,1698
+ray/train/v2/_internal/execution/storage.py,sha256=j4IL1nrVLETsiPb421MIkPyPcbKKixc8kpWth7o5rUo,21029
+ray/train/v2/_internal/execution/train_fn_utils.py,sha256=LyWzBgoDbVY0bQDxE8CWLO6Cu2OqqHFw9piW-v85YEs,10101
+ray/train/v2/_internal/execution/training_report.py,sha256=WT2UC7ktZXMdalBgAz0DQpiFCqyywFMRbLN_0dr3V4k,1124
+ray/train/v2/_internal/execution/worker_group/__init__.py,sha256=8qAup6gRMzStV4xTpYL_NjpTbWjVsvKseLuFfdKHcik,465
+ray/train/v2/_internal/execution/worker_group/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/__pycache__/poll.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/__pycache__/state.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/__pycache__/thread_runner.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/__pycache__/worker.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/__pycache__/worker_group.cpython-312.pyc,,
+ray/train/v2/_internal/execution/worker_group/poll.py,sha256=dsym_0uwLjqP8DUMr3qYnosBVpvW0yMBoQbtzH2Ua_U,4111
+ray/train/v2/_internal/execution/worker_group/state.py,sha256=nioXDTo6Tti1yVfztfHVmZD7GW7qpxg-PnwBmWU8ui4,4361
+ray/train/v2/_internal/execution/worker_group/thread_runner.py,sha256=6EMMcIm3hg7zcyfRDDhE-mbPU8BcBz_tqnD1QpTnG6s,3129
+ray/train/v2/_internal/execution/worker_group/worker.py,sha256=89ShFThLjTFB3-ti_fOImwBA0OK-WmRrJ2XfweUDFnQ,8649
+ray/train/v2/_internal/execution/worker_group/worker_group.py,sha256=pFE7JeYc7PAH82BSe3ZE7ux4_b_djU80Iwdg7q4ZhGI,32379
+ray/train/v2/_internal/logging/__init__.py,sha256=ikEwR6jPbgiSa4zan2lRHrS4wzx6rPy3j_ZilEHTu_o,66
+ray/train/v2/_internal/logging/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/logging/__pycache__/logging.cpython-312.pyc,,
+ray/train/v2/_internal/logging/__pycache__/patch_print.cpython-312.pyc,,
+ray/train/v2/_internal/logging/logging.py,sha256=XWrEU7Ue_XMlMdp2vyndAcnAmvQO-RMPW3cMxx75UeA,11618
+ray/train/v2/_internal/logging/patch_print.py,sha256=bKL-lBTCX8FSntz17gddJ9MwnRerb3LUuWntD462YwE,2557
+ray/train/v2/_internal/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/metrics/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/metrics/__pycache__/base.cpython-312.pyc,,
+ray/train/v2/_internal/metrics/__pycache__/controller.cpython-312.pyc,,
+ray/train/v2/_internal/metrics/__pycache__/worker.cpython-312.pyc,,
+ray/train/v2/_internal/metrics/base.py,sha256=wAvqYQfRtHY36LbGnz-piflX_PZrbF2axuMFfMPBkT8,4386
+ray/train/v2/_internal/metrics/controller.py,sha256=7iPtFJhvV25bgbQasfpaX4iMJsZtQ0Z8IytF3dG2tmE,2360
+ray/train/v2/_internal/metrics/worker.py,sha256=pvA0-jFQBE5ZCufDNImfi-fKtmezdfLBdBznPcsUIXE,1628
+ray/train/v2/_internal/migration_utils.py,sha256=-Slxd7i0_-JMkYSR3V-4I5UUMLF2Q2hUwTIsUusEZUA,2876
+ray/train/v2/_internal/state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/_internal/state/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/_internal/state/__pycache__/export.cpython-312.pyc,,
+ray/train/v2/_internal/state/__pycache__/schema.cpython-312.pyc,,
+ray/train/v2/_internal/state/__pycache__/state_actor.cpython-312.pyc,,
+ray/train/v2/_internal/state/__pycache__/state_manager.cpython-312.pyc,,
+ray/train/v2/_internal/state/__pycache__/util.cpython-312.pyc,,
+ray/train/v2/_internal/state/export.py,sha256=2SfPb7GhmtAByWISfOSU9YGZlNUuv6pwXeB-rqhTtgQ,4562
+ray/train/v2/_internal/state/schema.py,sha256=t2vH4YNzdHzE0VTX5MuuLlQvMnkMauvhLzhYV3lc0nk,9230
+ray/train/v2/_internal/state/state_actor.py,sha256=bsUSA9S5rMFyI82_q-c653G4vvGIBS-4xH6osxOLxFA,11304
+ray/train/v2/_internal/state/state_manager.py,sha256=XHx8B9InFExKnZ29C-J3_9hWk4P8DvBCcP7NtkbJLUs,8150
+ray/train/v2/_internal/state/util.py,sha256=sxo6HQsg977_npkBqRa20hk6mnfNioxAu-r2QXg_hSY,1486
+ray/train/v2/_internal/util.py,sha256=3ckxZUw2Emz-wOFSIDYraTh-anIKBzeijwQA0C5cpws,9074
+ray/train/v2/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/api/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/callback.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/config.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/context.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/data_parallel_trainer.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/exceptions.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/report_config.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/reported_checkpoint.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/result.cpython-312.pyc,,
+ray/train/v2/api/__pycache__/train_fn_utils.cpython-312.pyc,,
+ray/train/v2/api/callback.py,sha256=V3tu9O6RjRiV7pzqBp2pVSG9f-km1IyS4RseXRZDsFk,1716
+ray/train/v2/api/config.py,sha256=5pH_rXjFZ-U9iQ6C_t_y7bEsgRcnn-5UCEUwtwb_Mhk,13840
+ray/train/v2/api/context.py,sha256=GAApSAHl67G0dRvoA9-iXuRjOo6XDsEQw2fX3l-fD5U,7686
+ray/train/v2/api/data_parallel_trainer.py,sha256=HKmBHusvrpE9Z3T34eqCUFCwnPUQwsOnSCmsKpjL7aE,13192
+ray/train/v2/api/exceptions.py,sha256=FyT1uksSimHXTAScandF-DWFWUJhNmhQR2ot7EEbvaE,1739
+ray/train/v2/api/report_config.py,sha256=iTOESF5PsRCPPUROllPjsOAAjhKQpnDpc0zCbvJZRQ4,540
+ray/train/v2/api/reported_checkpoint.py,sha256=17Z1cThQewHA6c2TIQ_XQxNLrZtbGiktkox_WVR5aHk,511
+ray/train/v2/api/result.py,sha256=fRyx87X4oss-rasTqx-JAqyuMCugMhldKKQoFVkJYb0,4859
+ray/train/v2/api/train_fn_utils.py,sha256=5MbkV_VcgUmujDAk5CzUAXIlYtEeYiV1Wjv9HoMBw_8,10350
+ray/train/v2/horovod/__init__.py,sha256=Br95ZDGGdgreAX5Fgkc1Cmeqo-9A9dE6FaeATXL5Su0,39
+ray/train/v2/horovod/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/horovod/__pycache__/horovod_trainer.cpython-312.pyc,,
+ray/train/v2/horovod/horovod_trainer.py,sha256=voUsALtb6Rqf2TtpFo65PKwuRtBGFpZK-I7ypaOAyXI,1442
+ray/train/v2/jax/__init__.py,sha256=4bGMTXwBfDcULr4TsTVQ1YKH09_1rFaKlKXPDJOyZZ0,483
+ray/train/v2/jax/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/jax/__pycache__/config.cpython-312.pyc,,
+ray/train/v2/jax/__pycache__/jax_trainer.cpython-312.pyc,,
+ray/train/v2/jax/config.py,sha256=5n1tscfab6e_-SETtVCFx3661kkG4hwA8BlV-qLcV90,3994
+ray/train/v2/jax/jax_trainer.py,sha256=wJArvky0MYkxhueSpCdwFra_gss8NMy_nT17ILkGjYk,6629
+ray/train/v2/lightgbm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/lightgbm/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/lightgbm/__pycache__/lightgbm_trainer.cpython-312.pyc,,
+ray/train/v2/lightgbm/lightgbm_trainer.py,sha256=3_MvnEg56Q0PYNC_2oiG-AhspA2nJOyk8_XdaJ1NIqk,7197
+ray/train/v2/tensorflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/train/v2/tensorflow/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/tensorflow/__pycache__/tensorflow_trainer.cpython-312.pyc,,
+ray/train/v2/tensorflow/tensorflow_trainer.py,sha256=beHzhZDOdIxjgZiOnjIKIeUGv3R42qIown-aipZLjxA,7493
+ray/train/v2/torch/__init__.py,sha256=WCOUgvllFY-eSL_2aV37YvnxEipdYOHxKYf4_AcF4ro,92
+ray/train/v2/torch/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/torch/__pycache__/torch_trainer.cpython-312.pyc,,
+ray/train/v2/torch/__pycache__/train_loop_utils.cpython-312.pyc,,
+ray/train/v2/torch/torch_trainer.py,sha256=qgJd1WtY5AcKxr07oCqtiUof4RFt94DQS1OIUc9sfyg,8637
+ray/train/v2/torch/train_loop_utils.py,sha256=5GMS6DVgYYWrJ2WRX-Bj5hd8UY3UxPDyS7k55Yy62es,15589
+ray/train/v2/xgboost/__init__.py,sha256=eyoQprMTQg0Hl38SxtpZi8Cj40rny4l_qmnKbnPP89U,111
+ray/train/v2/xgboost/__pycache__/__init__.cpython-312.pyc,,
+ray/train/v2/xgboost/__pycache__/config.cpython-312.pyc,,
+ray/train/v2/xgboost/__pycache__/xgboost_trainer.cpython-312.pyc,,
+ray/train/v2/xgboost/config.py,sha256=LZQ2-dD2OShP1wXh-5i2HpnBuKSQC3vvGqWy3uA3ArY,711
+ray/train/v2/xgboost/xgboost_trainer.py,sha256=zhMKKt6D-DAGsP5fepZUDt4zoU5E4pWIqJGq_7xsREY,6853
+ray/train/xgboost/__init__.py,sha256=vcwud9pv4WZH3pZJuczmPLdpypdEcqU3enq7U0ufPsk,724
+ray/train/xgboost/__pycache__/__init__.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/_xgboost_utils.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/config.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/v2.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/xgboost_checkpoint.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/xgboost_predictor.cpython-312.pyc,,
+ray/train/xgboost/__pycache__/xgboost_trainer.cpython-312.pyc,,
+ray/train/xgboost/_xgboost_utils.py,sha256=nzU9fK9FT5abWrBVWRaL7gA0-5Zyc1Zw57UqWNhfrhc,8873
+ray/train/xgboost/config.py,sha256=upANRHKvyMwJfADAlXgCVVN6ueCO3H8bYUAc6nAD--8,6980
+ray/train/xgboost/v2.py,sha256=iqUfoyY4q5ohGyUkne-KC3Dwm4aWBMmsmJ2WtvWH46A,5655
+ray/train/xgboost/xgboost_checkpoint.py,sha256=svToFi-g7Uko5_R2C9-0B4ILJn1ep0tnOUi4YJP9yhs,2591
+ray/train/xgboost/xgboost_predictor.py,sha256=wZwT2pS1C1TQo-JLzZ4rNgP89qTlFOK-Sju5wnI2qIA,5718
+ray/train/xgboost/xgboost_trainer.py,sha256=6fXWk39vUlxia53ketrheS9lb_zNVx50e7Y2MK5EfOE,13184
+ray/tune/__init__.py,sha256=unATkNBZ-bueVCtajY4wclNV1CdGDf4_qlsRCg43GN8,2982
+ray/tune/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/__pycache__/callback.cpython-312.pyc,,
+ray/tune/__pycache__/constants.cpython-312.pyc,,
+ray/tune/__pycache__/context.cpython-312.pyc,,
+ray/tune/__pycache__/error.cpython-312.pyc,,
+ray/tune/__pycache__/progress_reporter.cpython-312.pyc,,
+ray/tune/__pycache__/registry.cpython-312.pyc,,
+ray/tune/__pycache__/resources.cpython-312.pyc,,
+ray/tune/__pycache__/result.cpython-312.pyc,,
+ray/tune/__pycache__/result_grid.cpython-312.pyc,,
+ray/tune/__pycache__/syncer.cpython-312.pyc,,
+ray/tune/__pycache__/tune.cpython-312.pyc,,
+ray/tune/__pycache__/tune_config.cpython-312.pyc,,
+ray/tune/__pycache__/tuner.cpython-312.pyc,,
+ray/tune/analysis/__init__.py,sha256=Sxr-eFig0Rj8jDrCJa32Daqjv_DO128TqnFqEdkG7Kg,103
+ray/tune/analysis/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/analysis/__pycache__/experiment_analysis.cpython-312.pyc,,
+ray/tune/analysis/experiment_analysis.py,sha256=5LpJ7jH1YYGjqWINP_hfvvV6SvEQZw3Z6RebJCQbjso,27759
+ray/tune/automl/__init__.py,sha256=UqhsT-DhBbmz6AOlDYz3ag6DZYRzRHo8htgqnIxmvlU,72
+ray/tune/automl/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/callback.py,sha256=sAXyNn5BIT_ykNIPggjlkDKy1nPgKn5K_1th0itvVYg,17047
+ray/tune/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/cli/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/cli/__pycache__/commands.cpython-312.pyc,,
+ray/tune/cli/__pycache__/scripts.cpython-312.pyc,,
+ray/tune/cli/commands.py,sha256=F8VgBXl7mDHSMkxHE_cYr9yIncpjeYYmq0LRerTQ8YE,9995
+ray/tune/cli/scripts.py,sha256=k3eklucFbN8SOEeLz2EBElrtZPG8G6abXzfjgY3FsIU,2826
+ray/tune/constants.py,sha256=XLkcZ-DJYx6a0fm5QcxdBR0n5VFdEN6Iylt_kkB4m70,1298
+ray/tune/context.py,sha256=YyyNGigLBB0NX1vyJswIyMK2cwI0p1tTMzz1uB5BGN4,3887
+ray/tune/error.py,sha256=16nO4FFJyHaxN-hjwaljJ8qCqjL7NV7oabY-DsTYDcM,1115
+ray/tune/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/examples/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/examples/__pycache__/async_hyperband_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/ax_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/bayesopt_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/bohb_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/cifar10_pytorch.cpython-312.pyc,,
+ray/tune/examples/__pycache__/custom_checkpointing_with_callback.cpython-312.pyc,,
+ray/tune/examples/__pycache__/custom_func_checkpointing.cpython-312.pyc,,
+ray/tune/examples/__pycache__/hyperband_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/hyperband_function_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/hyperopt_conditional_search_space_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/lightgbm_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/logging_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/mlflow_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/mlflow_ptl.cpython-312.pyc,,
+ray/tune/examples/__pycache__/mnist_ptl_mini.cpython-312.pyc,,
+ray/tune/examples/__pycache__/mnist_pytorch.cpython-312.pyc,,
+ray/tune/examples/__pycache__/mnist_pytorch_trainable.cpython-312.pyc,,
+ray/tune/examples/__pycache__/nevergrad_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/optuna_define_by_run_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/optuna_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/optuna_multiobjective_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pb2_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pb2_ppo_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_convnet_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_convnet_function_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_function.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_memnn_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_ppo_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/pbt_tune_cifar10_with_keras.cpython-312.pyc,,
+ray/tune/examples/__pycache__/tf_mnist_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/tune_basic_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/tune_mnist_keras.cpython-312.pyc,,
+ray/tune/examples/__pycache__/utils.cpython-312.pyc,,
+ray/tune/examples/__pycache__/xgboost_dynamic_resources_example.cpython-312.pyc,,
+ray/tune/examples/__pycache__/xgboost_example.cpython-312.pyc,,
+ray/tune/examples/async_hyperband_example.py,sha256=dQQe006pVOyhQIWtRmkkCFa_miGSzLwvvJK-c9iPBnQ,2067
+ray/tune/examples/ax_example.py,sha256=j5sWPm3zCSh2UyOy2AlwnPSB_bxfhp_nesmb41gXr68,2810
+ray/tune/examples/bayesopt_example.py,sha256=7YF_wuX9wtBQUtZOCUg6JfDlu3WiUHPf1_KQLswrcYw,1945
+ray/tune/examples/bohb_example.py,sha256=V-QCiyF6NfShT0S4lEOVjReyLJJ7P8BQbtnPR81otdU,3203
+ray/tune/examples/cifar10_pytorch.py,sha256=GdJQG7naNjVY3M30_qQidE3kQCTnrQo5psMJYYuV71Y,9332
+ray/tune/examples/custom_checkpointing_with_callback.py,sha256=8DHfbsYxaLOAegZuw4FSs0oNmMgB9vcuIQzl6btzlwM,8147
+ray/tune/examples/custom_func_checkpointing.py,sha256=8VWgM0S9QYER0d0syZUgZ8Z2kOm_0qIKGVSiIi1jzAU,2262
+ray/tune/examples/hyperband_example.py,sha256=t31YtY-Q-mMxZDusNhv1hYePGMWTgDabdFcGkLT1eBA,1437
+ray/tune/examples/hyperband_function_example.py,sha256=mMksl4C1acDCcXHfWcjqGl7xt5Z4nR5HNEj0fAyYII8,2499
+ray/tune/examples/hyperopt_conditional_search_space_example.py,sha256=DCKWem1Pt9Yw0tnzGxCYCWIkjnvKnyArYd6zXfXTxAk,3004
+ray/tune/examples/lightgbm_example.py,sha256=WOrcIWfKCtwKmpYLdquKRHqay_0-HxAiKImmhXvrxxw,3130
+ray/tune/examples/logging_example.py,sha256=acembYNzu5yE7odY5H1Jw3SH0DOJkjGRlPGaULjrfzQ,1877
+ray/tune/examples/mlflow_example.py,sha256=tNkRq5GtLUf6RU525t0zPrWxIxaXYET8UQ9zGuY-Ks4,3833
+ray/tune/examples/mlflow_ptl.py,sha256=bJ-2qrPH7NkBHkaP2cN_rfZl002-X7vUjRDEaxu47dA,3153
+ray/tune/examples/mnist_ptl_mini.py,sha256=IQvvczCYa5udukgsuwI_uO6RIfH2zHRVixXVi8EiMrg,5527
+ray/tune/examples/mnist_pytorch.py,sha256=QMJTB9x_d9bHAmqbaLNPhJT0ITxB3_xi6lGihk775IE,5044
+ray/tune/examples/mnist_pytorch_trainable.py,sha256=wbEurOpTEIXUqHyfFlgPUF8OmtC3CXixak1WqAAbSto,3106
+ray/tune/examples/nevergrad_example.py,sha256=VPIFuWdBhOyfPhO9nELxJWcH6LMQck5-uRx6svDp0Ps,2329
+ray/tune/examples/optuna_define_by_run_example.py,sha256=dskRFVkcnSCo6UiN0HNF-FxQqNFRH-KZiu29wK3VrkE,2969
+ray/tune/examples/optuna_example.py,sha256=6Yc5BXcUjah36ENicmxxh1ZORPvZpure4a_Xh2JDj4A,2165
+ray/tune/examples/optuna_multiobjective_example.py,sha256=1xvTmKklPOFCmeSLuT0qLQkIY3YhoMuX-1CGIgZuQ0I,2252
+ray/tune/examples/pb2_example.py,sha256=XHJeD1it3TwNfExzsvPd4SABF0b95ghxXMsZT1u1GgA,1818
+ray/tune/examples/pb2_ppo_example.py,sha256=_Cwb-o332TRH2kIBtDbH9oWT_GmKR5w9EPjvM2nB7wM,5391
+ray/tune/examples/pbt_convnet_example.py,sha256=7APMP4VygYfeB7SwOuDC3M1aWGwgl9m7m43F-n6zb04,4175
+ray/tune/examples/pbt_convnet_function_example.py,sha256=md6n9_BXoHobkUg2hDNB4Tw0rV_L3I2T4wb7VRH4oR0,4644
+ray/tune/examples/pbt_dcgan_mnist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/examples/pbt_dcgan_mnist/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/examples/pbt_dcgan_mnist/__pycache__/common.cpython-312.pyc,,
+ray/tune/examples/pbt_dcgan_mnist/__pycache__/pbt_dcgan_mnist_func.cpython-312.pyc,,
+ray/tune/examples/pbt_dcgan_mnist/__pycache__/pbt_dcgan_mnist_trainable.cpython-312.pyc,,
+ray/tune/examples/pbt_dcgan_mnist/common.py,sha256=zUOEcuyITpO9ae3lD3clYPO4IqyWIcpHhkDEgbmgGTg,8107
+ray/tune/examples/pbt_dcgan_mnist/pbt_dcgan_mnist_func.py,sha256=tBjz5sxL5LI6sh8sH3qoaYnn9XCKm9er4lChAIgUsqA,5964
+ray/tune/examples/pbt_dcgan_mnist/pbt_dcgan_mnist_trainable.py,sha256=oNr8QC_8ZiqxeC_ctCTrnEVBVftv5281wpsY2z8OuHo,5822
+ray/tune/examples/pbt_example.py,sha256=od1rVLlUqlgh3HkQ6wjbzuF9uxHnmDqLFV0w_1h1NCI,5070
+ray/tune/examples/pbt_function.py,sha256=xWqgcRMh81ykzKooz_QeUHyW1S0nuIuz_EuI9gLl4sU,6689
+ray/tune/examples/pbt_memnn_example.py,sha256=ovRr3xY-EXjHJ6vu4yRw6KKENMZ3omOxrX6D926jOrI,11098
+ray/tune/examples/pbt_ppo_example.py,sha256=BBXo4GsCGSoMP3KrZjDURqgFSAUHy2FHtXc4HlfY37o,2669
+ray/tune/examples/pbt_transformers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/examples/pbt_transformers/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/examples/pbt_transformers/__pycache__/pbt_transformers.cpython-312.pyc,,
+ray/tune/examples/pbt_transformers/__pycache__/utils.cpython-312.pyc,,
+ray/tune/examples/pbt_transformers/pbt_transformers.py,sha256=szWDgtp7maw9FIEwI22tVoVZbAIJbr1JbHW65sUWKhg,4932
+ray/tune/examples/pbt_transformers/utils.py,sha256=yx8y1lSDfDNKSivZGj6FTpeabxJhknsXJTjuLFERd9I,1571
+ray/tune/examples/pbt_tune_cifar10_with_keras.py,sha256=ZjoqcNy7yvD5t-lRTSCcvH6iv3bDIebnfX6zaODtTLE,7575
+ray/tune/examples/tf_mnist_example.py,sha256=JAwCI6tZVrfzMmvXaH8q3IxpwKHoFzKg_oPZ-bsqoXY,5060
+ray/tune/examples/tune_basic_example.py,sha256=X3zOhibZ_mD9OKyEwGXj3esUR9nF_ukp86c4JxqDvpo,1823
+ray/tune/examples/tune_mnist_keras.py,sha256=FffBGqx3fp2tG3RUJy0LKVmlwp-vs5apODgo-Vtw6rQ,2824
+ray/tune/examples/utils.py,sha256=G0HzAiN4HikUGD-zkR2zwdkJbXFjUs4LyeUwvhCT6wA,747
+ray/tune/examples/xgboost_dynamic_resources_example.py,sha256=GaAJCTb3pspAB3mXwuy-U8zZB4jAFR1-MGmpn0NxdEw,6516
+ray/tune/examples/xgboost_example.py,sha256=NiXiRAcZQlhb02x0qjh_b2qCweGIlhNFQoPNp2whYcw,3992
+ray/tune/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/execution/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/execution/__pycache__/class_cache.cpython-312.pyc,,
+ray/tune/execution/__pycache__/cluster_info.cpython-312.pyc,,
+ray/tune/execution/__pycache__/experiment_state.cpython-312.pyc,,
+ray/tune/execution/__pycache__/insufficient_resources_manager.cpython-312.pyc,,
+ray/tune/execution/__pycache__/placement_groups.cpython-312.pyc,,
+ray/tune/execution/__pycache__/tune_controller.cpython-312.pyc,,
+ray/tune/execution/class_cache.py,sha256=bf3E672_X6ZQXidavDoAFzGe_CKwlXBcK2lAke94GPU,2377
+ray/tune/execution/cluster_info.py,sha256=rwJC-i6hwLzF38tTVzjgYs64Xc6vwPJxGxU4QIVno7o,326
+ray/tune/execution/experiment_state.py,sha256=DBGYLLlFmw68rVLSzfivrOGmcfUMCwMObg0_WsJUO_o,11784
+ray/tune/execution/insufficient_resources_manager.py,sha256=P8L-ogsvuySa7Wti6rUpjM-OSCQWMX5rGzk5HtK3K9k,5982
+ray/tune/execution/placement_groups.py,sha256=FrtdH4Lu5VYMZFdPQFEpuQF0HuESqZ0cQX_1BgAiOTk,4176
+ray/tune/execution/tune_controller.py,sha256=hM13_kdvZdLYY-X-fyPgCpeuwUUSjPcGjgVA-bJmyDw,84556
+ray/tune/experiment/__init__.py,sha256=dSHNyUYJumOR_g6a3q945P960PsUc3LK34wJBkHyZMA,193
+ray/tune/experiment/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/experiment/__pycache__/config_parser.cpython-312.pyc,,
+ray/tune/experiment/__pycache__/experiment.cpython-312.pyc,,
+ray/tune/experiment/__pycache__/trial.cpython-312.pyc,,
+ray/tune/experiment/config_parser.py,sha256=bkrN9XDfPknDg51zxoY_t07AUgXIERuhyf38FR2VYEg,6997
+ray/tune/experiment/experiment.py,sha256=aajbMPPFmsmTVJO4-akr4nlVl6MFYRa3bMEOuZOGdBk,16042
+ray/tune/experiment/trial.py,sha256=Qt-47pD2eflVH334NLTmrigKexTmdE8rl8fDAkp7kvo,38610
+ray/tune/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/experimental/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/experimental/__pycache__/output.cpython-312.pyc,,
+ray/tune/experimental/output.py,sha256=lCfFrxrLVnFRx3G-Ev3M4SwhbZQHNxKdrvNaCVRon8k,32553
+ray/tune/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/impl/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/impl/__pycache__/config.cpython-312.pyc,,
+ray/tune/impl/__pycache__/out_of_band_serialize_dataset.cpython-312.pyc,,
+ray/tune/impl/__pycache__/placeholder.cpython-312.pyc,,
+ray/tune/impl/__pycache__/test_utils.cpython-312.pyc,,
+ray/tune/impl/__pycache__/tuner_internal.cpython-312.pyc,,
+ray/tune/impl/config.py,sha256=bgoW3SWEAQ3u7-J91ZcYNgtHv0Dwt_0IAEuZCB10Rnc,1746
+ray/tune/impl/out_of_band_serialize_dataset.py,sha256=NbgL8paX7IN3XPn9hy63qjvwdundFh-D5SWmHzrZ2VI,1056
+ray/tune/impl/placeholder.py,sha256=IWgvbFGRSty6tQnWL6CJljpGBxXgUFyT_9npR87j28w,8813
+ray/tune/impl/test_utils.py,sha256=pDWVF14rD8D8X6H9vvBf46oUNCCEwYIuBhe7QBvw0zc,2186
+ray/tune/impl/tuner_internal.py,sha256=vxmZB68aFPPfX26WtCVa76Xj7Y52wWcZm2EiPndurzY,27258
+ray/tune/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/tune/integration/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/integration/__pycache__/keras.cpython-312.pyc,,
+ray/tune/integration/__pycache__/lightgbm.cpython-312.pyc,,
+ray/tune/integration/__pycache__/pytorch_lightning.cpython-312.pyc,,
+ray/tune/integration/__pycache__/ray_train.cpython-312.pyc,,
+ray/tune/integration/__pycache__/xgboost.cpython-312.pyc,,
+ray/tune/integration/keras.py,sha256=bVl7D37MlxBqRRW89ESurVmNpvVcmxdqlRyjV0U5I6g,2516
+ray/tune/integration/lightgbm.py,sha256=PHFKgF5dtPohe7purkfljkd5RdvmSk2_lEhAVN7ee5w,3025
+ray/tune/integration/pytorch_lightning.py,sha256=fc8aG3F_C5IJa6xPQBhh6dlx8ILqam1XiWup1v8dlU0,7143
+ray/tune/integration/ray_train.py,sha256=Y0P_W1Y50PG2ftxGWTmIA5u_EOYiytleLspvag7td50,1513
+ray/tune/integration/xgboost.py,sha256=7mCYy2jx14dNW96dhbHIaJr5GizqRnl8EOZOCNmqinQ,3795
+ray/tune/logger/__init__.py,sha256=ec5n9fHOC6AYFe6mVK3_JQVsEm9QEQpiSUSDnChkjIQ,765
+ray/tune/logger/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/logger/__pycache__/aim.cpython-312.pyc,,
+ray/tune/logger/__pycache__/comet.cpython-312.pyc,,
+ray/tune/logger/__pycache__/csv.cpython-312.pyc,,
+ray/tune/logger/__pycache__/json.cpython-312.pyc,,
+ray/tune/logger/__pycache__/logger.cpython-312.pyc,,
+ray/tune/logger/__pycache__/mlflow.cpython-312.pyc,,
+ray/tune/logger/__pycache__/noop.cpython-312.pyc,,
+ray/tune/logger/__pycache__/tensorboardx.cpython-312.pyc,,
+ray/tune/logger/__pycache__/unified.cpython-312.pyc,,
+ray/tune/logger/__pycache__/wandb.cpython-312.pyc,,
+ray/tune/logger/aim.py,sha256=Dr0sTPvOYY4IbMo0sVHve5qQ20bAskfu8FvR176s0s8,6820
+ray/tune/logger/comet.py,sha256=09ZFx30_O-Ixa438gkpdb2VXyQaL6R9i6z6gJwqaiPw,117
+ray/tune/logger/csv.py,sha256=y412dEU18vaRZCCMDQ_pQ1lBLuD7l5ToY_f2ZTaGgtI,4149
+ray/tune/logger/json.py,sha256=bWXvg9cP-q4BNLxO53XGHZmeVHkYrKM7V7Q-upKPHCc,4179
+ray/tune/logger/logger.py,sha256=27FXI7Z9pBazie_NMhU0ERXk28jVJZh_SJhLBIs0oRc,8122
+ray/tune/logger/mlflow.py,sha256=iyOkDQZjyMwfBVvjBjWnwRyumU_Z63qTzUqjy3EMWCc,121
+ray/tune/logger/noop.py,sha256=j_LlYmHYMOELf6Umqyu5H4z3MoVd7WF33ozGYcgIM_I,246
+ray/tune/logger/tensorboardx.py,sha256=bE3Py7OaYAI8tUZgIKww6qka3AvYKJaA8nnwdf_sltY,12347
+ray/tune/logger/unified.py,sha256=GdRmhAZI9PgEb_U_X0gWqSz78xHi0EcBnCKzDvJhEmk,2318
+ray/tune/logger/wandb.py,sha256=huF0YInAclUPEwJ2nFnvv54eZ-Lz_dkRM-5PbCth5p0,117
+ray/tune/progress_reporter.py,sha256=pzNY4dsrNgAu_RyPtv6B9XsrU_Z5UVioZw_z9eEXL1c,58718
+ray/tune/registry.py,sha256=gxZ-onnXvApjJofIfzfmj1zwIXXmjPmdGjjL7kil9yA,9426
+ray/tune/resources.py,sha256=W54BRyE7POQi4xErotDBEDHPy_Cqb3EdkcLVFwk2N9o,2535
+ray/tune/result.py,sha256=BCyS4NQkXLnABeAMJsjw22Cluf1k_gMuftgYMQZy5GY,3484
+ray/tune/result_grid.py,sha256=_U17twulIJfXgERyZ5gFfyp5ZOWZya2Fynbyq_QNzaA,10432
+ray/tune/schedulers/__init__.py,sha256=MFKKUApee95fgsLJyxo1pABzyxw94XEU-0erobMIlfY,3021
+ray/tune/schedulers/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/async_hyperband.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/hb_bohb.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/hyperband.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/median_stopping_rule.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/pb2.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/pb2_utils.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/pbt.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/resource_changing_scheduler.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/trial_scheduler.cpython-312.pyc,,
+ray/tune/schedulers/__pycache__/util.cpython-312.pyc,,
+ray/tune/schedulers/async_hyperband.py,sha256=aKMF2Gptft3k4uB92HlP6iel8gGtZdj6leqovh_5I9Q,10201
+ray/tune/schedulers/hb_bohb.py,sha256=NkeBKbw8YlG8TV8QnlHeDtpiTh7neX5emedfIng6YZY,7700
+ray/tune/schedulers/hyperband.py,sha256=lIZxmYc4rMoNRaMy3Ybj6jTHP0KnbQK7AEXs_yB_nOw,24762
+ray/tune/schedulers/median_stopping_rule.py,sha256=Hsdw1N04HSfxoek_lx-RSmDQCfbFiO7ZJy-uYg3c9WY,8420
+ray/tune/schedulers/pb2.py,sha256=B9pdab2Yg6vnYGIyE9gJA3pst9HINESNZEx_if69S3M,19672
+ray/tune/schedulers/pb2_utils.py,sha256=DJh_vANYbvsjpCTTi7Gpqw-1Gz1Kun9vQruF7FIHJfA,5744
+ray/tune/schedulers/pbt.py,sha256=yOed19OPI9aVjuFHOqpJEk5YQexistX7cZ4kjCHVBUk,49324
+ray/tune/schedulers/resource_changing_scheduler.py,sha256=872qzWmz2-7XktCkNHcYgHR2w5RgT4-mntEErecEy38,34329
+ray/tune/schedulers/trial_scheduler.py,sha256=xnUinLLItShYuayPJtr-sEnJPgT0rXajApJsOyEw1XY,5484
+ray/tune/schedulers/util.py,sha256=37XMePBGzv0-pOfOMQZ2GVOtY_XeLNJ7QpsdUrwz6Yg,918
+ray/tune/search/__init__.py,sha256=LZKDcDeQNR2ZQ3YhM-5iBzxFn_QhHwF0LkzotW6bOsk,4485
+ray/tune/search/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/__pycache__/_mock.cpython-312.pyc,,
+ray/tune/search/__pycache__/basic_variant.cpython-312.pyc,,
+ray/tune/search/__pycache__/concurrency_limiter.cpython-312.pyc,,
+ray/tune/search/__pycache__/repeater.cpython-312.pyc,,
+ray/tune/search/__pycache__/sample.cpython-312.pyc,,
+ray/tune/search/__pycache__/search_algorithm.cpython-312.pyc,,
+ray/tune/search/__pycache__/search_generator.cpython-312.pyc,,
+ray/tune/search/__pycache__/searcher.cpython-312.pyc,,
+ray/tune/search/__pycache__/util.cpython-312.pyc,,
+ray/tune/search/__pycache__/variant_generator.cpython-312.pyc,,
+ray/tune/search/_mock.py,sha256=sC3y8LmTi2zqoPmGy67nsM1iC5NWiSldVXHwdVwbVR8,1744
+ray/tune/search/ax/__init__.py,sha256=FGvbOwy-mtT3_YS6ZMBHJ7Msbnk9jEr_zbOPyMp4-SY,74
+ray/tune/search/ax/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/ax/__pycache__/ax_search.cpython-312.pyc,,
+ray/tune/search/ax/ax_search.py,sha256=OTWLYN9V_5rYx1QkfLvAeUwOEpL77QWjLpXWsfJ1SGQ,15666
+ray/tune/search/basic_variant.py,sha256=5msq-EgbP94nSP0ntp_MrpkPxDZMsteU3YoL7bH4IJs,15380
+ray/tune/search/bayesopt/__init__.py,sha256=3DznYC9QeZGRzZGjZYr7cKhBm1nVEX2xbnkKmgqoYLU,98
+ray/tune/search/bayesopt/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/bayesopt/__pycache__/bayesopt_search.cpython-312.pyc,,
+ray/tune/search/bayesopt/bayesopt_search.py,sha256=tkpwbTlcc2TCalJb9D2lq8gVANM54MeQ57li2BfJrHI,16238
+ray/tune/search/bohb/__init__.py,sha256=eKU452gquD3_rEMMumU38h4Z1lHuzWW_6NFgz6MArWI,92
+ray/tune/search/bohb/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/bohb/__pycache__/bohb_search.cpython-312.pyc,,
+ray/tune/search/bohb/bohb_search.py,sha256=vUxGJGPYjUfT0ZVV8NNvjCLx3s5Bph3nfhsfNqawSug,13730
+ray/tune/search/concurrency_limiter.py,sha256=f7L2xdhYUe-Knbuu993zwvEWJ7w3Ev4VvOOChAfxoyQ,6325
+ray/tune/search/hebo/__init__.py,sha256=f9h2WIEtOVo_cYCBccYXV_kptAZU78xwUoB90__OyKE,82
+ray/tune/search/hebo/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/hebo/__pycache__/hebo_search.cpython-312.pyc,,
+ray/tune/search/hebo/hebo_search.py,sha256=TgPCMkWpwBQgpHWsC67RD4H1vSd2EFMJNAoOHMV4JxU,16848
+ray/tune/search/hyperopt/__init__.py,sha256=wImM0x3WS0f-1MTuKfzwXQa6drX4gmofGidJJJTdJ8Q,98
+ray/tune/search/hyperopt/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/hyperopt/__pycache__/hyperopt_search.cpython-312.pyc,,
+ray/tune/search/hyperopt/hyperopt_search.py,sha256=Tl4lEGMHvTcyd2qL5oIUlL3N8OtAxg6_7IWr2zwG2_I,21122
+ray/tune/search/nevergrad/__init__.py,sha256=vNDjYIDWJVHqmZqMVbO-Dc0FMD5bLtzDluwrcOtQFYs,102
+ray/tune/search/nevergrad/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/nevergrad/__pycache__/nevergrad_search.cpython-312.pyc,,
+ray/tune/search/nevergrad/nevergrad_search.py,sha256=UET3mGhhKqm1j5EmvWhR1oa2XuNPvH5rZwy4yYq1Sg4,13533
+ray/tune/search/optuna/__init__.py,sha256=3DqongrESbcBwTYXC1QGWvWczh84NJZHx5ddXC45SpM,90
+ray/tune/search/optuna/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/optuna/__pycache__/optuna_search.cpython-312.pyc,,
+ray/tune/search/optuna/optuna_search.py,sha256=bSrCj6AHsHep7vBpWO6ad3OVN6tbiMTtkZRzns7k6Dg,26571
+ray/tune/search/repeater.py,sha256=sYH0yj6SyH8KVxNuxeG-3tc5abdaIEmzr9V2wXzUnaU,7007
+ray/tune/search/sample.py,sha256=MCOprQKuW8aMh3viuQTcZ5wibmDYOO3uCnnyC_O0vQg,24262
+ray/tune/search/search_algorithm.py,sha256=3IkZPXBSthMLAFCMDESjg-8hqMroSypeE1k8zp8aJXA,3941
+ray/tune/search/search_generator.py,sha256=U1lqGxnB009bC-yONENTWeuG_KXSM3xtbOaiokgZuF4,8185
+ray/tune/search/searcher.py,sha256=TLiB-cKkQO3USnEYTZ0NRjefrDAw9lIcA1TDhBZrbiM,21389
+ray/tune/search/util.py,sha256=KGL7-NTlMaSVTsJFId1jYXdr18NEzCTTthDxSyh64NM,976
+ray/tune/search/variant_generator.py,sha256=3huEv3KJbShQwg_vc8P0REJZk_H-j4qko_t-i5nBZtE,17420
+ray/tune/search/zoopt/__init__.py,sha256=DzlNJOuxvCW1ETYkol-xIqW3BVTv5LCR8-ZCmfCoHko,86
+ray/tune/search/zoopt/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/search/zoopt/__pycache__/zoopt_search.cpython-312.pyc,,
+ray/tune/search/zoopt/zoopt_search.py,sha256=QhjgQbiTzOT3zYeGZJyun6sw-E-EATlvqp5L-iTQqWY,12441
+ray/tune/stopper/__init__.py,sha256=h0MRFnWskA0aPSK4Jz38W6fOOsuFX1PNkuZA2tkGYkk,636
+ray/tune/stopper/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/experiment_plateau.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/function_stopper.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/maximum_iteration.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/noop.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/stopper.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/timeout.cpython-312.pyc,,
+ray/tune/stopper/__pycache__/trial_plateau.cpython-312.pyc,,
+ray/tune/stopper/experiment_plateau.py,sha256=FTcNkmHOlX-xs7SupuTx7kIiTZZcSB2TjFuCsEIxATQ,3208
+ray/tune/stopper/function_stopper.py,sha256=YtkP6O6j5cmgwwGEwEmaJUvAJuG_4pN6eSSqdl0O2vQ,1142
+ray/tune/stopper/maximum_iteration.py,sha256=zhZ3_KbpwKYzSJEi_5iD6RX-UGVOzsvJXqNiBy5V5CE,656
+ray/tune/stopper/noop.py,sha256=e4Pj9GzYi6grdb8InDn_GaVbiXKK8QtuPGZO7X4Ey1M,238
+ray/tune/stopper/stopper.py,sha256=FEDD_OY9yQyLQOl4oTkzl8J32FUfa7J-FK93OQOC5hY,3079
+ray/tune/stopper/timeout.py,sha256=dW3sZ8ID5744dVMKMcvgZC0RppyjPFijRerQtrbkWIM,1813
+ray/tune/stopper/trial_plateau.py,sha256=RjnRrBt3xyyy0UZ4N7K--jr0GY2ikQB9v2gVVX5VRuc,3332
+ray/tune/syncer.py,sha256=1FsX0uGqGTPv1Nc_pb-gJy3FYhltjQqa-dQsiZd7T8s,2172
+ray/tune/trainable/__init__.py,sha256=Px0KvVAvgKo53W5CxXO0hl1ss7sbKapwLdB47rz1XOQ,287
+ray/tune/trainable/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/trainable/__pycache__/function_trainable.cpython-312.pyc,,
+ray/tune/trainable/__pycache__/metadata.cpython-312.pyc,,
+ray/tune/trainable/__pycache__/trainable.cpython-312.pyc,,
+ray/tune/trainable/__pycache__/trainable_fn_utils.cpython-312.pyc,,
+ray/tune/trainable/__pycache__/util.cpython-312.pyc,,
+ray/tune/trainable/function_trainable.py,sha256=5KlTqGR1T_zkKm8UKIW5MOh9H3OKxeWVQu6fkdwUdFI,9803
+ray/tune/trainable/metadata.py,sha256=qa-8UdMYEwmFPQyKwwf8MlcEmNCZVxTZP1Yokxvuxws,3522
+ray/tune/trainable/trainable.py,sha256=zVaK1t6rZKQ-2lgrhTlnJjkZMnWdUoHIfaIzq9uNtj8,36971
+ray/tune/trainable/trainable_fn_utils.py,sha256=9LSGcKb5ryVZtbmBSSe_WuylknYaZGarDYN28-d1IEo,2163
+ray/tune/trainable/util.py,sha256=NJwfX7qp93JHO0tElJeArkpvXlRmzz9IvNHxNHGUEcc,7754
+ray/tune/tune.py,sha256=PwcNizZ3v86G2tGTbVVzPv3x9CXJKdj39bDdTzUN1so,47425
+ray/tune/tune_config.py,sha256=e_EtXwnV2mrxUjTPGFV0Q01nQciF5xGEyNDadm9dTsA,4718
+ray/tune/tuner.py,sha256=XnZ26xA6OUoOWeVvhYdVmkaDXRuWr8rv8g2RmUE9WcE,15588
+ray/tune/utils/__init__.py,sha256=lj3-3FcZUD9njRCJNAcysjTyeWE79vQiKN35JQkTO98,523
+ray/tune/utils/__pycache__/__init__.cpython-312.pyc,,
+ray/tune/utils/__pycache__/callback.cpython-312.pyc,,
+ray/tune/utils/__pycache__/file_transfer.cpython-312.pyc,,
+ray/tune/utils/__pycache__/log.cpython-312.pyc,,
+ray/tune/utils/__pycache__/mock.cpython-312.pyc,,
+ray/tune/utils/__pycache__/mock_trainable.cpython-312.pyc,,
+ray/tune/utils/__pycache__/object_cache.cpython-312.pyc,,
+ray/tune/utils/__pycache__/release_test_util.cpython-312.pyc,,
+ray/tune/utils/__pycache__/resource_updater.cpython-312.pyc,,
+ray/tune/utils/__pycache__/serialization.cpython-312.pyc,,
+ray/tune/utils/__pycache__/util.cpython-312.pyc,,
+ray/tune/utils/callback.py,sha256=OW8dyefQRoIeeJubttbDl_GeuwJoIMLC3zMg4NsUfpI,6414
+ray/tune/utils/file_transfer.py,sha256=5ulHz0vHDltBwk9JVjuIzzCjrtfo4Ba_iDm30Oxcbyc,17431
+ray/tune/utils/log.py,sha256=3Yco2YXLRj-4hi_LX40jCs9s7jMeIrMntNArPr9_REI,1462
+ray/tune/utils/mock.py,sha256=QvIWuqMDJWUvgzB4Pmg6A7yk--afZiX5KLJLFzmXwUE,4004
+ray/tune/utils/mock_trainable.py,sha256=z60orHWJyU9TBPGAsSmpeb40Ib37n3ERMRpbgw6Cs-U,1884
+ray/tune/utils/object_cache.py,sha256=pIwWqbGTjF9t0vXgZMj3BqrqcQN-R-kg3LDzfg7o6SU,5512
+ray/tune/utils/release_test_util.py,sha256=OISH3mXL0tGmocbgnMVTGqvP3BexgI1_9Us3nMI-c6E,5775
+ray/tune/utils/resource_updater.py,sha256=XHJ9W6xyXYhVNvO12yw1Qkht2K-S99_sylbrnwn_O2Q,12890
+ray/tune/utils/serialization.py,sha256=Ozwf3oJw2vFTnvH9w9N8lwO6Lxqf8GDhlaKuY1LiJO4,1343
+ray/tune/utils/util.py,sha256=rrTBH6vgYswnbwNTtP6bdv1bCSweCaIDRNtv5p_o4Co,19672
+ray/types.py,sha256=2cbqnQ29__PbiqmZDvTOtMn_Gkrpaab21Gil84vWtGg,412
+ray/util/__init__.py,sha256=1nnBeLNGflZzp4wJN_LXNF76STbzVASRAgZ8haHhAUk,2432
+ray/util/__pycache__/__init__.cpython-312.pyc,,
+ray/util/__pycache__/actor_group.cpython-312.pyc,,
+ray/util/__pycache__/actor_pool.cpython-312.pyc,,
+ray/util/__pycache__/annotations.cpython-312.pyc,,
+ray/util/__pycache__/check_open_ports.cpython-312.pyc,,
+ray/util/__pycache__/check_serialize.cpython-312.pyc,,
+ray/util/__pycache__/client_connect.cpython-312.pyc,,
+ray/util/__pycache__/common.cpython-312.pyc,,
+ray/util/__pycache__/debug.cpython-312.pyc,,
+ray/util/__pycache__/debugpy.cpython-312.pyc,,
+ray/util/__pycache__/helpers.cpython-312.pyc,,
+ray/util/__pycache__/iter.cpython-312.pyc,,
+ray/util/__pycache__/iter_metrics.cpython-312.pyc,,
+ray/util/__pycache__/metrics.cpython-312.pyc,,
+ray/util/__pycache__/placement_group.cpython-312.pyc,,
+ray/util/__pycache__/queue.cpython-312.pyc,,
+ray/util/__pycache__/rpdb.cpython-312.pyc,,
+ray/util/__pycache__/scheduling_strategies.cpython-312.pyc,,
+ray/util/__pycache__/serialization.cpython-312.pyc,,
+ray/util/__pycache__/serialization_addons.cpython-312.pyc,,
+ray/util/__pycache__/timer.cpython-312.pyc,,
+ray/util/__pycache__/tpu.cpython-312.pyc,,
+ray/util/accelerators/__init__.py,sha256=Njp2eQWKT1RA_OQw17N4uLja-6Q9dS1NzmhUc2tCKy8,1778
+ray/util/accelerators/__pycache__/__init__.cpython-312.pyc,,
+ray/util/accelerators/__pycache__/accelerators.cpython-312.pyc,,
+ray/util/accelerators/accelerators.py,sha256=gYt6xIF3IlOauwkO8dqbE7eK8ttH94NHAYez3Yt1uC0,1543
+ray/util/actor_group.py,sha256=Rp11YN7CUQh2-E7z6IYKmWxsR63T7T-filELvOxmeNU,7980
+ray/util/actor_pool.py,sha256=GuHc26UUgi-HfD2g5H0ld8SALLML4NPzBxJBdRGvIbg,14541
+ray/util/annotations.py,sha256=b2sUaicwDjGrwGo8_st55yYjYpJtMhOXGIqurljoUfI,7988
+ray/util/check_open_ports.py,sha256=PJAi3nqAtbZMX2lFV6RMbhBQSNz0hG9MEZgbhcsCoO4,5953
+ray/util/check_serialize.py,sha256=8tXLE98b-FKx0IPCvkB2UrutfhcNQAVYbaTa9ZAgmuo,8440
+ray/util/client/__init__.py,sha256=cpBeoaTQVDDe-qGVyu1N_cLtm5Nn-fabmLjMG9Ejcb4,10615
+ray/util/client/__pycache__/__init__.cpython-312.pyc,,
+ray/util/client/__pycache__/api.cpython-312.pyc,,
+ray/util/client/__pycache__/client_app.cpython-312.pyc,,
+ray/util/client/__pycache__/client_pickler.cpython-312.pyc,,
+ray/util/client/__pycache__/common.cpython-312.pyc,,
+ray/util/client/__pycache__/dataclient.cpython-312.pyc,,
+ray/util/client/__pycache__/logsclient.cpython-312.pyc,,
+ray/util/client/__pycache__/options.cpython-312.pyc,,
+ray/util/client/__pycache__/ray_client_helpers.cpython-312.pyc,,
+ray/util/client/__pycache__/runtime_context.cpython-312.pyc,,
+ray/util/client/__pycache__/worker.cpython-312.pyc,,
+ray/util/client/api.py,sha256=8DDkJXHkffR0HmRPIeW92S2qc9wHWhjpfdyBHkaW84U,15343
+ray/util/client/client_app.py,sha256=5L1raW-tDAq3gx51UDJuZ-LQBG5MFxJmEuH5ytVGNTU,1823
+ray/util/client/client_pickler.py,sha256=Cd9YpoU9LWgxb6_en5EpuKxrR6KdNNdumavyfkfC-Tk,6012
+ray/util/client/common.py,sha256=QdJ_cXYPP6fMUKaz2RMzqmWsqHzN_lu5HhbInTitEC4,35219
+ray/util/client/dataclient.py,sha256=cghggVjUFcu0l21eRGx23d46-gO-J2J5Dxt_0yTSKqI,22951
+ray/util/client/logsclient.py,sha256=XctFwOjWkCWZzyLlSrIxY6da5wfkbSWZKAuE_CAq8O4,4945
+ray/util/client/options.py,sha256=8OVVdUfZKP8voCdWdncMDsUHsNaVxJ5lFI1klqwAKEQ,1850
+ray/util/client/ray_client_helpers.py,sha256=xuaV4KLYxIluBqs9UmxZ-A4mBk9mXuQtDzhpOv3jTiQ,2491
+ray/util/client/runtime_context.py,sha256=1thIfUOK9CXpX9JYot85Wzrm9Qb4S47cZ7Ks7Ct5sCg,1886
+ray/util/client/server/__init__.py,sha256=og0t86nF86MB8dm80ZrQ3A9og0aUvf6TXKeH0I5Z6AA,56
+ray/util/client/server/__main__.py,sha256=Oj32656eOno-hNr_Hqq47_oQn0TfFBkXnIGeRrYhk94,90
+ray/util/client/server/__pycache__/__init__.cpython-312.pyc,,
+ray/util/client/server/__pycache__/__main__.cpython-312.pyc,,
+ray/util/client/server/__pycache__/dataservicer.cpython-312.pyc,,
+ray/util/client/server/__pycache__/logservicer.cpython-312.pyc,,
+ray/util/client/server/__pycache__/proxier.cpython-312.pyc,,
+ray/util/client/server/__pycache__/server.cpython-312.pyc,,
+ray/util/client/server/__pycache__/server_pickler.cpython-312.pyc,,
+ray/util/client/server/__pycache__/server_stubs.cpython-312.pyc,,
+ray/util/client/server/dataservicer.py,sha256=Ii63Tsf0Ddiirw4CHYGCdhB5slFixxB7uvcjbEnP2Go,18813
+ray/util/client/server/logservicer.py,sha256=8vBPneYS4lGKclmmGwz1TdpgNNOULAgajpXhfyP3MHw,4278
+ray/util/client/server/proxier.py,sha256=xS0xyq-jujrgZdbq0kkztG0nr033i0em1Ibww__QdGg,36320
+ray/util/client/server/server.py,sha256=4JOmmqOYv5Fi6ScPV2vQ_7O8JUg3NRY8wKSOjf-dTy4,38964
+ray/util/client/server/server_pickler.py,sha256=fOp3atcvQKD7t9qlrPrdhkDDB9M6AKX-VRhTJtEmAaU,4450
+ray/util/client/server/server_stubs.py,sha256=SQStHr7bDuV4Qac58UYRoiKVZShvl8gWcnGFcTh3Roo,1717
+ray/util/client/worker.py,sha256=2P2RCBGNjoEZTrE9-UjKCvdKOpBgcQBxFMSaFsnPxTY,36789
+ray/util/client_connect.py,sha256=0mLL2Ul-AmPyJ0XVf7hPbfkI9vop-73vMRj_IESuPs4,2604
+ray/util/collective/__init__.py,sha256=AciLTYN7_qmrUN3Ps6GennMiL1UT9yxq5vETRIPT9g4,1084
+ray/util/collective/__pycache__/__init__.cpython-312.pyc,,
+ray/util/collective/__pycache__/collective.cpython-312.pyc,,
+ray/util/collective/__pycache__/const.cpython-312.pyc,,
+ray/util/collective/__pycache__/types.cpython-312.pyc,,
+ray/util/collective/__pycache__/util.cpython-312.pyc,,
+ray/util/collective/collective.py,sha256=0gd8mEFgUBD1WPhiGRa4bq0KYuhw7xio80DB-4Vz-ck,29043
+ray/util/collective/collective_group/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/util/collective/collective_group/__pycache__/__init__.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/base_collective_group.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/cuda_stream.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/nccl_collective_group.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/nccl_util.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/nixl_backend.cpython-312.pyc,,
+ray/util/collective/collective_group/__pycache__/torch_gloo_collective_group.cpython-312.pyc,,
+ray/util/collective/collective_group/base_collective_group.py,sha256=4PBLssG2vscr1OEH3pmh6kISSb4yYCu1FYt5qpOhyEE,2330
+ray/util/collective/collective_group/cuda_stream.py,sha256=migikW0Lc1JzMvZzjmJZQPwEZkjlFuhZ93c2iznUfkY,2970
+ray/util/collective/collective_group/nccl_collective_group.py,sha256=YTtsHZlq_gzuq_ygN948UDZNbStX50VA-MOIKHTqzeo,30843
+ray/util/collective/collective_group/nccl_util.py,sha256=iRVG9w3YyhwG6jIBLk_M1CCPkR4egS8voWruyiw1HQs,9121
+ray/util/collective/collective_group/nixl_backend.py,sha256=7lQucJzdonwabnOW_6u9oXxW97AILUkurpA_3SompZs,5367
+ray/util/collective/collective_group/torch_gloo_collective_group.py,sha256=pfMN5LGxxFM4fzSevZoQHy3Quzmg1L9OucjMCnqRRRk,8873
+ray/util/collective/const.py,sha256=J9r6vjcBtLpvZt6LM-LqWenEX1ThZgF8ClQS6r5lf9A,865
+ray/util/collective/types.py,sha256=h0W92XES6V6n80aeT_NvGvweArddwy74yrNsZxdAa5M,4339
+ray/util/collective/util.py,sha256=pj6-5Xs3Q906csM1IjH-_Dxge6djSh6Bs-0B8-p8BuA,2118
+ray/util/common.py,sha256=POgfMjbcuHZHBGpGzngIjygaG7Jsz0jfigjCJJYfs7Y,24
+ray/util/dask/__init__.py,sha256=EQzConA8dAgrRCwsnDUK-mxPjHGMVPlyL_8zPow4mE8,1907
+ray/util/dask/__pycache__/__init__.cpython-312.pyc,,
+ray/util/dask/__pycache__/callbacks.cpython-312.pyc,,
+ray/util/dask/__pycache__/common.cpython-312.pyc,,
+ray/util/dask/__pycache__/optimizations.cpython-312.pyc,,
+ray/util/dask/__pycache__/scheduler.cpython-312.pyc,,
+ray/util/dask/__pycache__/scheduler_utils.cpython-312.pyc,,
+ray/util/dask/callbacks.py,sha256=dU1eNe9mv60Kx5QsRturvfqy089B8JQa5M6C2BYsops,10511
+ray/util/dask/common.py,sha256=CNU5d8PqYOAdBHnWQ_zTOvuRROF12goEu6yKpMtzWA8,2652
+ray/util/dask/optimizations.py,sha256=JmdKArdxvXvxmCCRMrGpcK-czywmE0mwYZQX1I3_IUE,5214
+ray/util/dask/scheduler.py,sha256=c58_XD2TFYmuKZUa3q4lceqr-MxtUp2kopOXlX7Yor8,24439
+ray/util/dask/scheduler_utils.py,sha256=i3PoenkdXF-sPOfmZ8of59Mr6J_Af54bOqFELkCGDxo,11604
+ray/util/debug.py,sha256=pTAHxWuIW9_MzxrDHrgyXGTgB_JWP9VkJXnoG-78kjc,8644
+ray/util/debugpy.py,sha256=imtLu_l9-9Y0jDKt4rdoIx6ATnEbXghlF4AL9dIa4Ns,4120
+ray/util/helpers.py,sha256=rmzCv75sCtfi5uew_oJGMxN_HuE48yiE1DeAx5FLvHc,8437
+ray/util/horovod/__init__.py,sha256=eHQd2sGhmlA42dukBrIGIOj0k8k5f5BExabycshR5yE,172
+ray/util/horovod/__pycache__/__init__.cpython-312.pyc,,
+ray/util/iter.py,sha256=P9dWumFhzkYU71L304tMD5ysB4zqN4vjNRL3y7RywTg,46939
+ray/util/iter_metrics.py,sha256=U_7iDnbTItUA20EYx_8Q2Kg7a9TCM8eVd57qr4pY8so,2227
+ray/util/joblib/__init__.py,sha256=ze4lZZU0ltg367PHT2svbmEcnKfuz1OPdq_E4afv5eg,588
+ray/util/joblib/__pycache__/__init__.cpython-312.pyc,,
+ray/util/joblib/__pycache__/ray_backend.cpython-312.pyc,,
+ray/util/joblib/ray_backend.py,sha256=BUa14yXsh-jWJo547vim28zh7qy9_9P4v3KQ6oyyeAM,3343
+ray/util/lightgbm/__init__.py,sha256=qSTKqnmHoVx06HHOmxZOoBDFjbP7fStk32Us_YOz6jM,179
+ray/util/lightgbm/__pycache__/__init__.cpython-312.pyc,,
+ray/util/metrics.py,sha256=aUDqY0r1kQZnhkt4eqF_V-inv1vV3K9Nha2DqlN4O78,12497
+ray/util/multiprocessing/__init__.py,sha256=zccBaK6B-qJRU-OSD21drLG_2YLd5uwT4M8XM5f8uRM,133
+ray/util/multiprocessing/__pycache__/__init__.cpython-312.pyc,,
+ray/util/multiprocessing/__pycache__/pool.cpython-312.pyc,,
+ray/util/multiprocessing/pool.py,sha256=05bzkY_mmxSA9Z41p8UhdiWS6Yn7YOyvY7QQnEmG-P0,37603
+ray/util/placement_group.py,sha256=DhngDcBWImnPKxrl_sglSaQ8wtAK-dbNayvvTLV4N68,20584
+ray/util/queue.py,sha256=poQwHozIhvCFMmQFHjalkhxCUJWJDc9_Km4Wlc150JQ,10146
+ray/util/rpdb.py,sha256=007znx7dwiHrY7DoPm0-6TtpJK-UBLTzsKcy60lxEPk,12146
+ray/util/scheduling_strategies.py,sha256=NHkU87Xu7jsJ_d0-P7lTrl_Wu3Jw0ePnNxVg02TiWTo,7622
+ray/util/serialization.py,sha256=l4lV_6Uk1xqWoN6zJZ15GMIeW1BFl-r1DjBYk61IMG8,2009
+ray/util/serialization_addons.py,sha256=Dk7qHli9jWqiptn0vgBlBqz1Az4y3A9YKTG9wun3XWg,1093
+ray/util/sgd/__init__.py,sha256=XhFzqjBfLVHFPk0-z2kBrIOSCxmuiKlsomJdzNp6fM0,152
+ray/util/sgd/__pycache__/__init__.cpython-312.pyc,,
+ray/util/spark/__init__.py,sha256=sdHl65enTyL0YHmVtSdlNN8W35IPznfFmtMq5EN7R8c,277
+ray/util/spark/__pycache__/__init__.cpython-312.pyc,,
+ray/util/spark/__pycache__/cluster_init.cpython-312.pyc,,
+ray/util/spark/__pycache__/databricks_hook.cpython-312.pyc,,
+ray/util/spark/__pycache__/start_hook_base.cpython-312.pyc,,
+ray/util/spark/__pycache__/start_ray_node.cpython-312.pyc,,
+ray/util/spark/__pycache__/utils.cpython-312.pyc,,
+ray/util/spark/cluster_init.py,sha256=wIuH0vuQ8yCVXF7APigK53ly80NYW6WblPgjrkv7aVc,76922
+ray/util/spark/databricks_hook.py,sha256=g6J_8Wrpj602SpxolTjTQHl5TlnTGAAEgyHHtvtFDtg,8866
+ray/util/spark/start_hook_base.py,sha256=Pn71SPD5ujBSfeAabw5frTc6Aun923CKjdHRMTKXL3A,417
+ray/util/spark/start_ray_node.py,sha256=slssPvLI6olZxcEi8nkun3cCvKtz7EJNATHZp-d2gIU,8191
+ray/util/spark/utils.py,sha256=gS9JKapJwh9P8IiE6QqyRIpylDdSZVVwGHBJ-GE887Q,17083
+ray/util/state/__init__.py,sha256=tNuhczvLDGWskB6rV9dyAQt8A14wLmhraSXoXNempU0,900
+ray/util/state/__pycache__/__init__.cpython-312.pyc,,
+ray/util/state/__pycache__/api.cpython-312.pyc,,
+ray/util/state/__pycache__/common.cpython-312.pyc,,
+ray/util/state/__pycache__/exception.cpython-312.pyc,,
+ray/util/state/__pycache__/state_cli.cpython-312.pyc,,
+ray/util/state/__pycache__/state_manager.cpython-312.pyc,,
+ray/util/state/__pycache__/util.cpython-312.pyc,,
+ray/util/state/api.py,sha256=SbpeSDUYzH7QeqTTr_wIXGHepTQg1z2OXT3dWaC2ALE,54583
+ray/util/state/common.py,sha256=4lMFNwLCVV5gRI7sQWrxVzRFCh9aauFOaQ5J7zMBKVQ,67705
+ray/util/state/exception.py,sha256=SQWb86M80lNy4-TBfy1LoPjWJEBat-knpYCSHORZ8UY,268
+ray/util/state/state_cli.py,sha256=gPpfU-2-BV1CzFuIO12hiHiea57l_kQRfy_DzKD9GsI,35507
+ray/util/state/state_manager.py,sha256=jXKysRINzfwlkAjMAb1ZFY96CU1R3GPDEKy998wDPt8,18981
+ray/util/state/util.py,sha256=D8_E3F0HFTCiUxEZJlVKgm6OAKW3U2GaCJhodbzZvSQ,1932
+ray/util/timer.py,sha256=enqflszQt04fVHtcQLyE4XsfeFlNxpSFXKuHHMvAgs8,1877
+ray/util/tpu.py,sha256=qGoEgQeNppP3R3mkna97pQ5aUtzfjB4F27LIpyAZMmI,9154
+ray/util/tracing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ray/util/tracing/__pycache__/__init__.cpython-312.pyc,,
+ray/util/tracing/__pycache__/setup_local_tmp_tracing.cpython-312.pyc,,
+ray/util/tracing/__pycache__/setup_tempo_tracing.cpython-312.pyc,,
+ray/util/tracing/__pycache__/tracing_helper.cpython-312.pyc,,
+ray/util/tracing/setup_local_tmp_tracing.py,sha256=5z80HbDVGONAO7BWKujKZEEn-W16k6nHxVlfelTICsc,828
+ray/util/tracing/setup_tempo_tracing.py,sha256=nu0TnqW2IO85bCf4eB6XJbgLo-lPXYj6p3sLP8Pc5lw,860
+ray/util/tracing/tracing_helper.py,sha256=DGWtpVjv9wkEndKYlL1QefXQfajiGcvEYbYfB_bEsns,19076
+ray/util/xgboost/__init__.py,sha256=fiAT1Q_R0kf1TNR3QBZXzB-Z3xXxff8w9NCOkJhX_0M,176
+ray/util/xgboost/__pycache__/__init__.cpython-312.pyc,,
+ray/widgets/__init__.py,sha256=mKmmZs53_ii5nlBmT26kDX1x7a4RyfPvjc6XFvZ_EKI,138
+ray/widgets/__pycache__/__init__.cpython-312.pyc,,
+ray/widgets/__pycache__/render.cpython-312.pyc,,
+ray/widgets/__pycache__/util.cpython-312.pyc,,
+ray/widgets/render.py,sha256=CdxaN0PMIrPF-tp24Zqqv9Z5sB3MdzIxh-2Yh4SaoZA,1226
+ray/widgets/templates/context.html.j2,sha256=GJ5t67VpG9DWYqvMqIlG0kVf5Jl8w5sBZ7eBTlw_mKA,235
+ray/widgets/templates/context_dashrow.html.j2,sha256=0sod0SSVgvvBtGmJkbdjH29aBb8c_0s5ygz-Navqeuc,182
+ray/widgets/templates/context_logo.html.j2,sha256=2eq4DsgvKXyTZx4LQwp2hvA4Ix7v0HDM1yQ45bBCuIo,5571
+ray/widgets/templates/context_table.html.j2,sha256=G2BmWG2Mra_eEC53UOb7UEpA0vr8LX1dHbg-1DK6C3E,475
+ray/widgets/templates/divider.html.j2,sha256=YWaWbc6yc3AhN4kpOlvIFz0-8eIsKpQ1OXDxiw34I3c,210
+ray/widgets/templates/rendered_html_common.html.j2,sha256=6e3NaDpVWHzniqTOI0ywpnoS3YcgraMwUHh3_N6VurM,59
+ray/widgets/templates/run_config.html.j2,sha256=3Txr5nDnn20wx1iwd8tpBJPGzTQXrBB1_yDfaDdVj48,325
+ray/widgets/templates/scrollableTable.html.j2,sha256=TAOV7gvFXvG1WD9gyt2ZBmDvzra1Mbu6Nl5yaNAW3h8,367
+ray/widgets/templates/title_data.html.j2,sha256=j_B2qxqblbBvoiysGrjZZzKTp8Xw5cYSdUn5b-VJiYo,255
+ray/widgets/templates/title_data_mini.html.j2,sha256=w04_i3wa7yKK7QN39TqWyF25mF3Mm0nVvPbGJuqjClo,100
+ray/widgets/templates/trial_progress.html.j2,sha256=7dRrXFrsMyj4PBSLaNgoaUVttEv-pX0-nblnUvR30lI,277
+ray/widgets/templates/tune_status.html.j2,sha256=zzyLLQhWzhW5n1tIuX8Zkq3STVnvQPCH_5w_7bDdPLg,1097
+ray/widgets/templates/tune_status_messages.html.j2,sha256=0jm-6FxHpop1liapLUKvlpxiO-E-hIjWG5x1RJupLKU,507
+ray/widgets/util.py,sha256=swpokHhNLkM1MWFQJfOS05oeH_GgdagnNgxGrgQALRs,6345
+ray/workflow/__init__.py,sha256=GC8BYy1jvKcsffzDj1YTypsDpOtQAzCzgvgRt_rjwcY,185
+ray/workflow/__pycache__/__init__.cpython-312.pyc,,
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/REQUESTED b/lib/python3.12/site-packages/ray-2.52.1.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/WHEEL b/lib/python3.12/site-packages/ray-2.52.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..8de3dd04b181d011138aec587ebd86c84184fa09
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (80.9.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-linux_x86_64
+
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/entry_points.txt b/lib/python3.12/site-packages/ray-2.52.1.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b48cc567cd0a4f5eda8af8e202bb381f835de5d0
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/entry_points.txt
@@ -0,0 +1,4 @@
+[console_scripts]
+ray = ray.scripts.scripts:main
+serve = ray.serve.scripts:cli
+tune = ray.tune.cli.scripts:cli
diff --git a/lib/python3.12/site-packages/ray-2.52.1.dist-info/top_level.txt b/lib/python3.12/site-packages/ray-2.52.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e0275631807fc1c22cd3593cd1c48d29d1bbaff9
--- /dev/null
+++ b/lib/python3.12/site-packages/ray-2.52.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+ray
diff --git a/lib/python3.12/site-packages/setuptools/__init__.py b/lib/python3.12/site-packages/setuptools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3e78edab6e1bc7839bf73fcd969e4e76f42f83e
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/__init__.py
@@ -0,0 +1,250 @@
+"""Extensions to the 'distutils' for large or complex distributions"""
+# mypy: disable_error_code=override
+# Command.reinitialize_command has an extra **kw param that distutils doesn't have
+# Can't disable on the exact line because distutils doesn't exists on Python 3.12
+# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any,
+# and a [unused-ignore] to be raised on 3.12+
+
+from __future__ import annotations
+
+import functools
+import os
+import sys
+from abc import abstractmethod
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, TypeVar, overload
+
+sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip
+# workaround for #4476
+sys.modules.pop('backports', None)
+
+import _distutils_hack.override # noqa: F401
+
+from . import logging, monkey
+from .depends import Require
+from .discovery import PackageFinder, PEP420PackageFinder
+from .dist import Distribution
+from .extension import Extension
+from .version import __version__ as __version__
+from .warnings import SetuptoolsDeprecationWarning
+
+import distutils.core
+
+__all__ = [
+ 'setup',
+ 'Distribution',
+ 'Command',
+ 'Extension',
+ 'Require',
+ 'SetuptoolsDeprecationWarning',
+ 'find_packages',
+ 'find_namespace_packages',
+]
+
+_CommandT = TypeVar("_CommandT", bound="_Command")
+
+bootstrap_install_from = None
+
+find_packages = PackageFinder.find
+find_namespace_packages = PEP420PackageFinder.find
+
+
+def _install_setup_requires(attrs):
+ # Note: do not use `setuptools.Distribution` directly, as
+ # our PEP 517 backend patch `distutils.core.Distribution`.
+ class MinimalDistribution(distutils.core.Distribution):
+ """
+ A minimal version of a distribution for supporting the
+ fetch_build_eggs interface.
+ """
+
+ def __init__(self, attrs: Mapping[str, object]) -> None:
+ _incl = 'dependency_links', 'setup_requires'
+ filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
+ super().__init__(filtered)
+ # Prevent accidentally triggering discovery with incomplete set of attrs
+ self.set_defaults._disable()
+
+ def _get_project_config_files(self, filenames=None):
+ """Ignore ``pyproject.toml``, they are not related to setup_requires"""
+ try:
+ cfg, _toml = super()._split_standard_project_metadata(filenames)
+ except Exception:
+ return filenames, ()
+ return cfg, ()
+
+ def finalize_options(self):
+ """
+ Disable finalize_options to avoid building the working set.
+ Ref #2158.
+ """
+
+ dist = MinimalDistribution(attrs)
+
+ # Honor setup.cfg's options.
+ dist.parse_config_files(ignore_option_errors=True)
+ if dist.setup_requires:
+ _fetch_build_eggs(dist)
+
+
+def _fetch_build_eggs(dist: Distribution):
+ try:
+ dist.fetch_build_eggs(dist.setup_requires)
+ except Exception as ex:
+ msg = """
+ It is possible a package already installed in your system
+ contains an version that is invalid according to PEP 440.
+ You can try `pip install --use-pep517` as a workaround for this problem,
+ or rely on a new virtual environment.
+
+ If the problem refers to a package that is not installed yet,
+ please contact that package's maintainers or distributors.
+ """
+ if "InvalidVersion" in ex.__class__.__name__:
+ if hasattr(ex, "add_note"):
+ ex.add_note(msg) # PEP 678
+ else:
+ dist.announce(f"\n{msg}\n")
+ raise
+
+
+def setup(**attrs) -> Distribution:
+ logging.configure()
+ # Make sure we have any requirements needed to interpret 'attrs'.
+ _install_setup_requires(attrs)
+ # Override return type of distutils.core.Distribution with setuptools.dist.Distribution
+ # (implicitly implemented via `setuptools.monkey.patch_all`).
+ return distutils.core.setup(**attrs) # type: ignore[return-value]
+
+
+setup.__doc__ = distutils.core.setup.__doc__
+
+if TYPE_CHECKING:
+ # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962
+ from distutils.core import Command as _Command
+else:
+ _Command = monkey.get_unpatched(distutils.core.Command)
+
+
+class Command(_Command):
+ """
+ Setuptools internal actions are organized using a *command design pattern*.
+ This means that each action (or group of closely related actions) executed during
+ the build should be implemented as a ``Command`` subclass.
+
+ These commands are abstractions and do not necessarily correspond to a command that
+ can (or should) be executed via a terminal, in a CLI fashion (although historically
+ they would).
+
+ When creating a new command from scratch, custom defined classes **SHOULD** inherit
+ from ``setuptools.Command`` and implement a few mandatory methods.
+ Between these mandatory methods, are listed:
+ :meth:`initialize_options`, :meth:`finalize_options` and :meth:`run`.
+
+ A useful analogy for command classes is to think of them as subroutines with local
+ variables called "options". The options are "declared" in :meth:`initialize_options`
+ and "defined" (given their final values, aka "finalized") in :meth:`finalize_options`,
+ both of which must be defined by every command class. The "body" of the subroutine,
+ (where it does all the work) is the :meth:`run` method.
+ Between :meth:`initialize_options` and :meth:`finalize_options`, ``setuptools`` may set
+ the values for options/attributes based on user's input (or circumstance),
+ which means that the implementation should be careful to not overwrite values in
+ :meth:`finalize_options` unless necessary.
+
+ Please note that other commands (or other parts of setuptools) may also overwrite
+ the values of the command's options/attributes multiple times during the build
+ process.
+ Therefore it is important to consistently implement :meth:`initialize_options` and
+ :meth:`finalize_options`. For example, all derived attributes (or attributes that
+ depend on the value of other attributes) **SHOULD** be recomputed in
+ :meth:`finalize_options`.
+
+ When overwriting existing commands, custom defined classes **MUST** abide by the
+ same APIs implemented by the original class. They also **SHOULD** inherit from the
+ original class.
+ """
+
+ command_consumes_arguments = False
+ distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
+
+ def __init__(self, dist: Distribution, **kw) -> None:
+ """
+ Construct the command for dist, updating
+ vars(self) with any keyword parameters.
+ """
+ super().__init__(dist)
+ vars(self).update(kw)
+
+ @overload
+ def reinitialize_command(
+ self, command: str, reinit_subcommands: bool = False, **kw
+ ) -> Command: ... # override distutils.cmd.Command with setuptools.Command
+ @overload
+ def reinitialize_command(
+ self, command: _CommandT, reinit_subcommands: bool = False, **kw
+ ) -> _CommandT: ...
+ def reinitialize_command(
+ self, command: str | _Command, reinit_subcommands: bool = False, **kw
+ ) -> Command | _Command:
+ cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
+ vars(cmd).update(kw)
+ return cmd # pyright: ignore[reportReturnType] # pypa/distutils#307
+
+ @abstractmethod
+ def initialize_options(self) -> None:
+ """
+ Set or (reset) all options/attributes/caches used by the command
+ to their default values. Note that these values may be overwritten during
+ the build.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def finalize_options(self) -> None:
+ """
+ Set final values for all options/attributes used by the command.
+ Most of the time, each option/attribute/cache should only be set if it does not
+ have any value yet (e.g. ``if self.attr is None: self.attr = val``).
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def run(self) -> None:
+ """
+ Execute the actions intended by the command.
+ (Side effects **SHOULD** only take place when :meth:`run` is executed,
+ for example, creating new files or writing to the terminal output).
+ """
+ raise NotImplementedError
+
+
+def _find_all_simple(path):
+ """
+ Find all files under 'path'
+ """
+ results = (
+ os.path.join(base, file)
+ for base, dirs, files in os.walk(path, followlinks=True)
+ for file in files
+ )
+ return filter(os.path.isfile, results)
+
+
+def findall(dir=os.curdir):
+ """
+ Find all files under 'dir' and return the list of full filenames.
+ Unless dir is '.', return full filenames with dir prepended.
+ """
+ files = _find_all_simple(dir)
+ if dir == os.curdir:
+ make_rel = functools.partial(os.path.relpath, start=dir)
+ files = map(make_rel, files)
+ return list(files)
+
+
+class sic(str):
+ """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
+
+
+# Apply monkey patches
+monkey.patch_all()
diff --git a/lib/python3.12/site-packages/setuptools/_core_metadata.py b/lib/python3.12/site-packages/setuptools/_core_metadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..a52d5cf755cdd6a502e752c2f7a3afa3b25897d5
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_core_metadata.py
@@ -0,0 +1,337 @@
+"""
+Handling of Core Metadata for Python packages (including reading and writing).
+
+See: https://packaging.python.org/en/latest/specifications/core-metadata/
+"""
+
+from __future__ import annotations
+
+import os
+import stat
+import textwrap
+from email import message_from_file
+from email.message import Message
+from tempfile import NamedTemporaryFile
+
+from packaging.markers import Marker
+from packaging.requirements import Requirement
+from packaging.utils import canonicalize_name, canonicalize_version
+from packaging.version import Version
+
+from . import _normalization, _reqs
+from ._static import is_static
+from .warnings import SetuptoolsDeprecationWarning
+
+from distutils.util import rfc822_escape
+
+
+def get_metadata_version(self):
+ mv = getattr(self, 'metadata_version', None)
+ if mv is None:
+ mv = Version('2.4')
+ self.metadata_version = mv
+ return mv
+
+
+def rfc822_unescape(content: str) -> str:
+ """Reverse RFC-822 escaping by removing leading whitespaces from content."""
+ lines = content.splitlines()
+ if len(lines) == 1:
+ return lines[0].lstrip()
+ return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:]))))
+
+
+def _read_field_from_msg(msg: Message, field: str) -> str | None:
+ """Read Message header field."""
+ value = msg[field]
+ if value == 'UNKNOWN':
+ return None
+ return value
+
+
+def _read_field_unescaped_from_msg(msg: Message, field: str) -> str | None:
+ """Read Message header field and apply rfc822_unescape."""
+ value = _read_field_from_msg(msg, field)
+ if value is None:
+ return value
+ return rfc822_unescape(value)
+
+
+def _read_list_from_msg(msg: Message, field: str) -> list[str] | None:
+ """Read Message header field and return all results as list."""
+ values = msg.get_all(field, None)
+ if values == []:
+ return None
+ return values
+
+
+def _read_payload_from_msg(msg: Message) -> str | None:
+ value = str(msg.get_payload()).strip()
+ if value == 'UNKNOWN' or not value:
+ return None
+ return value
+
+
+def read_pkg_file(self, file):
+ """Reads the metadata values from a file object."""
+ msg = message_from_file(file)
+
+ self.metadata_version = Version(msg['metadata-version'])
+ self.name = _read_field_from_msg(msg, 'name')
+ self.version = _read_field_from_msg(msg, 'version')
+ self.description = _read_field_from_msg(msg, 'summary')
+ # we are filling author only.
+ self.author = _read_field_from_msg(msg, 'author')
+ self.maintainer = None
+ self.author_email = _read_field_from_msg(msg, 'author-email')
+ self.maintainer_email = None
+ self.url = _read_field_from_msg(msg, 'home-page')
+ self.download_url = _read_field_from_msg(msg, 'download-url')
+ self.license = _read_field_unescaped_from_msg(msg, 'license')
+ self.license_expression = _read_field_unescaped_from_msg(msg, 'license-expression')
+
+ self.long_description = _read_field_unescaped_from_msg(msg, 'description')
+ if self.long_description is None and self.metadata_version >= Version('2.1'):
+ self.long_description = _read_payload_from_msg(msg)
+ self.description = _read_field_from_msg(msg, 'summary')
+
+ if 'keywords' in msg:
+ self.keywords = _read_field_from_msg(msg, 'keywords').split(',')
+
+ self.platforms = _read_list_from_msg(msg, 'platform')
+ self.classifiers = _read_list_from_msg(msg, 'classifier')
+
+ # PEP 314 - these fields only exist in 1.1
+ if self.metadata_version == Version('1.1'):
+ self.requires = _read_list_from_msg(msg, 'requires')
+ self.provides = _read_list_from_msg(msg, 'provides')
+ self.obsoletes = _read_list_from_msg(msg, 'obsoletes')
+ else:
+ self.requires = None
+ self.provides = None
+ self.obsoletes = None
+
+ self.license_files = _read_list_from_msg(msg, 'license-file')
+
+
+def single_line(val):
+ """
+ Quick and dirty validation for Summary pypa/setuptools#1390.
+ """
+ if '\n' in val:
+ # TODO: Replace with `raise ValueError("newlines not allowed")`
+ # after reviewing #2893.
+ msg = "newlines are not allowed in `summary` and will break in the future"
+ SetuptoolsDeprecationWarning.emit("Invalid config.", msg)
+ # due_date is undefined. Controversial change, there was a lot of push back.
+ val = val.strip().split('\n')[0]
+ return val
+
+
+def write_pkg_info(self, base_dir):
+ """Write the PKG-INFO file into the release tree."""
+ temp = ""
+ final = os.path.join(base_dir, 'PKG-INFO')
+ try:
+ # Use a temporary file while writing to avoid race conditions
+ # (e.g. `importlib.metadata` reading `.egg-info/PKG-INFO`):
+ with NamedTemporaryFile("w", encoding="utf-8", dir=base_dir, delete=False) as f:
+ temp = f.name
+ self.write_pkg_file(f)
+ permissions = stat.S_IMODE(os.lstat(temp).st_mode)
+ os.chmod(temp, permissions | stat.S_IRGRP | stat.S_IROTH)
+ os.replace(temp, final) # atomic operation.
+ finally:
+ if temp and os.path.exists(temp):
+ os.remove(temp)
+
+
+# Based on Python 3.5 version
+def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
+ """Write the PKG-INFO format data to a file object."""
+ version = self.get_metadata_version()
+
+ def write_field(key, value):
+ file.write(f"{key}: {value}\n")
+
+ write_field('Metadata-Version', str(version))
+ write_field('Name', self.get_name())
+ write_field('Version', self.get_version())
+
+ summary = self.get_description()
+ if summary:
+ write_field('Summary', single_line(summary))
+
+ optional_fields = (
+ ('Home-page', 'url'),
+ ('Download-URL', 'download_url'),
+ ('Author', 'author'),
+ ('Author-email', 'author_email'),
+ ('Maintainer', 'maintainer'),
+ ('Maintainer-email', 'maintainer_email'),
+ )
+
+ for field, attr in optional_fields:
+ attr_val = getattr(self, attr, None)
+ if attr_val is not None:
+ write_field(field, attr_val)
+
+ if license_expression := self.license_expression:
+ write_field('License-Expression', license_expression)
+ elif license := self.get_license():
+ write_field('License', rfc822_escape(license))
+
+ for label, url in self.project_urls.items():
+ write_field('Project-URL', f'{label}, {url}')
+
+ keywords = ','.join(self.get_keywords())
+ if keywords:
+ write_field('Keywords', keywords)
+
+ platforms = self.get_platforms() or []
+ for platform in platforms:
+ write_field('Platform', platform)
+
+ self._write_list(file, 'Classifier', self.get_classifiers())
+
+ # PEP 314
+ self._write_list(file, 'Requires', self.get_requires())
+ self._write_list(file, 'Provides', self.get_provides())
+ self._write_list(file, 'Obsoletes', self.get_obsoletes())
+
+ # Setuptools specific for PEP 345
+ if hasattr(self, 'python_requires'):
+ write_field('Requires-Python', self.python_requires)
+
+ # PEP 566
+ if self.long_description_content_type:
+ write_field('Description-Content-Type', self.long_description_content_type)
+
+ safe_license_files = map(_safe_license_file, self.license_files or [])
+ self._write_list(file, 'License-File', safe_license_files)
+ _write_requirements(self, file)
+
+ for field, attr in _POSSIBLE_DYNAMIC_FIELDS.items():
+ if (val := getattr(self, attr, None)) and not is_static(val):
+ write_field('Dynamic', field)
+
+ long_description = self.get_long_description()
+ if long_description:
+ file.write(f"\n{long_description}")
+ if not long_description.endswith("\n"):
+ file.write("\n")
+
+
+def _write_requirements(self, file):
+ for req in _reqs.parse(self.install_requires):
+ file.write(f"Requires-Dist: {req}\n")
+
+ processed_extras = {}
+ for augmented_extra, reqs in self.extras_require.items():
+ # Historically, setuptools allows "augmented extras": `:`
+ unsafe_extra, _, condition = augmented_extra.partition(":")
+ unsafe_extra = unsafe_extra.strip()
+ extra = _normalization.safe_extra(unsafe_extra)
+
+ if extra:
+ _write_provides_extra(file, processed_extras, extra, unsafe_extra)
+ for req in _reqs.parse_strings(reqs):
+ r = _include_extra(req, extra, condition.strip())
+ file.write(f"Requires-Dist: {r}\n")
+
+ return processed_extras
+
+
+def _include_extra(req: str, extra: str, condition: str) -> Requirement:
+ r = Requirement(req) # create a fresh object that can be modified
+ parts = (
+ f"({r.marker})" if r.marker else None,
+ f"({condition})" if condition else None,
+ f"extra == {extra!r}" if extra else None,
+ )
+ r.marker = Marker(" and ".join(x for x in parts if x))
+ return r
+
+
+def _write_provides_extra(file, processed_extras, safe, unsafe):
+ previous = processed_extras.get(safe)
+ if previous == unsafe:
+ SetuptoolsDeprecationWarning.emit(
+ 'Ambiguity during "extra" normalization for dependencies.',
+ f"""
+ {previous!r} and {unsafe!r} normalize to the same value:\n
+ {safe!r}\n
+ In future versions, setuptools might halt the build process.
+ """,
+ see_url="https://peps.python.org/pep-0685/",
+ )
+ else:
+ processed_extras[safe] = unsafe
+ file.write(f"Provides-Extra: {safe}\n")
+
+
+# from pypa/distutils#244; needed only until that logic is always available
+def get_fullname(self):
+ return _distribution_fullname(self.get_name(), self.get_version())
+
+
+def _distribution_fullname(name: str, version: str) -> str:
+ """
+ >>> _distribution_fullname('setup.tools', '1.0-2')
+ 'setup_tools-1.0.post2'
+ >>> _distribution_fullname('setup-tools', '1.2post2')
+ 'setup_tools-1.2.post2'
+ >>> _distribution_fullname('setup-tools', '1.0-r2')
+ 'setup_tools-1.0.post2'
+ >>> _distribution_fullname('setup.tools', '1.0.post')
+ 'setup_tools-1.0.post0'
+ >>> _distribution_fullname('setup.tools', '1.0+ubuntu-1')
+ 'setup_tools-1.0+ubuntu.1'
+ """
+ return "{}-{}".format(
+ canonicalize_name(name).replace('-', '_'),
+ canonicalize_version(version, strip_trailing_zero=False),
+ )
+
+
+def _safe_license_file(file):
+ # XXX: Do we need this after the deprecation discussed in #4892, #4896??
+ normalized = os.path.normpath(file).replace(os.sep, "/")
+ if "../" in normalized:
+ return os.path.basename(normalized) # Temporarily restore pre PEP639 behaviour
+ return normalized
+
+
+_POSSIBLE_DYNAMIC_FIELDS = {
+ # Core Metadata Field x related Distribution attribute
+ "author": "author",
+ "author-email": "author_email",
+ "classifier": "classifiers",
+ "description": "long_description",
+ "description-content-type": "long_description_content_type",
+ "download-url": "download_url",
+ "home-page": "url",
+ "keywords": "keywords",
+ "license": "license",
+ # XXX: License-File is complicated because the user gives globs that are expanded
+ # during the build. Without special handling it is likely always
+ # marked as Dynamic, which is an acceptable outcome according to:
+ # https://github.com/pypa/setuptools/issues/4629#issuecomment-2331233677
+ "license-file": "license_files",
+ "license-expression": "license_expression", # PEP 639
+ "maintainer": "maintainer",
+ "maintainer-email": "maintainer_email",
+ "obsoletes": "obsoletes",
+ # "obsoletes-dist": "obsoletes_dist", # NOT USED
+ "platform": "platforms",
+ "project-url": "project_urls",
+ "provides": "provides",
+ # "provides-dist": "provides_dist", # NOT USED
+ "provides-extra": "extras_require",
+ "requires": "requires",
+ "requires-dist": "install_requires",
+ # "requires-external": "requires_external", # NOT USED
+ "requires-python": "python_requires",
+ "summary": "description",
+ # "supported-platform": "supported_platforms", # NOT USED
+}
diff --git a/lib/python3.12/site-packages/setuptools/_discovery.py b/lib/python3.12/site-packages/setuptools/_discovery.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1b4a0ee0351d69de5f0ab7d65bbe6958e398916
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_discovery.py
@@ -0,0 +1,33 @@
+import functools
+import operator
+
+import packaging.requirements
+
+
+# from coherent.build.discovery
+def extras_from_dep(dep):
+ try:
+ markers = packaging.requirements.Requirement(dep).marker._markers
+ except AttributeError:
+ markers = ()
+ return set(
+ marker[2].value
+ for marker in markers
+ if isinstance(marker, tuple) and marker[0].value == 'extra'
+ )
+
+
+def extras_from_deps(deps):
+ """
+ >>> extras_from_deps(['requests'])
+ set()
+ >>> extras_from_deps(['pytest; extra == "test"'])
+ {'test'}
+ >>> sorted(extras_from_deps([
+ ... 'requests',
+ ... 'pytest; extra == "test"',
+ ... 'pytest-cov; extra == "test"',
+ ... 'sphinx; extra=="doc"']))
+ ['doc', 'test']
+ """
+ return functools.reduce(operator.or_, map(extras_from_dep, deps), set())
diff --git a/lib/python3.12/site-packages/setuptools/_entry_points.py b/lib/python3.12/site-packages/setuptools/_entry_points.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd5dd2c8ac99783a2edb617278627ba170a9872b
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_entry_points.py
@@ -0,0 +1,94 @@
+import functools
+import itertools
+import operator
+
+from jaraco.functools import pass_none
+from jaraco.text import yield_lines
+from more_itertools import consume
+
+from ._importlib import metadata
+from ._itertools import ensure_unique
+from .errors import OptionError
+
+
+def ensure_valid(ep):
+ """
+ Exercise one of the dynamic properties to trigger
+ the pattern match.
+
+ This function is deprecated in favor of importlib_metadata 8.7 and
+ Python 3.14 importlib.metadata, which validates entry points on
+ construction.
+ """
+ try:
+ ep.extras
+ except (AttributeError, AssertionError) as ex:
+ # Why both? See https://github.com/python/importlib_metadata/issues/488
+ msg = (
+ f"Problems to parse {ep}.\nPlease ensure entry-point follows the spec: "
+ "https://packaging.python.org/en/latest/specifications/entry-points/"
+ )
+ raise OptionError(msg) from ex
+
+
+def load_group(value, group):
+ """
+ Given a value of an entry point or series of entry points,
+ return each as an EntryPoint.
+ """
+ # normalize to a single sequence of lines
+ lines = yield_lines(value)
+ text = f'[{group}]\n' + '\n'.join(lines)
+ return metadata.EntryPoints._from_text(text)
+
+
+def by_group_and_name(ep):
+ return ep.group, ep.name
+
+
+def validate(eps: metadata.EntryPoints):
+ """
+ Ensure entry points are unique by group and name and validate each.
+ """
+ consume(map(ensure_valid, ensure_unique(eps, key=by_group_and_name)))
+ return eps
+
+
+@functools.singledispatch
+def load(eps):
+ """
+ Given a Distribution.entry_points, produce EntryPoints.
+ """
+ groups = itertools.chain.from_iterable(
+ load_group(value, group) for group, value in eps.items()
+ )
+ return validate(metadata.EntryPoints(groups))
+
+
+@load.register(str)
+def _(eps):
+ r"""
+ >>> ep, = load('[console_scripts]\nfoo=bar')
+ >>> ep.group
+ 'console_scripts'
+ >>> ep.name
+ 'foo'
+ >>> ep.value
+ 'bar'
+ """
+ return validate(metadata.EntryPoints(metadata.EntryPoints._from_text(eps)))
+
+
+load.register(type(None), lambda x: x)
+
+
+@pass_none
+def render(eps: metadata.EntryPoints):
+ by_group = operator.attrgetter('group')
+ groups = itertools.groupby(sorted(eps, key=by_group), by_group)
+
+ return '\n'.join(f'[{group}]\n{render_items(items)}\n' for group, items in groups)
+
+
+def render_items(eps):
+ return '\n'.join(f'{ep.name} = {ep.value}' for ep in sorted(eps))
diff --git a/lib/python3.12/site-packages/setuptools/_imp.py b/lib/python3.12/site-packages/setuptools/_imp.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1d9f29218987d4f830f2d57aca9e3f74d00a095
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_imp.py
@@ -0,0 +1,87 @@
+"""
+Re-implementation of find_module and get_frozen_object
+from the deprecated imp module.
+"""
+
+import importlib.machinery
+import importlib.util
+import os
+import tokenize
+from importlib.util import module_from_spec
+
+PY_SOURCE = 1
+PY_COMPILED = 2
+C_EXTENSION = 3
+C_BUILTIN = 6
+PY_FROZEN = 7
+
+
+def find_spec(module, paths):
+ finder = (
+ importlib.machinery.PathFinder().find_spec
+ if isinstance(paths, list)
+ else importlib.util.find_spec
+ )
+ return finder(module, paths)
+
+
+def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+ spec = find_spec(module, paths)
+ if spec is None:
+ raise ImportError(f"Can't find {module}")
+ if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
+ spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
+
+ kind = -1
+ file = None
+ static = isinstance(spec.loader, type)
+ if (
+ spec.origin == 'frozen'
+ or static
+ and issubclass(spec.loader, importlib.machinery.FrozenImporter)
+ ):
+ kind = PY_FROZEN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compatibility
+ elif (
+ spec.origin == 'built-in'
+ or static
+ and issubclass(spec.loader, importlib.machinery.BuiltinImporter)
+ ):
+ kind = C_BUILTIN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compatibility
+ elif spec.has_location:
+ path = spec.origin
+ suffix = os.path.splitext(path)[1]
+ mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
+
+ if suffix in importlib.machinery.SOURCE_SUFFIXES:
+ kind = PY_SOURCE
+ file = tokenize.open(path)
+ elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
+ kind = PY_COMPILED
+ file = open(path, 'rb')
+ elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
+ kind = C_EXTENSION
+
+ else:
+ path = None
+ suffix = mode = ''
+
+ return file, path, (suffix, mode, kind)
+
+
+def get_frozen_object(module, paths=None):
+ spec = find_spec(module, paths)
+ if not spec:
+ raise ImportError(f"Can't find {module}")
+ return spec.loader.get_code(module)
+
+
+def get_module(module, paths, info):
+ spec = find_spec(module, paths)
+ if not spec:
+ raise ImportError(f"Can't find {module}")
+ return module_from_spec(spec)
diff --git a/lib/python3.12/site-packages/setuptools/_importlib.py b/lib/python3.12/site-packages/setuptools/_importlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce0fd52653b56c9c2cb2b2c7bfb35e3ec3c61408
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_importlib.py
@@ -0,0 +1,9 @@
+import sys
+
+if sys.version_info < (3, 10):
+ import importlib_metadata as metadata # pragma: no cover
+else:
+ import importlib.metadata as metadata # noqa: F401
+
+
+import importlib.resources as resources # noqa: F401
diff --git a/lib/python3.12/site-packages/setuptools/_itertools.py b/lib/python3.12/site-packages/setuptools/_itertools.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6ca841353ce39ac4361013f5c8160d69028d0d8
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_itertools.py
@@ -0,0 +1,23 @@
+from more_itertools import consume # noqa: F401
+
+
+# copied from jaraco.itertools 6.1
+def ensure_unique(iterable, key=lambda x: x):
+ """
+ Wrap an iterable to raise a ValueError if non-unique values are encountered.
+
+ >>> list(ensure_unique('abc'))
+ ['a', 'b', 'c']
+ >>> consume(ensure_unique('abca'))
+ Traceback (most recent call last):
+ ...
+ ValueError: Duplicate element 'a' encountered.
+ """
+ seen = set()
+ seen_add = seen.add
+ for element in iterable:
+ k = key(element)
+ if k in seen:
+ raise ValueError(f"Duplicate element {element!r} encountered.")
+ seen_add(k)
+ yield element
diff --git a/lib/python3.12/site-packages/setuptools/_normalization.py b/lib/python3.12/site-packages/setuptools/_normalization.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b8d4ddbf900d648e30d61d96d9b9993c7c57663
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_normalization.py
@@ -0,0 +1,180 @@
+"""
+Helpers for normalization as expected in wheel/sdist/module file names
+and core metadata
+"""
+
+import re
+from typing import TYPE_CHECKING
+
+import packaging
+
+# https://packaging.python.org/en/latest/specifications/core-metadata/#name
+_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE)
+_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9._-]+", re.IGNORECASE)
+_NON_ALPHANUMERIC = re.compile(r"[^A-Z0-9]+", re.IGNORECASE)
+_PEP440_FALLBACK = re.compile(
+ r"^v?(?P(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.IGNORECASE
+)
+
+
+def safe_identifier(name: str) -> str:
+ """Make a string safe to be used as Python identifier.
+ >>> safe_identifier("12abc")
+ '_12abc'
+ >>> safe_identifier("__editable__.myns.pkg-78.9.3_local")
+ '__editable___myns_pkg_78_9_3_local'
+ """
+ safe = re.sub(r'\W|^(?=\d)', '_', name)
+ assert safe.isidentifier()
+ return safe
+
+
+def safe_name(component: str) -> str:
+ """Escape a component used as a project name according to Core Metadata.
+ >>> safe_name("hello world")
+ 'hello-world'
+ >>> safe_name("hello?world")
+ 'hello-world'
+ >>> safe_name("hello_world")
+ 'hello_world'
+ """
+ return _UNSAFE_NAME_CHARS.sub("-", component)
+
+
+def safe_version(version: str) -> str:
+ """Convert an arbitrary string into a valid version string.
+ Can still raise an ``InvalidVersion`` exception.
+ To avoid exceptions use ``best_effort_version``.
+ >>> safe_version("1988 12 25")
+ '1988.12.25'
+ >>> safe_version("v0.2.1")
+ '0.2.1'
+ >>> safe_version("v0.2?beta")
+ '0.2b0'
+ >>> safe_version("v0.2 beta")
+ '0.2b0'
+ >>> safe_version("ubuntu lts")
+ Traceback (most recent call last):
+ ...
+ packaging.version.InvalidVersion: Invalid version: 'ubuntu.lts'
+ """
+ v = version.replace(' ', '.')
+ try:
+ return str(packaging.version.Version(v))
+ except packaging.version.InvalidVersion:
+ attempt = _UNSAFE_NAME_CHARS.sub("-", v)
+ return str(packaging.version.Version(attempt))
+
+
+def best_effort_version(version: str) -> str:
+ """Convert an arbitrary string into a version-like string.
+ Fallback when ``safe_version`` is not safe enough.
+ >>> best_effort_version("v0.2 beta")
+ '0.2b0'
+ >>> best_effort_version("ubuntu lts")
+ '0.dev0+sanitized.ubuntu.lts'
+ >>> best_effort_version("0.23ubuntu1")
+ '0.23.dev0+sanitized.ubuntu1'
+ >>> best_effort_version("0.23-")
+ '0.23.dev0+sanitized'
+ >>> best_effort_version("0.-_")
+ '0.dev0+sanitized'
+ >>> best_effort_version("42.+?1")
+ '42.dev0+sanitized.1'
+ """
+ try:
+ return safe_version(version)
+ except packaging.version.InvalidVersion:
+ v = version.replace(' ', '.')
+ match = _PEP440_FALLBACK.search(v)
+ if match:
+ safe = match["safe"]
+ rest = v[len(safe) :]
+ else:
+ safe = "0"
+ rest = version
+ safe_rest = _NON_ALPHANUMERIC.sub(".", rest).strip(".")
+ local = f"sanitized.{safe_rest}".strip(".")
+ return safe_version(f"{safe}.dev0+{local}")
+
+
+def safe_extra(extra: str) -> str:
+ """Normalize extra name according to PEP 685
+ >>> safe_extra("_FrIeNdLy-._.-bArD")
+ 'friendly-bard'
+ >>> safe_extra("FrIeNdLy-._.-bArD__._-")
+ 'friendly-bard'
+ """
+ return _NON_ALPHANUMERIC.sub("-", extra).strip("-").lower()
+
+
+def filename_component(value: str) -> str:
+ """Normalize each component of a filename (e.g. distribution/version part of wheel)
+ Note: ``value`` needs to be already normalized.
+ >>> filename_component("my-pkg")
+ 'my_pkg'
+ """
+ return value.replace("-", "_").strip("_")
+
+
+def filename_component_broken(value: str) -> str:
+ """
+ Produce the incorrect filename component for compatibility.
+
+ See pypa/setuptools#4167 for detailed analysis.
+
+ TODO: replace this with filename_component after pip 24 is
+ nearly-ubiquitous.
+
+ >>> filename_component_broken('foo_bar-baz')
+ 'foo-bar-baz'
+ """
+ return value.replace('_', '-')
+
+
+def safer_name(value: str) -> str:
+ """Like ``safe_name`` but can be used as filename component for wheel"""
+ # See bdist_wheel.safer_name
+ return (
+ # Per https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
+ re
+ .sub(r"[-_.]+", "-", safe_name(value))
+ .lower()
+ # Per https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
+ .replace("-", "_")
+ )
+
+
+def safer_best_effort_version(value: str) -> str:
+ """Like ``best_effort_version`` but can be used as filename component for wheel"""
+ # See bdist_wheel.safer_verion
+ # TODO: Replace with only safe_version in the future (no need for best effort)
+ return filename_component(best_effort_version(value))
+
+
+def _missing_canonicalize_license_expression(expression: str) -> str:
+ """
+ Defer import error to affect only users that actually use it
+ https://github.com/pypa/setuptools/issues/4894
+ >>> _missing_canonicalize_license_expression("a OR b")
+ Traceback (most recent call last):
+ ...
+ ImportError: ...Cannot import `packaging.licenses`...
+ """
+ raise ImportError(
+ "Cannot import `packaging.licenses`."
+ """
+ Setuptools>=77.0.0 requires "packaging>=24.2" to work properly.
+ Please make sure you have a suitable version installed.
+ """
+ )
+
+
+try:
+ from packaging.licenses import (
+ canonicalize_license_expression as _canonicalize_license_expression,
+ )
+except ImportError: # pragma: nocover
+ if not TYPE_CHECKING:
+ # XXX: pyright is still upset even with # pyright: ignore[reportAssignmentType]
+ _canonicalize_license_expression = _missing_canonicalize_license_expression
diff --git a/lib/python3.12/site-packages/setuptools/_path.py b/lib/python3.12/site-packages/setuptools/_path.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b78022934d89599e642a10f861b382947031be9
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_path.py
@@ -0,0 +1,93 @@
+from __future__ import annotations
+
+import contextlib
+import os
+import sys
+from typing import TYPE_CHECKING, TypeVar, Union
+
+from more_itertools import unique_everseen
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+StrPath: TypeAlias = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath
+StrPathT = TypeVar("StrPathT", bound=Union[str, os.PathLike[str]])
+
+
+def ensure_directory(path):
+ """Ensure that the parent directory of `path` exists"""
+ dirname = os.path.dirname(path)
+ os.makedirs(dirname, exist_ok=True)
+
+
+def same_path(p1: StrPath, p2: StrPath) -> bool:
+ """Differs from os.path.samefile because it does not require paths to exist.
+ Purely string based (no comparison between i-nodes).
+ >>> same_path("a/b", "./a/b")
+ True
+ >>> same_path("a/b", "a/./b")
+ True
+ >>> same_path("a/b", "././a/b")
+ True
+ >>> same_path("a/b", "./a/b/c/..")
+ True
+ >>> same_path("a/b", "../a/b/c")
+ False
+ >>> same_path("a", "a/b")
+ False
+ """
+ return normpath(p1) == normpath(p2)
+
+
+def _cygwin_patch(filename: StrPath): # pragma: nocover
+ """
+ Contrary to POSIX 2008, on Cygwin, getcwd (3) contains
+ symlink components. Using
+ os.path.abspath() works around this limitation. A fix in os.getcwd()
+ would probably better, in Cygwin even more so, except
+ that this seems to be by design...
+ """
+ return os.path.abspath(filename) if sys.platform == 'cygwin' else filename
+
+
+def normpath(filename: StrPath) -> str:
+ """Normalize a file/dir name for comparison purposes."""
+ return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
+
+
+@contextlib.contextmanager
+def paths_on_pythonpath(paths):
+ """
+ Add the indicated paths to the head of the PYTHONPATH environment
+ variable so that subprocesses will also see the packages at
+ these paths.
+
+ Do this in a context that restores the value on exit.
+
+ >>> getfixture('monkeypatch').setenv('PYTHONPATH', 'anything')
+ >>> with paths_on_pythonpath(['foo', 'bar']):
+ ... assert 'foo' in os.environ['PYTHONPATH']
+ ... assert 'anything' in os.environ['PYTHONPATH']
+ >>> os.environ['PYTHONPATH']
+ 'anything'
+
+ >>> getfixture('monkeypatch').delenv('PYTHONPATH')
+ >>> with paths_on_pythonpath(['foo', 'bar']):
+ ... assert 'foo' in os.environ['PYTHONPATH']
+ >>> os.environ.get('PYTHONPATH')
+ """
+ nothing = object()
+ orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
+ current_pythonpath = os.environ.get('PYTHONPATH', '')
+ try:
+ prefix = os.pathsep.join(unique_everseen(paths))
+ to_join = filter(None, [prefix, current_pythonpath])
+ new_path = os.pathsep.join(to_join)
+ if new_path:
+ os.environ['PYTHONPATH'] = new_path
+ yield
+ finally:
+ if orig_pythonpath is nothing:
+ os.environ.pop('PYTHONPATH', None)
+ else:
+ os.environ['PYTHONPATH'] = orig_pythonpath
diff --git a/lib/python3.12/site-packages/setuptools/_reqs.py b/lib/python3.12/site-packages/setuptools/_reqs.py
new file mode 100644
index 0000000000000000000000000000000000000000..7be56cbf35d4a7cbeacdca1c18599433d7fb2f7f
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_reqs.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Iterator
+from functools import lru_cache
+from typing import TYPE_CHECKING, Callable, TypeVar, Union, overload
+
+import jaraco.text as text
+from packaging.requirements import Requirement
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+_T = TypeVar("_T")
+_StrOrIter: TypeAlias = Union[str, Iterable[str]]
+
+
+parse_req: Callable[[str], Requirement] = lru_cache()(Requirement)
+# Setuptools parses the same requirement many times
+# (e.g. first for validation than for normalisation),
+# so it might be worth to cache.
+
+
+def parse_strings(strs: _StrOrIter) -> Iterator[str]:
+ """
+ Yield requirement strings for each specification in `strs`.
+
+ `strs` must be a string, or a (possibly-nested) iterable thereof.
+ """
+ return text.join_continuation(map(text.drop_comment, text.yield_lines(strs)))
+
+
+# These overloads are only needed because of a mypy false-positive, pyright gets it right
+# https://github.com/python/mypy/issues/3737
+@overload
+def parse(strs: _StrOrIter) -> Iterator[Requirement]: ...
+@overload
+def parse(strs: _StrOrIter, parser: Callable[[str], _T]) -> Iterator[_T]: ...
+def parse(strs: _StrOrIter, parser: Callable[[str], _T] = parse_req) -> Iterator[_T]: # type: ignore[assignment]
+ """
+ Parse requirements.
+ """
+ return map(parser, parse_strings(strs))
diff --git a/lib/python3.12/site-packages/setuptools/_scripts.py b/lib/python3.12/site-packages/setuptools/_scripts.py
new file mode 100644
index 0000000000000000000000000000000000000000..88bf02f927ba7cb47731fc0984f4b4f135fbdd45
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_scripts.py
@@ -0,0 +1,361 @@
+from __future__ import annotations
+
+import os
+import re
+import shlex
+import shutil
+import struct
+import subprocess
+import sys
+import textwrap
+from collections.abc import Iterable
+from typing import TYPE_CHECKING, TypedDict
+
+from ._importlib import metadata, resources
+
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
+from .warnings import SetuptoolsWarning
+
+from distutils.command.build_scripts import first_line_re
+from distutils.util import get_platform
+
+
+class _SplitArgs(TypedDict, total=False):
+ comments: bool
+ posix: bool
+
+
+class CommandSpec(list):
+ """
+ A command spec for a #! header, specified as a list of arguments akin to
+ those passed to Popen.
+ """
+
+ options: list[str] = []
+ split_args = _SplitArgs()
+
+ @classmethod
+ def best(cls):
+ """
+ Choose the best CommandSpec class based on environmental conditions.
+ """
+ return cls
+
+ @classmethod
+ def _sys_executable(cls):
+ _default = os.path.normpath(sys.executable)
+ return os.environ.get('__PYVENV_LAUNCHER__', _default)
+
+ @classmethod
+ def from_param(cls, param: Self | str | Iterable[str] | None) -> Self:
+ """
+ Construct a CommandSpec from a parameter to build_scripts, which may
+ be None.
+ """
+ if isinstance(param, cls):
+ return param
+ if isinstance(param, str):
+ return cls.from_string(param)
+ if isinstance(param, Iterable):
+ return cls(param)
+ if param is None:
+ return cls.from_environment()
+ raise TypeError(f"Argument has an unsupported type {type(param)}")
+
+ @classmethod
+ def from_environment(cls):
+ return cls([cls._sys_executable()])
+
+ @classmethod
+ def from_string(cls, string: str) -> Self:
+ """
+ Construct a command spec from a simple string representing a command
+ line parseable by shlex.split.
+ """
+ items = shlex.split(string, **cls.split_args)
+ return cls(items)
+
+ def install_options(self, script_text: str):
+ self.options = shlex.split(self._extract_options(script_text))
+ cmdline = subprocess.list2cmdline(self)
+ if not isascii(cmdline):
+ self.options[:0] = ['-x']
+
+ @staticmethod
+ def _extract_options(orig_script):
+ """
+ Extract any options from the first line of the script.
+ """
+ first = (orig_script + '\n').splitlines()[0]
+ match = _first_line_re().match(first)
+ options = match.group(1) or '' if match else ''
+ return options.strip()
+
+ def as_header(self):
+ return self._render(self + list(self.options))
+
+ @staticmethod
+ def _strip_quotes(item):
+ _QUOTES = '"\''
+ for q in _QUOTES:
+ if item.startswith(q) and item.endswith(q):
+ return item[1:-1]
+ return item
+
+ @staticmethod
+ def _render(items):
+ cmdline = subprocess.list2cmdline(
+ CommandSpec._strip_quotes(item.strip()) for item in items
+ )
+ return '#!' + cmdline + '\n'
+
+
+class WindowsCommandSpec(CommandSpec):
+ split_args = _SplitArgs(posix=False)
+
+
+class ScriptWriter:
+ """
+ Encapsulates behavior around writing entry point scripts for console and
+ gui apps.
+ """
+
+ template = textwrap.dedent(
+ r"""
+ # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
+ import re
+ import sys
+
+ # for compatibility with easy_install; see #2198
+ __requires__ = %(spec)r
+
+ try:
+ from importlib.metadata import distribution
+ except ImportError:
+ try:
+ from importlib_metadata import distribution
+ except ImportError:
+ from pkg_resources import load_entry_point
+
+
+ def importlib_load_entry_point(spec, group, name):
+ dist_name, _, _ = spec.partition('==')
+ matches = (
+ entry_point
+ for entry_point in distribution(dist_name).entry_points
+ if entry_point.group == group and entry_point.name == name
+ )
+ return next(matches).load()
+
+
+ globals().setdefault('load_entry_point', importlib_load_entry_point)
+
+
+ if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
+ sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)())
+ """
+ ).lstrip()
+
+ command_spec_class = CommandSpec
+
+ @classmethod
+ def get_args(cls, dist, header=None):
+ """
+ Yield write_script() argument tuples for a distribution's
+ console_scripts and gui_scripts entry points.
+ """
+
+ # If distribution is not an importlib.metadata.Distribution, assume
+ # it's a pkg_resources.Distribution and transform it.
+ if not hasattr(dist, 'entry_points'):
+ SetuptoolsWarning.emit("Unsupported distribution encountered.")
+ dist = metadata.Distribution.at(dist.egg_info)
+
+ if header is None:
+ header = cls.get_header()
+ spec = f'{dist.name}=={dist.version}'
+ for type_ in 'console', 'gui':
+ group = f'{type_}_scripts'
+ for ep in dist.entry_points.select(group=group):
+ name = ep.name
+ cls._ensure_safe_name(ep.name)
+ script_text = cls.template % locals()
+ args = cls._get_script_args(type_, ep.name, header, script_text)
+ yield from args
+
+ @staticmethod
+ def _ensure_safe_name(name):
+ """
+ Prevent paths in *_scripts entry point names.
+ """
+ has_path_sep = re.search(r'[\\/]', name)
+ if has_path_sep:
+ raise ValueError("Path separators not allowed in script names")
+
+ @classmethod
+ def best(cls):
+ """
+ Select the best ScriptWriter for this environment.
+ """
+ if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):
+ return WindowsScriptWriter.best()
+ else:
+ return cls
+
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ # Simply write the stub with no extension.
+ yield (name, header + script_text)
+
+ @classmethod
+ def get_header(
+ cls,
+ script_text: str = "",
+ executable: str | CommandSpec | Iterable[str] | None = None,
+ ) -> str:
+ """Create a #! line, getting options (if any) from script_text"""
+ cmd = cls.command_spec_class.best().from_param(executable)
+ cmd.install_options(script_text)
+ return cmd.as_header()
+
+
+class WindowsScriptWriter(ScriptWriter):
+ command_spec_class = WindowsCommandSpec
+
+ @classmethod
+ def best(cls):
+ """
+ Select the best ScriptWriter suitable for Windows
+ """
+ writer_lookup = dict(
+ executable=WindowsExecutableLauncherWriter,
+ natural=cls,
+ )
+ # for compatibility, use the executable launcher by default
+ launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
+ return writer_lookup[launcher]
+
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ "For Windows, add a .py extension"
+ ext = dict(console='.pya', gui='.pyw')[type_]
+ if ext not in os.environ['PATHEXT'].lower().split(';'):
+ msg = (
+ "{ext} not listed in PATHEXT; scripts will not be "
+ "recognized as executables."
+ ).format(**locals())
+ SetuptoolsWarning.emit(msg)
+ old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
+ old.remove(ext)
+ header = cls._adjust_header(type_, header)
+ blockers = [name + x for x in old]
+ yield name + ext, header + script_text, 't', blockers
+
+ @classmethod
+ def _adjust_header(cls, type_, orig_header):
+ """
+ Make sure 'pythonw' is used for gui and 'python' is used for
+ console (regardless of what sys.executable is).
+ """
+ pattern = 'pythonw.exe'
+ repl = 'python.exe'
+ if type_ == 'gui':
+ pattern, repl = repl, pattern
+ pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)
+ new_header = pattern_ob.sub(string=orig_header, repl=repl)
+ return new_header if cls._use_header(new_header) else orig_header
+
+ @staticmethod
+ def _use_header(new_header):
+ """
+ Should _adjust_header use the replaced header?
+
+ On non-windows systems, always use. On
+ Windows systems, only use the replaced header if it resolves
+ to an executable on the system.
+ """
+ clean_header = new_header[2:-1].strip('"')
+ return sys.platform != 'win32' or shutil.which(clean_header)
+
+
+class WindowsExecutableLauncherWriter(WindowsScriptWriter):
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ """
+ For Windows, add a .py extension and an .exe launcher
+ """
+ if type_ == 'gui':
+ launcher_type = 'gui'
+ ext = '-script.pyw'
+ old = ['.pyw']
+ else:
+ launcher_type = 'cli'
+ ext = '-script.py'
+ old = ['.py', '.pyc', '.pyo']
+ hdr = cls._adjust_header(type_, header)
+ blockers = [name + x for x in old]
+ yield (name + ext, hdr + script_text, 't', blockers)
+ yield (
+ name + '.exe',
+ get_win_launcher(launcher_type),
+ 'b', # write in binary mode
+ )
+ if not is_64bit():
+ # install a manifest for the launcher to prevent Windows
+ # from detecting it as an installer (which it will for
+ # launchers like easy_install.exe). Consider only
+ # adding a manifest for launchers detected as installers.
+ # See Distribute #143 for details.
+ m_name = name + '.exe.manifest'
+ yield (m_name, load_launcher_manifest(name), 't')
+
+
+def get_win_launcher(type):
+ """
+ Load the Windows launcher (executable) suitable for launching a script.
+
+ `type` should be either 'cli' or 'gui'
+
+ Returns the executable as a byte string.
+ """
+ launcher_fn = f'{type}.exe'
+ if is_64bit():
+ if get_platform() == "win-arm64":
+ launcher_fn = launcher_fn.replace(".", "-arm64.")
+ else:
+ launcher_fn = launcher_fn.replace(".", "-64.")
+ else:
+ launcher_fn = launcher_fn.replace(".", "-32.")
+ return resources.files('setuptools').joinpath(launcher_fn).read_bytes()
+
+
+def load_launcher_manifest(name):
+ res = resources.files(__name__).joinpath('launcher manifest.xml')
+ return res.read_text(encoding='utf-8') % vars()
+
+
+def _first_line_re():
+ """
+ Return a regular expression based on first_line_re suitable for matching
+ strings.
+ """
+ if isinstance(first_line_re.pattern, str):
+ return first_line_re
+
+ # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.
+ return re.compile(first_line_re.pattern.decode())
+
+
+def is_64bit():
+ return struct.calcsize("P") == 8
+
+
+def isascii(s):
+ try:
+ s.encode('ascii')
+ except UnicodeError:
+ return False
+ return True
diff --git a/lib/python3.12/site-packages/setuptools/_shutil.py b/lib/python3.12/site-packages/setuptools/_shutil.py
new file mode 100644
index 0000000000000000000000000000000000000000..660459a1102eed0da597ebd7c1a38cb4eee99791
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_shutil.py
@@ -0,0 +1,59 @@
+"""Convenience layer on top of stdlib's shutil and os"""
+
+import os
+import stat
+from typing import Callable, TypeVar
+
+from .compat import py311
+
+from distutils import log
+
+try:
+ from os import chmod # pyright: ignore[reportAssignmentType]
+ # Losing type-safety w/ pyright, but that's ok
+except ImportError: # pragma: no cover
+ # Jython compatibility
+ def chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway
+ pass
+
+
+_T = TypeVar("_T")
+
+
+def attempt_chmod_verbose(path, mode):
+ log.debug("changing mode of %s to %o", path, mode)
+ try:
+ chmod(path, mode)
+ except OSError as e: # pragma: no cover
+ log.debug("chmod failed: %s", e)
+
+
+# Must match shutil._OnExcCallback
+def _auto_chmod(
+ func: Callable[..., _T], arg: str, exc: BaseException
+) -> _T: # pragma: no cover
+ """shutils onexc callback to automatically call chmod for certain functions."""
+ # Only retry for scenarios known to have an issue
+ if func in [os.unlink, os.remove] and os.name == 'nt':
+ attempt_chmod_verbose(arg, stat.S_IWRITE)
+ return func(arg)
+ raise exc
+
+
+def rmtree(path, ignore_errors=False, onexc=_auto_chmod):
+ """
+ Similar to ``shutil.rmtree`` but automatically executes ``chmod``
+ for well know Windows failure scenarios.
+ """
+ return py311.shutil_rmtree(path, ignore_errors, onexc)
+
+
+def rmdir(path, **opts):
+ if os.path.isdir(path):
+ rmtree(path, **opts)
+
+
+def current_umask():
+ tmp = os.umask(0o022)
+ os.umask(tmp)
+ return tmp
diff --git a/lib/python3.12/site-packages/setuptools/_static.py b/lib/python3.12/site-packages/setuptools/_static.py
new file mode 100644
index 0000000000000000000000000000000000000000..af35862cf8b759a0e60110ce9e92bfdb1b49bc5f
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/_static.py
@@ -0,0 +1,188 @@
+from functools import wraps
+from typing import TypeVar
+
+import packaging.specifiers
+
+from .warnings import SetuptoolsDeprecationWarning
+
+
+class Static:
+ """
+ Wrapper for built-in object types that are allow setuptools to identify
+ static core metadata (in opposition to ``Dynamic``, as defined :pep:`643`).
+
+ The trick is to mark values with :class:`Static` when they come from
+ ``pyproject.toml`` or ``setup.cfg``, so if any plugin overwrite the value
+ with a built-in, setuptools will be able to recognise the change.
+
+ We inherit from built-in classes, so that we don't need to change the existing
+ code base to deal with the new types.
+ We also should strive for immutability objects to avoid changes after the
+ initial parsing.
+ """
+
+ _mutated_: bool = False # TODO: Remove after deprecation warning is solved
+
+
+def _prevent_modification(target: type, method: str, copying: str) -> None:
+ """
+ Because setuptools is very flexible we cannot fully prevent
+ plugins and user customizations from modifying static values that were
+ parsed from config files.
+ But we can attempt to block "in-place" mutations and identify when they
+ were done.
+ """
+ fn = getattr(target, method, None)
+ if fn is None:
+ return
+
+ @wraps(fn)
+ def _replacement(self: Static, *args, **kwargs):
+ # TODO: After deprecation period raise NotImplementedError instead of warning
+ # which obviated the existence and checks of the `_mutated_` attribute.
+ self._mutated_ = True
+ SetuptoolsDeprecationWarning.emit(
+ "Direct modification of value will be disallowed",
+ f"""
+ In an effort to implement PEP 643, direct/in-place changes of static values
+ that come from configuration files are deprecated.
+ If you need to modify this value, please first create a copy with {copying}
+ and make sure conform to all relevant standards when overriding setuptools
+ functionality (https://packaging.python.org/en/latest/specifications/).
+ """,
+ due_date=(2025, 10, 10), # Initially introduced in 2024-09-06
+ )
+ return fn(self, *args, **kwargs)
+
+ _replacement.__doc__ = "" # otherwise doctest may fail.
+ setattr(target, method, _replacement)
+
+
+class Str(str, Static):
+ pass
+
+
+class Tuple(tuple, Static):
+ pass
+
+
+class List(list, Static):
+ """
+ :meta private:
+ >>> x = List([1, 2, 3])
+ >>> is_static(x)
+ True
+ >>> x += [0] # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ SetuptoolsDeprecationWarning: Direct modification ...
+ >>> is_static(x) # no longer static after modification
+ False
+ >>> y = list(x)
+ >>> y.clear()
+ >>> y
+ []
+ >>> y == x
+ False
+ >>> is_static(List(y))
+ True
+ """
+
+
+# Make `List` immutable-ish
+# (certain places of setuptools/distutils issue a warn if we use tuple instead of list)
+for _method in (
+ '__delitem__',
+ '__iadd__',
+ '__setitem__',
+ 'append',
+ 'clear',
+ 'extend',
+ 'insert',
+ 'remove',
+ 'reverse',
+ 'pop',
+):
+ _prevent_modification(List, _method, "`list(value)`")
+
+
+class Dict(dict, Static):
+ """
+ :meta private:
+ >>> x = Dict({'a': 1, 'b': 2})
+ >>> is_static(x)
+ True
+ >>> x['c'] = 0 # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ SetuptoolsDeprecationWarning: Direct modification ...
+ >>> x._mutated_
+ True
+ >>> is_static(x) # no longer static after modification
+ False
+ >>> y = dict(x)
+ >>> y.popitem()
+ ('b', 2)
+ >>> y == x
+ False
+ >>> is_static(Dict(y))
+ True
+ """
+
+
+# Make `Dict` immutable-ish (we cannot inherit from types.MappingProxyType):
+for _method in (
+ '__delitem__',
+ '__ior__',
+ '__setitem__',
+ 'clear',
+ 'pop',
+ 'popitem',
+ 'setdefault',
+ 'update',
+):
+ _prevent_modification(Dict, _method, "`dict(value)`")
+
+
+class SpecifierSet(packaging.specifiers.SpecifierSet, Static):
+ """Not exactly a built-in type but useful for ``requires-python``"""
+
+
+T = TypeVar("T")
+
+
+def noop(value: T) -> T:
+ """
+ >>> noop(42)
+ 42
+ """
+ return value
+
+
+_CONVERSIONS = {str: Str, tuple: Tuple, list: List, dict: Dict}
+
+
+def attempt_conversion(value: T) -> T:
+ """
+ >>> is_static(attempt_conversion("hello"))
+ True
+ >>> is_static(object())
+ False
+ """
+ return _CONVERSIONS.get(type(value), noop)(value) # type: ignore[call-overload]
+
+
+def is_static(value: object) -> bool:
+ """
+ >>> is_static(a := Dict({'a': 1}))
+ True
+ >>> is_static(dict(a))
+ False
+ >>> is_static(b := List([1, 2, 3]))
+ True
+ >>> is_static(list(b))
+ False
+ """
+ return isinstance(value, Static) and not value._mutated_
+
+
+EMPTY_LIST = List()
+EMPTY_DICT = Dict()
diff --git a/lib/python3.12/site-packages/setuptools/archive_util.py b/lib/python3.12/site-packages/setuptools/archive_util.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a02010bb2af2be0487730d6a32080877b9ac220
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/archive_util.py
@@ -0,0 +1,219 @@
+"""Utilities for extracting common archive formats"""
+
+import contextlib
+import os
+import posixpath
+import shutil
+import tarfile
+import zipfile
+
+from ._path import ensure_directory
+
+from distutils.errors import DistutilsError
+
+__all__ = [
+ "unpack_archive",
+ "unpack_zipfile",
+ "unpack_tarfile",
+ "default_filter",
+ "UnrecognizedFormat",
+ "extraction_drivers",
+ "unpack_directory",
+]
+
+
+class UnrecognizedFormat(DistutilsError):
+ """Couldn't recognize the archive type"""
+
+
+def default_filter(src, dst):
+ """The default progress/filter callback; returns True for all files"""
+ return dst
+
+
+def unpack_archive(
+ filename, extract_dir, progress_filter=default_filter, drivers=None
+) -> None:
+ """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat``
+
+ `progress_filter` is a function taking two arguments: a source path
+ internal to the archive ('/'-separated), and a filesystem path where it
+ will be extracted. The callback must return the desired extract path
+ (which may be the same as the one passed in), or else ``None`` to skip
+ that file or directory. The callback can thus be used to report on the
+ progress of the extraction, as well as to filter the items extracted or
+ alter their extraction paths.
+
+ `drivers`, if supplied, must be a non-empty sequence of functions with the
+ same signature as this function (minus the `drivers` argument), that raise
+ ``UnrecognizedFormat`` if they do not support extracting the designated
+ archive type. The `drivers` are tried in sequence until one is found that
+ does not raise an error, or until all are exhausted (in which case
+ ``UnrecognizedFormat`` is raised). If you do not supply a sequence of
+ drivers, the module's ``extraction_drivers`` constant will be used, which
+ means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that
+ order.
+ """
+ for driver in drivers or extraction_drivers:
+ try:
+ driver(filename, extract_dir, progress_filter)
+ except UnrecognizedFormat:
+ continue
+ else:
+ return
+ else:
+ raise UnrecognizedFormat(f"Not a recognized archive type: {filename}")
+
+
+def unpack_directory(filename, extract_dir, progress_filter=default_filter) -> None:
+ """ "Unpack" a directory, using the same interface as for archives
+
+ Raises ``UnrecognizedFormat`` if `filename` is not a directory
+ """
+ if not os.path.isdir(filename):
+ raise UnrecognizedFormat(f"{filename} is not a directory")
+
+ paths = {
+ filename: ('', extract_dir),
+ }
+ for base, dirs, files in os.walk(filename):
+ src, dst = paths[base]
+ for d in dirs:
+ paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d)
+ for f in files:
+ target = os.path.join(dst, f)
+ target = progress_filter(src + f, target)
+ if not target:
+ # skip non-files
+ continue
+ ensure_directory(target)
+ f = os.path.join(base, f)
+ shutil.copyfile(f, target)
+ shutil.copystat(f, target)
+
+
+def unpack_zipfile(filename, extract_dir, progress_filter=default_filter) -> None:
+ """Unpack zip `filename` to `extract_dir`
+
+ Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined
+ by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation
+ of the `progress_filter` argument.
+ """
+
+ if not zipfile.is_zipfile(filename):
+ raise UnrecognizedFormat(f"{filename} is not a zip file")
+
+ with zipfile.ZipFile(filename) as z:
+ _unpack_zipfile_obj(z, extract_dir, progress_filter)
+
+
+def _unpack_zipfile_obj(zipfile_obj, extract_dir, progress_filter=default_filter):
+ """Internal/private API used by other parts of setuptools.
+ Similar to ``unpack_zipfile``, but receives an already opened :obj:`zipfile.ZipFile`
+ object instead of a filename.
+ """
+ for info in zipfile_obj.infolist():
+ name = info.filename
+
+ # don't extract absolute paths or ones with .. in them
+ if name.startswith('/') or '..' in name.split('/'):
+ continue
+
+ target = os.path.join(extract_dir, *name.split('/'))
+ target = progress_filter(name, target)
+ if not target:
+ continue
+ if name.endswith('/'):
+ # directory
+ ensure_directory(target)
+ else:
+ # file
+ ensure_directory(target)
+ data = zipfile_obj.read(info.filename)
+ with open(target, 'wb') as f:
+ f.write(data)
+ unix_attributes = info.external_attr >> 16
+ if unix_attributes:
+ os.chmod(target, unix_attributes)
+
+
+def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):
+ """Resolve any links and extract link targets as normal files."""
+ while tar_member_obj is not None and (
+ tar_member_obj.islnk() or tar_member_obj.issym()
+ ):
+ linkpath = tar_member_obj.linkname
+ if tar_member_obj.issym():
+ base = posixpath.dirname(tar_member_obj.name)
+ linkpath = posixpath.join(base, linkpath)
+ linkpath = posixpath.normpath(linkpath)
+ tar_member_obj = tar_obj._getmember(linkpath)
+
+ is_file_or_dir = tar_member_obj is not None and (
+ tar_member_obj.isfile() or tar_member_obj.isdir()
+ )
+ if is_file_or_dir:
+ return tar_member_obj
+
+ raise LookupError('Got unknown file type')
+
+
+def _iter_open_tar(tar_obj, extract_dir, progress_filter):
+ """Emit member-destination pairs from a tar archive."""
+ # don't do any chowning!
+ tar_obj.chown = lambda *args: None
+
+ with contextlib.closing(tar_obj):
+ for member in tar_obj:
+ name = member.name
+ # don't extract absolute paths or ones with .. in them
+ if name.startswith('/') or '..' in name.split('/'):
+ continue
+
+ prelim_dst = os.path.join(extract_dir, *name.split('/'))
+
+ try:
+ member = _resolve_tar_file_or_dir(tar_obj, member)
+ except LookupError:
+ continue
+
+ final_dst = progress_filter(name, prelim_dst)
+ if not final_dst:
+ continue
+
+ if final_dst.endswith(os.sep):
+ final_dst = final_dst[:-1]
+
+ yield member, final_dst
+
+
+def unpack_tarfile(filename, extract_dir, progress_filter=default_filter) -> bool:
+ """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
+
+ Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined
+ by ``tarfile.open()``). See ``unpack_archive()`` for an explanation
+ of the `progress_filter` argument.
+ """
+ try:
+ tarobj = tarfile.open(filename)
+ except tarfile.TarError as e:
+ raise UnrecognizedFormat(
+ f"{filename} is not a compressed or uncompressed tar file"
+ ) from e
+
+ for member, final_dst in _iter_open_tar(
+ tarobj,
+ extract_dir,
+ progress_filter,
+ ):
+ try:
+ # XXX Ugh
+ tarobj._extract_member(member, final_dst)
+ except tarfile.ExtractError:
+ # chown/chmod/mkfifo/mknode/makedev failed
+ pass
+
+ return True
+
+
+extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
diff --git a/lib/python3.12/site-packages/setuptools/build_meta.py b/lib/python3.12/site-packages/setuptools/build_meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dc04f6cbb7f1f012b26b99cc3b204cabaefdf4a
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/build_meta.py
@@ -0,0 +1,554 @@
+"""A PEP 517 interface to setuptools
+
+Previously, when a user or a command line tool (let's call it a "frontend")
+needed to make a request of setuptools to take a certain action, for
+example, generating a list of installation requirements, the frontend
+would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
+
+PEP 517 defines a different method of interfacing with setuptools. Rather
+than calling "setup.py" directly, the frontend should:
+
+ 1. Set the current directory to the directory with a setup.py file
+ 2. Import this module into a safe python interpreter (one in which
+ setuptools can potentially set global variables or crash hard).
+ 3. Call one of the functions defined in PEP 517.
+
+What each function does is defined in PEP 517. However, here is a "casual"
+definition of the functions (this definition should not be relied on for
+bug reports or API stability):
+
+ - `build_wheel`: build a wheel in the folder and return the basename
+ - `get_requires_for_build_wheel`: get the `setup_requires` to build
+ - `prepare_metadata_for_build_wheel`: get the `install_requires`
+ - `build_sdist`: build an sdist in the folder and return the basename
+ - `get_requires_for_build_sdist`: get the `setup_requires` to build
+
+Again, this is not a formal definition! Just a "taste" of the module.
+"""
+
+from __future__ import annotations
+
+import contextlib
+import io
+import os
+import shlex
+import shutil
+import sys
+import tempfile
+import tokenize
+import warnings
+from collections.abc import Iterable, Iterator, Mapping
+from pathlib import Path
+from typing import TYPE_CHECKING, NoReturn, Union
+
+import setuptools
+
+from . import errors
+from ._path import StrPath, same_path
+from ._reqs import parse_strings
+from .warnings import SetuptoolsDeprecationWarning
+
+import distutils
+from distutils.util import strtobool
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+__all__ = [
+ 'get_requires_for_build_sdist',
+ 'get_requires_for_build_wheel',
+ 'prepare_metadata_for_build_wheel',
+ 'build_wheel',
+ 'build_sdist',
+ 'get_requires_for_build_editable',
+ 'prepare_metadata_for_build_editable',
+ 'build_editable',
+ '__legacy__',
+ 'SetupRequirementsError',
+]
+
+
+class SetupRequirementsError(BaseException):
+ def __init__(self, specifiers) -> None:
+ self.specifiers = specifiers
+
+
+class Distribution(setuptools.dist.Distribution):
+ def fetch_build_eggs(self, specifiers) -> NoReturn:
+ specifier_list = list(parse_strings(specifiers))
+
+ raise SetupRequirementsError(specifier_list)
+
+ @classmethod
+ @contextlib.contextmanager
+ def patch(cls) -> Iterator[None]:
+ """
+ Replace
+ distutils.dist.Distribution with this class
+ for the duration of this context.
+ """
+ orig = distutils.core.Distribution
+ distutils.core.Distribution = cls # type: ignore[misc] # monkeypatching
+ try:
+ yield
+ finally:
+ distutils.core.Distribution = orig # type: ignore[misc] # monkeypatching
+
+
+@contextlib.contextmanager
+def no_install_setup_requires():
+ """Temporarily disable installing setup_requires
+
+ Under PEP 517, the backend reports build dependencies to the frontend,
+ and the frontend is responsible for ensuring they're installed.
+ So setuptools (acting as a backend) should not try to install them.
+ """
+ orig = setuptools._install_setup_requires
+ setuptools._install_setup_requires = lambda attrs: None
+ try:
+ yield
+ finally:
+ setuptools._install_setup_requires = orig
+
+
+def _get_immediate_subdirectories(a_dir):
+ return [
+ name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))
+ ]
+
+
+def _file_with_extension(directory: StrPath, extension: str | tuple[str, ...]):
+ matching = (f for f in os.listdir(directory) if f.endswith(extension))
+ try:
+ (file,) = matching
+ except ValueError:
+ raise ValueError(
+ 'No distribution was found. Ensure that `setup.py` '
+ 'is not empty and that it calls `setup()`.'
+ ) from None
+ return file
+
+
+def _open_setup_script(setup_script):
+ if not os.path.exists(setup_script):
+ # Supply a default setup.py
+ return io.StringIO("from setuptools import setup; setup()")
+
+ return tokenize.open(setup_script)
+
+
+@contextlib.contextmanager
+def suppress_known_deprecation():
+ with warnings.catch_warnings():
+ warnings.filterwarnings('ignore', 'setup.py install is deprecated')
+ yield
+
+
+_ConfigSettings: TypeAlias = Union[Mapping[str, Union[str, list[str], None]], None]
+"""
+Currently the user can run::
+
+ pip install -e . --config-settings key=value
+ python -m build -C--key=value -C key=value
+
+- pip will pass both key and value as strings and overwriting repeated keys
+ (pypa/pip#11059).
+- build will accumulate values associated with repeated keys in a list.
+ It will also accept keys with no associated value.
+ This means that an option passed by build can be ``str | list[str] | None``.
+- PEP 517 specifies that ``config_settings`` is an optional dict.
+"""
+
+
+class _ConfigSettingsTranslator:
+ """Translate ``config_settings`` into distutils-style command arguments.
+ Only a limited number of options is currently supported.
+ """
+
+ # See pypa/setuptools#1928 pypa/setuptools#2491
+
+ def _get_config(self, key: str, config_settings: _ConfigSettings) -> list[str]:
+ """
+ Get the value of a specific key in ``config_settings`` as a list of strings.
+
+ >>> fn = _ConfigSettingsTranslator()._get_config
+ >>> fn("--global-option", None)
+ []
+ >>> fn("--global-option", {})
+ []
+ >>> fn("--global-option", {'--global-option': 'foo'})
+ ['foo']
+ >>> fn("--global-option", {'--global-option': ['foo']})
+ ['foo']
+ >>> fn("--global-option", {'--global-option': 'foo'})
+ ['foo']
+ >>> fn("--global-option", {'--global-option': 'foo bar'})
+ ['foo', 'bar']
+ """
+ cfg = config_settings or {}
+ opts = cfg.get(key) or []
+ return shlex.split(opts) if isinstance(opts, str) else opts
+
+ def _global_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
+ """
+ Let the user specify ``verbose`` or ``quiet`` + escape hatch via
+ ``--global-option``.
+ Note: ``-v``, ``-vv``, ``-vvv`` have similar effects in setuptools,
+ so we just have to cover the basic scenario ``-v``.
+
+ >>> fn = _ConfigSettingsTranslator()._global_args
+ >>> list(fn(None))
+ []
+ >>> list(fn({"verbose": "False"}))
+ ['-q']
+ >>> list(fn({"verbose": "1"}))
+ ['-v']
+ >>> list(fn({"--verbose": None}))
+ ['-v']
+ >>> list(fn({"verbose": "true", "--global-option": "-q --no-user-cfg"}))
+ ['-v', '-q', '--no-user-cfg']
+ >>> list(fn({"--quiet": None}))
+ ['-q']
+ """
+ cfg = config_settings or {}
+ falsey = {"false", "no", "0", "off"}
+ if "verbose" in cfg or "--verbose" in cfg:
+ level = str(cfg.get("verbose") or cfg.get("--verbose") or "1")
+ yield ("-q" if level.lower() in falsey else "-v")
+ if "quiet" in cfg or "--quiet" in cfg:
+ level = str(cfg.get("quiet") or cfg.get("--quiet") or "1")
+ yield ("-v" if level.lower() in falsey else "-q")
+
+ yield from self._get_config("--global-option", config_settings)
+
+ def __dist_info_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
+ """
+ The ``dist_info`` command accepts ``tag-date`` and ``tag-build``.
+
+ .. warning::
+ We cannot use this yet as it requires the ``sdist`` and ``bdist_wheel``
+ commands run in ``build_sdist`` and ``build_wheel`` to reuse the egg-info
+ directory created in ``prepare_metadata_for_build_wheel``.
+
+ >>> fn = _ConfigSettingsTranslator()._ConfigSettingsTranslator__dist_info_args
+ >>> list(fn(None))
+ []
+ >>> list(fn({"tag-date": "False"}))
+ ['--no-date']
+ >>> list(fn({"tag-date": None}))
+ ['--no-date']
+ >>> list(fn({"tag-date": "true", "tag-build": ".a"}))
+ ['--tag-date', '--tag-build', '.a']
+ """
+ cfg = config_settings or {}
+ if "tag-date" in cfg:
+ val = strtobool(str(cfg["tag-date"] or "false"))
+ yield ("--tag-date" if val else "--no-date")
+ if "tag-build" in cfg:
+ yield from ["--tag-build", str(cfg["tag-build"])]
+
+ def _editable_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
+ """
+ The ``editable_wheel`` command accepts ``editable-mode=strict``.
+
+ >>> fn = _ConfigSettingsTranslator()._editable_args
+ >>> list(fn(None))
+ []
+ >>> list(fn({"editable-mode": "strict"}))
+ ['--mode', 'strict']
+ """
+ cfg = config_settings or {}
+ mode = cfg.get("editable-mode") or cfg.get("editable_mode")
+ if not mode:
+ return
+ yield from ["--mode", str(mode)]
+
+ def _arbitrary_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
+ """
+ Users may expect to pass arbitrary lists of arguments to a command
+ via "--global-option" (example provided in PEP 517 of a "escape hatch").
+
+ >>> fn = _ConfigSettingsTranslator()._arbitrary_args
+ >>> list(fn(None))
+ []
+ >>> list(fn({}))
+ []
+ >>> list(fn({'--build-option': 'foo'}))
+ ['foo']
+ >>> list(fn({'--build-option': ['foo']}))
+ ['foo']
+ >>> list(fn({'--build-option': 'foo'}))
+ ['foo']
+ >>> list(fn({'--build-option': 'foo bar'}))
+ ['foo', 'bar']
+ >>> list(fn({'--global-option': 'foo'}))
+ []
+ """
+ yield from self._get_config("--build-option", config_settings)
+
+
+class _BuildMetaBackend(_ConfigSettingsTranslator):
+ def _get_build_requires(
+ self, config_settings: _ConfigSettings, requirements: list[str]
+ ):
+ sys.argv = [
+ *sys.argv[:1],
+ *self._global_args(config_settings),
+ "egg_info",
+ ]
+ try:
+ with Distribution.patch():
+ self.run_setup()
+ except SetupRequirementsError as e:
+ requirements += e.specifiers
+
+ return requirements
+
+ def run_setup(self, setup_script: str = 'setup.py') -> None:
+ # Note that we can reuse our build directory between calls
+ # Correctness comes first, then optimization later
+ __file__ = os.path.abspath(setup_script)
+ __name__ = '__main__'
+
+ with _open_setup_script(__file__) as f:
+ code = f.read().replace(r'\r\n', r'\n')
+
+ try:
+ exec(code, locals())
+ except SystemExit as e:
+ if e.code:
+ raise
+ # We ignore exit code indicating success
+ SetuptoolsDeprecationWarning.emit(
+ "Running `setup.py` directly as CLI tool is deprecated.",
+ "Please avoid using `sys.exit(0)` or similar statements "
+ "that don't fit in the paradigm of a configuration file.",
+ see_url="https://blog.ganssle.io/articles/2021/10/"
+ "setup-py-deprecated.html",
+ )
+
+ def get_requires_for_build_wheel(
+ self, config_settings: _ConfigSettings = None
+ ) -> list[str]:
+ return self._get_build_requires(config_settings, requirements=[])
+
+ def get_requires_for_build_sdist(
+ self, config_settings: _ConfigSettings = None
+ ) -> list[str]:
+ return self._get_build_requires(config_settings, requirements=[])
+
+ def _bubble_up_info_directory(
+ self, metadata_directory: StrPath, suffix: str
+ ) -> str:
+ """
+ PEP 517 requires that the .dist-info directory be placed in the
+ metadata_directory. To comply, we MUST copy the directory to the root.
+
+ Returns the basename of the info directory, e.g. `proj-0.0.0.dist-info`.
+ """
+ info_dir = self._find_info_directory(metadata_directory, suffix)
+ if not same_path(info_dir.parent, metadata_directory):
+ shutil.move(str(info_dir), metadata_directory)
+ # PEP 517 allow other files and dirs to exist in metadata_directory
+ return info_dir.name
+
+ def _find_info_directory(self, metadata_directory: StrPath, suffix: str) -> Path:
+ for parent, dirs, _ in os.walk(metadata_directory):
+ candidates = [f for f in dirs if f.endswith(suffix)]
+
+ if len(candidates) != 0 or len(dirs) != 1:
+ assert len(candidates) == 1, f"Multiple {suffix} directories found"
+ return Path(parent, candidates[0])
+
+ msg = f"No {suffix} directory found in {metadata_directory}"
+ raise errors.InternalError(msg)
+
+ def prepare_metadata_for_build_wheel(
+ self, metadata_directory: StrPath, config_settings: _ConfigSettings = None
+ ) -> str:
+ sys.argv = [
+ *sys.argv[:1],
+ *self._global_args(config_settings),
+ "dist_info",
+ "--output-dir",
+ str(metadata_directory),
+ "--keep-egg-info",
+ ]
+ with no_install_setup_requires():
+ self.run_setup()
+
+ self._bubble_up_info_directory(metadata_directory, ".egg-info")
+ return self._bubble_up_info_directory(metadata_directory, ".dist-info")
+
+ def _build_with_temp_dir(
+ self,
+ setup_command: Iterable[str],
+ result_extension: str | tuple[str, ...],
+ result_directory: StrPath,
+ config_settings: _ConfigSettings,
+ arbitrary_args: Iterable[str] = (),
+ ):
+ result_directory = os.path.abspath(result_directory)
+
+ # Build in a temporary directory, then copy to the target.
+ os.makedirs(result_directory, exist_ok=True)
+
+ with tempfile.TemporaryDirectory(
+ prefix=".tmp-", dir=result_directory
+ ) as tmp_dist_dir:
+ sys.argv = [
+ *sys.argv[:1],
+ *self._global_args(config_settings),
+ *setup_command,
+ "--dist-dir",
+ tmp_dist_dir,
+ *arbitrary_args,
+ ]
+ with no_install_setup_requires():
+ self.run_setup()
+
+ result_basename = _file_with_extension(tmp_dist_dir, result_extension)
+ result_path = os.path.join(result_directory, result_basename)
+ if os.path.exists(result_path):
+ # os.rename will fail overwriting on non-Unix.
+ os.remove(result_path)
+ os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
+
+ return result_basename
+
+ def build_wheel(
+ self,
+ wheel_directory: StrPath,
+ config_settings: _ConfigSettings = None,
+ metadata_directory: StrPath | None = None,
+ ) -> str:
+ def _build(cmd: list[str]):
+ with suppress_known_deprecation():
+ return self._build_with_temp_dir(
+ cmd,
+ '.whl',
+ wheel_directory,
+ config_settings,
+ self._arbitrary_args(config_settings),
+ )
+
+ if metadata_directory is None:
+ return _build(['bdist_wheel'])
+
+ try:
+ return _build(['bdist_wheel', '--dist-info-dir', str(metadata_directory)])
+ except SystemExit as ex: # pragma: nocover
+ # pypa/setuptools#4683
+ if "--dist-info-dir not recognized" not in str(ex):
+ raise
+ _IncompatibleBdistWheel.emit()
+ return _build(['bdist_wheel'])
+
+ def build_sdist(
+ self, sdist_directory: StrPath, config_settings: _ConfigSettings = None
+ ) -> str:
+ return self._build_with_temp_dir(
+ ['sdist', '--formats', 'gztar'], '.tar.gz', sdist_directory, config_settings
+ )
+
+ def _get_dist_info_dir(self, metadata_directory: StrPath | None) -> str | None:
+ if not metadata_directory:
+ return None
+ dist_info_candidates = list(Path(metadata_directory).glob("*.dist-info"))
+ assert len(dist_info_candidates) <= 1
+ return str(dist_info_candidates[0]) if dist_info_candidates else None
+
+ def build_editable(
+ self,
+ wheel_directory: StrPath,
+ config_settings: _ConfigSettings = None,
+ metadata_directory: StrPath | None = None,
+ ) -> str:
+ # XXX can or should we hide our editable_wheel command normally?
+ info_dir = self._get_dist_info_dir(metadata_directory)
+ opts = ["--dist-info-dir", info_dir] if info_dir else []
+ cmd = ["editable_wheel", *opts, *self._editable_args(config_settings)]
+ with suppress_known_deprecation():
+ return self._build_with_temp_dir(
+ cmd, ".whl", wheel_directory, config_settings
+ )
+
+ def get_requires_for_build_editable(
+ self, config_settings: _ConfigSettings = None
+ ) -> list[str]:
+ return self.get_requires_for_build_wheel(config_settings)
+
+ def prepare_metadata_for_build_editable(
+ self, metadata_directory: StrPath, config_settings: _ConfigSettings = None
+ ) -> str:
+ return self.prepare_metadata_for_build_wheel(
+ metadata_directory, config_settings
+ )
+
+
+class _BuildMetaLegacyBackend(_BuildMetaBackend):
+ """Compatibility backend for setuptools
+
+ This is a version of setuptools.build_meta that endeavors
+ to maintain backwards
+ compatibility with pre-PEP 517 modes of invocation. It
+ exists as a temporary
+ bridge between the old packaging mechanism and the new
+ packaging mechanism,
+ and will eventually be removed.
+ """
+
+ def run_setup(self, setup_script: str = 'setup.py') -> None:
+ # In order to maintain compatibility with scripts assuming that
+ # the setup.py script is in a directory on the PYTHONPATH, inject
+ # '' into sys.path. (pypa/setuptools#1642)
+ sys_path = list(sys.path) # Save the original path
+
+ script_dir = os.path.dirname(os.path.abspath(setup_script))
+ if script_dir not in sys.path:
+ sys.path.insert(0, script_dir)
+
+ # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
+ # get the directory of the source code. They expect it to refer to the
+ # setup.py script.
+ sys_argv_0 = sys.argv[0]
+ sys.argv[0] = setup_script
+
+ try:
+ super().run_setup(setup_script=setup_script)
+ finally:
+ # While PEP 517 frontends should be calling each hook in a fresh
+ # subprocess according to the standard (and thus it should not be
+ # strictly necessary to restore the old sys.path), we'll restore
+ # the original path so that the path manipulation does not persist
+ # within the hook after run_setup is called.
+ sys.path[:] = sys_path
+ sys.argv[0] = sys_argv_0
+
+
+class _IncompatibleBdistWheel(SetuptoolsDeprecationWarning):
+ _SUMMARY = "wheel.bdist_wheel is deprecated, please import it from setuptools"
+ _DETAILS = """
+ Ensure that any custom bdist_wheel implementation is a subclass of
+ setuptools.command.bdist_wheel.bdist_wheel.
+ """
+ _DUE_DATE = (2025, 10, 15)
+ # Initially introduced in 2024/10/15, but maybe too disruptive to be enforced?
+ _SEE_URL = "https://github.com/pypa/wheel/pull/631"
+
+
+# The primary backend
+_BACKEND = _BuildMetaBackend()
+
+get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
+get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
+prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
+build_wheel = _BACKEND.build_wheel
+build_sdist = _BACKEND.build_sdist
+get_requires_for_build_editable = _BACKEND.get_requires_for_build_editable
+prepare_metadata_for_build_editable = _BACKEND.prepare_metadata_for_build_editable
+build_editable = _BACKEND.build_editable
+
+
+# The legacy backend
+__legacy__ = _BuildMetaLegacyBackend()
diff --git a/lib/python3.12/site-packages/setuptools/cli-32.exe b/lib/python3.12/site-packages/setuptools/cli-32.exe
new file mode 100644
index 0000000000000000000000000000000000000000..65c3cd99cc7433f271a5b9387abdd1ddb949d1a6
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/cli-32.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/cli-64.exe b/lib/python3.12/site-packages/setuptools/cli-64.exe
new file mode 100644
index 0000000000000000000000000000000000000000..3ea50eebfe3f0113b231a318cc1ad6e238afd60d
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/cli-64.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/cli-arm64.exe b/lib/python3.12/site-packages/setuptools/cli-arm64.exe
new file mode 100644
index 0000000000000000000000000000000000000000..da96455a07a0bad4cde5dc5626544325f82c722b
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/cli-arm64.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/cli.exe b/lib/python3.12/site-packages/setuptools/cli.exe
new file mode 100644
index 0000000000000000000000000000000000000000..65c3cd99cc7433f271a5b9387abdd1ddb949d1a6
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/cli.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/depends.py b/lib/python3.12/site-packages/setuptools/depends.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5223b79561c36d9b6c45ead78288098e1cb0f1d
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/depends.py
@@ -0,0 +1,185 @@
+from __future__ import annotations
+
+import contextlib
+import dis
+import marshal
+import sys
+from types import CodeType
+from typing import Any, Literal, TypeVar
+
+from packaging.version import Version
+
+from . import _imp
+from ._imp import PY_COMPILED, PY_FROZEN, PY_SOURCE, find_module
+
+_T = TypeVar("_T")
+
+__all__ = ['Require', 'find_module']
+
+
+class Require:
+ """A prerequisite to building or installing a distribution"""
+
+ def __init__(
+ self,
+ name,
+ requested_version,
+ module,
+ homepage: str = '',
+ attribute=None,
+ format=None,
+ ) -> None:
+ if format is None and requested_version is not None:
+ format = Version
+
+ if format is not None:
+ requested_version = format(requested_version)
+ if attribute is None:
+ attribute = '__version__'
+
+ self.__dict__.update(locals())
+ del self.self
+
+ def full_name(self):
+ """Return full package/distribution name, w/version"""
+ if self.requested_version is not None:
+ return f'{self.name}-{self.requested_version}'
+ return self.name
+
+ def version_ok(self, version):
+ """Is 'version' sufficiently up-to-date?"""
+ return (
+ self.attribute is None
+ or self.format is None
+ or str(version) != "unknown"
+ and self.format(version) >= self.requested_version
+ )
+
+ def get_version(
+ self, paths=None, default: _T | Literal["unknown"] = "unknown"
+ ) -> _T | Literal["unknown"] | None | Any:
+ """Get version number of installed module, 'None', or 'default'
+
+ Search 'paths' for module. If not found, return 'None'. If found,
+ return the extracted version attribute, or 'default' if no version
+ attribute was specified, or the value cannot be determined without
+ importing the module. The version is formatted according to the
+ requirement's version format (if any), unless it is 'None' or the
+ supplied 'default'.
+ """
+
+ if self.attribute is None:
+ try:
+ f, _p, _i = find_module(self.module, paths)
+ except ImportError:
+ return None
+ if f:
+ f.close()
+ return default
+
+ v = get_module_constant(self.module, self.attribute, default, paths)
+
+ if v is not None and v is not default and self.format is not None:
+ return self.format(v)
+
+ return v
+
+ def is_present(self, paths=None):
+ """Return true if dependency is present on 'paths'"""
+ return self.get_version(paths) is not None
+
+ def is_current(self, paths=None):
+ """Return true if dependency is present and up-to-date on 'paths'"""
+ version = self.get_version(paths)
+ if version is None:
+ return False
+ return self.version_ok(str(version))
+
+
+def maybe_close(f):
+ @contextlib.contextmanager
+ def empty():
+ yield
+ return
+
+ if not f:
+ return empty()
+
+ return contextlib.closing(f)
+
+
+# Some objects are not available on some platforms.
+# XXX it'd be better to test assertions about bytecode instead.
+if not sys.platform.startswith('java') and sys.platform != 'cli':
+
+ def get_module_constant(
+ module, symbol, default: _T | int = -1, paths=None
+ ) -> _T | int | None | Any:
+ """Find 'module' by searching 'paths', and extract 'symbol'
+
+ Return 'None' if 'module' does not exist on 'paths', or it does not define
+ 'symbol'. If the module defines 'symbol' as a constant, return the
+ constant. Otherwise, return 'default'."""
+
+ try:
+ f, path, (_suffix, _mode, kind) = info = find_module(module, paths)
+ except ImportError:
+ # Module doesn't exist
+ return None
+
+ with maybe_close(f):
+ if kind == PY_COMPILED:
+ f.read(8) # skip magic & date
+ code = marshal.load(f)
+ elif kind == PY_FROZEN:
+ code = _imp.get_frozen_object(module, paths)
+ elif kind == PY_SOURCE:
+ code = compile(f.read(), path, 'exec')
+ else:
+ # Not something we can parse; we'll have to import it. :(
+ imported = _imp.get_module(module, paths, info)
+ return getattr(imported, symbol, None)
+
+ return extract_constant(code, symbol, default)
+
+ def extract_constant(
+ code: CodeType, symbol: str, default: _T | int = -1
+ ) -> _T | int | None | Any:
+ """Extract the constant value of 'symbol' from 'code'
+
+ If the name 'symbol' is bound to a constant value by the Python code
+ object 'code', return that value. If 'symbol' is bound to an expression,
+ return 'default'. Otherwise, return 'None'.
+
+ Return value is based on the first assignment to 'symbol'. 'symbol' must
+ be a global, or at least a non-"fast" local in the code block. That is,
+ only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
+ must be present in 'code.co_names'.
+ """
+ if symbol not in code.co_names:
+ # name's not there, can't possibly be an assignment
+ return None
+
+ name_idx = list(code.co_names).index(symbol)
+
+ STORE_NAME = dis.opmap['STORE_NAME']
+ STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
+ LOAD_CONST = dis.opmap['LOAD_CONST']
+
+ const = default
+
+ for byte_code in dis.Bytecode(code):
+ op = byte_code.opcode
+ arg = byte_code.arg
+
+ if op == LOAD_CONST:
+ assert arg is not None
+ const = code.co_consts[arg]
+ elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):
+ return const
+ else:
+ const = default
+
+ return None
+
+ __all__ += ['get_module_constant', 'extract_constant']
diff --git a/lib/python3.12/site-packages/setuptools/discovery.py b/lib/python3.12/site-packages/setuptools/discovery.py
new file mode 100644
index 0000000000000000000000000000000000000000..296d3193ab429f64bc5f3bfdcad7ee6e1050dc6d
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/discovery.py
@@ -0,0 +1,614 @@
+"""Automatic discovery of Python modules and packages (for inclusion in the
+distribution) and other config values.
+
+For the purposes of this module, the following nomenclature is used:
+
+- "src-layout": a directory representing a Python project that contains a "src"
+ folder. Everything under the "src" folder is meant to be included in the
+ distribution when packaging the project. Example::
+
+ .
+ ├── tox.ini
+ ├── pyproject.toml
+ └── src/
+ └── mypkg/
+ ├── __init__.py
+ ├── mymodule.py
+ └── my_data_file.txt
+
+- "flat-layout": a Python project that does not use "src-layout" but instead
+ have a directory under the project root for each package::
+
+ .
+ ├── tox.ini
+ ├── pyproject.toml
+ └── mypkg/
+ ├── __init__.py
+ ├── mymodule.py
+ └── my_data_file.txt
+
+- "single-module": a project that contains a single Python script direct under
+ the project root (no directory used)::
+
+ .
+ ├── tox.ini
+ ├── pyproject.toml
+ └── mymodule.py
+
+"""
+
+from __future__ import annotations
+
+import itertools
+import os
+from collections.abc import Iterable, Iterator, Mapping
+from fnmatch import fnmatchcase
+from glob import glob
+from pathlib import Path
+from typing import TYPE_CHECKING, ClassVar
+
+import _distutils_hack.override # noqa: F401
+
+from ._path import StrPath
+
+from distutils import log
+from distutils.util import convert_path
+
+if TYPE_CHECKING:
+ from setuptools import Distribution
+
+chain_iter = itertools.chain.from_iterable
+
+
+def _valid_name(path: StrPath) -> bool:
+ # Ignore invalid names that cannot be imported directly
+ return os.path.basename(path).isidentifier()
+
+
+class _Filter:
+ """
+ Given a list of patterns, create a callable that will be true only if
+ the input matches at least one of the patterns.
+ """
+
+ def __init__(self, *patterns: str) -> None:
+ self._patterns = dict.fromkeys(patterns)
+
+ def __call__(self, item: str) -> bool:
+ return any(fnmatchcase(item, pat) for pat in self._patterns)
+
+ def __contains__(self, item: str) -> bool:
+ return item in self._patterns
+
+
+class _Finder:
+ """Base class that exposes functionality for module/package finders"""
+
+ ALWAYS_EXCLUDE: ClassVar[tuple[str, ...]] = ()
+ DEFAULT_EXCLUDE: ClassVar[tuple[str, ...]] = ()
+
+ @classmethod
+ def find(
+ cls,
+ where: StrPath = '.',
+ exclude: Iterable[str] = (),
+ include: Iterable[str] = ('*',),
+ ) -> list[str]:
+ """Return a list of all Python items (packages or modules, depending on
+ the finder implementation) found within directory ``where``.
+
+ ``where`` is the root directory which will be searched.
+ It should be supplied as a "cross-platform" (i.e. URL-style) path;
+ it will be converted to the appropriate local path syntax.
+
+ ``exclude`` is a sequence of names to exclude; ``*`` can be used
+ as a wildcard in the names.
+ When finding packages, ``foo.*`` will exclude all subpackages of ``foo``
+ (but not ``foo`` itself).
+
+ ``include`` is a sequence of names to include.
+ If it's specified, only the named items will be included.
+ If it's not specified, all found items will be included.
+ ``include`` can contain shell style wildcard patterns just like
+ ``exclude``.
+ """
+
+ exclude = exclude or cls.DEFAULT_EXCLUDE
+ return list(
+ cls._find_iter(
+ convert_path(str(where)),
+ _Filter(*cls.ALWAYS_EXCLUDE, *exclude),
+ _Filter(*include),
+ )
+ )
+
+ @classmethod
+ def _find_iter(
+ cls, where: StrPath, exclude: _Filter, include: _Filter
+ ) -> Iterator[str]:
+ raise NotImplementedError
+
+
+class PackageFinder(_Finder):
+ """
+ Generate a list of all Python packages found within a directory
+ """
+
+ ALWAYS_EXCLUDE = ("ez_setup", "*__pycache__")
+
+ @classmethod
+ def _find_iter(
+ cls, where: StrPath, exclude: _Filter, include: _Filter
+ ) -> Iterator[str]:
+ """
+ All the packages found in 'where' that pass the 'include' filter, but
+ not the 'exclude' filter.
+ """
+ for root, dirs, files in os.walk(str(where), followlinks=True):
+ # Copy dirs to iterate over it, then empty dirs.
+ all_dirs = dirs[:]
+ dirs[:] = []
+
+ for dir in all_dirs:
+ full_path = os.path.join(root, dir)
+ rel_path = os.path.relpath(full_path, where)
+ package = rel_path.replace(os.path.sep, '.')
+
+ # Skip directory trees that are not valid packages
+ if '.' in dir or not cls._looks_like_package(full_path, package):
+ continue
+
+ # Should this package be included?
+ if include(package) and not exclude(package):
+ yield package
+
+ # Early pruning if there is nothing else to be scanned
+ if f"{package}*" in exclude or f"{package}.*" in exclude:
+ continue
+
+ # Keep searching subdirectories, as there may be more packages
+ # down there, even if the parent was excluded.
+ dirs.append(dir)
+
+ @staticmethod
+ def _looks_like_package(path: StrPath, _package_name: str) -> bool:
+ """Does a directory look like a package?"""
+ return os.path.isfile(os.path.join(path, '__init__.py'))
+
+
+class PEP420PackageFinder(PackageFinder):
+ @staticmethod
+ def _looks_like_package(_path: StrPath, _package_name: str) -> bool:
+ return True
+
+
+class ModuleFinder(_Finder):
+ """Find isolated Python modules.
+ This function will **not** recurse subdirectories.
+ """
+
+ @classmethod
+ def _find_iter(
+ cls, where: StrPath, exclude: _Filter, include: _Filter
+ ) -> Iterator[str]:
+ for file in glob(os.path.join(where, "*.py")):
+ module, _ext = os.path.splitext(os.path.basename(file))
+
+ if not cls._looks_like_module(module):
+ continue
+
+ if include(module) and not exclude(module):
+ yield module
+
+ _looks_like_module = staticmethod(_valid_name)
+
+
+# We have to be extra careful in the case of flat layout to not include files
+# and directories not meant for distribution (e.g. tool-related)
+
+
+class FlatLayoutPackageFinder(PEP420PackageFinder):
+ _EXCLUDE = (
+ "ci",
+ "bin",
+ "debian",
+ "doc",
+ "docs",
+ "documentation",
+ "manpages",
+ "news",
+ "newsfragments",
+ "changelog",
+ "test",
+ "tests",
+ "unit_test",
+ "unit_tests",
+ "example",
+ "examples",
+ "scripts",
+ "tools",
+ "util",
+ "utils",
+ "python",
+ "build",
+ "dist",
+ "venv",
+ "env",
+ "requirements",
+ # ---- Task runners / Build tools ----
+ "tasks", # invoke
+ "fabfile", # fabric
+ "site_scons", # SCons
+ # ---- Other tools ----
+ "benchmark",
+ "benchmarks",
+ "exercise",
+ "exercises",
+ "htmlcov", # Coverage.py
+ # ---- Hidden directories/Private packages ----
+ "[._]*",
+ )
+
+ DEFAULT_EXCLUDE = tuple(chain_iter((p, f"{p}.*") for p in _EXCLUDE))
+ """Reserved package names"""
+
+ @staticmethod
+ def _looks_like_package(_path: StrPath, package_name: str) -> bool:
+ names = package_name.split('.')
+ # Consider PEP 561
+ root_pkg_is_valid = names[0].isidentifier() or names[0].endswith("-stubs")
+ return root_pkg_is_valid and all(name.isidentifier() for name in names[1:])
+
+
+class FlatLayoutModuleFinder(ModuleFinder):
+ DEFAULT_EXCLUDE = (
+ "setup",
+ "conftest",
+ "test",
+ "tests",
+ "example",
+ "examples",
+ "build",
+ # ---- Task runners ----
+ "toxfile",
+ "noxfile",
+ "pavement",
+ "dodo",
+ "tasks",
+ "fabfile",
+ # ---- Other tools ----
+ "[Ss][Cc]onstruct", # SCons
+ "conanfile", # Connan: C/C++ build tool
+ "manage", # Django
+ "benchmark",
+ "benchmarks",
+ "exercise",
+ "exercises",
+ # ---- Hidden files/Private modules ----
+ "[._]*",
+ )
+ """Reserved top-level module names"""
+
+
+def _find_packages_within(root_pkg: str, pkg_dir: StrPath) -> list[str]:
+ nested = PEP420PackageFinder.find(pkg_dir)
+ return [root_pkg] + [".".join((root_pkg, n)) for n in nested]
+
+
+class ConfigDiscovery:
+ """Fill-in metadata and options that can be automatically derived
+ (from other metadata/options, the file system or conventions)
+ """
+
+ def __init__(self, distribution: Distribution) -> None:
+ self.dist = distribution
+ self._called = False
+ self._disabled = False
+ self._skip_ext_modules = False
+
+ def _disable(self):
+ """Internal API to disable automatic discovery"""
+ self._disabled = True
+
+ def _ignore_ext_modules(self):
+ """Internal API to disregard ext_modules.
+
+ Normally auto-discovery would not be triggered if ``ext_modules`` are set
+ (this is done for backward compatibility with existing packages relying on
+ ``setup.py`` or ``setup.cfg``). However, ``setuptools`` can call this function
+ to ignore given ``ext_modules`` and proceed with the auto-discovery if
+ ``packages`` and ``py_modules`` are not given (e.g. when using pyproject.toml
+ metadata).
+ """
+ self._skip_ext_modules = True
+
+ @property
+ def _root_dir(self) -> StrPath:
+ # The best is to wait until `src_root` is set in dist, before using _root_dir.
+ return self.dist.src_root or os.curdir
+
+ @property
+ def _package_dir(self) -> dict[str, str]:
+ if self.dist.package_dir is None:
+ return {}
+ return self.dist.package_dir
+
+ def __call__(
+ self, force: bool = False, name: bool = True, ignore_ext_modules: bool = False
+ ) -> None:
+ """Automatically discover missing configuration fields
+ and modifies the given ``distribution`` object in-place.
+
+ Note that by default this will only have an effect the first time the
+ ``ConfigDiscovery`` object is called.
+
+ To repeatedly invoke automatic discovery (e.g. when the project
+ directory changes), please use ``force=True`` (or create a new
+ ``ConfigDiscovery`` instance).
+ """
+ if force is False and (self._called or self._disabled):
+ # Avoid overhead of multiple calls
+ return
+
+ self._analyse_package_layout(ignore_ext_modules)
+ if name:
+ self.analyse_name() # depends on ``packages`` and ``py_modules``
+
+ self._called = True
+
+ def _explicitly_specified(self, ignore_ext_modules: bool) -> bool:
+ """``True`` if the user has specified some form of package/module listing"""
+ ignore_ext_modules = ignore_ext_modules or self._skip_ext_modules
+ ext_modules = not (self.dist.ext_modules is None or ignore_ext_modules)
+ return (
+ self.dist.packages is not None
+ or self.dist.py_modules is not None
+ or ext_modules
+ or hasattr(self.dist, "configuration")
+ and self.dist.configuration
+ # ^ Some projects use numpy.distutils.misc_util.Configuration
+ )
+
+ def _analyse_package_layout(self, ignore_ext_modules: bool) -> bool:
+ if self._explicitly_specified(ignore_ext_modules):
+ # For backward compatibility, just try to find modules/packages
+ # when nothing is given
+ return True
+
+ log.debug(
+ "No `packages` or `py_modules` configuration, performing "
+ "automatic discovery."
+ )
+
+ return (
+ self._analyse_explicit_layout()
+ or self._analyse_src_layout()
+ # flat-layout is the trickiest for discovery so it should be last
+ or self._analyse_flat_layout()
+ )
+
+ def _analyse_explicit_layout(self) -> bool:
+ """The user can explicitly give a package layout via ``package_dir``"""
+ package_dir = self._package_dir.copy() # don't modify directly
+ package_dir.pop("", None) # This falls under the "src-layout" umbrella
+ root_dir = self._root_dir
+
+ if not package_dir:
+ return False
+
+ log.debug(f"`explicit-layout` detected -- analysing {package_dir}")
+ pkgs = chain_iter(
+ _find_packages_within(pkg, os.path.join(root_dir, parent_dir))
+ for pkg, parent_dir in package_dir.items()
+ )
+ self.dist.packages = list(pkgs)
+ log.debug(f"discovered packages -- {self.dist.packages}")
+ return True
+
+ def _analyse_src_layout(self) -> bool:
+ """Try to find all packages or modules under the ``src`` directory
+ (or anything pointed by ``package_dir[""]``).
+
+ The "src-layout" is relatively safe for automatic discovery.
+ We assume that everything within is meant to be included in the
+ distribution.
+
+ If ``package_dir[""]`` is not given, but the ``src`` directory exists,
+ this function will set ``package_dir[""] = "src"``.
+ """
+ package_dir = self._package_dir
+ src_dir = os.path.join(self._root_dir, package_dir.get("", "src"))
+ if not os.path.isdir(src_dir):
+ return False
+
+ log.debug(f"`src-layout` detected -- analysing {src_dir}")
+ package_dir.setdefault("", os.path.basename(src_dir))
+ self.dist.package_dir = package_dir # persist eventual modifications
+ self.dist.packages = PEP420PackageFinder.find(src_dir)
+ self.dist.py_modules = ModuleFinder.find(src_dir)
+ log.debug(f"discovered packages -- {self.dist.packages}")
+ log.debug(f"discovered py_modules -- {self.dist.py_modules}")
+ return True
+
+ def _analyse_flat_layout(self) -> bool:
+ """Try to find all packages and modules under the project root.
+
+ Since the ``flat-layout`` is more dangerous in terms of accidentally including
+ extra files/directories, this function is more conservative and will raise an
+ error if multiple packages or modules are found.
+
+ This assumes that multi-package dists are uncommon and refuse to support that
+ use case in order to be able to prevent unintended errors.
+ """
+ log.debug(f"`flat-layout` detected -- analysing {self._root_dir}")
+ return self._analyse_flat_packages() or self._analyse_flat_modules()
+
+ def _analyse_flat_packages(self) -> bool:
+ self.dist.packages = FlatLayoutPackageFinder.find(self._root_dir)
+ top_level = remove_nested_packages(remove_stubs(self.dist.packages))
+ log.debug(f"discovered packages -- {self.dist.packages}")
+ self._ensure_no_accidental_inclusion(top_level, "packages")
+ return bool(top_level)
+
+ def _analyse_flat_modules(self) -> bool:
+ self.dist.py_modules = FlatLayoutModuleFinder.find(self._root_dir)
+ log.debug(f"discovered py_modules -- {self.dist.py_modules}")
+ self._ensure_no_accidental_inclusion(self.dist.py_modules, "modules")
+ return bool(self.dist.py_modules)
+
+ def _ensure_no_accidental_inclusion(self, detected: list[str], kind: str):
+ if len(detected) > 1:
+ from inspect import cleandoc
+
+ from setuptools.errors import PackageDiscoveryError
+
+ msg = f"""Multiple top-level {kind} discovered in a flat-layout: {detected}.
+
+ To avoid accidental inclusion of unwanted files or directories,
+ setuptools will not proceed with this build.
+
+ If you are trying to create a single distribution with multiple {kind}
+ on purpose, you should not rely on automatic discovery.
+ Instead, consider the following options:
+
+ 1. set up custom discovery (`find` directive with `include` or `exclude`)
+ 2. use a `src-layout`
+ 3. explicitly set `py_modules` or `packages` with a list of names
+
+ To find more information, look for "package discovery" on setuptools docs.
+ """
+ raise PackageDiscoveryError(cleandoc(msg))
+
+ def analyse_name(self) -> None:
+ """The packages/modules are the essential contribution of the author.
+ Therefore the name of the distribution can be derived from them.
+ """
+ if self.dist.metadata.name or self.dist.name:
+ # get_name() is not reliable (can return "UNKNOWN")
+ return
+
+ log.debug("No `name` configuration, performing automatic discovery")
+
+ name = (
+ self._find_name_single_package_or_module()
+ or self._find_name_from_packages()
+ )
+ if name:
+ self.dist.metadata.name = name
+
+ def _find_name_single_package_or_module(self) -> str | None:
+ """Exactly one module or package"""
+ for field in ('packages', 'py_modules'):
+ items = getattr(self.dist, field, None) or []
+ if items and len(items) == 1:
+ log.debug(f"Single module/package detected, name: {items[0]}")
+ return items[0]
+
+ return None
+
+ def _find_name_from_packages(self) -> str | None:
+ """Try to find the root package that is not a PEP 420 namespace"""
+ if not self.dist.packages:
+ return None
+
+ packages = remove_stubs(sorted(self.dist.packages, key=len))
+ package_dir = self.dist.package_dir or {}
+
+ parent_pkg = find_parent_package(packages, package_dir, self._root_dir)
+ if parent_pkg:
+ log.debug(f"Common parent package detected, name: {parent_pkg}")
+ return parent_pkg
+
+ log.warn("No parent package detected, impossible to derive `name`")
+ return None
+
+
+def remove_nested_packages(packages: list[str]) -> list[str]:
+ """Remove nested packages from a list of packages.
+
+ >>> remove_nested_packages(["a", "a.b1", "a.b2", "a.b1.c1"])
+ ['a']
+ >>> remove_nested_packages(["a", "b", "c.d", "c.d.e.f", "g.h", "a.a1"])
+ ['a', 'b', 'c.d', 'g.h']
+ """
+ pkgs = sorted(packages, key=len)
+ top_level = pkgs[:]
+ size = len(pkgs)
+ for i, name in enumerate(reversed(pkgs)):
+ if any(name.startswith(f"{other}.") for other in top_level):
+ top_level.pop(size - i - 1)
+
+ return top_level
+
+
+def remove_stubs(packages: list[str]) -> list[str]:
+ """Remove type stubs (:pep:`561`) from a list of packages.
+
+ >>> remove_stubs(["a", "a.b", "a-stubs", "a-stubs.b.c", "b", "c-stubs"])
+ ['a', 'a.b', 'b']
+ """
+ return [pkg for pkg in packages if not pkg.split(".")[0].endswith("-stubs")]
+
+
+def find_parent_package(
+ packages: list[str], package_dir: Mapping[str, str], root_dir: StrPath
+) -> str | None:
+ """Find the parent package that is not a namespace."""
+ packages = sorted(packages, key=len)
+ common_ancestors = []
+ for i, name in enumerate(packages):
+ if not all(n.startswith(f"{name}.") for n in packages[i + 1 :]):
+ # Since packages are sorted by length, this condition is able
+ # to find a list of all common ancestors.
+ # When there is divergence (e.g. multiple root packages)
+ # the list will be empty
+ break
+ common_ancestors.append(name)
+
+ for name in common_ancestors:
+ pkg_path = find_package_path(name, package_dir, root_dir)
+ init = os.path.join(pkg_path, "__init__.py")
+ if os.path.isfile(init):
+ return name
+
+ return None
+
+
+def find_package_path(
+ name: str, package_dir: Mapping[str, str], root_dir: StrPath
+) -> str:
+ """Given a package name, return the path where it should be found on
+ disk, considering the ``package_dir`` option.
+
+ >>> path = find_package_path("my.pkg", {"": "root/is/nested"}, ".")
+ >>> path.replace(os.sep, "/")
+ './root/is/nested/my/pkg'
+
+ >>> path = find_package_path("my.pkg", {"my": "root/is/nested"}, ".")
+ >>> path.replace(os.sep, "/")
+ './root/is/nested/pkg'
+
+ >>> path = find_package_path("my.pkg", {"my.pkg": "root/is/nested"}, ".")
+ >>> path.replace(os.sep, "/")
+ './root/is/nested'
+
+ >>> path = find_package_path("other.pkg", {"my.pkg": "root/is/nested"}, ".")
+ >>> path.replace(os.sep, "/")
+ './other/pkg'
+ """
+ parts = name.split(".")
+ for i in range(len(parts), 0, -1):
+ # Look backwards, the most specific package_dir first
+ partial_name = ".".join(parts[:i])
+ if partial_name in package_dir:
+ parent = package_dir[partial_name]
+ return os.path.join(root_dir, parent, *parts[i:])
+
+ parent = package_dir.get("") or ""
+ return os.path.join(root_dir, *parent.split("/"), *parts)
+
+
+def construct_package_dir(packages: list[str], package_path: StrPath) -> dict[str, str]:
+ parent_pkgs = remove_nested_packages(packages)
+ prefix = Path(package_path).parts
+ return {pkg: "/".join([*prefix, *pkg.split(".")]) for pkg in parent_pkgs}
diff --git a/lib/python3.12/site-packages/setuptools/dist.py b/lib/python3.12/site-packages/setuptools/dist.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3d1e5f91e1086cc8c975600fa0c8cbf1f7187fc
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/dist.py
@@ -0,0 +1,1124 @@
+from __future__ import annotations
+
+import functools
+import io
+import itertools
+import numbers
+import os
+import re
+import sys
+from collections.abc import Iterable, Iterator, MutableMapping, Sequence
+from glob import glob
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Union
+
+from more_itertools import partition, unique_everseen
+from packaging.markers import InvalidMarker, Marker
+from packaging.specifiers import InvalidSpecifier, SpecifierSet
+from packaging.version import Version
+
+from . import (
+ _entry_points,
+ _reqs,
+ _static,
+ command as _, # noqa: F401 # imported for side-effects
+)
+from ._importlib import metadata
+from ._normalization import _canonicalize_license_expression
+from ._path import StrPath
+from ._reqs import _StrOrIter
+from .config import pyprojecttoml, setupcfg
+from .discovery import ConfigDiscovery
+from .errors import InvalidConfigError
+from .monkey import get_unpatched
+from .warnings import InformationOnly, SetuptoolsDeprecationWarning
+
+import distutils.cmd
+import distutils.command
+import distutils.core
+import distutils.dist
+import distutils.log
+from distutils.debug import DEBUG
+from distutils.errors import DistutilsOptionError, DistutilsSetupError
+from distutils.fancy_getopt import translate_longopt
+from distutils.util import strtobool
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+
+__all__ = ['Distribution']
+
+_sequence = tuple, list
+"""
+:meta private:
+
+Supported iterable types that are known to be:
+- ordered (which `set` isn't)
+- not match a str (which `Sequence[str]` does)
+- not imply a nested type (like `dict`)
+for use with `isinstance`.
+"""
+_Sequence: TypeAlias = Union[tuple[str, ...], list[str]]
+# This is how stringifying _Sequence would look in Python 3.10
+_sequence_type_repr = "tuple[str, ...] | list[str]"
+_OrderedStrSequence: TypeAlias = Union[str, dict[str, Any], Sequence[str]]
+"""
+:meta private:
+Avoid single-use iterable. Disallow sets.
+A poor approximation of an OrderedSequence (dict doesn't match a Sequence).
+"""
+
+
+def __getattr__(name: str) -> Any: # pragma: no cover
+ if name == "sequence":
+ SetuptoolsDeprecationWarning.emit(
+ "`setuptools.dist.sequence` is an internal implementation detail.",
+ "Please define your own `sequence = tuple, list` instead.",
+ due_date=(2025, 8, 28), # Originally added on 2024-08-27
+ )
+ return _sequence
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
+def check_importable(dist, attr, value):
+ try:
+ ep = metadata.EntryPoint(value=value, name=None, group=None)
+ assert not ep.extras
+ except (TypeError, ValueError, AttributeError, AssertionError) as e:
+ raise DistutilsSetupError(
+ f"{attr!r} must be importable 'module:attrs' string (got {value!r})"
+ ) from e
+
+
+def assert_string_list(dist, attr: str, value: _Sequence) -> None:
+ """Verify that value is a string list"""
+ try:
+ # verify that value is a list or tuple to exclude unordered
+ # or single-use iterables
+ assert isinstance(value, _sequence)
+ # verify that elements of value are strings
+ assert ''.join(value) != value
+ except (TypeError, ValueError, AttributeError, AssertionError) as e:
+ raise DistutilsSetupError(
+ f"{attr!r} must be of type <{_sequence_type_repr}> (got {value!r})"
+ ) from e
+
+
+def check_nsp(dist, attr, value):
+ """Verify that namespace packages are valid"""
+ ns_packages = value
+ assert_string_list(dist, attr, ns_packages)
+ for nsp in ns_packages:
+ if not dist.has_contents_for(nsp):
+ raise DistutilsSetupError(
+ f"Distribution contains no modules or packages for namespace package {nsp!r}"
+ )
+ parent, _sep, _child = nsp.rpartition('.')
+ if parent and parent not in ns_packages:
+ distutils.log.warn(
+ "WARNING: %r is declared as a package namespace, but %r"
+ " is not: please correct this in setup.py",
+ nsp,
+ parent,
+ )
+ SetuptoolsDeprecationWarning.emit(
+ "The namespace_packages parameter is deprecated.",
+ "Please replace its usage with implicit namespaces (PEP 420).",
+ see_docs="references/keywords.html#keyword-namespace-packages",
+ # TODO: define due_date, it may break old packages that are no longer
+ # maintained (e.g. sphinxcontrib extensions) when installed from source.
+ # Warning officially introduced in May 2022, however the deprecation
+ # was mentioned much earlier in the docs (May 2020, see #2149).
+ )
+
+
+def check_extras(dist, attr, value):
+ """Verify that extras_require mapping is valid"""
+ try:
+ list(itertools.starmap(_check_extra, value.items()))
+ except (TypeError, ValueError, AttributeError) as e:
+ raise DistutilsSetupError(
+ "'extras_require' must be a dictionary whose values are "
+ "strings or lists of strings containing valid project/version "
+ "requirement specifiers."
+ ) from e
+
+
+def _check_extra(extra, reqs):
+ _name, _sep, marker = extra.partition(':')
+ try:
+ _check_marker(marker)
+ except InvalidMarker:
+ msg = f"Invalid environment marker: {marker} ({extra!r})"
+ raise DistutilsSetupError(msg) from None
+ list(_reqs.parse(reqs))
+
+
+def _check_marker(marker):
+ if not marker:
+ return
+ m = Marker(marker)
+ m.evaluate()
+
+
+def assert_bool(dist, attr, value):
+ """Verify that value is True, False, 0, or 1"""
+ if bool(value) != value:
+ raise DistutilsSetupError(f"{attr!r} must be a boolean value (got {value!r})")
+
+
+def invalid_unless_false(dist, attr, value):
+ if not value:
+ DistDeprecationWarning.emit(f"{attr} is ignored.")
+ # TODO: should there be a `due_date` here?
+ return
+ raise DistutilsSetupError(f"{attr} is invalid.")
+
+
+def check_requirements(dist, attr: str, value: _OrderedStrSequence) -> None:
+ """Verify that install_requires is a valid requirements list"""
+ try:
+ list(_reqs.parse(value))
+ if isinstance(value, set):
+ raise TypeError("Unordered types are not allowed")
+ except (TypeError, ValueError) as error:
+ msg = (
+ f"{attr!r} must be a string or iterable of strings "
+ f"containing valid project/version requirement specifiers; {error}"
+ )
+ raise DistutilsSetupError(msg) from error
+
+
+def check_specifier(dist, attr, value):
+ """Verify that value is a valid version specifier"""
+ try:
+ SpecifierSet(value)
+ except (InvalidSpecifier, AttributeError) as error:
+ msg = f"{attr!r} must be a string containing valid version specifiers; {error}"
+ raise DistutilsSetupError(msg) from error
+
+
+def check_entry_points(dist, attr, value):
+ """Verify that entry_points map is parseable"""
+ try:
+ _entry_points.load(value)
+ except Exception as e:
+ raise DistutilsSetupError(e) from e
+
+
+def check_package_data(dist, attr, value):
+ """Verify that value is a dictionary of package names to glob lists"""
+ if not isinstance(value, dict):
+ raise DistutilsSetupError(
+ f"{attr!r} must be a dictionary mapping package names to lists of "
+ "string wildcard patterns"
+ )
+ for k, v in value.items():
+ if not isinstance(k, str):
+ raise DistutilsSetupError(
+ f"keys of {attr!r} dict must be strings (got {k!r})"
+ )
+ assert_string_list(dist, f'values of {attr!r} dict', v)
+
+
+def check_packages(dist, attr, value):
+ for pkgname in value:
+ if not re.match(r'\w+(\.\w+)*', pkgname):
+ distutils.log.warn(
+ "WARNING: %r not a valid package name; please use only "
+ ".-separated package names in setup.py",
+ pkgname,
+ )
+
+
+if TYPE_CHECKING:
+ # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962
+ from distutils.core import Distribution as _Distribution
+else:
+ _Distribution = get_unpatched(distutils.core.Distribution)
+
+
+class Distribution(_Distribution):
+ """Distribution with support for tests and package data
+
+ This is an enhanced version of 'distutils.dist.Distribution' that
+ effectively adds the following new optional keyword arguments to 'setup()':
+
+ 'install_requires' -- a string or sequence of strings specifying project
+ versions that the distribution requires when installed, in the format
+ used by 'pkg_resources.require()'. They will be installed
+ automatically when the package is installed. If you wish to use
+ packages that are not available in PyPI, or want to give your users an
+ alternate download location, you can add a 'find_links' option to the
+ '[easy_install]' section of your project's 'setup.cfg' file, and then
+ setuptools will scan the listed web pages for links that satisfy the
+ requirements.
+
+ 'extras_require' -- a dictionary mapping names of optional "extras" to the
+ additional requirement(s) that using those extras incurs. For example,
+ this::
+
+ extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
+
+ indicates that the distribution can optionally provide an extra
+ capability called "reST", but it can only be used if docutils and
+ reSTedit are installed. If the user installs your package using
+ EasyInstall and requests one of your extras, the corresponding
+ additional requirements will be installed if needed.
+
+ 'package_data' -- a dictionary mapping package names to lists of filenames
+ or globs to use to find data files contained in the named packages.
+ If the dictionary has filenames or globs listed under '""' (the empty
+ string), those names will be searched for in every package, in addition
+ to any names for the specific package. Data files found using these
+ names/globs will be installed along with the package, in the same
+ location as the package. Note that globs are allowed to reference
+ the contents of non-package subdirectories, as long as you use '/' as
+ a path separator. (Globs are automatically converted to
+ platform-specific paths at runtime.)
+
+ In addition to these new keywords, this class also has several new methods
+ for manipulating the distribution's contents. For example, the 'include()'
+ and 'exclude()' methods can be thought of as in-place add and subtract
+ commands that add or remove packages, modules, extensions, and so on from
+ the distribution.
+ """
+
+ _DISTUTILS_UNSUPPORTED_METADATA = {
+ 'long_description_content_type': lambda: None,
+ 'project_urls': dict,
+ 'provides_extras': dict, # behaves like an ordered set
+ 'license_expression': lambda: None,
+ 'license_file': lambda: None,
+ 'license_files': lambda: None,
+ 'install_requires': list,
+ 'extras_require': dict,
+ }
+
+ # Used by build_py, editable_wheel and install_lib commands for legacy namespaces
+ namespace_packages: list[str] #: :meta private: DEPRECATED
+
+ # Any: Dynamic assignment results in Incompatible types in assignment
+ def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None:
+ have_package_data = hasattr(self, "package_data")
+ if not have_package_data:
+ self.package_data: dict[str, list[str]] = {}
+ attrs = attrs or {}
+ self.dist_files: list[tuple[str, str, str]] = []
+ self.include_package_data: bool | None = None
+ self.exclude_package_data: dict[str, list[str]] | None = None
+ # Filter-out setuptools' specific options.
+ self.src_root: str | None = attrs.pop("src_root", None)
+ self.dependency_links: list[str] = attrs.pop('dependency_links', [])
+ self.setup_requires: list[str] = attrs.pop('setup_requires', [])
+ for ep in metadata.entry_points(group='distutils.setup_keywords'):
+ vars(self).setdefault(ep.name, None)
+
+ metadata_only = set(self._DISTUTILS_UNSUPPORTED_METADATA)
+ metadata_only -= {"install_requires", "extras_require"}
+ dist_attrs = {k: v for k, v in attrs.items() if k not in metadata_only}
+ _Distribution.__init__(self, dist_attrs)
+
+ # Private API (setuptools-use only, not restricted to Distribution)
+ # Stores files that are referenced by the configuration and need to be in the
+ # sdist (e.g. `version = file: VERSION.txt`)
+ self._referenced_files = set[str]()
+
+ self.set_defaults = ConfigDiscovery(self)
+
+ self._set_metadata_defaults(attrs)
+
+ self.metadata.version = self._normalize_version(self.metadata.version)
+ self._finalize_requires()
+
+ def _validate_metadata(self):
+ required = {"name"}
+ provided = {
+ key
+ for key in vars(self.metadata)
+ if getattr(self.metadata, key, None) is not None
+ }
+ missing = required - provided
+
+ if missing:
+ msg = f"Required package metadata is missing: {missing}"
+ raise DistutilsSetupError(msg)
+
+ def _set_metadata_defaults(self, attrs):
+ """
+ Fill-in missing metadata fields not supported by distutils.
+ Some fields may have been set by other tools (e.g. pbr).
+ Those fields (vars(self.metadata)) take precedence to
+ supplied attrs.
+ """
+ for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():
+ vars(self.metadata).setdefault(option, attrs.get(option, default()))
+
+ @staticmethod
+ def _normalize_version(version):
+ from . import sic
+
+ if isinstance(version, numbers.Number):
+ # Some people apparently take "version number" too literally :)
+ version = str(version)
+ elif isinstance(version, sic) or version is None:
+ return version
+
+ normalized = str(Version(version))
+ if version != normalized:
+ InformationOnly.emit(f"Normalizing '{version}' to '{normalized}'")
+ return normalized
+ return version
+
+ def _finalize_requires(self):
+ """
+ Set `metadata.python_requires` and fix environment markers
+ in `install_requires` and `extras_require`.
+ """
+ if getattr(self, 'python_requires', None):
+ self.metadata.python_requires = self.python_requires
+
+ self._normalize_requires()
+ self.metadata.install_requires = self.install_requires
+ self.metadata.extras_require = self.extras_require
+
+ if self.extras_require:
+ for extra in self.extras_require.keys():
+ # Setuptools allows a weird ": syntax for extras
+ extra = extra.split(':')[0]
+ if extra:
+ self.metadata.provides_extras.setdefault(extra)
+
+ def _normalize_requires(self):
+ """Make sure requirement-related attributes exist and are normalized"""
+ install_requires = getattr(self, "install_requires", None) or []
+ extras_require = getattr(self, "extras_require", None) or {}
+
+ # Preserve the "static"-ness of values parsed from config files
+ list_ = _static.List if _static.is_static(install_requires) else list
+ self.install_requires = list_(map(str, _reqs.parse(install_requires)))
+
+ dict_ = _static.Dict if _static.is_static(extras_require) else dict
+ self.extras_require = dict_(
+ (k, list(map(str, _reqs.parse(v or [])))) for k, v in extras_require.items()
+ )
+
+ def _finalize_license_expression(self) -> None:
+ """
+ Normalize license and license_expression.
+ >>> dist = Distribution({"license_expression": _static.Str("mit aNd gpl-3.0-OR-later")})
+ >>> _static.is_static(dist.metadata.license_expression)
+ True
+ >>> dist._finalize_license_expression()
+ >>> _static.is_static(dist.metadata.license_expression) # preserve "static-ness"
+ True
+ >>> print(dist.metadata.license_expression)
+ MIT AND GPL-3.0-or-later
+ """
+ classifiers = self.metadata.get_classifiers()
+ license_classifiers = [cl for cl in classifiers if cl.startswith("License :: ")]
+
+ license_expr = self.metadata.license_expression
+ if license_expr:
+ str_ = _static.Str if _static.is_static(license_expr) else str
+ normalized = str_(_canonicalize_license_expression(license_expr))
+ if license_expr != normalized:
+ InformationOnly.emit(f"Normalizing '{license_expr}' to '{normalized}'")
+ self.metadata.license_expression = normalized
+ if license_classifiers:
+ raise InvalidConfigError(
+ "License classifiers have been superseded by license expressions "
+ "(see https://peps.python.org/pep-0639/). Please remove:\n\n"
+ + "\n".join(license_classifiers),
+ )
+ elif license_classifiers:
+ pypa_guides = "guides/writing-pyproject-toml/#license"
+ SetuptoolsDeprecationWarning.emit(
+ "License classifiers are deprecated.",
+ "Please consider removing the following classifiers in favor of a "
+ "SPDX license expression:\n\n" + "\n".join(license_classifiers),
+ see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
+ # Warning introduced on 2025-02-17
+ # TODO: Should we add a due date? It may affect old/unmaintained
+ # packages in the ecosystem and cause problems...
+ )
+
+ def _finalize_license_files(self) -> None:
+ """Compute names of all license files which should be included."""
+ license_files: list[str] | None = self.metadata.license_files
+ patterns = license_files or []
+
+ license_file: str | None = self.metadata.license_file
+ if license_file and license_file not in patterns:
+ patterns.append(license_file)
+
+ if license_files is None and license_file is None:
+ # Default patterns match the ones wheel uses
+ # See https://wheel.readthedocs.io/en/stable/user_guide.html
+ # -> 'Including license files in the generated wheel file'
+ patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']
+ files = self._expand_patterns(patterns, enforce_match=False)
+ else: # Patterns explicitly given by the user
+ files = self._expand_patterns(patterns, enforce_match=True)
+
+ self.metadata.license_files = list(unique_everseen(files))
+
+ @classmethod
+ def _expand_patterns(
+ cls, patterns: list[str], enforce_match: bool = True
+ ) -> Iterator[str]:
+ """
+ >>> getfixture('sample_project_cwd')
+ >>> list(Distribution._expand_patterns(['LICENSE.txt']))
+ ['LICENSE.txt']
+ >>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*']))
+ ['pyproject.toml', 'LICENSE.txt']
+ >>> list(Distribution._expand_patterns(['src/**/*.dat']))
+ ['src/sample/package_data.dat']
+ """
+ return (
+ path.replace(os.sep, "/")
+ for pattern in patterns
+ for path in sorted(cls._find_pattern(pattern, enforce_match))
+ if not path.endswith('~') and os.path.isfile(path)
+ )
+
+ @staticmethod
+ def _find_pattern(pattern: str, enforce_match: bool = True) -> list[str]:
+ r"""
+ >>> getfixture('sample_project_cwd')
+ >>> Distribution._find_pattern("LICENSE.txt")
+ ['LICENSE.txt']
+ >>> Distribution._find_pattern("/LICENSE.MIT")
+ Traceback (most recent call last):
+ ...
+ setuptools.errors.InvalidConfigError: Pattern '/LICENSE.MIT' should be relative...
+ >>> Distribution._find_pattern("../LICENSE.MIT")
+ Traceback (most recent call last):
+ ...
+ setuptools.warnings.SetuptoolsDeprecationWarning: ...Pattern '../LICENSE.MIT' cannot contain '..'...
+ >>> Distribution._find_pattern("LICEN{CSE*")
+ Traceback (most recent call last):
+ ...
+ setuptools.warnings.SetuptoolsDeprecationWarning: ...Pattern 'LICEN{CSE*' contains invalid characters...
+ """
+ pypa_guides = "specifications/glob-patterns/"
+ if ".." in pattern:
+ SetuptoolsDeprecationWarning.emit(
+ f"Pattern {pattern!r} cannot contain '..'",
+ """
+ Please ensure the files specified are contained by the root
+ of the Python package (normally marked by `pyproject.toml`).
+ """,
+ see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
+ due_date=(2027, 2, 18), # Introduced in 2025-03-20
+ # Replace with InvalidConfigError after deprecation
+ )
+ if pattern.startswith((os.sep, "/")) or ":\\" in pattern:
+ raise InvalidConfigError(
+ f"Pattern {pattern!r} should be relative and must not start with '/'"
+ )
+ if re.match(r'^[\w\-\.\/\*\?\[\]]+$', pattern) is None:
+ SetuptoolsDeprecationWarning.emit(
+ "Please provide a valid glob pattern.",
+ "Pattern {pattern!r} contains invalid characters.",
+ pattern=pattern,
+ see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
+ due_date=(2027, 2, 18), # Introduced in 2025-02-20
+ )
+
+ found = glob(pattern, recursive=True)
+
+ if enforce_match and not found:
+ SetuptoolsDeprecationWarning.emit(
+ "Cannot find any files for the given pattern.",
+ "Pattern {pattern!r} did not match any files.",
+ pattern=pattern,
+ due_date=(2027, 2, 18), # Introduced in 2025-02-20
+ # PEP 639 requires us to error, but as a transition period
+ # we will only issue a warning to give people time to prepare.
+ # After the transition, this should raise an InvalidConfigError.
+ )
+ return found
+
+ # FIXME: 'Distribution._parse_config_files' is too complex (14)
+ def _parse_config_files(self, filenames=None): # noqa: C901
+ """
+ Adapted from distutils.dist.Distribution.parse_config_files,
+ this method provides the same functionality in subtly-improved
+ ways.
+ """
+ from configparser import ConfigParser
+
+ # Ignore install directory options if we have a venv
+ ignore_options = (
+ []
+ if sys.prefix == sys.base_prefix
+ else [
+ 'install-base',
+ 'install-platbase',
+ 'install-lib',
+ 'install-platlib',
+ 'install-purelib',
+ 'install-headers',
+ 'install-scripts',
+ 'install-data',
+ 'prefix',
+ 'exec-prefix',
+ 'home',
+ 'user',
+ 'root',
+ ]
+ )
+
+ ignore_options = frozenset(ignore_options)
+
+ if filenames is None:
+ filenames = self.find_config_files()
+
+ if DEBUG:
+ self.announce("Distribution.parse_config_files():")
+
+ parser = ConfigParser()
+ parser.optionxform = str
+ for filename in filenames:
+ with open(filename, encoding='utf-8') as reader:
+ if DEBUG:
+ self.announce(" reading {filename}".format(**locals()))
+ parser.read_file(reader)
+ for section in parser.sections():
+ options = parser.options(section)
+ opt_dict = self.get_option_dict(section)
+
+ for opt in options:
+ if opt == '__name__' or opt in ignore_options:
+ continue
+
+ val = parser.get(section, opt)
+ opt = self._enforce_underscore(opt, section)
+ opt = self._enforce_option_lowercase(opt, section)
+ opt_dict[opt] = (filename, val)
+
+ # Make the ConfigParser forget everything (so we retain
+ # the original filenames that options come from)
+ parser.__init__()
+
+ if 'global' not in self.command_options:
+ return
+
+ # If there was a "global" section in the config file, use it
+ # to set Distribution options.
+
+ for opt, (src, val) in self.command_options['global'].items():
+ alias = self.negative_opt.get(opt)
+ if alias:
+ val = not strtobool(val)
+ elif opt in ('verbose', 'dry_run'): # ugh!
+ val = strtobool(val)
+
+ try:
+ setattr(self, alias or opt, val)
+ except ValueError as e:
+ raise DistutilsOptionError(e) from e
+
+ def _enforce_underscore(self, opt: str, section: str) -> str:
+ if "-" not in opt or self._skip_setupcfg_normalization(section):
+ return opt
+
+ underscore_opt = opt.replace('-', '_')
+ affected = f"(Affected: {self.metadata.name})." if self.metadata.name else ""
+ SetuptoolsDeprecationWarning.emit(
+ f"Invalid dash-separated key {opt!r} in {section!r} (setup.cfg), "
+ f"please use the underscore name {underscore_opt!r} instead.",
+ f"""
+ Usage of dash-separated {opt!r} will not be supported in future
+ versions. Please use the underscore name {underscore_opt!r} instead.
+ {affected}
+
+ Available configuration options are listed in:
+ https://setuptools.pypa.io/en/latest/userguide/declarative_config.html
+ """,
+ see_url="https://github.com/pypa/setuptools/discussions/5011",
+ due_date=(2026, 3, 3),
+ # Warning initially introduced in 3 Mar 2021
+ )
+ return underscore_opt
+
+ def _enforce_option_lowercase(self, opt: str, section: str) -> str:
+ if opt.islower() or self._skip_setupcfg_normalization(section):
+ return opt
+
+ lowercase_opt = opt.lower()
+ affected = f"(Affected: {self.metadata.name})." if self.metadata.name else ""
+ SetuptoolsDeprecationWarning.emit(
+ f"Invalid uppercase key {opt!r} in {section!r} (setup.cfg), "
+ f"please use lowercase {lowercase_opt!r} instead.",
+ f"""
+ Usage of uppercase key {opt!r} in {section!r} will not be supported in
+ future versions. Please use lowercase {lowercase_opt!r} instead.
+ {affected}
+
+ Available configuration options are listed in:
+ https://setuptools.pypa.io/en/latest/userguide/declarative_config.html
+ """,
+ see_url="https://github.com/pypa/setuptools/discussions/5011",
+ due_date=(2026, 3, 3),
+ # Warning initially introduced in 6 Mar 2021
+ )
+ return lowercase_opt
+
+ def _skip_setupcfg_normalization(self, section: str) -> bool:
+ skip = (
+ 'options.extras_require',
+ 'options.data_files',
+ 'options.entry_points',
+ 'options.package_data',
+ 'options.exclude_package_data',
+ )
+ return section in skip or not self._is_setuptools_section(section)
+
+ def _is_setuptools_section(self, section: str) -> bool:
+ return (
+ section == "metadata"
+ or section.startswith("options")
+ or section in _setuptools_commands()
+ )
+
+ # FIXME: 'Distribution._set_command_options' is too complex (14)
+ def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
+ """
+ Set the options for 'command_obj' from 'option_dict'. Basically
+ this means copying elements of a dictionary ('option_dict') to
+ attributes of an instance ('command').
+
+ 'command_obj' must be a Command instance. If 'option_dict' is not
+ supplied, uses the standard option dictionary for this command
+ (from 'self.command_options').
+
+ (Adopted from distutils.dist.Distribution._set_command_options)
+ """
+ command_name = command_obj.get_command_name()
+ if option_dict is None:
+ option_dict = self.get_option_dict(command_name)
+
+ if DEBUG:
+ self.announce(f" setting options for '{command_name}' command:")
+ for option, (source, value) in option_dict.items():
+ if DEBUG:
+ self.announce(f" {option} = {value} (from {source})")
+ try:
+ bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
+ except AttributeError:
+ bool_opts = []
+ try:
+ neg_opt = command_obj.negative_opt
+ except AttributeError:
+ neg_opt = {}
+
+ try:
+ is_string = isinstance(value, str)
+ if option in neg_opt and is_string:
+ setattr(command_obj, neg_opt[option], not strtobool(value))
+ elif option in bool_opts and is_string:
+ setattr(command_obj, option, strtobool(value))
+ elif hasattr(command_obj, option):
+ setattr(command_obj, option, value)
+ else:
+ raise DistutilsOptionError(
+ f"error in {source}: command '{command_name}' has no such option '{option}'"
+ )
+ except ValueError as e:
+ raise DistutilsOptionError(e) from e
+
+ def _get_project_config_files(self, filenames: Iterable[StrPath] | None):
+ """Add default file and split between INI and TOML"""
+ tomlfiles = []
+ standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")
+ if filenames is not None:
+ parts = partition(lambda f: Path(f).suffix == ".toml", filenames)
+ filenames = list(parts[0]) # 1st element => predicate is False
+ tomlfiles = list(parts[1]) # 2nd element => predicate is True
+ elif standard_project_metadata.exists():
+ tomlfiles = [standard_project_metadata]
+ return filenames, tomlfiles
+
+ def parse_config_files(
+ self,
+ filenames: Iterable[StrPath] | None = None,
+ ignore_option_errors: bool = False,
+ ) -> None:
+ """Parses configuration files from various levels
+ and loads configuration.
+ """
+ inifiles, tomlfiles = self._get_project_config_files(filenames)
+
+ self._parse_config_files(filenames=inifiles)
+
+ setupcfg.parse_configuration(
+ self, self.command_options, ignore_option_errors=ignore_option_errors
+ )
+ for filename in tomlfiles:
+ pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
+
+ self._finalize_requires()
+ self._finalize_license_expression()
+ self._finalize_license_files()
+
+ def fetch_build_eggs(self, requires: _StrOrIter) -> list[metadata.Distribution]:
+ """Resolve pre-setup requirements"""
+ from .installer import _fetch_build_eggs
+
+ return _fetch_build_eggs(self, requires)
+
+ def finalize_options(self) -> None:
+ """
+ Allow plugins to apply arbitrary operations to the
+ distribution. Each hook may optionally define a 'order'
+ to influence the order of execution. Smaller numbers
+ go first and the default is 0.
+ """
+ group = 'setuptools.finalize_distribution_options'
+
+ def by_order(hook):
+ return getattr(hook, 'order', 0)
+
+ defined = metadata.entry_points(group=group)
+ filtered = itertools.filterfalse(self._removed, defined)
+ loaded = map(lambda e: e.load(), filtered)
+ for ep in sorted(loaded, key=by_order):
+ ep(self)
+
+ @staticmethod
+ def _removed(ep):
+ """
+ When removing an entry point, if metadata is loaded
+ from an older version of Setuptools, that removed
+ entry point will attempt to be loaded and will fail.
+ See #2765 for more details.
+ """
+ removed = {
+ # removed 2021-09-05
+ '2to3_doctests',
+ }
+ return ep.name in removed
+
+ def _finalize_setup_keywords(self):
+ for ep in metadata.entry_points(group='distutils.setup_keywords'):
+ value = getattr(self, ep.name, None)
+ if value is not None:
+ ep.load()(self, ep.name, value)
+
+ def get_egg_cache_dir(self) -> str:
+ from . import windows_support
+
+ egg_cache_dir = os.path.join(os.curdir, '.eggs')
+ if not os.path.exists(egg_cache_dir):
+ os.mkdir(egg_cache_dir)
+ windows_support.hide_file(egg_cache_dir)
+ readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')
+ with open(readme_txt_filename, 'w', encoding="utf-8") as f:
+ f.write(
+ 'This directory contains eggs that were downloaded '
+ 'by setuptools to build, test, and run plug-ins.\n\n'
+ )
+ f.write(
+ 'This directory caches those eggs to prevent '
+ 'repeated downloads.\n\n'
+ )
+ f.write('However, it is safe to delete this directory.\n\n')
+
+ return egg_cache_dir
+
+ def fetch_build_egg(self, req):
+ """Fetch an egg needed for building"""
+ from .installer import fetch_build_egg
+
+ return fetch_build_egg(self, req)
+
+ def get_command_class(self, command: str) -> type[distutils.cmd.Command]: # type: ignore[override] # Not doing complex overrides yet
+ """Pluggable version of get_command_class()"""
+ if command in self.cmdclass:
+ return self.cmdclass[command]
+
+ # Special case bdist_wheel so it's never loaded from "wheel"
+ if command == 'bdist_wheel':
+ from .command.bdist_wheel import bdist_wheel
+
+ return bdist_wheel
+
+ eps = metadata.entry_points(group='distutils.commands', name=command)
+ for ep in eps:
+ self.cmdclass[command] = cmdclass = ep.load()
+ return cmdclass
+ else:
+ return _Distribution.get_command_class(self, command)
+
+ def print_commands(self):
+ for ep in metadata.entry_points(group='distutils.commands'):
+ if ep.name not in self.cmdclass:
+ cmdclass = ep.load()
+ self.cmdclass[ep.name] = cmdclass
+ return _Distribution.print_commands(self)
+
+ def get_command_list(self):
+ for ep in metadata.entry_points(group='distutils.commands'):
+ if ep.name not in self.cmdclass:
+ cmdclass = ep.load()
+ self.cmdclass[ep.name] = cmdclass
+ return _Distribution.get_command_list(self)
+
+ def include(self, **attrs) -> None:
+ """Add items to distribution that are named in keyword arguments
+
+ For example, 'dist.include(py_modules=["x"])' would add 'x' to
+ the distribution's 'py_modules' attribute, if it was not already
+ there.
+
+ Currently, this method only supports inclusion for attributes that are
+ lists or tuples. If you need to add support for adding to other
+ attributes in this or a subclass, you can add an '_include_X' method,
+ where 'X' is the name of the attribute. The method will be called with
+ the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})'
+ will try to call 'dist._include_foo({"bar":"baz"})', which can then
+ handle whatever special inclusion logic is needed.
+ """
+ for k, v in attrs.items():
+ include = getattr(self, '_include_' + k, None)
+ if include:
+ include(v)
+ else:
+ self._include_misc(k, v)
+
+ def exclude_package(self, package: str) -> None:
+ """Remove packages, modules, and extensions in named package"""
+
+ pfx = package + '.'
+ if self.packages:
+ self.packages = [
+ p for p in self.packages if p != package and not p.startswith(pfx)
+ ]
+
+ if self.py_modules:
+ self.py_modules = [
+ p for p in self.py_modules if p != package and not p.startswith(pfx)
+ ]
+
+ if self.ext_modules:
+ self.ext_modules = [
+ p
+ for p in self.ext_modules
+ if p.name != package and not p.name.startswith(pfx)
+ ]
+
+ def has_contents_for(self, package: str) -> bool:
+ """Return true if 'exclude_package(package)' would do something"""
+
+ pfx = package + '.'
+
+ for p in self.iter_distribution_names():
+ if p == package or p.startswith(pfx):
+ return True
+
+ return False
+
+ def _exclude_misc(self, name: str, value: _Sequence) -> None:
+ """Handle 'exclude()' for list/tuple attrs without a special handler"""
+ if not isinstance(value, _sequence):
+ raise DistutilsSetupError(
+ f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})"
+ )
+ try:
+ old = getattr(self, name)
+ except AttributeError as e:
+ raise DistutilsSetupError(f"{name}: No such distribution setting") from e
+ if old is not None and not isinstance(old, _sequence):
+ raise DistutilsSetupError(
+ name + ": this setting cannot be changed via include/exclude"
+ )
+ elif old:
+ setattr(self, name, [item for item in old if item not in value])
+
+ def _include_misc(self, name: str, value: _Sequence) -> None:
+ """Handle 'include()' for list/tuple attrs without a special handler"""
+
+ if not isinstance(value, _sequence):
+ raise DistutilsSetupError(
+ f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})"
+ )
+ try:
+ old = getattr(self, name)
+ except AttributeError as e:
+ raise DistutilsSetupError(f"{name}: No such distribution setting") from e
+ if old is None:
+ setattr(self, name, value)
+ elif not isinstance(old, _sequence):
+ raise DistutilsSetupError(
+ name + ": this setting cannot be changed via include/exclude"
+ )
+ else:
+ new = [item for item in value if item not in old]
+ setattr(self, name, list(old) + new)
+
+ def exclude(self, **attrs) -> None:
+ """Remove items from distribution that are named in keyword arguments
+
+ For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
+ the distribution's 'py_modules' attribute. Excluding packages uses
+ the 'exclude_package()' method, so all of the package's contained
+ packages, modules, and extensions are also excluded.
+
+ Currently, this method only supports exclusion from attributes that are
+ lists or tuples. If you need to add support for excluding from other
+ attributes in this or a subclass, you can add an '_exclude_X' method,
+ where 'X' is the name of the attribute. The method will be called with
+ the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})'
+ will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
+ handle whatever special exclusion logic is needed.
+ """
+ for k, v in attrs.items():
+ exclude = getattr(self, '_exclude_' + k, None)
+ if exclude:
+ exclude(v)
+ else:
+ self._exclude_misc(k, v)
+
+ def _exclude_packages(self, packages: _Sequence) -> None:
+ if not isinstance(packages, _sequence):
+ raise DistutilsSetupError(
+ f"packages: setting must be of type <{_sequence_type_repr}> (got {packages!r})"
+ )
+ list(map(self.exclude_package, packages))
+
+ def _parse_command_opts(self, parser, args):
+ # Remove --with-X/--without-X options when processing command args
+ self.global_options = self.__class__.global_options
+ self.negative_opt = self.__class__.negative_opt
+
+ # First, expand any aliases
+ command = args[0]
+ aliases = self.get_option_dict('aliases')
+ while command in aliases:
+ _src, alias = aliases[command]
+ del aliases[command] # ensure each alias can expand only once!
+ import shlex
+
+ args[:1] = shlex.split(alias, True)
+ command = args[0]
+
+ nargs = _Distribution._parse_command_opts(self, parser, args)
+
+ # Handle commands that want to consume all remaining arguments
+ cmd_class = self.get_command_class(command)
+ if getattr(cmd_class, 'command_consumes_arguments', None):
+ self.get_option_dict(command)['args'] = ("command line", nargs)
+ if nargs is not None:
+ return []
+
+ return nargs
+
+ def get_cmdline_options(self) -> dict[str, dict[str, str | None]]:
+ """Return a '{cmd: {opt:val}}' map of all command-line options
+
+ Option names are all long, but do not include the leading '--', and
+ contain dashes rather than underscores. If the option doesn't take
+ an argument (e.g. '--quiet'), the 'val' is 'None'.
+
+ Note that options provided by config files are intentionally excluded.
+ """
+
+ d: dict[str, dict[str, str | None]] = {}
+
+ for cmd, opts in self.command_options.items():
+ val: str | None
+ for opt, (src, val) in opts.items():
+ if src != "command line":
+ continue
+
+ opt = opt.replace('_', '-')
+
+ if val == 0:
+ cmdobj = self.get_command_obj(cmd)
+ neg_opt = self.negative_opt.copy()
+ neg_opt.update(getattr(cmdobj, 'negative_opt', {}))
+ for neg, pos in neg_opt.items():
+ if pos == opt:
+ opt = neg
+ val = None
+ break
+ else:
+ raise AssertionError("Shouldn't be able to get here")
+
+ elif val == 1:
+ val = None
+
+ d.setdefault(cmd, {})[opt] = val
+
+ return d
+
+ def iter_distribution_names(self) -> Iterator[str]:
+ """Yield all packages, modules, and extension names in distribution"""
+
+ yield from self.packages or ()
+
+ yield from self.py_modules or ()
+
+ for ext in self.ext_modules or ():
+ if isinstance(ext, tuple):
+ name, _buildinfo = ext
+ else:
+ name = ext.name
+ name = name.removesuffix('module')
+ yield name
+
+ def handle_display_options(self, option_order):
+ """If there were any non-global "display-only" options
+ (--help-commands or the metadata display options) on the command
+ line, display the requested info and return true; else return
+ false.
+ """
+ import sys
+
+ if self.help_commands:
+ return _Distribution.handle_display_options(self, option_order)
+
+ # Stdout may be StringIO (e.g. in tests)
+ if not isinstance(sys.stdout, io.TextIOWrapper):
+ return _Distribution.handle_display_options(self, option_order)
+
+ # Don't wrap stdout if utf-8 is already the encoding. Provides
+ # workaround for #334.
+ if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):
+ return _Distribution.handle_display_options(self, option_order)
+
+ # Print metadata in UTF-8 no matter the platform
+ encoding = sys.stdout.encoding
+ sys.stdout.reconfigure(encoding='utf-8')
+ try:
+ return _Distribution.handle_display_options(self, option_order)
+ finally:
+ sys.stdout.reconfigure(encoding=encoding)
+
+ def run_command(self, command) -> None:
+ self.set_defaults()
+ # Postpone defaults until all explicit configuration is considered
+ # (setup() args, config files, command line and plugins)
+
+ super().run_command(command)
+
+
+@functools.cache
+def _setuptools_commands() -> set[str]:
+ try:
+ # Use older API for importlib.metadata compatibility
+ entry_points = metadata.distribution('setuptools').entry_points
+ eps: Iterable[str] = (ep.name for ep in entry_points)
+ except metadata.PackageNotFoundError:
+ # during bootstrapping, distribution doesn't exist
+ eps = []
+ return {*distutils.command.__all__, *eps}
+
+
+class DistDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in dist in
+ setuptools. Not ignored by default, unlike DeprecationWarning."""
diff --git a/lib/python3.12/site-packages/setuptools/errors.py b/lib/python3.12/site-packages/setuptools/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..990ecbf4e2f18eb188addc9e0466152a20193a90
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/errors.py
@@ -0,0 +1,67 @@
+"""setuptools.errors
+
+Provides exceptions used by setuptools modules.
+"""
+
+from __future__ import annotations
+
+from distutils import errors as _distutils_errors
+
+# Re-export errors from distutils to facilitate the migration to PEP632
+
+ByteCompileError = _distutils_errors.DistutilsByteCompileError
+CCompilerError = _distutils_errors.CCompilerError
+ClassError = _distutils_errors.DistutilsClassError
+CompileError = _distutils_errors.CompileError
+ExecError = _distutils_errors.DistutilsExecError
+FileError = _distutils_errors.DistutilsFileError
+InternalError = _distutils_errors.DistutilsInternalError
+LibError = _distutils_errors.LibError
+LinkError = _distutils_errors.LinkError
+ModuleError = _distutils_errors.DistutilsModuleError
+OptionError = _distutils_errors.DistutilsOptionError
+PlatformError = _distutils_errors.DistutilsPlatformError
+PreprocessError = _distutils_errors.PreprocessError
+SetupError = _distutils_errors.DistutilsSetupError
+TemplateError = _distutils_errors.DistutilsTemplateError
+UnknownFileError = _distutils_errors.UnknownFileError
+
+# The root error class in the hierarchy
+BaseError = _distutils_errors.DistutilsError
+
+
+class InvalidConfigError(OptionError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+
+ """Error used for invalid configurations."""
+
+
+class RemovedConfigError(OptionError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+
+ """Error used for configurations that were deprecated and removed."""
+
+
+class RemovedCommandError(BaseError, RuntimeError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+
+ """Error used for commands that have been removed in setuptools.
+
+ Since ``setuptools`` is built on ``distutils``, simply removing a command
+ from ``setuptools`` will make the behavior fall back to ``distutils``; this
+ error is raised if a command exists in ``distutils`` but has been actively
+ removed in ``setuptools``.
+ """
+
+
+class PackageDiscoveryError(BaseError, RuntimeError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+
+ """Impossible to perform automatic discovery of packages and/or modules.
+
+ The current project layout or given discovery options can lead to problems when
+ scanning the project directory.
+
+ Setuptools might also refuse to complete auto-discovery if an error prone condition
+ is detected (e.g. when a project is organised as a flat-layout but contains
+ multiple directories that can be taken as top-level packages inside a single
+ distribution [*]_). In these situations the users are encouraged to be explicit
+ about which packages to include or to make the discovery parameters more specific.
+
+ .. [*] Since multi-package distributions are uncommon it is very likely that the
+ developers did not intend for all the directories to be packaged, and are just
+ leaving auxiliary code in the repository top-level, such as maintenance-related
+ scripts.
+ """
diff --git a/lib/python3.12/site-packages/setuptools/extension.py b/lib/python3.12/site-packages/setuptools/extension.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e63cbe12aefc3b52a5d1ca2fe3e1ca2e172e29d
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/extension.py
@@ -0,0 +1,179 @@
+from __future__ import annotations
+
+import functools
+import re
+from collections.abc import Iterable
+from typing import TYPE_CHECKING
+
+from setuptools._path import StrPath
+
+from .monkey import get_unpatched
+
+import distutils.core
+import distutils.errors
+import distutils.extension
+
+
+def _have_cython() -> bool:
+ """
+ Return True if Cython can be imported.
+ """
+ cython_impl = 'Cython.Distutils.build_ext'
+ try:
+ # from (cython_impl) import build_ext
+ __import__(cython_impl, fromlist=['build_ext']).build_ext
+ except Exception:
+ return False
+ return True
+
+
+# for compatibility
+have_pyrex = _have_cython
+if TYPE_CHECKING:
+ # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962
+ from distutils.core import Extension as _Extension
+else:
+ _Extension = get_unpatched(distutils.core.Extension)
+
+
+class Extension(_Extension):
+ """
+ Describes a single extension module.
+
+ This means that all source files will be compiled into a single binary file
+ ``.`` (with ```` derived from ``name`` and
+ ```` defined by one of the values in
+ ``importlib.machinery.EXTENSION_SUFFIXES``).
+
+ In the case ``.pyx`` files are passed as ``sources and`` ``Cython`` is **not**
+ installed in the build environment, ``setuptools`` may also try to look for the
+ equivalent ``.cpp`` or ``.c`` files.
+
+ :arg str name:
+ the full name of the extension, including any packages -- ie.
+ *not* a filename or pathname, but Python dotted name
+
+ :arg Iterable[str | os.PathLike[str]] sources:
+ iterable of source filenames, (except strings, which could be misinterpreted
+ as a single filename), relative to the distribution root
+ (where the setup script lives), in Unix form (slash-separated)
+ for portability. Source files may be C, C++, SWIG (.i),
+ platform-specific resource files, or whatever else is recognized
+ by the "build_ext" command as source for a Python extension.
+
+ :keyword list[str] include_dirs:
+ list of directories to search for C/C++ header files (in Unix
+ form for portability)
+
+ :keyword list[tuple[str, str|None]] define_macros:
+ list of macros to define; each macro is defined using a 2-tuple:
+ the first item corresponding to the name of the macro and the second
+ item either a string with its value or None to
+ define it without a particular value (equivalent of "#define
+ FOO" in source or -DFOO on Unix C compiler command line)
+
+ :keyword list[str] undef_macros:
+ list of macros to undefine explicitly
+
+ :keyword list[str] library_dirs:
+ list of directories to search for C/C++ libraries at link time
+
+ :keyword list[str] libraries:
+ list of library names (not filenames or paths) to link against
+
+ :keyword list[str] runtime_library_dirs:
+ list of directories to search for C/C++ libraries at run time
+ (for shared extensions, this is when the extension is loaded).
+ Setting this will cause an exception during build on Windows
+ platforms.
+
+ :keyword list[str] extra_objects:
+ list of extra files to link with (eg. object files not implied
+ by 'sources', static library that must be explicitly specified,
+ binary resource files, etc.)
+
+ :keyword list[str] extra_compile_args:
+ any extra platform- and compiler-specific information to use
+ when compiling the source files in 'sources'. For platforms and
+ compilers where "command line" makes sense, this is typically a
+ list of command-line arguments, but for other platforms it could
+ be anything.
+
+ :keyword list[str] extra_link_args:
+ any extra platform- and compiler-specific information to use
+ when linking object files together to create the extension (or
+ to create a new static Python interpreter). Similar
+ interpretation as for 'extra_compile_args'.
+
+ :keyword list[str] export_symbols:
+ list of symbols to be exported from a shared extension. Not
+ used on all platforms, and not generally necessary for Python
+ extensions, which typically export exactly one symbol: "init" +
+ extension_name.
+
+ :keyword list[str] swig_opts:
+ any extra options to pass to SWIG if a source file has the .i
+ extension.
+
+ :keyword list[str] depends:
+ list of files that the extension depends on
+
+ :keyword str language:
+ extension language (i.e. "c", "c++", "objc"). Will be detected
+ from the source extensions if not provided.
+
+ :keyword bool optional:
+ specifies that a build failure in the extension should not abort the
+ build process, but simply not install the failing extension.
+
+ :keyword bool py_limited_api:
+ opt-in flag for the usage of :doc:`Python's limited API `.
+
+ :raises setuptools.errors.PlatformError: if ``runtime_library_dirs`` is
+ specified on Windows. (since v63)
+ """
+
+ # These 4 are set and used in setuptools/command/build_ext.py
+ # The lack of a default value and risk of `AttributeError` is purposeful
+ # to avoid people forgetting to call finalize_options if they modify the extension list.
+ # See example/rationale in https://github.com/pypa/setuptools/issues/4529.
+ _full_name: str #: Private API, internal use only.
+ _links_to_dynamic: bool #: Private API, internal use only.
+ _needs_stub: bool #: Private API, internal use only.
+ _file_name: str #: Private API, internal use only.
+
+ def __init__(
+ self,
+ name: str,
+ sources: Iterable[StrPath],
+ *args,
+ py_limited_api: bool = False,
+ **kw,
+ ) -> None:
+ # The *args is needed for compatibility as calls may use positional
+ # arguments. py_limited_api may be set only via keyword.
+ self.py_limited_api = py_limited_api
+ super().__init__(
+ name,
+ sources, # type: ignore[arg-type] # Vendored version of setuptools supports PathLike
+ *args,
+ **kw,
+ )
+
+ def _convert_pyx_sources_to_lang(self):
+ """
+ Replace sources with .pyx extensions to sources with the target
+ language extension. This mechanism allows language authors to supply
+ pre-converted sources but to prefer the .pyx sources.
+ """
+ if _have_cython():
+ # the build has Cython, so allow it to compile the .pyx files
+ return
+ lang = self.language or ''
+ target_ext = '.cpp' if lang.lower() == 'c++' else '.c'
+ sub = functools.partial(re.sub, '.pyx$', target_ext)
+ self.sources = list(map(sub, self.sources))
+
+
+class Library(Extension):
+ """Just like a regular Extension, but built as a library instead"""
diff --git a/lib/python3.12/site-packages/setuptools/glob.py b/lib/python3.12/site-packages/setuptools/glob.py
new file mode 100644
index 0000000000000000000000000000000000000000..1dfff2cd50ff87b8cef9d936f1fc9d4a2478b136
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/glob.py
@@ -0,0 +1,185 @@
+"""
+Filename globbing utility. Mostly a copy of `glob` from Python 3.5.
+
+Changes include:
+ * `yield from` and PEP3102 `*` removed.
+ * Hidden files are not ignored.
+"""
+
+from __future__ import annotations
+
+import fnmatch
+import os
+import re
+from collections.abc import Iterable, Iterator
+from typing import TYPE_CHECKING, AnyStr, overload
+
+if TYPE_CHECKING:
+ from _typeshed import BytesPath, StrOrBytesPath, StrPath
+
+__all__ = ["glob", "iglob", "escape"]
+
+
+def glob(pathname: AnyStr, recursive: bool = False) -> list[AnyStr]:
+ """Return a list of paths matching a pathname pattern.
+
+ The pattern may contain simple shell-style wildcards a la
+ fnmatch. However, unlike fnmatch, filenames starting with a
+ dot are special cases that are not matched by '*' and '?'
+ patterns.
+
+ If recursive is true, the pattern '**' will match any files and
+ zero or more directories and subdirectories.
+ """
+ return list(iglob(pathname, recursive=recursive))
+
+
+def iglob(pathname: AnyStr, recursive: bool = False) -> Iterator[AnyStr]:
+ """Return an iterator which yields the paths matching a pathname pattern.
+
+ The pattern may contain simple shell-style wildcards a la
+ fnmatch. However, unlike fnmatch, filenames starting with a
+ dot are special cases that are not matched by '*' and '?'
+ patterns.
+
+ If recursive is true, the pattern '**' will match any files and
+ zero or more directories and subdirectories.
+ """
+ it = _iglob(pathname, recursive)
+ if recursive and _isrecursive(pathname):
+ s = next(it) # skip empty string
+ assert not s
+ return it
+
+
+def _iglob(pathname: AnyStr, recursive: bool) -> Iterator[AnyStr]:
+ dirname, basename = os.path.split(pathname)
+ glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob1
+
+ if not has_magic(pathname):
+ if basename:
+ if os.path.lexists(pathname):
+ yield pathname
+ else:
+ # Patterns ending with a slash should match only directories
+ if os.path.isdir(dirname):
+ yield pathname
+ return
+
+ if not dirname:
+ yield from glob_in_dir(dirname, basename)
+ return
+ # `os.path.split()` returns the argument itself as a dirname if it is a
+ # drive or UNC path. Prevent an infinite recursion if a drive or UNC path
+ # contains magic characters (i.e. r'\\?\C:').
+ if dirname != pathname and has_magic(dirname):
+ dirs: Iterable[AnyStr] = _iglob(dirname, recursive)
+ else:
+ dirs = [dirname]
+ if not has_magic(basename):
+ glob_in_dir = glob0
+ for dirname in dirs:
+ for name in glob_in_dir(dirname, basename):
+ yield os.path.join(dirname, name)
+
+
+# These 2 helper functions non-recursively glob inside a literal directory.
+# They return a list of basenames. `glob1` accepts a pattern while `glob0`
+# takes a literal basename (so it only has to check for its existence).
+
+
+@overload
+def glob1(dirname: StrPath, pattern: str) -> list[str]: ...
+@overload
+def glob1(dirname: BytesPath, pattern: bytes) -> list[bytes]: ...
+def glob1(dirname: StrOrBytesPath, pattern: str | bytes) -> list[str] | list[bytes]:
+ if not dirname:
+ if isinstance(pattern, bytes):
+ dirname = os.curdir.encode('ASCII')
+ else:
+ dirname = os.curdir
+ try:
+ names = os.listdir(dirname)
+ except OSError:
+ return []
+ # mypy false-positives: str or bytes type possibility is always kept in sync
+ return fnmatch.filter(names, pattern) # type: ignore[type-var, return-value]
+
+
+def glob0(dirname, basename):
+ if not basename:
+ # `os.path.split()` returns an empty basename for paths ending with a
+ # directory separator. 'q*x/' should match only directories.
+ if os.path.isdir(dirname):
+ return [basename]
+ else:
+ if os.path.lexists(os.path.join(dirname, basename)):
+ return [basename]
+ return []
+
+
+# This helper function recursively yields relative pathnames inside a literal
+# directory.
+
+
+@overload
+def glob2(dirname: StrPath, pattern: str) -> Iterator[str]: ...
+@overload
+def glob2(dirname: BytesPath, pattern: bytes) -> Iterator[bytes]: ...
+def glob2(dirname: StrOrBytesPath, pattern: str | bytes) -> Iterator[str | bytes]:
+ assert _isrecursive(pattern)
+ yield pattern[:0]
+ yield from _rlistdir(dirname)
+
+
+# Recursively yields relative pathnames inside a literal directory.
+@overload
+def _rlistdir(dirname: StrPath) -> Iterator[str]: ...
+@overload
+def _rlistdir(dirname: BytesPath) -> Iterator[bytes]: ...
+def _rlistdir(dirname: StrOrBytesPath) -> Iterator[str | bytes]:
+ if not dirname:
+ if isinstance(dirname, bytes):
+ dirname = os.curdir.encode('ASCII')
+ else:
+ dirname = os.curdir
+ try:
+ names = os.listdir(dirname)
+ except OSError:
+ return
+ for x in names:
+ yield x
+ # mypy false-positives: str or bytes type possibility is always kept in sync
+ path = os.path.join(dirname, x) if dirname else x # type: ignore[arg-type]
+ for y in _rlistdir(path):
+ yield os.path.join(x, y) # type: ignore[arg-type]
+
+
+magic_check = re.compile('([*?[])')
+magic_check_bytes = re.compile(b'([*?[])')
+
+
+def has_magic(s: str | bytes) -> bool:
+ if isinstance(s, bytes):
+ return magic_check_bytes.search(s) is not None
+ else:
+ return magic_check.search(s) is not None
+
+
+def _isrecursive(pattern: str | bytes) -> bool:
+ if isinstance(pattern, bytes):
+ return pattern == b'**'
+ else:
+ return pattern == '**'
+
+
+def escape(pathname):
+ """Escape all special characters."""
+ # Escaping is done by wrapping any of "*?[" between square brackets.
+ # Metacharacters do not work in the drive part and shouldn't be escaped.
+ drive, pathname = os.path.splitdrive(pathname)
+ if isinstance(pathname, bytes):
+ pathname = magic_check_bytes.sub(rb'[\1]', pathname)
+ else:
+ pathname = magic_check.sub(r'[\1]', pathname)
+ return drive + pathname
diff --git a/lib/python3.12/site-packages/setuptools/gui-32.exe b/lib/python3.12/site-packages/setuptools/gui-32.exe
new file mode 100644
index 0000000000000000000000000000000000000000..1eb430c6d614a5daea4139badc09c222a4b0e72a
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/gui-32.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/gui-64.exe b/lib/python3.12/site-packages/setuptools/gui-64.exe
new file mode 100644
index 0000000000000000000000000000000000000000..031cb77c17ba8d8a983448268851d612e05e80d1
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/gui-64.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/gui-arm64.exe b/lib/python3.12/site-packages/setuptools/gui-arm64.exe
new file mode 100644
index 0000000000000000000000000000000000000000..1e00ffacb182c2af206e5dd9d9fbc41d236da0d1
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/gui-arm64.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/gui.exe b/lib/python3.12/site-packages/setuptools/gui.exe
new file mode 100644
index 0000000000000000000000000000000000000000..1eb430c6d614a5daea4139badc09c222a4b0e72a
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/gui.exe differ
diff --git a/lib/python3.12/site-packages/setuptools/installer.py b/lib/python3.12/site-packages/setuptools/installer.py
new file mode 100644
index 0000000000000000000000000000000000000000..36a8b092279780d730fd0a3cc20c1c76bd72c8a0
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/installer.py
@@ -0,0 +1,155 @@
+from __future__ import annotations
+
+import glob
+import itertools
+import os
+import subprocess
+import sys
+import tempfile
+
+import packaging.requirements
+import packaging.utils
+
+from . import _reqs
+from ._importlib import metadata
+from .warnings import SetuptoolsDeprecationWarning
+from .wheel import Wheel
+
+from distutils import log
+from distutils.errors import DistutilsError
+
+
+def _fixup_find_links(find_links):
+ """Ensure find-links option end-up being a list of strings."""
+ if isinstance(find_links, str):
+ return find_links.split()
+ assert isinstance(find_links, (tuple, list))
+ return find_links
+
+
+def fetch_build_egg(dist, req) -> metadata.Distribution | metadata.PathDistribution:
+ """Fetch an egg needed for building.
+
+ Use pip/wheel to fetch/build a wheel."""
+ _DeprecatedInstaller.emit()
+ _warn_wheel_not_available(dist)
+ return _fetch_build_egg_no_warn(dist, req)
+
+
+def _present(req):
+ return any(_dist_matches_req(dist, req) for dist in metadata.distributions())
+
+
+def _fetch_build_eggs(dist, requires: _reqs._StrOrIter) -> list[metadata.Distribution]:
+ _DeprecatedInstaller.emit(stacklevel=3)
+ _warn_wheel_not_available(dist)
+
+ parsed_reqs = _reqs.parse(requires)
+
+ missing_reqs = itertools.filterfalse(_present, parsed_reqs)
+
+ needed_reqs = (
+ req for req in missing_reqs if not req.marker or req.marker.evaluate()
+ )
+ resolved_dists = [_fetch_build_egg_no_warn(dist, req) for req in needed_reqs]
+ for dist in resolved_dists:
+ # dist.locate_file('') is the directory containing EGG-INFO, where the importabl
+ # contents can be found.
+ sys.path.insert(0, str(dist.locate_file('')))
+ return resolved_dists
+
+
+def _dist_matches_req(egg_dist, req):
+ return (
+ packaging.utils.canonicalize_name(egg_dist.name)
+ == packaging.utils.canonicalize_name(req.name)
+ and egg_dist.version in req.specifier
+ )
+
+
+def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) # FIXME
+ # Ignore environment markers; if supplied, it is required.
+ req = strip_marker(req)
+ # Take easy_install options into account, but do not override relevant
+ # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
+ # take precedence.
+ opts = dist.get_option_dict('easy_install')
+ if 'allow_hosts' in opts:
+ raise DistutilsError(
+ 'the `allow-hosts` option is not supported '
+ 'when using pip to install requirements.'
+ )
+ quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in os.environ
+ if 'PIP_INDEX_URL' in os.environ:
+ index_url = None
+ elif 'index_url' in opts:
+ index_url = opts['index_url'][1]
+ else:
+ index_url = None
+ find_links = (
+ _fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts else []
+ )
+ if dist.dependency_links:
+ find_links.extend(dist.dependency_links)
+ eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
+ cached_dists = metadata.Distribution.discover(path=glob.glob(f'{eggs_dir}/*.egg'))
+ for egg_dist in cached_dists:
+ if _dist_matches_req(egg_dist, req):
+ return egg_dist
+ with tempfile.TemporaryDirectory() as tmpdir:
+ cmd = [
+ sys.executable,
+ '-m',
+ 'pip',
+ '--disable-pip-version-check',
+ 'wheel',
+ '--no-deps',
+ '-w',
+ tmpdir,
+ ]
+ if quiet:
+ cmd.append('--quiet')
+ if index_url is not None:
+ cmd.extend(('--index-url', index_url))
+ for link in find_links or []:
+ cmd.extend(('--find-links', link))
+ # If requirement is a PEP 508 direct URL, directly pass
+ # the URL to pip, as `req @ url` does not work on the
+ # command line.
+ cmd.append(req.url or str(req))
+ try:
+ subprocess.check_call(cmd)
+ except subprocess.CalledProcessError as e:
+ raise DistutilsError(str(e)) from e
+ wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
+ dist_location = os.path.join(eggs_dir, wheel.egg_name())
+ wheel.install_as_egg(dist_location)
+ return metadata.Distribution.at(dist_location + '/EGG-INFO')
+
+
+def strip_marker(req) -> packaging.requirements.Requirement:
+ """
+ Return a new requirement without the environment marker to avoid
+ calling pip with something like `babel; extra == "i18n"`, which
+ would always be ignored.
+ """
+ # create a copy to avoid mutating the input
+ req = packaging.requirements.Requirement(str(req))
+ req.marker = None
+ return req
+
+
+def _warn_wheel_not_available(dist):
+ try:
+ metadata.distribution('wheel')
+ except metadata.PackageNotFoundError:
+ dist.announce('WARNING: The wheel package is not available.', log.WARN)
+
+
+class _DeprecatedInstaller(SetuptoolsDeprecationWarning):
+ _SUMMARY = "setuptools.installer and fetch_build_eggs are deprecated."
+ _DETAILS = """
+ Requirements should be satisfied by a PEP 517 installer.
+ If you are using pip, you can try `pip install --use-pep517`.
+ """
+ _DUE_DATE = 2025, 10, 31
diff --git a/lib/python3.12/site-packages/setuptools/launch.py b/lib/python3.12/site-packages/setuptools/launch.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d162647d55777d7afa1bf1e44a6c200a3f82419
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/launch.py
@@ -0,0 +1,36 @@
+"""
+Launch the Python script on the command line after
+setuptools is bootstrapped via import.
+"""
+
+# Note that setuptools gets imported implicitly by the
+# invocation of this script using python -m setuptools.launch
+
+import sys
+import tokenize
+
+
+def run() -> None:
+ """
+ Run the script in sys.argv[1] as if it had
+ been invoked naturally.
+ """
+ __builtins__
+ script_name = sys.argv[1]
+ namespace = dict(
+ __file__=script_name,
+ __name__='__main__',
+ __doc__=None,
+ )
+ sys.argv[:] = sys.argv[1:]
+
+ open_ = getattr(tokenize, 'open', open)
+ with open_(script_name) as fid:
+ script = fid.read()
+ norm_script = script.replace('\\r\\n', '\\n')
+ code = compile(norm_script, script_name, 'exec')
+ exec(code, namespace)
+
+
+if __name__ == '__main__':
+ run()
diff --git a/lib/python3.12/site-packages/setuptools/logging.py b/lib/python3.12/site-packages/setuptools/logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..532da899f7dc02f9fea9a44c429086b98fe043d8
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/logging.py
@@ -0,0 +1,40 @@
+import inspect
+import logging
+import sys
+
+from . import monkey
+
+import distutils.log
+
+
+def _not_warning(record):
+ return record.levelno < logging.WARNING
+
+
+def configure() -> None:
+ """
+ Configure logging to emit warning and above to stderr
+ and everything else to stdout. This behavior is provided
+ for compatibility with distutils.log but may change in
+ the future.
+ """
+ err_handler = logging.StreamHandler()
+ err_handler.setLevel(logging.WARNING)
+ out_handler = logging.StreamHandler(sys.stdout)
+ out_handler.addFilter(_not_warning)
+ handlers = err_handler, out_handler
+ logging.basicConfig(
+ format="{message}", style='{', handlers=handlers, level=logging.DEBUG
+ )
+ if inspect.ismodule(distutils.dist.log):
+ monkey.patch_func(set_threshold, distutils.log, 'set_threshold')
+ # For some reason `distutils.log` module is getting cached in `distutils.dist`
+ # and then loaded again when patched,
+ # implying: id(distutils.log) != id(distutils.dist.log).
+ # Make sure the same module object is used everywhere:
+ distutils.dist.log = distutils.log
+
+
+def set_threshold(level: int) -> int:
+ logging.root.setLevel(level * 10)
+ return set_threshold.unpatched(level)
diff --git a/lib/python3.12/site-packages/setuptools/modified.py b/lib/python3.12/site-packages/setuptools/modified.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ba02fab68734e1e96fd50d7c4b6ffb1442717fb
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/modified.py
@@ -0,0 +1,18 @@
+try:
+ # Ensure a DistutilsError raised by these methods is the same as distutils.errors.DistutilsError
+ from distutils._modified import (
+ newer,
+ newer_group,
+ newer_pairwise,
+ newer_pairwise_group,
+ )
+except ImportError:
+ # fallback for SETUPTOOLS_USE_DISTUTILS=stdlib, because _modified never existed in stdlib
+ from ._distutils._modified import (
+ newer,
+ newer_group,
+ newer_pairwise,
+ newer_pairwise_group,
+ )
+
+__all__ = ['newer', 'newer_pairwise', 'newer_group', 'newer_pairwise_group']
diff --git a/lib/python3.12/site-packages/setuptools/monkey.py b/lib/python3.12/site-packages/setuptools/monkey.py
new file mode 100644
index 0000000000000000000000000000000000000000..24bb8180f960a2cd62f352a41241e107e9521750
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/monkey.py
@@ -0,0 +1,126 @@
+"""
+Monkey patching of distutils.
+"""
+
+from __future__ import annotations
+
+import inspect
+import platform
+import sys
+import types
+from typing import TypeVar, cast, overload
+
+import distutils.filelist
+
+_T = TypeVar("_T")
+_UnpatchT = TypeVar("_UnpatchT", type, types.FunctionType)
+
+
+__all__: list[str] = []
+"""
+Everything is private. Contact the project team
+if you think you need this functionality.
+"""
+
+
+def _get_mro(cls):
+ """
+ Returns the bases classes for cls sorted by the MRO.
+
+ Works around an issue on Jython where inspect.getmro will not return all
+ base classes if multiple classes share the same name. Instead, this
+ function will return a tuple containing the class itself, and the contents
+ of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
+ """
+ if platform.python_implementation() == "Jython":
+ return (cls,) + cls.__bases__
+ return inspect.getmro(cls)
+
+
+@overload
+def get_unpatched(item: _UnpatchT) -> _UnpatchT: ...
+@overload
+def get_unpatched(item: object) -> None: ...
+def get_unpatched(
+ item: type | types.FunctionType | object,
+) -> type | types.FunctionType | None:
+ if isinstance(item, type):
+ return get_unpatched_class(item)
+ if isinstance(item, types.FunctionType):
+ return get_unpatched_function(item)
+ return None
+
+
+def get_unpatched_class(cls: type[_T]) -> type[_T]:
+ """Protect against re-patching the distutils if reloaded
+
+ Also ensures that no other distutils extension monkeypatched the distutils
+ first.
+ """
+ external_bases = (
+ cast(type[_T], cls)
+ for cls in _get_mro(cls)
+ if not cls.__module__.startswith('setuptools')
+ )
+ base = next(external_bases)
+ if not base.__module__.startswith('distutils'):
+ msg = f"distutils has already been patched by {cls!r}"
+ raise AssertionError(msg)
+ return base
+
+
+def patch_all() -> None:
+ import setuptools
+
+ # we can't patch distutils.cmd, alas
+ distutils.core.Command = setuptools.Command # type: ignore[misc,assignment] # monkeypatching
+
+ _patch_distribution_metadata()
+
+ # Install Distribution throughout the distutils
+ for module in distutils.dist, distutils.core, distutils.cmd:
+ module.Distribution = setuptools.dist.Distribution
+
+ # Install the patched Extension
+ distutils.core.Extension = setuptools.extension.Extension # type: ignore[misc,assignment] # monkeypatching
+ distutils.extension.Extension = setuptools.extension.Extension # type: ignore[misc,assignment] # monkeypatching
+ if 'distutils.command.build_ext' in sys.modules:
+ sys.modules[
+ 'distutils.command.build_ext'
+ ].Extension = setuptools.extension.Extension
+
+
+def _patch_distribution_metadata():
+ from . import _core_metadata
+
+ """Patch write_pkg_file and read_pkg_file for higher metadata standards"""
+ for attr in (
+ 'write_pkg_info',
+ 'write_pkg_file',
+ 'read_pkg_file',
+ 'get_metadata_version',
+ 'get_fullname',
+ ):
+ new_val = getattr(_core_metadata, attr)
+ setattr(distutils.dist.DistributionMetadata, attr, new_val)
+
+
+def patch_func(replacement, target_mod, func_name) -> None:
+ """
+ Patch func_name in target_mod with replacement
+
+ Important - original must be resolved by name to avoid
+ patching an already patched function.
+ """
+ original = getattr(target_mod, func_name)
+
+ # set the 'unpatched' attribute on the replacement to
+ # point to the original.
+ vars(replacement).setdefault('unpatched', original)
+
+ # replace the function in the original module
+ setattr(target_mod, func_name, replacement)
+
+
+def get_unpatched_function(candidate):
+ return candidate.unpatched
diff --git a/lib/python3.12/site-packages/setuptools/msvc.py b/lib/python3.12/site-packages/setuptools/msvc.py
new file mode 100644
index 0000000000000000000000000000000000000000..f506c8222dc1994dfd155f3b2c2404c62fc50659
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/msvc.py
@@ -0,0 +1,1557 @@
+"""
+Environment info about Microsoft Compilers.
+
+>>> getfixture('windows_only')
+>>> ei = EnvironmentInfo('amd64')
+"""
+
+from __future__ import annotations
+
+import contextlib
+import itertools
+import json
+import os
+import os.path
+import platform
+from typing import TYPE_CHECKING, TypedDict, overload
+
+from more_itertools import unique_everseen
+
+from ._path import StrPath
+from .compat import py310
+
+import distutils.errors
+
+if TYPE_CHECKING:
+ from typing_extensions import LiteralString, NotRequired
+
+# https://github.com/python/mypy/issues/8166
+if not TYPE_CHECKING and platform.system() == 'Windows':
+ import winreg
+ from os import environ
+else:
+ # Mock winreg and environ so the module can be imported on this platform.
+
+ class winreg:
+ HKEY_USERS = None
+ HKEY_CURRENT_USER = None
+ HKEY_LOCAL_MACHINE = None
+ HKEY_CLASSES_ROOT = None
+
+ environ: dict[str, str] = dict()
+
+
+class PlatformInfo:
+ """
+ Current and Target Architectures information.
+
+ Parameters
+ ----------
+ arch: str
+ Target architecture.
+ """
+
+ current_cpu = environ.get('processor_architecture', '').lower()
+
+ def __init__(self, arch: str) -> None:
+ self.arch = arch.lower().replace('x64', 'amd64')
+
+ @property
+ def target_cpu(self) -> str:
+ """
+ Return Target CPU architecture.
+
+ Return
+ ------
+ str
+ Target CPU
+ """
+ return self.arch[self.arch.find('_') + 1 :]
+
+ def target_is_x86(self) -> bool:
+ """
+ Return True if target CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
+ return self.target_cpu == 'x86'
+
+ def current_is_x86(self) -> bool:
+ """
+ Return True if current CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
+ return self.current_cpu == 'x86'
+
+ def current_dir(self, hidex86=False, x64=False) -> str:
+ """
+ Current platform specific subfolder.
+
+ Parameters
+ ----------
+ hidex86: bool
+ return '' and not '\x86' if architecture is x86.
+ x64: bool
+ return '\x64' and not '\amd64' if architecture is amd64.
+
+ Return
+ ------
+ str
+ subfolder: '\target', or '' (see hidex86 parameter)
+ """
+ return (
+ ''
+ if (self.current_cpu == 'x86' and hidex86)
+ else r'\x64'
+ if (self.current_cpu == 'amd64' and x64)
+ else rf'\{self.current_cpu}'
+ )
+
+ def target_dir(self, hidex86=False, x64=False) -> str:
+ r"""
+ Target platform specific subfolder.
+
+ Parameters
+ ----------
+ hidex86: bool
+ return '' and not '\x86' if architecture is x86.
+ x64: bool
+ return '\x64' and not '\amd64' if architecture is amd64.
+
+ Return
+ ------
+ str
+ subfolder: '\current', or '' (see hidex86 parameter)
+ """
+ return (
+ ''
+ if (self.target_cpu == 'x86' and hidex86)
+ else r'\x64'
+ if (self.target_cpu == 'amd64' and x64)
+ else rf'\{self.target_cpu}'
+ )
+
+ def cross_dir(self, forcex86=False) -> str:
+ r"""
+ Cross platform specific subfolder.
+
+ Parameters
+ ----------
+ forcex86: bool
+ Use 'x86' as current architecture even if current architecture is
+ not x86.
+
+ Return
+ ------
+ str
+ subfolder: '' if target architecture is current architecture,
+ '\current_target' if not.
+ """
+ current = 'x86' if forcex86 else self.current_cpu
+ return (
+ ''
+ if self.target_cpu == current
+ else self.target_dir().replace('\\', f'\\{current}_')
+ )
+
+
+class RegistryInfo:
+ """
+ Microsoft Visual Studio related registry information.
+
+ Parameters
+ ----------
+ platform_info: PlatformInfo
+ "PlatformInfo" instance.
+ """
+
+ HKEYS = (
+ winreg.HKEY_USERS,
+ winreg.HKEY_CURRENT_USER,
+ winreg.HKEY_LOCAL_MACHINE,
+ winreg.HKEY_CLASSES_ROOT,
+ )
+
+ def __init__(self, platform_info: PlatformInfo) -> None:
+ self.pi = platform_info
+
+ @property
+ def visualstudio(self) -> LiteralString:
+ """
+ Microsoft Visual Studio root registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return 'VisualStudio'
+
+ @property
+ def sxs(self) -> LiteralString:
+ """
+ Microsoft Visual Studio SxS registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return os.path.join(self.visualstudio, 'SxS')
+
+ @property
+ def vc(self) -> LiteralString:
+ """
+ Microsoft Visual C++ VC7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return os.path.join(self.sxs, 'VC7')
+
+ @property
+ def vs(self) -> LiteralString:
+ """
+ Microsoft Visual Studio VS7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return os.path.join(self.sxs, 'VS7')
+
+ @property
+ def vc_for_python(self) -> LiteralString:
+ """
+ Microsoft Visual C++ for Python registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return r'DevDiv\VCForPython'
+
+ @property
+ def microsoft_sdk(self) -> LiteralString:
+ """
+ Microsoft SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return 'Microsoft SDKs'
+
+ @property
+ def windows_sdk(self) -> LiteralString:
+ """
+ Microsoft Windows/Platform SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return os.path.join(self.microsoft_sdk, 'Windows')
+
+ @property
+ def netfx_sdk(self) -> LiteralString:
+ """
+ Microsoft .NET Framework SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return os.path.join(self.microsoft_sdk, 'NETFXSDK')
+
+ @property
+ def windows_kits_roots(self) -> LiteralString:
+ """
+ Microsoft Windows Kits Roots registry key.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ return r'Windows Kits\Installed Roots'
+
+ @overload
+ def microsoft(self, key: LiteralString, x86: bool = False) -> LiteralString: ...
+ @overload
+ def microsoft(self, key: str, x86: bool = False) -> str: ... # type: ignore[misc]
+ def microsoft(self, key: str, x86: bool = False) -> str:
+ """
+ Return key in Microsoft software registry.
+
+ Parameters
+ ----------
+ key: str
+ Registry key path where look.
+ x86: bool
+ Force x86 software registry.
+
+ Return
+ ------
+ str
+ Registry key
+ """
+ node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
+ return os.path.join('Software', node64, 'Microsoft', key)
+
+ def lookup(self, key: str, name: str) -> str | None:
+ """
+ Look for values in registry in Microsoft software registry.
+
+ Parameters
+ ----------
+ key: str
+ Registry key path where look.
+ name: str
+ Value name to find.
+
+ Return
+ ------
+ str | None
+ value
+ """
+ key_read = winreg.KEY_READ
+ openkey = winreg.OpenKey
+ closekey = winreg.CloseKey
+ ms = self.microsoft
+ for hkey in self.HKEYS:
+ bkey = None
+ try:
+ bkey = openkey(hkey, ms(key), 0, key_read)
+ except OSError:
+ if not self.pi.current_is_x86():
+ try:
+ bkey = openkey(hkey, ms(key, True), 0, key_read)
+ except OSError:
+ continue
+ else:
+ continue
+ try:
+ return winreg.QueryValueEx(bkey, name)[0]
+ except OSError:
+ pass
+ finally:
+ if bkey:
+ closekey(bkey)
+ return None
+
+
+class SystemInfo:
+ """
+ Microsoft Windows and Visual Studio related system information.
+
+ Parameters
+ ----------
+ registry_info: RegistryInfo
+ "RegistryInfo" instance.
+ vc_ver: float
+ Required Microsoft Visual C++ version.
+ """
+
+ # Variables and properties in this class use originals CamelCase variables
+ # names from Microsoft source files for more easy comparison.
+ WinDir = environ.get('WinDir', '')
+ ProgramFiles = environ.get('ProgramFiles', '')
+ ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
+
+ def __init__(
+ self, registry_info: RegistryInfo, vc_ver: float | None = None
+ ) -> None:
+ self.ri = registry_info
+ self.pi = self.ri.pi
+
+ self.known_vs_paths = self.find_programdata_vs_vers()
+
+ # Except for VS15+, VC version is aligned with VS version
+ self.vs_ver = self.vc_ver = vc_ver or self._find_latest_available_vs_ver()
+
+ def _find_latest_available_vs_ver(self):
+ """
+ Find the latest VC version
+
+ Return
+ ------
+ float
+ version
+ """
+ reg_vc_vers = self.find_reg_vs_vers()
+
+ if not (reg_vc_vers or self.known_vs_paths):
+ raise distutils.errors.DistutilsPlatformError(
+ 'No Microsoft Visual C++ version found'
+ )
+
+ vc_vers = set(reg_vc_vers)
+ vc_vers.update(self.known_vs_paths)
+ return max(vc_vers)
+
+ def find_reg_vs_vers(self) -> list[float]:
+ """
+ Find Microsoft Visual Studio versions available in registry.
+
+ Return
+ ------
+ list of float
+ Versions
+ """
+ ms = self.ri.microsoft
+ vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
+ vs_vers = []
+ for hkey, key in itertools.product(self.ri.HKEYS, vckeys):
+ try:
+ bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
+ except OSError:
+ continue
+ with bkey:
+ subkeys, values, _ = winreg.QueryInfoKey(bkey)
+ for i in range(values):
+ with contextlib.suppress(ValueError):
+ ver = float(winreg.EnumValue(bkey, i)[0])
+ if ver not in vs_vers:
+ vs_vers.append(ver)
+ for i in range(subkeys):
+ with contextlib.suppress(ValueError):
+ ver = float(winreg.EnumKey(bkey, i))
+ if ver not in vs_vers:
+ vs_vers.append(ver)
+ return sorted(vs_vers)
+
+ def find_programdata_vs_vers(self) -> dict[float, str]:
+ r"""
+ Find Visual studio 2017+ versions from information in
+ "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
+
+ Return
+ ------
+ dict
+ float version as key, path as value.
+ """
+ vs_versions: dict[float, str] = {}
+ instances_dir = r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
+
+ try:
+ hashed_names = os.listdir(instances_dir)
+
+ except OSError:
+ # Directory not exists with all Visual Studio versions
+ return vs_versions
+
+ for name in hashed_names:
+ try:
+ # Get VS installation path from "state.json" file
+ state_path = os.path.join(instances_dir, name, 'state.json')
+ with open(state_path, 'rt', encoding='utf-8') as state_file:
+ state = json.load(state_file)
+ vs_path = state['installationPath']
+
+ # Raises OSError if this VS installation does not contain VC
+ os.listdir(os.path.join(vs_path, r'VC\Tools\MSVC'))
+
+ # Store version and path
+ vs_versions[self._as_float_version(state['installationVersion'])] = (
+ vs_path
+ )
+
+ except (OSError, KeyError):
+ # Skip if "state.json" file is missing or bad format
+ continue
+
+ return vs_versions
+
+ @staticmethod
+ def _as_float_version(version):
+ """
+ Return a string version as a simplified float version (major.minor)
+
+ Parameters
+ ----------
+ version: str
+ Version.
+
+ Return
+ ------
+ float
+ version
+ """
+ return float('.'.join(version.split('.')[:2]))
+
+ @property
+ def VSInstallDir(self) -> str:
+ """
+ Microsoft Visual Studio directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ # Default path
+ default = os.path.join(
+ self.ProgramFilesx86, f'Microsoft Visual Studio {self.vs_ver:0.1f}'
+ )
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vs, f'{self.vs_ver:0.1f}') or default
+
+ @property
+ def VCInstallDir(self) -> str:
+ """
+ Microsoft Visual C++ directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ path = self._guess_vc() or self._guess_vc_legacy()
+
+ if not os.path.isdir(path):
+ msg = 'Microsoft Visual C++ directory not found'
+ raise distutils.errors.DistutilsPlatformError(msg)
+
+ return path
+
+ def _guess_vc(self):
+ """
+ Locate Visual C++ for VS2017+.
+
+ Return
+ ------
+ str
+ path
+ """
+ if self.vs_ver <= 14.0:
+ return ''
+
+ try:
+ # First search in known VS paths
+ vs_dir = self.known_vs_paths[self.vs_ver]
+ except KeyError:
+ # Else, search with path from registry
+ vs_dir = self.VSInstallDir
+
+ guess_vc = os.path.join(vs_dir, r'VC\Tools\MSVC')
+
+ # Subdir with VC exact version as name
+ try:
+ # Update the VC version with real one instead of VS version
+ vc_ver = os.listdir(guess_vc)[-1]
+ self.vc_ver = self._as_float_version(vc_ver)
+ return os.path.join(guess_vc, vc_ver)
+ except (OSError, IndexError):
+ return ''
+
+ def _guess_vc_legacy(self):
+ """
+ Locate Visual C++ for versions prior to 2017.
+
+ Return
+ ------
+ str
+ path
+ """
+ default = os.path.join(
+ self.ProgramFilesx86,
+ rf'Microsoft Visual Studio {self.vs_ver:0.1f}\VC',
+ )
+
+ # Try to get "VC++ for Python" path from registry as default path
+ reg_path = os.path.join(self.ri.vc_for_python, f'{self.vs_ver:0.1f}')
+ python_vc = self.ri.lookup(reg_path, 'installdir')
+ default_vc = os.path.join(python_vc, 'VC') if python_vc else default
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vc, f'{self.vs_ver:0.1f}') or default_vc
+
+ @property
+ def WindowsSdkVersion(self) -> tuple[LiteralString, ...]:
+ """
+ Microsoft Windows SDK versions for specified MSVC++ version.
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ if self.vs_ver <= 9.0:
+ return '7.0', '6.1', '6.0a'
+ elif self.vs_ver == 10.0:
+ return '7.1', '7.0a'
+ elif self.vs_ver == 11.0:
+ return '8.0', '8.0a'
+ elif self.vs_ver == 12.0:
+ return '8.1', '8.1a'
+ elif self.vs_ver >= 14.0:
+ return '10.0', '8.1'
+ return ()
+
+ @property
+ def WindowsSdkLastVersion(self) -> str:
+ """
+ Microsoft Windows SDK last version.
+
+ Return
+ ------
+ str
+ version
+ """
+ return self._use_last_dir_name(os.path.join(self.WindowsSdkDir, 'lib'))
+
+ @property
+ def WindowsSdkDir(self) -> str: # noqa: C901 # is too complex (12) # FIXME
+ """
+ Microsoft Windows SDK directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ sdkdir: str | None = ''
+ for ver in self.WindowsSdkVersion:
+ # Try to get it from registry
+ loc = os.path.join(self.ri.windows_sdk, f'v{ver}')
+ sdkdir = self.ri.lookup(loc, 'installationfolder')
+ if sdkdir:
+ break
+ if not sdkdir or not os.path.isdir(sdkdir):
+ # Try to get "VC++ for Python" version from registry
+ path = os.path.join(self.ri.vc_for_python, f'{self.vc_ver:0.1f}')
+ install_base = self.ri.lookup(path, 'installdir')
+ if install_base:
+ sdkdir = os.path.join(install_base, 'WinSDK')
+ if not sdkdir or not os.path.isdir(sdkdir):
+ # If fail, use default new path
+ for ver in self.WindowsSdkVersion:
+ intver = ver[: ver.rfind('.')]
+ path = rf'Microsoft SDKs\Windows Kits\{intver}'
+ d = os.path.join(self.ProgramFiles, path)
+ if os.path.isdir(d):
+ sdkdir = d
+ if not sdkdir or not os.path.isdir(sdkdir):
+ # If fail, use default old path
+ for ver in self.WindowsSdkVersion:
+ path = rf'Microsoft SDKs\Windows\v{ver}'
+ d = os.path.join(self.ProgramFiles, path)
+ if os.path.isdir(d):
+ sdkdir = d
+ if not sdkdir:
+ # If fail, use Platform SDK
+ sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
+ return sdkdir
+
+ @property
+ def WindowsSDKExecutablePath(self) -> str | None:
+ """
+ Microsoft Windows SDK executable directory.
+
+ Return
+ ------
+ str | None
+ path
+ """
+ # Find WinSDK NetFx Tools registry dir name
+ if self.vs_ver <= 11.0:
+ netfxver = 35
+ arch = ''
+ else:
+ netfxver = 40
+ hidex86 = True if self.vs_ver <= 12.0 else False
+ arch = self.pi.current_dir(x64=True, hidex86=hidex86).replace('\\', '-')
+ fx = f'WinSDK-NetFx{netfxver}Tools{arch}'
+
+ # list all possibles registry paths
+ regpaths = []
+ if self.vs_ver >= 14.0:
+ for ver in self.NetFxSdkVersion:
+ regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
+
+ for ver in self.WindowsSdkVersion:
+ regpaths += [os.path.join(self.ri.windows_sdk, f'v{ver}A', fx)]
+
+ # Return installation folder from the more recent path
+ for path in regpaths:
+ execpath = self.ri.lookup(path, 'installationfolder')
+ if execpath:
+ return execpath
+
+ return None
+
+ @property
+ def FSharpInstallDir(self) -> str:
+ """
+ Microsoft Visual F# directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ path = os.path.join(self.ri.visualstudio, rf'{self.vs_ver:0.1f}\Setup\F#')
+ return self.ri.lookup(path, 'productdir') or ''
+
+ @property
+ def UniversalCRTSdkDir(self) -> str | None:
+ """
+ Microsoft Universal CRT SDK directory.
+
+ Return
+ ------
+ str | None
+ path
+ """
+ # Set Kit Roots versions for specified MSVC++ version
+ vers = ('10', '81') if self.vs_ver >= 14.0 else ()
+
+ # Find path of the more recent Kit
+ for ver in vers:
+ sdkdir = self.ri.lookup(self.ri.windows_kits_roots, f'kitsroot{ver}')
+ if sdkdir:
+ return sdkdir
+
+ return None
+
+ @property
+ def UniversalCRTSdkLastVersion(self) -> str:
+ """
+ Microsoft Universal C Runtime SDK last version.
+
+ Return
+ ------
+ str
+ version
+ """
+ try:
+ return self._use_last_dir_name(os.path.join(self.UniversalCRTSdkDir, 'lib')) # type: ignore[arg-type] # Expected TypeError
+ except TypeError as ex:
+ py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
+ raise
+
+ @property
+ def NetFxSdkVersion(self) -> tuple[LiteralString, ...]:
+ """
+ Microsoft .NET Framework SDK versions.
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ # Set FxSdk versions for specified VS version
+ return (
+ ('4.7.2', '4.7.1', '4.7', '4.6.2', '4.6.1', '4.6', '4.5.2', '4.5.1', '4.5')
+ if self.vs_ver >= 14.0
+ else ()
+ )
+
+ @property
+ def NetFxSdkDir(self) -> str | None:
+ """
+ Microsoft .NET Framework SDK directory.
+
+ Return
+ ------
+ str | None
+ path
+ """
+ sdkdir: str | None = ''
+ for ver in self.NetFxSdkVersion:
+ loc = os.path.join(self.ri.netfx_sdk, ver)
+ sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
+ if sdkdir:
+ break
+ return sdkdir
+
+ @property
+ def FrameworkDir32(self) -> str:
+ """
+ Microsoft .NET Framework 32bit directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ # Default path
+ guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
+
+ @property
+ def FrameworkDir64(self) -> str:
+ """
+ Microsoft .NET Framework 64bit directory.
+
+ Return
+ ------
+ str
+ path
+ """
+ # Default path
+ guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
+
+ @property
+ def FrameworkVersion32(self) -> tuple[str, ...]:
+ """
+ Microsoft .NET Framework 32bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ return self._find_dot_net_versions(32)
+
+ @property
+ def FrameworkVersion64(self) -> tuple[str, ...]:
+ """
+ Microsoft .NET Framework 64bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ return self._find_dot_net_versions(64)
+
+ def _find_dot_net_versions(self, bits) -> tuple[str, ...]:
+ """
+ Find Microsoft .NET Framework versions.
+
+ Parameters
+ ----------
+ bits: int
+ Platform number of bits: 32 or 64.
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ # Find actual .NET version in registry
+ reg_ver = self.ri.lookup(self.ri.vc, f'frameworkver{bits}')
+ dot_net_dir = getattr(self, f'FrameworkDir{bits}')
+ ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
+
+ # Set .NET versions for specified MSVC++ version
+ if self.vs_ver >= 12.0:
+ return ver, 'v4.0'
+ elif self.vs_ver >= 10.0:
+ return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
+ elif self.vs_ver == 9.0:
+ return 'v3.5', 'v2.0.50727'
+ elif self.vs_ver == 8.0:
+ return 'v3.0', 'v2.0.50727'
+ return ()
+
+ @staticmethod
+ def _use_last_dir_name(path: StrPath, prefix: str = '') -> str:
+ """
+ Return name of the last dir in path or '' if no dir found.
+
+ Parameters
+ ----------
+ path: StrPath
+ Use dirs in this path
+ prefix: str
+ Use only dirs starting by this prefix
+
+ Return
+ ------
+ str
+ name
+ """
+ matching_dirs = (
+ dir_name
+ for dir_name in reversed(os.listdir(path))
+ if os.path.isdir(os.path.join(path, dir_name))
+ and dir_name.startswith(prefix)
+ )
+ return next(matching_dirs, '')
+
+
+class _EnvironmentDict(TypedDict):
+ include: str
+ lib: str
+ libpath: str
+ path: str
+ py_vcruntime_redist: NotRequired[str | None]
+
+
+class EnvironmentInfo:
+ """
+ Return environment variables for specified Microsoft Visual C++ version
+ and platform : Lib, Include, Path and libpath.
+
+ This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
+
+ Script created by analysing Microsoft environment configuration files like
+ "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
+
+ Parameters
+ ----------
+ arch: str
+ Target architecture.
+ vc_ver: float
+ Required Microsoft Visual C++ version. If not set, autodetect the last
+ version.
+ vc_min_ver: float
+ Minimum Microsoft Visual C++ version.
+ """
+
+ # Variables and properties in this class use originals CamelCase variables
+ # names from Microsoft source files for more easy comparison.
+
+ def __init__(self, arch, vc_ver=None, vc_min_ver=0) -> None:
+ self.pi = PlatformInfo(arch)
+ self.ri = RegistryInfo(self.pi)
+ self.si = SystemInfo(self.ri, vc_ver)
+
+ if self.vc_ver < vc_min_ver:
+ err = 'No suitable Microsoft Visual C++ version found'
+ raise distutils.errors.DistutilsPlatformError(err)
+
+ @property
+ def vs_ver(self):
+ """
+ Microsoft Visual Studio.
+
+ Return
+ ------
+ float
+ version
+ """
+ return self.si.vs_ver
+
+ @property
+ def vc_ver(self):
+ """
+ Microsoft Visual C++ version.
+
+ Return
+ ------
+ float
+ version
+ """
+ return self.si.vc_ver
+
+ @property
+ def VSTools(self):
+ """
+ Microsoft Visual Studio Tools.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ paths = [r'Common7\IDE', r'Common7\Tools']
+
+ if self.vs_ver >= 14.0:
+ arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
+ paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
+ paths += [r'Team Tools\Performance Tools']
+ paths += [rf'Team Tools\Performance Tools{arch_subdir}']
+
+ return [os.path.join(self.si.VSInstallDir, path) for path in paths]
+
+ @property
+ def VCIncludes(self):
+ """
+ Microsoft Visual C++ & Microsoft Foundation Class Includes.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ return [
+ os.path.join(self.si.VCInstallDir, 'Include'),
+ os.path.join(self.si.VCInstallDir, r'ATLMFC\Include'),
+ ]
+
+ @property
+ def VCLibraries(self):
+ """
+ Microsoft Visual C++ & Microsoft Foundation Class Libraries.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver >= 15.0:
+ arch_subdir = self.pi.target_dir(x64=True)
+ else:
+ arch_subdir = self.pi.target_dir(hidex86=True)
+ paths = [f'Lib{arch_subdir}', rf'ATLMFC\Lib{arch_subdir}']
+
+ if self.vs_ver >= 14.0:
+ paths += [rf'Lib\store{arch_subdir}']
+
+ return [os.path.join(self.si.VCInstallDir, path) for path in paths]
+
+ @property
+ def VCStoreRefs(self):
+ """
+ Microsoft Visual C++ store references Libraries.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 14.0:
+ return []
+ return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
+
+ @property
+ def VCTools(self):
+ """
+ Microsoft Visual C++ Tools.
+
+ Return
+ ------
+ list of str
+ paths
+
+ When host CPU is ARM, the tools should be found for ARM.
+
+ >>> getfixture('windows_only')
+ >>> mp = getfixture('monkeypatch')
+ >>> mp.setattr(PlatformInfo, 'current_cpu', 'arm64')
+ >>> ei = EnvironmentInfo(arch='irrelevant')
+ >>> paths = ei.VCTools
+ >>> any('HostARM64' in path for path in paths)
+ True
+ """
+ si = self.si
+ tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
+
+ forcex86 = True if self.vs_ver <= 10.0 else False
+ arch_subdir = self.pi.cross_dir(forcex86)
+ if arch_subdir:
+ tools += [os.path.join(si.VCInstallDir, f'Bin{arch_subdir}')]
+
+ if self.vs_ver == 14.0:
+ path = f'Bin{self.pi.current_dir(hidex86=True)}'
+ tools += [os.path.join(si.VCInstallDir, path)]
+
+ elif self.vs_ver >= 15.0:
+ host_id = self.pi.current_cpu.replace('amd64', 'x64').upper()
+ host_dir = os.path.join('bin', f'Host{host_id}%s')
+ tools += [
+ os.path.join(si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))
+ ]
+
+ if self.pi.current_cpu != self.pi.target_cpu:
+ tools += [
+ os.path.join(
+ si.VCInstallDir, host_dir % self.pi.current_dir(x64=True)
+ )
+ ]
+
+ else:
+ tools += [os.path.join(si.VCInstallDir, 'Bin')]
+
+ return tools
+
+ @property
+ def OSLibraries(self):
+ """
+ Microsoft Windows SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver <= 10.0:
+ arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
+ return [os.path.join(self.si.WindowsSdkDir, f'Lib{arch_subdir}')]
+
+ else:
+ arch_subdir = self.pi.target_dir(x64=True)
+ lib = os.path.join(self.si.WindowsSdkDir, 'lib')
+ libver = self._sdk_subdir
+ return [os.path.join(lib, f'{libver}um{arch_subdir}')]
+
+ @property
+ def OSIncludes(self):
+ """
+ Microsoft Windows SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ include = os.path.join(self.si.WindowsSdkDir, 'include')
+
+ if self.vs_ver <= 10.0:
+ return [include, os.path.join(include, 'gl')]
+
+ else:
+ if self.vs_ver >= 14.0:
+ sdkver = self._sdk_subdir
+ else:
+ sdkver = ''
+ return [
+ os.path.join(include, f'{sdkver}shared'),
+ os.path.join(include, f'{sdkver}um'),
+ os.path.join(include, f'{sdkver}winrt'),
+ ]
+
+ @property
+ def OSLibpath(self):
+ """
+ Microsoft Windows SDK Libraries Paths.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ ref = os.path.join(self.si.WindowsSdkDir, 'References')
+ libpath = []
+
+ if self.vs_ver <= 9.0:
+ libpath += self.OSLibraries
+
+ if self.vs_ver >= 11.0:
+ libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
+
+ if self.vs_ver >= 14.0:
+ libpath += [
+ ref,
+ os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
+ os.path.join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
+ os.path.join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
+ os.path.join(
+ ref, 'Windows.Networking.Connectivity.WwanContract', '1.0.0.0'
+ ),
+ os.path.join(
+ self.si.WindowsSdkDir,
+ 'ExtensionSDKs',
+ 'Microsoft.VCLibs',
+ f'{self.vs_ver:0.1f}',
+ 'References',
+ 'CommonConfiguration',
+ 'neutral',
+ ),
+ ]
+ return libpath
+
+ @property
+ def SdkTools(self):
+ """
+ Microsoft Windows SDK Tools.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ return list(self._sdk_tools())
+
+ def _sdk_tools(self):
+ """
+ Microsoft Windows SDK Tools paths generator.
+
+ Return
+ ------
+ generator of str
+ paths
+ """
+ if self.vs_ver < 15.0:
+ bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
+ yield os.path.join(self.si.WindowsSdkDir, bin_dir)
+
+ if not self.pi.current_is_x86():
+ arch_subdir = self.pi.current_dir(x64=True)
+ path = f'Bin{arch_subdir}'
+ yield os.path.join(self.si.WindowsSdkDir, path)
+
+ if self.vs_ver in (10.0, 11.0):
+ if self.pi.target_is_x86():
+ arch_subdir = ''
+ else:
+ arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
+ path = rf'Bin\NETFX 4.0 Tools{arch_subdir}'
+ yield os.path.join(self.si.WindowsSdkDir, path)
+
+ elif self.vs_ver >= 15.0:
+ path = os.path.join(self.si.WindowsSdkDir, 'Bin')
+ arch_subdir = self.pi.current_dir(x64=True)
+ sdkver = self.si.WindowsSdkLastVersion
+ yield os.path.join(path, f'{sdkver}{arch_subdir}')
+
+ if self.si.WindowsSDKExecutablePath:
+ yield self.si.WindowsSDKExecutablePath
+
+ @property
+ def _sdk_subdir(self) -> str:
+ """
+ Microsoft Windows SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
+ """
+ ucrtver = self.si.WindowsSdkLastVersion
+ return (f'{ucrtver}\\') if ucrtver else ''
+
+ @property
+ def SdkSetup(self):
+ """
+ Microsoft Windows SDK Setup.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver > 9.0:
+ return []
+
+ return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
+
+ @property
+ def FxTools(self):
+ """
+ Microsoft .NET Framework Tools.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ pi = self.pi
+ si = self.si
+
+ if self.vs_ver <= 10.0:
+ include32 = True
+ include64 = not pi.target_is_x86() and not pi.current_is_x86()
+ else:
+ include32 = pi.target_is_x86() or pi.current_is_x86()
+ include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64'
+
+ tools = []
+ if include32:
+ tools += [
+ os.path.join(si.FrameworkDir32, ver) for ver in si.FrameworkVersion32
+ ]
+ if include64:
+ tools += [
+ os.path.join(si.FrameworkDir64, ver) for ver in si.FrameworkVersion64
+ ]
+ return tools
+
+ @property
+ def NetFxSDKLibraries(self):
+ """
+ Microsoft .Net Framework SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
+ return []
+
+ arch_subdir = self.pi.target_dir(x64=True)
+ return [os.path.join(self.si.NetFxSdkDir, rf'lib\um{arch_subdir}')]
+
+ @property
+ def NetFxSDKIncludes(self):
+ """
+ Microsoft .Net Framework SDK Includes.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
+ return []
+
+ return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
+
+ @property
+ def VsTDb(self):
+ """
+ Microsoft Visual Studio Team System Database.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
+
+ @property
+ def MSBuild(self):
+ """
+ Microsoft Build Engine.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 12.0:
+ return []
+ elif self.vs_ver < 15.0:
+ base_path = self.si.ProgramFilesx86
+ arch_subdir = self.pi.current_dir(hidex86=True)
+ else:
+ base_path = self.si.VSInstallDir
+ arch_subdir = ''
+
+ path = rf'MSBuild\{self.vs_ver:0.1f}\bin{arch_subdir}'
+ build = [os.path.join(base_path, path)]
+
+ if self.vs_ver >= 15.0:
+ # Add Roslyn C# & Visual Basic Compiler
+ build += [os.path.join(base_path, path, 'Roslyn')]
+
+ return build
+
+ @property
+ def HTMLHelpWorkshop(self):
+ """
+ Microsoft HTML Help Workshop.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 11.0:
+ return []
+
+ return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
+
+ @property
+ def UCRTLibraries(self) -> list[str]:
+ """
+ Microsoft Universal C Runtime SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 14.0:
+ return []
+
+ arch_subdir = self.pi.target_dir(x64=True)
+ try:
+ lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') # type: ignore[arg-type] # Expected TypeError
+ except TypeError as ex:
+ py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
+ raise
+ ucrtver = self._ucrt_subdir
+ return [os.path.join(lib, f'{ucrtver}ucrt{arch_subdir}')]
+
+ @property
+ def UCRTIncludes(self) -> list[str]:
+ """
+ Microsoft Universal C Runtime SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if self.vs_ver < 14.0:
+ return []
+
+ try:
+ include = os.path.join(self.si.UniversalCRTSdkDir, 'include') # type: ignore[arg-type] # Expected TypeError
+ except TypeError as ex:
+ py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
+ raise
+ return [os.path.join(include, f'{self._ucrt_subdir}ucrt')]
+
+ @property
+ def _ucrt_subdir(self) -> str:
+ """
+ Microsoft Universal C Runtime SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
+ """
+ ucrtver = self.si.UniversalCRTSdkLastVersion
+ return (f'{ucrtver}\\') if ucrtver else ''
+
+ @property
+ def FSharp(self):
+ """
+ Microsoft Visual F#.
+
+ Return
+ ------
+ list of str
+ paths
+ """
+ if 11.0 > self.vs_ver > 12.0:
+ return []
+
+ return [self.si.FSharpInstallDir]
+
+ @property
+ def VCRuntimeRedist(self) -> str | None:
+ """
+ Microsoft Visual C++ runtime redistributable dll.
+
+ Returns the first suitable path found or None.
+ """
+ vcruntime = f'vcruntime{self.vc_ver}0.dll'
+ arch_subdir = self.pi.target_dir(x64=True).strip('\\')
+
+ # Installation prefixes candidates
+ prefixes = []
+ tools_path = self.si.VCInstallDir
+ redist_path = os.path.dirname(tools_path.replace(r'\Tools', r'\Redist'))
+ if os.path.isdir(redist_path):
+ # Redist version may not be exactly the same as tools
+ redist_path = os.path.join(redist_path, os.listdir(redist_path)[-1])
+ prefixes += [redist_path, os.path.join(redist_path, 'onecore')]
+
+ prefixes += [os.path.join(tools_path, 'redist')] # VS14 legacy path
+
+ # CRT directory
+ crt_dirs = (
+ f'Microsoft.VC{self.vc_ver * 10}.CRT',
+ # Sometime store in directory with VS version instead of VC
+ f'Microsoft.VC{int(self.vs_ver) * 10}.CRT',
+ )
+
+ # vcruntime path
+ candidate_paths = (
+ os.path.join(prefix, arch_subdir, crt_dir, vcruntime)
+ for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs)
+ )
+ return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682
+
+ def return_env(self, exists: bool = True) -> _EnvironmentDict:
+ """
+ Return environment dict.
+
+ Parameters
+ ----------
+ exists: bool
+ It True, only return existing paths.
+
+ Return
+ ------
+ dict
+ environment
+ """
+ env = _EnvironmentDict(
+ include=self._build_paths(
+ 'include',
+ [
+ self.VCIncludes,
+ self.OSIncludes,
+ self.UCRTIncludes,
+ self.NetFxSDKIncludes,
+ ],
+ exists,
+ ),
+ lib=self._build_paths(
+ 'lib',
+ [
+ self.VCLibraries,
+ self.OSLibraries,
+ self.FxTools,
+ self.UCRTLibraries,
+ self.NetFxSDKLibraries,
+ ],
+ exists,
+ ),
+ libpath=self._build_paths(
+ 'libpath',
+ [self.VCLibraries, self.FxTools, self.VCStoreRefs, self.OSLibpath],
+ exists,
+ ),
+ path=self._build_paths(
+ 'path',
+ [
+ self.VCTools,
+ self.VSTools,
+ self.VsTDb,
+ self.SdkTools,
+ self.SdkSetup,
+ self.FxTools,
+ self.MSBuild,
+ self.HTMLHelpWorkshop,
+ self.FSharp,
+ ],
+ exists,
+ ),
+ )
+ if self.vs_ver >= 14 and self.VCRuntimeRedist:
+ env['py_vcruntime_redist'] = self.VCRuntimeRedist
+ return env
+
+ def _build_paths(self, name, spec_path_lists, exists):
+ """
+ Given an environment variable name and specified paths,
+ return a pathsep-separated string of paths containing
+ unique, extant, directories from those paths and from
+ the environment variable. Raise an error if no paths
+ are resolved.
+
+ Parameters
+ ----------
+ name: str
+ Environment variable name
+ spec_path_lists: list of str
+ Paths
+ exists: bool
+ It True, only return existing paths.
+
+ Return
+ ------
+ str
+ Pathsep-separated paths
+ """
+ # flatten spec_path_lists
+ spec_paths = itertools.chain.from_iterable(spec_path_lists)
+ env_paths = environ.get(name, '').split(os.pathsep)
+ paths = itertools.chain(spec_paths, env_paths)
+ extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
+ if not extant_paths:
+ msg = f"{name.upper()} environment variable is empty"
+ raise distutils.errors.DistutilsPlatformError(msg)
+ unique_paths = unique_everseen(extant_paths)
+ return os.pathsep.join(unique_paths)
diff --git a/lib/python3.12/site-packages/setuptools/namespaces.py b/lib/python3.12/site-packages/setuptools/namespaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..85ea2ebd654c480b8c19d1715b3772c4bcfd812e
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/namespaces.py
@@ -0,0 +1,106 @@
+import itertools
+import os
+
+from .compat import py312
+
+from distutils import log
+
+flatten = itertools.chain.from_iterable
+
+
+class Installer:
+ nspkg_ext = '-nspkg.pth'
+
+ def install_namespaces(self) -> None:
+ nsp = self._get_all_ns_packages()
+ if not nsp:
+ return
+ filename = self._get_nspkg_file()
+ self.outputs.append(filename)
+ log.info("Installing %s", filename)
+ lines = map(self._gen_nspkg_line, nsp)
+
+ if self.dry_run:
+ # always generate the lines, even in dry run
+ list(lines)
+ return
+
+ with open(filename, 'wt', encoding=py312.PTH_ENCODING) as f:
+ # Python<3.13 requires encoding="locale" instead of "utf-8"
+ # See: python/cpython#77102
+ f.writelines(lines)
+
+ def uninstall_namespaces(self) -> None:
+ filename = self._get_nspkg_file()
+ if not os.path.exists(filename):
+ return
+ log.info("Removing %s", filename)
+ os.remove(filename)
+
+ def _get_nspkg_file(self):
+ filename, _ = os.path.splitext(self._get_target())
+ return filename + self.nspkg_ext
+
+ def _get_target(self):
+ return self.target
+
+ _nspkg_tmpl = (
+ "import sys, types, os",
+ "p = os.path.join(%(root)s, *%(pth)r)",
+ "importlib = __import__('importlib.util')",
+ "__import__('importlib.machinery')",
+ (
+ "m = "
+ "sys.modules.setdefault(%(pkg)r, "
+ "importlib.util.module_from_spec("
+ "importlib.machinery.PathFinder.find_spec(%(pkg)r, "
+ "[os.path.dirname(p)])))"
+ ),
+ ("m = m or sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))"),
+ "mp = (m or []) and m.__dict__.setdefault('__path__',[])",
+ "(p not in mp) and mp.append(p)",
+ )
+ "lines for the namespace installer"
+
+ _nspkg_tmpl_multi = ('m and setattr(sys.modules[%(parent)r], %(child)r, m)',)
+ "additional line(s) when a parent package is indicated"
+
+ def _get_root(self):
+ return "sys._getframe(1).f_locals['sitedir']"
+
+ def _gen_nspkg_line(self, pkg):
+ pth = tuple(pkg.split('.'))
+ root = self._get_root()
+ tmpl_lines = self._nspkg_tmpl
+ parent, sep, child = pkg.rpartition('.')
+ if parent:
+ tmpl_lines += self._nspkg_tmpl_multi
+ return ';'.join(tmpl_lines) % locals() + '\n'
+
+ def _get_all_ns_packages(self):
+ """Return sorted list of all package namespaces"""
+ pkgs = self.distribution.namespace_packages or []
+ return sorted(set(flatten(map(self._pkg_names, pkgs))))
+
+ @staticmethod
+ def _pkg_names(pkg):
+ """
+ Given a namespace package, yield the components of that
+ package.
+
+ >>> names = Installer._pkg_names('a.b.c')
+ >>> set(names) == set(['a', 'a.b', 'a.b.c'])
+ True
+ """
+ parts = pkg.split('.')
+ while parts:
+ yield '.'.join(parts)
+ parts.pop()
+
+
+class DevelopInstaller(Installer):
+ def _get_root(self):
+ return repr(str(self.egg_path))
+
+ def _get_target(self):
+ return self.egg_link
diff --git a/lib/python3.12/site-packages/setuptools/script (dev).tmpl b/lib/python3.12/site-packages/setuptools/script (dev).tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..39a24b04888e79df51e2237577b303a2f901be63
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/script (dev).tmpl
@@ -0,0 +1,6 @@
+# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
+__requires__ = %(spec)r
+__import__('pkg_resources').require(%(spec)r)
+__file__ = %(dev_path)r
+with open(__file__) as f:
+ exec(compile(f.read(), __file__, 'exec'))
diff --git a/lib/python3.12/site-packages/setuptools/script.tmpl b/lib/python3.12/site-packages/setuptools/script.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..ff5efbcab3b58063dd84787181c26a95fb663d94
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/script.tmpl
@@ -0,0 +1,3 @@
+# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r
+__requires__ = %(spec)r
+__import__('pkg_resources').run_script(%(spec)r, %(script_name)r)
diff --git a/lib/python3.12/site-packages/setuptools/tests/__init__.py b/lib/python3.12/site-packages/setuptools/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb70bfb7115a2a94a8b942b31cafc3a550f0c005
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/__init__.py
@@ -0,0 +1,13 @@
+import locale
+import sys
+
+import pytest
+
+__all__ = ['fail_on_ascii']
+
+if sys.version_info >= (3, 11):
+ locale_encoding = locale.getencoding()
+else:
+ locale_encoding = locale.getpreferredencoding(False)
+is_ascii = locale_encoding == 'ANSI_X3.4-1968'
+fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale")
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/__init__.py b/lib/python3.12/site-packages/setuptools/tests/config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_apply_pyprojecttoml.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_apply_pyprojecttoml.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b907946e36ee8a4d92f58c7dec657ed1030b1e58
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_apply_pyprojecttoml.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_expand.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_expand.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..adf1e275d1d1b8d465f4d04523a1202e626dbc52
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_expand.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_pyprojecttoml_dynamic_deps.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_pyprojecttoml_dynamic_deps.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7a7206f1faa3cbd2eb0639deb105076313aa440a
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_pyprojecttoml_dynamic_deps.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_setupcfg.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_setupcfg.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..02efd092af4c84b01a7a7b2bfebf0e1147be5795
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/__pycache__/test_setupcfg.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/downloads/__init__.py b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..00a16423f448e7773f5e6dc9b365efe80f40a778
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__init__.py
@@ -0,0 +1,59 @@
+from __future__ import annotations
+
+import re
+import time
+from pathlib import Path
+from urllib.error import HTTPError
+from urllib.request import urlopen
+
+__all__ = ["DOWNLOAD_DIR", "retrieve_file", "output_file", "urls_from_file"]
+
+
+NAME_REMOVE = ("http://", "https://", "github.com/", "/raw/")
+DOWNLOAD_DIR = Path(__file__).parent
+
+
+# ----------------------------------------------------------------------
+# Please update ./preload.py accordingly when modifying this file
+# ----------------------------------------------------------------------
+
+
+def output_file(url: str, download_dir: Path = DOWNLOAD_DIR) -> Path:
+ file_name = url.strip()
+ for part in NAME_REMOVE:
+ file_name = file_name.replace(part, '').strip().strip('/:').strip()
+ return Path(download_dir, re.sub(r"[^\-_\.\w\d]+", "_", file_name))
+
+
+def retrieve_file(url: str, download_dir: Path = DOWNLOAD_DIR, wait: float = 5) -> Path:
+ path = output_file(url, download_dir)
+ if path.exists():
+ print(f"Skipping {url} (already exists: {path})")
+ else:
+ download_dir.mkdir(exist_ok=True, parents=True)
+ print(f"Downloading {url} to {path}")
+ try:
+ download(url, path)
+ except HTTPError:
+ time.sleep(wait) # wait a few seconds and try again.
+ download(url, path)
+ return path
+
+
+def urls_from_file(list_file: Path) -> list[str]:
+ """``list_file`` should be a text file where each line corresponds to a URL to
+ download.
+ """
+ print(f"file: {list_file}")
+ content = list_file.read_text(encoding="utf-8")
+ return [url for url in content.splitlines() if not url.startswith("#")]
+
+
+def download(url: str, dest: Path):
+ with urlopen(url) as f:
+ data = f.read()
+
+ with open(dest, "wb") as f:
+ f.write(data)
+
+ assert Path(dest).exists()
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ab1712336be1199853da7ee5ad84192a22c1939e
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/preload.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/preload.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b9f655b615eb8f34238a3f3c552d4158cf16b70
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/config/downloads/__pycache__/preload.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/downloads/preload.py b/lib/python3.12/site-packages/setuptools/tests/config/downloads/preload.py
new file mode 100644
index 0000000000000000000000000000000000000000..8eeb5dd75d3dcb375cee5acaf11ad385084bff5a
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/downloads/preload.py
@@ -0,0 +1,18 @@
+"""This file can be used to preload files needed for testing.
+
+For example you can use::
+
+ cd setuptools/tests/config
+ python -m downloads.preload setupcfg_examples.txt
+
+to make sure the `setup.cfg` examples are downloaded before starting the tests.
+"""
+
+import sys
+from pathlib import Path
+
+from . import retrieve_file, urls_from_file
+
+if __name__ == "__main__":
+ urls = urls_from_file(Path(sys.argv[1]))
+ list(map(retrieve_file, urls))
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/setupcfg_examples.txt b/lib/python3.12/site-packages/setuptools/tests/config/setupcfg_examples.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6aab887ff1fe631d97f1abea90a8448040746a12
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/setupcfg_examples.txt
@@ -0,0 +1,22 @@
+# ====================================================================
+# Some popular packages that use setup.cfg (and others not so popular)
+# Reference: https://hugovk.github.io/top-pypi-packages/
+# ====================================================================
+https://github.com/pypa/setuptools/raw/52c990172fec37766b3566679724aa8bf70ae06d/setup.cfg
+https://github.com/pypa/wheel/raw/0acd203cd896afec7f715aa2ff5980a403459a3b/setup.cfg
+https://github.com/python/importlib_metadata/raw/2f05392ca980952a6960d82b2f2d2ea10aa53239/setup.cfg
+https://github.com/jaraco/skeleton/raw/d9008b5c510cd6969127a6a2ab6f832edddef296/setup.cfg
+https://github.com/jaraco/zipp/raw/700d3a96390e970b6b962823bfea78b4f7e1c537/setup.cfg
+https://github.com/pallets/jinja/raw/7d72eb7fefb7dce065193967f31f805180508448/setup.cfg
+https://github.com/tkem/cachetools/raw/2fd87a94b8d3861d80e9e4236cd480bfdd21c90d/setup.cfg
+https://github.com/aio-libs/aiohttp/raw/5e0e6b7080f2408d5f1dd544c0e1cf88378b7b10/setup.cfg
+https://github.com/pallets/flask/raw/9486b6cf57bd6a8a261f67091aca8ca78eeec1e3/setup.cfg
+https://github.com/pallets/click/raw/6411f425fae545f42795665af4162006b36c5e4a/setup.cfg
+https://github.com/sqlalchemy/sqlalchemy/raw/533f5718904b620be8d63f2474229945d6f8ba5d/setup.cfg
+https://github.com/pytest-dev/pluggy/raw/461ef63291d13589c4e21aa182cd1529257e9a0a/setup.cfg
+https://github.com/pytest-dev/pytest/raw/c7be96dae487edbd2f55b561b31b68afac1dabe6/setup.cfg
+https://github.com/platformdirs/platformdirs/raw/7b7852128dd6f07511b618d6edea35046bd0c6ff/setup.cfg
+https://github.com/pandas-dev/pandas/raw/bc17343f934a33dc231c8c74be95d8365537c376/setup.cfg
+https://github.com/django/django/raw/4e249d11a6e56ca8feb4b055b681cec457ef3a3d/setup.cfg
+https://github.com/pyscaffold/pyscaffold/raw/de7aa5dc059fbd04307419c667cc4961bc9df4b8/setup.cfg
+https://github.com/pypa/virtualenv/raw/f92eda6e3da26a4d28c2663ffb85c4960bdb990c/setup.cfg
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py b/lib/python3.12/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f48c4316d9e9c3d09fe2abda9bd4aab63d90355
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/test_apply_pyprojecttoml.py
@@ -0,0 +1,774 @@
+"""Make sure that applying the configuration from pyproject.toml is equivalent to
+applying a similar configuration from setup.cfg
+
+To run these tests offline, please have a look on ``./downloads/preload.py``
+"""
+
+from __future__ import annotations
+
+import io
+import re
+import tarfile
+from inspect import cleandoc
+from pathlib import Path
+from unittest.mock import Mock
+
+import pytest
+from ini2toml.api import LiteTranslator
+from packaging.metadata import Metadata
+
+import setuptools # noqa: F401 # ensure monkey patch to metadata
+from setuptools._static import is_static
+from setuptools.command.egg_info import write_requirements
+from setuptools.config import expand, pyprojecttoml, setupcfg
+from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter
+from setuptools.dist import Distribution
+from setuptools.errors import InvalidConfigError, RemovedConfigError
+from setuptools.warnings import InformationOnly, SetuptoolsDeprecationWarning
+
+from .downloads import retrieve_file, urls_from_file
+
+HERE = Path(__file__).parent
+EXAMPLES_FILE = "setupcfg_examples.txt"
+
+
+def makedist(path, **attrs):
+ return Distribution({"src_root": path, **attrs})
+
+
+def _mock_expand_patterns(patterns, *_, **__):
+ """
+ Allow comparing the given patterns for 2 dist objects.
+ We need to strip special chars to avoid errors when validating.
+ """
+ return [
+ re.sub("[^a-z0-9]+", "", p, flags=re.IGNORECASE) or "empty" for p in patterns
+ ]
+
+
+@pytest.mark.parametrize("url", urls_from_file(HERE / EXAMPLES_FILE))
+@pytest.mark.filterwarnings("ignore")
+@pytest.mark.uses_network
+def test_apply_pyproject_equivalent_to_setupcfg(url, monkeypatch, tmp_path):
+ monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.0.1"))
+ monkeypatch.setattr(
+ Distribution, "_expand_patterns", Mock(side_effect=_mock_expand_patterns)
+ )
+ setupcfg_example = retrieve_file(url)
+ pyproject_example = Path(tmp_path, "pyproject.toml")
+ setupcfg_text = setupcfg_example.read_text(encoding="utf-8")
+ toml_config = LiteTranslator().translate(setupcfg_text, "setup.cfg")
+ pyproject_example.write_text(toml_config, encoding="utf-8")
+
+ dist_toml = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject_example)
+ dist_cfg = setupcfg.apply_configuration(makedist(tmp_path), setupcfg_example)
+
+ pkg_info_toml = core_metadata(dist_toml)
+ pkg_info_cfg = core_metadata(dist_cfg)
+ assert pkg_info_toml == pkg_info_cfg
+
+ if any(getattr(d, "license_files", None) for d in (dist_toml, dist_cfg)):
+ assert set(dist_toml.license_files) == set(dist_cfg.license_files)
+
+ if any(getattr(d, "entry_points", None) for d in (dist_toml, dist_cfg)):
+ print(dist_cfg.entry_points)
+ ep_toml = {
+ (k, *sorted(i.replace(" ", "") for i in v))
+ for k, v in dist_toml.entry_points.items()
+ }
+ ep_cfg = {
+ (k, *sorted(i.replace(" ", "") for i in v))
+ for k, v in dist_cfg.entry_points.items()
+ }
+ assert ep_toml == ep_cfg
+
+ if any(getattr(d, "package_data", None) for d in (dist_toml, dist_cfg)):
+ pkg_data_toml = {(k, *sorted(v)) for k, v in dist_toml.package_data.items()}
+ pkg_data_cfg = {(k, *sorted(v)) for k, v in dist_cfg.package_data.items()}
+ assert pkg_data_toml == pkg_data_cfg
+
+ if any(getattr(d, "data_files", None) for d in (dist_toml, dist_cfg)):
+ data_files_toml = {(k, *sorted(v)) for k, v in dist_toml.data_files}
+ data_files_cfg = {(k, *sorted(v)) for k, v in dist_cfg.data_files}
+ assert data_files_toml == data_files_cfg
+
+ assert set(dist_toml.install_requires) == set(dist_cfg.install_requires)
+ if any(getattr(d, "extras_require", None) for d in (dist_toml, dist_cfg)):
+ extra_req_toml = {(k, *sorted(v)) for k, v in dist_toml.extras_require.items()}
+ extra_req_cfg = {(k, *sorted(v)) for k, v in dist_cfg.extras_require.items()}
+ assert extra_req_toml == extra_req_cfg
+
+
+PEP621_EXAMPLE = """\
+[project]
+name = "spam"
+version = "2020.0.0"
+description = "Lovely Spam! Wonderful Spam!"
+readme = "README.rst"
+requires-python = ">=3.8"
+license-files = ["LICENSE.txt"] # Updated to be PEP 639 compliant
+keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
+authors = [
+ {email = "hi@pradyunsg.me"},
+ {name = "Tzu-Ping Chung"}
+]
+maintainers = [
+ {name = "Brett Cannon", email = "brett@python.org"},
+ {name = "John X. Ãørçeč", email = "john@utf8.org"},
+ {name = "Γαμα קּ 東", email = "gama@utf8.org"},
+]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Programming Language :: Python"
+]
+
+dependencies = [
+ "httpx",
+ "gidgethub[httpx]>4.0.0",
+ "django>2.1; os_name != 'nt'",
+ "django>2.0; os_name == 'nt'"
+]
+
+[project.optional-dependencies]
+test = [
+ "pytest < 5.0.0",
+ "pytest-cov[all]"
+]
+
+[project.urls]
+homepage = "http://example.com"
+documentation = "http://readthedocs.org"
+repository = "http://github.com"
+changelog = "http://github.com/me/spam/blob/master/CHANGELOG.md"
+
+[project.scripts]
+spam-cli = "spam:main_cli"
+
+[project.gui-scripts]
+spam-gui = "spam:main_gui"
+
+[project.entry-points."spam.magical"]
+tomatoes = "spam:main_tomatoes"
+"""
+
+PEP621_INTERNATIONAL_EMAIL_EXAMPLE = """\
+[project]
+name = "spam"
+version = "2020.0.0"
+authors = [
+ {email = "hi@pradyunsg.me"},
+ {name = "Tzu-Ping Chung"}
+]
+maintainers = [
+ {name = "अंकित अहलावत", email = "ankit@example.com"},
+]
+"""
+
+PEP621_EXAMPLE_SCRIPT = """
+def main_cli(): pass
+def main_gui(): pass
+def main_tomatoes(): pass
+"""
+
+PEP639_LICENSE_TEXT = """\
+[project]
+name = "spam"
+version = "2020.0.0"
+authors = [
+ {email = "hi@pradyunsg.me"},
+ {name = "Tzu-Ping Chung"}
+]
+license = {text = "MIT"}
+"""
+
+PEP639_LICENSE_EXPRESSION = """\
+[project]
+name = "spam"
+version = "2020.0.0"
+authors = [
+ {email = "hi@pradyunsg.me"},
+ {name = "Tzu-Ping Chung"}
+]
+license = "mit or apache-2.0" # should be normalized in metadata
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Programming Language :: Python",
+]
+"""
+
+
+def _pep621_example_project(
+ tmp_path,
+ readme="README.rst",
+ pyproject_text=PEP621_EXAMPLE,
+):
+ pyproject = tmp_path / "pyproject.toml"
+ text = pyproject_text
+ replacements = {'readme = "README.rst"': f'readme = "{readme}"'}
+ for orig, subst in replacements.items():
+ text = text.replace(orig, subst)
+ pyproject.write_text(text, encoding="utf-8")
+
+ (tmp_path / readme).write_text("hello world", encoding="utf-8")
+ (tmp_path / "LICENSE.txt").write_text("--- LICENSE stub ---", encoding="utf-8")
+ (tmp_path / "spam.py").write_text(PEP621_EXAMPLE_SCRIPT, encoding="utf-8")
+ return pyproject
+
+
+def test_pep621_example(tmp_path):
+ """Make sure the example in PEP 621 works"""
+ pyproject = _pep621_example_project(tmp_path)
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert set(dist.metadata.license_files) == {"LICENSE.txt"}
+
+
+@pytest.mark.parametrize(
+ ("readme", "ctype"),
+ [
+ ("Readme.txt", "text/plain"),
+ ("readme.md", "text/markdown"),
+ ("text.rst", "text/x-rst"),
+ ],
+)
+def test_readme_content_type(tmp_path, readme, ctype):
+ pyproject = _pep621_example_project(tmp_path, readme)
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert dist.metadata.long_description_content_type == ctype
+
+
+def test_undefined_content_type(tmp_path):
+ pyproject = _pep621_example_project(tmp_path, "README.tex")
+ with pytest.raises(ValueError, match="Undefined content type for README.tex"):
+ pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+
+def test_no_explicit_content_type_for_missing_extension(tmp_path):
+ pyproject = _pep621_example_project(tmp_path, "README")
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert dist.metadata.long_description_content_type is None
+
+
+@pytest.mark.parametrize(
+ ("pyproject_text", "expected_maintainers_meta_value"),
+ (
+ pytest.param(
+ PEP621_EXAMPLE,
+ (
+ 'Brett Cannon , "John X. Ãørçeč" , '
+ 'Γαμα קּ 東 '
+ ),
+ id='non-international-emails',
+ ),
+ pytest.param(
+ PEP621_INTERNATIONAL_EMAIL_EXAMPLE,
+ 'Ankit Ahlawat <अंकित@उदाहरण.भारत>',
+ marks=pytest.mark.xfail(
+ reason="CPython's `email.headerregistry.Address` only supports "
+ 'RFC 5322, as of Oct 20, 2025 and latest Python 3.13.0',
+ strict=True,
+ ),
+ id='international-email',
+ ),
+ ),
+)
+def test_utf8_maintainer_in_metadata( # issue-3663
+ expected_maintainers_meta_value,
+ pyproject_text,
+ tmp_path,
+):
+ pyproject = _pep621_example_project(
+ tmp_path,
+ "README",
+ pyproject_text=pyproject_text,
+ )
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert dist.metadata.maintainer_email == expected_maintainers_meta_value
+ pkg_file = tmp_path / "PKG-FILE"
+ with open(pkg_file, "w", encoding="utf-8") as fh:
+ dist.metadata.write_pkg_file(fh)
+ content = pkg_file.read_text(encoding="utf-8")
+ assert f"Maintainer-email: {expected_maintainers_meta_value}" in content
+
+
+@pytest.mark.parametrize(
+ (
+ 'pyproject_text',
+ 'license',
+ 'license_expression',
+ 'content_str',
+ 'not_content_str',
+ ),
+ (
+ pytest.param(
+ PEP639_LICENSE_TEXT,
+ 'MIT',
+ None,
+ 'License: MIT',
+ 'License-Expression: ',
+ id='license-text',
+ marks=[
+ pytest.mark.filterwarnings(
+ "ignore:.project.license. as a TOML table is deprecated",
+ )
+ ],
+ ),
+ pytest.param(
+ PEP639_LICENSE_EXPRESSION,
+ None,
+ 'MIT OR Apache-2.0',
+ 'License-Expression: MIT OR Apache-2.0',
+ 'License: ',
+ id='license-expression',
+ ),
+ ),
+)
+def test_license_in_metadata(
+ license,
+ license_expression,
+ content_str,
+ not_content_str,
+ pyproject_text,
+ tmp_path,
+):
+ pyproject = _pep621_example_project(
+ tmp_path,
+ "README",
+ pyproject_text=pyproject_text,
+ )
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert dist.metadata.license == license
+ assert dist.metadata.license_expression == license_expression
+ pkg_file = tmp_path / "PKG-FILE"
+ with open(pkg_file, "w", encoding="utf-8") as fh:
+ dist.metadata.write_pkg_file(fh)
+ content = pkg_file.read_text(encoding="utf-8")
+ assert "Metadata-Version: 2.4" in content
+ assert content_str in content
+ assert not_content_str not in content
+
+
+def test_license_classifier_with_license_expression(tmp_path):
+ text = PEP639_LICENSE_EXPRESSION.rsplit("\n", 2)[0]
+ pyproject = _pep621_example_project(
+ tmp_path,
+ "README",
+ f"{text}\n \"License :: OSI Approved :: MIT License\"\n]",
+ )
+ msg = "License classifiers have been superseded by license expressions"
+ with pytest.raises(InvalidConfigError, match=msg) as exc:
+ pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ assert "License :: OSI Approved :: MIT License" in str(exc.value)
+
+
+def test_license_classifier_without_license_expression(tmp_path):
+ text = """\
+ [project]
+ name = "spam"
+ version = "2020.0.0"
+ license = {text = "mit or apache-2.0"}
+ classifiers = ["License :: OSI Approved :: MIT License"]
+ """
+ pyproject = _pep621_example_project(tmp_path, "README", text)
+
+ msg1 = "License classifiers are deprecated(?:.|\n)*MIT License"
+ msg2 = ".project.license. as a TOML table is deprecated"
+ with (
+ pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
+ pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
+ ):
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ # Check license classifier is still included
+ assert dist.metadata.get_classifiers() == ["License :: OSI Approved :: MIT License"]
+
+
+class TestLicenseFiles:
+ def base_pyproject(
+ self,
+ tmp_path,
+ additional_text="",
+ license_toml='license = {file = "LICENSE.txt"}\n',
+ ):
+ text = PEP639_LICENSE_EXPRESSION
+
+ # Sanity-check
+ assert 'license = "mit or apache-2.0"' in text
+ assert 'license-files' not in text
+ assert "[tool.setuptools]" not in text
+
+ text = re.sub(
+ r"(license = .*)\n",
+ license_toml,
+ text,
+ count=1,
+ )
+ assert license_toml in text # sanity check
+ text = f"{text}\n{additional_text}\n"
+ pyproject = _pep621_example_project(tmp_path, "README", pyproject_text=text)
+ return pyproject
+
+ def base_pyproject_license_pep639(self, tmp_path, additional_text=""):
+ return self.base_pyproject(
+ tmp_path,
+ additional_text=additional_text,
+ license_toml='license = "licenseref-Proprietary"'
+ '\nlicense-files = ["_FILE*"]\n',
+ )
+
+ def test_both_license_and_license_files_defined(self, tmp_path):
+ setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
+ pyproject = self.base_pyproject(tmp_path, setuptools_config)
+
+ (tmp_path / "_FILE.txt").touch()
+ (tmp_path / "_FILE.rst").touch()
+
+ # Would normally match the `license_files` patterns, but we want to exclude it
+ # by being explicit. On the other hand, contents should be added to `license`
+ license = tmp_path / "LICENSE.txt"
+ license.write_text("LicenseRef-Proprietary\n", encoding="utf-8")
+
+ msg1 = "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'"
+ msg2 = ".project.license. as a TOML table is deprecated"
+ with (
+ pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
+ pytest.warns(SetuptoolsDeprecationWarning, match=msg2),
+ ):
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+ assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
+ assert dist.metadata.license == "LicenseRef-Proprietary\n"
+
+ def test_both_license_and_license_files_defined_pep639(self, tmp_path):
+ # Set license and license-files
+ pyproject = self.base_pyproject_license_pep639(tmp_path)
+
+ (tmp_path / "_FILE.txt").touch()
+ (tmp_path / "_FILE.rst").touch()
+
+ msg = "Normalizing.*LicenseRef"
+ with pytest.warns(InformationOnly, match=msg):
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ assert set(dist.metadata.license_files) == {"_FILE.rst", "_FILE.txt"}
+ assert dist.metadata.license is None
+ assert dist.metadata.license_expression == "LicenseRef-Proprietary"
+
+ def test_license_files_defined_twice(self, tmp_path):
+ # Set project.license-files and tools.setuptools.license-files
+ setuptools_config = '[tool.setuptools]\nlicense-files = ["_FILE*"]'
+ pyproject = self.base_pyproject_license_pep639(tmp_path, setuptools_config)
+
+ msg = "'project.license-files' is defined already. Remove 'tool.setuptools.license-files'"
+ with pytest.raises(InvalidConfigError, match=msg):
+ pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ def test_default_patterns(self, tmp_path):
+ setuptools_config = '[tool.setuptools]\nzip-safe = false'
+ # ^ used just to trigger section validation
+ pyproject = self.base_pyproject(tmp_path, setuptools_config, license_toml="")
+
+ license_files = "LICENCE-a.html COPYING-abc.txt AUTHORS-xyz NOTICE,def".split()
+
+ for fname in license_files:
+ (tmp_path / fname).write_text(f"{fname}\n", encoding="utf-8")
+
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ assert (tmp_path / "LICENSE.txt").exists() # from base example
+ assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"}
+
+ def test_missing_patterns(self, tmp_path):
+ pyproject = self.base_pyproject_license_pep639(tmp_path)
+ assert list(tmp_path.glob("_FILE*")) == [] # sanity check
+
+ msg1 = "Cannot find any files for the given pattern.*"
+ msg2 = "Normalizing 'licenseref-Proprietary' to 'LicenseRef-Proprietary'"
+ with (
+ pytest.warns(SetuptoolsDeprecationWarning, match=msg1),
+ pytest.warns(InformationOnly, match=msg2),
+ ):
+ pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ def test_deprecated_file_expands_to_text(self, tmp_path):
+ """Make sure the old example with ``license = {text = ...}`` works"""
+
+ assert 'license-files = ["LICENSE.txt"]' in PEP621_EXAMPLE # sanity check
+ text = PEP621_EXAMPLE.replace(
+ 'license-files = ["LICENSE.txt"]',
+ 'license = {file = "LICENSE.txt"}',
+ )
+ pyproject = _pep621_example_project(tmp_path, pyproject_text=text)
+
+ msg = ".project.license. as a TOML table is deprecated"
+ with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
+ dist = pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+ assert dist.metadata.license == "--- LICENSE stub ---"
+ assert set(dist.metadata.license_files) == {"LICENSE.txt"} # auto-filled
+
+
+class TestPyModules:
+ # https://github.com/pypa/setuptools/issues/4316
+
+ def dist(self, name):
+ toml_config = f"""
+ [project]
+ name = "test"
+ version = "42.0"
+ [tool.setuptools]
+ py-modules = [{name!r}]
+ """
+ pyproject = Path("pyproject.toml")
+ pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
+ return pyprojecttoml.apply_configuration(Distribution({}), pyproject)
+
+ @pytest.mark.parametrize("module", ["pip-run", "abc-d.λ-xyz-e"])
+ def test_valid_module_name(self, tmp_path, monkeypatch, module):
+ monkeypatch.chdir(tmp_path)
+ assert module in self.dist(module).py_modules
+
+ @pytest.mark.parametrize("module", ["pip run", "-pip-run", "pip-run-stubs"])
+ def test_invalid_module_name(self, tmp_path, monkeypatch, module):
+ monkeypatch.chdir(tmp_path)
+ with pytest.raises(ValueError, match="py-modules"):
+ self.dist(module).py_modules
+
+
+class TestExtModules:
+ def test_pyproject_sets_attribute(self, tmp_path, monkeypatch):
+ monkeypatch.chdir(tmp_path)
+ pyproject = Path("pyproject.toml")
+ toml_config = """
+ [project]
+ name = "test"
+ version = "42.0"
+ [tool.setuptools]
+ ext-modules = [
+ {name = "my.ext", sources = ["hello.c", "world.c"]}
+ ]
+ """
+ pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
+ with pytest.warns(pyprojecttoml._ExperimentalConfiguration):
+ dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
+ assert len(dist.ext_modules) == 1
+ assert dist.ext_modules[0].name == "my.ext"
+ assert set(dist.ext_modules[0].sources) == {"hello.c", "world.c"}
+
+
+class TestDeprecatedFields:
+ def test_namespace_packages(self, tmp_path):
+ pyproject = tmp_path / "pyproject.toml"
+ config = """
+ [project]
+ name = "myproj"
+ version = "42"
+ [tool.setuptools]
+ namespace-packages = ["myproj.pkg"]
+ """
+ pyproject.write_text(cleandoc(config), encoding="utf-8")
+ with pytest.raises(RemovedConfigError, match="namespace-packages"):
+ pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
+
+
+class TestPresetField:
+ def pyproject(self, tmp_path, dynamic, extra_content=""):
+ content = f"[project]\nname = 'proj'\ndynamic = {dynamic!r}\n"
+ if "version" not in dynamic:
+ content += "version = '42'\n"
+ file = tmp_path / "pyproject.toml"
+ file.write_text(content + extra_content, encoding="utf-8")
+ return file
+
+ @pytest.mark.parametrize(
+ ("attr", "field", "value"),
+ [
+ ("license_expression", "license", "MIT"),
+ pytest.param(
+ *("license", "license", "Not SPDX"),
+ marks=[pytest.mark.filterwarnings("ignore:.*license. overwritten")],
+ ),
+ ("classifiers", "classifiers", ["Private :: Classifier"]),
+ ("entry_points", "scripts", {"console_scripts": ["foobar=foobar:main"]}),
+ ("entry_points", "gui-scripts", {"gui_scripts": ["bazquux=bazquux:main"]}),
+ pytest.param(
+ *("install_requires", "dependencies", ["six"]),
+ marks=[
+ pytest.mark.filterwarnings("ignore:.*install_requires. overwritten")
+ ],
+ ),
+ ],
+ )
+ def test_not_listed_in_dynamic(self, tmp_path, attr, field, value):
+ """Setuptools cannot set a field if not listed in ``dynamic``"""
+ pyproject = self.pyproject(tmp_path, [])
+ dist = makedist(tmp_path, **{attr: value})
+ msg = re.compile(f"defined outside of `pyproject.toml`:.*{field}", re.DOTALL)
+ with pytest.warns(_MissingDynamic, match=msg):
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+
+ dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
+ assert not dist_value
+
+ @pytest.mark.parametrize(
+ ("attr", "field", "value"),
+ [
+ ("license_expression", "license", "MIT"),
+ ("install_requires", "dependencies", []),
+ ("extras_require", "optional-dependencies", {}),
+ ("install_requires", "dependencies", ["six"]),
+ ("classifiers", "classifiers", ["Private :: Classifier"]),
+ ],
+ )
+ def test_listed_in_dynamic(self, tmp_path, attr, field, value):
+ pyproject = self.pyproject(tmp_path, [field])
+ dist = makedist(tmp_path, **{attr: value})
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+ dist_value = _some_attrgetter(f"metadata.{attr}", attr)(dist)
+ assert dist_value == value
+
+ def test_license_files_exempt_from_dynamic(self, monkeypatch, tmp_path):
+ """
+ license-file is currently not considered in the context of dynamic.
+ As per 2025-02-19, https://packaging.python.org/en/latest/specifications/pyproject-toml/#license-files
+ allows setuptools to fill-in `license-files` the way it sees fit:
+
+ > If the license-files key is not defined, tools can decide how to handle license files.
+ > For example they can choose not to include any files or use their own
+ > logic to discover the appropriate files in the distribution.
+
+ Using license_files from setup.py to fill-in the value is in accordance
+ with this rule.
+ """
+ monkeypatch.chdir(tmp_path)
+ pyproject = self.pyproject(tmp_path, [])
+ dist = makedist(tmp_path, license_files=["LIC*"])
+ (tmp_path / "LIC1").write_text("42", encoding="utf-8")
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+ assert dist.metadata.license_files == ["LIC1"]
+
+ def test_warning_overwritten_dependencies(self, tmp_path):
+ src = "[project]\nname='pkg'\nversion='0.1'\ndependencies=['click']\n"
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(src, encoding="utf-8")
+ dist = makedist(tmp_path, install_requires=["wheel"])
+ with pytest.warns(match="`install_requires` overwritten"):
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+ assert "wheel" not in dist.install_requires
+
+ def test_optional_dependencies_dont_remove_env_markers(self, tmp_path):
+ """
+ Internally setuptools converts dependencies with markers to "extras".
+ If ``install_requires`` is given by ``setup.py``, we have to ensure that
+ applying ``optional-dependencies`` does not overwrite the mandatory
+ dependencies with markers (see #3204).
+ """
+ # If setuptools replace its internal mechanism that uses `requires.txt`
+ # this test has to be rewritten to adapt accordingly
+ extra = "\n[project.optional-dependencies]\nfoo = ['bar>1']\n"
+ pyproject = self.pyproject(tmp_path, ["dependencies"], extra)
+ install_req = ['importlib-resources (>=3.0.0) ; python_version < "3.7"']
+ dist = makedist(tmp_path, install_requires=install_req)
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+ assert "foo" in dist.extras_require
+ egg_info = dist.get_command_obj("egg_info")
+ write_requirements(egg_info, tmp_path, tmp_path / "requires.txt")
+ reqs = (tmp_path / "requires.txt").read_text(encoding="utf-8")
+ assert "importlib-resources" in reqs
+ assert "bar" in reqs
+ assert ':python_version < "3.7"' in reqs
+
+ @pytest.mark.parametrize(
+ ("field", "group"),
+ [("scripts", "console_scripts"), ("gui-scripts", "gui_scripts")],
+ )
+ @pytest.mark.filterwarnings("error")
+ def test_scripts_dont_require_dynamic_entry_points(self, tmp_path, field, group):
+ # Issue 3862
+ pyproject = self.pyproject(tmp_path, [field])
+ dist = makedist(tmp_path, entry_points={group: ["foobar=foobar:main"]})
+ dist = pyprojecttoml.apply_configuration(dist, pyproject)
+ assert group in dist.entry_points
+
+
+class TestMeta:
+ def test_example_file_in_sdist(self, setuptools_sdist):
+ """Meta test to ensure tests can run from sdist"""
+ with tarfile.open(setuptools_sdist) as tar:
+ assert any(name.endswith(EXAMPLES_FILE) for name in tar.getnames())
+
+
+class TestInteropCommandLineParsing:
+ def test_version(self, tmp_path, monkeypatch, capsys):
+ # See pypa/setuptools#4047
+ # This test can be removed once the CLI interface of setup.py is removed
+ monkeypatch.chdir(tmp_path)
+ toml_config = """
+ [project]
+ name = "test"
+ version = "42.0"
+ """
+ pyproject = Path(tmp_path, "pyproject.toml")
+ pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
+ opts = {"script_args": ["--version"]}
+ dist = pyprojecttoml.apply_configuration(Distribution(opts), pyproject)
+ dist.parse_command_line() # <-- there should be no exception here.
+ captured = capsys.readouterr()
+ assert "42.0" in captured.out
+
+
+class TestStaticConfig:
+ def test_mark_static_fields(self, tmp_path, monkeypatch):
+ monkeypatch.chdir(tmp_path)
+ toml_config = """
+ [project]
+ name = "test"
+ version = "42.0"
+ dependencies = ["hello"]
+ keywords = ["world"]
+ classifiers = ["private :: hello world"]
+ [tool.setuptools]
+ obsoletes = ["abcd"]
+ provides = ["abcd"]
+ platforms = ["abcd"]
+ """
+ pyproject = Path(tmp_path, "pyproject.toml")
+ pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
+ dist = pyprojecttoml.apply_configuration(Distribution({}), pyproject)
+ assert is_static(dist.install_requires)
+ assert is_static(dist.metadata.keywords)
+ assert is_static(dist.metadata.classifiers)
+ assert is_static(dist.metadata.obsoletes)
+ assert is_static(dist.metadata.provides)
+ assert is_static(dist.metadata.platforms)
+
+
+# --- Auxiliary Functions ---
+
+
+def core_metadata(dist) -> str:
+ with io.StringIO() as buffer:
+ dist.metadata.write_pkg_file(buffer)
+ pkg_file_txt = buffer.getvalue()
+
+ # Make sure core metadata is valid
+ Metadata.from_email(pkg_file_txt, validate=True) # can raise exceptions
+
+ skip_prefixes: tuple[str, ...] = ()
+ skip_lines = set()
+ # ---- DIFF NORMALISATION ----
+ # PEP 621 is very particular about author/maintainer metadata conversion, so skip
+ skip_prefixes += ("Author:", "Author-email:", "Maintainer:", "Maintainer-email:")
+ # May be redundant with Home-page
+ skip_prefixes += ("Project-URL: Homepage,", "Home-page:")
+ # May be missing in original (relying on default) but backfilled in the TOML
+ skip_prefixes += ("Description-Content-Type:",)
+ # Remove empty lines
+ skip_lines.add("")
+
+ result = []
+ for line in pkg_file_txt.splitlines():
+ if line.startswith(skip_prefixes) or line in skip_lines:
+ continue
+ result.append(line + "\n")
+
+ return "".join(result)
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/test_expand.py b/lib/python3.12/site-packages/setuptools/tests/config/test_expand.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5710ec63d7d9d4ed7b709203bb2fc4b512f2093
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/test_expand.py
@@ -0,0 +1,247 @@
+import os
+import sys
+from pathlib import Path
+
+import pytest
+
+from setuptools._static import is_static
+from setuptools.config import expand
+from setuptools.discovery import find_package_path
+
+from distutils.errors import DistutilsOptionError
+
+
+def write_files(files, root_dir):
+ for file, content in files.items():
+ path = root_dir / file
+ path.parent.mkdir(exist_ok=True, parents=True)
+ path.write_text(content, encoding="utf-8")
+
+
+def test_glob_relative(tmp_path, monkeypatch):
+ files = {
+ "dir1/dir2/dir3/file1.txt",
+ "dir1/dir2/file2.txt",
+ "dir1/file3.txt",
+ "a.ini",
+ "b.ini",
+ "dir1/c.ini",
+ "dir1/dir2/a.ini",
+ }
+
+ write_files({k: "" for k in files}, tmp_path)
+ patterns = ["**/*.txt", "[ab].*", "**/[ac].ini"]
+ monkeypatch.chdir(tmp_path)
+ assert set(expand.glob_relative(patterns)) == files
+ # Make sure the same APIs work outside cwd
+ assert set(expand.glob_relative(patterns, tmp_path)) == files
+
+
+def test_read_files(tmp_path, monkeypatch):
+ dir_ = tmp_path / "dir_"
+ (tmp_path / "_dir").mkdir(exist_ok=True)
+ (tmp_path / "a.txt").touch()
+ files = {"a.txt": "a", "dir1/b.txt": "b", "dir1/dir2/c.txt": "c"}
+ write_files(files, dir_)
+
+ secrets = Path(str(dir_) + "secrets")
+ secrets.mkdir(exist_ok=True)
+ write_files({"secrets.txt": "secret keys"}, secrets)
+
+ with monkeypatch.context() as m:
+ m.chdir(dir_)
+ assert expand.read_files(list(files)) == "a\nb\nc"
+
+ cannot_access_msg = r"Cannot access '.*\.\..a\.txt'"
+ with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
+ expand.read_files(["../a.txt"])
+
+ cannot_access_secrets_msg = r"Cannot access '.*secrets\.txt'"
+ with pytest.raises(DistutilsOptionError, match=cannot_access_secrets_msg):
+ expand.read_files(["../dir_secrets/secrets.txt"])
+
+ # Make sure the same APIs work outside cwd
+ assert expand.read_files(list(files), dir_) == "a\nb\nc"
+ with pytest.raises(DistutilsOptionError, match=cannot_access_msg):
+ expand.read_files(["../a.txt"], dir_)
+
+
+class TestReadAttr:
+ @pytest.mark.parametrize(
+ "example",
+ [
+ # No cookie means UTF-8:
+ b"__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
+ # If a cookie is present, honor it:
+ b"# -*- coding: utf-8 -*-\n__version__ = '\xc3\xa9'\nraise SystemExit(1)\n",
+ b"# -*- coding: latin1 -*-\n__version__ = '\xe9'\nraise SystemExit(1)\n",
+ ],
+ )
+ def test_read_attr_encoding_cookie(self, example, tmp_path):
+ (tmp_path / "mod.py").write_bytes(example)
+ assert expand.read_attr('mod.__version__', root_dir=tmp_path) == 'é'
+
+ def test_read_attr(self, tmp_path, monkeypatch):
+ files = {
+ "pkg/__init__.py": "",
+ "pkg/sub/__init__.py": "VERSION = '0.1.1'",
+ "pkg/sub/mod.py": (
+ "VALUES = {'a': 0, 'b': {42}, 'c': (0, 1, 1)}\nraise SystemExit(1)"
+ ),
+ }
+ write_files(files, tmp_path)
+
+ with monkeypatch.context() as m:
+ m.chdir(tmp_path)
+ # Make sure it can read the attr statically without evaluating the module
+ version = expand.read_attr('pkg.sub.VERSION')
+ values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'})
+
+ assert version == '0.1.1'
+ assert is_static(values)
+
+ assert values['a'] == 0
+ assert values['b'] == {42}
+ assert is_static(values)
+
+ # Make sure the same APIs work outside cwd
+ assert expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path) == '0.1.1'
+ values = expand.read_attr('lib.mod.VALUES', {'lib': 'pkg/sub'}, tmp_path)
+ assert values['c'] == (0, 1, 1)
+
+ @pytest.mark.parametrize(
+ "example",
+ [
+ "VERSION: str\nVERSION = '0.1.1'\nraise SystemExit(1)\n",
+ "VERSION: str = '0.1.1'\nraise SystemExit(1)\n",
+ ],
+ )
+ def test_read_annotated_attr(self, tmp_path, example):
+ files = {
+ "pkg/__init__.py": "",
+ "pkg/sub/__init__.py": example,
+ }
+ write_files(files, tmp_path)
+ # Make sure this attribute can be read statically
+ version = expand.read_attr('pkg.sub.VERSION', root_dir=tmp_path)
+ assert version == '0.1.1'
+ assert is_static(version)
+
+ @pytest.mark.parametrize(
+ "example",
+ [
+ "VERSION = (lambda: '0.1.1')()\n",
+ "def fn(): return '0.1.1'\nVERSION = fn()\n",
+ "VERSION: str = (lambda: '0.1.1')()\n",
+ ],
+ )
+ def test_read_dynamic_attr(self, tmp_path, monkeypatch, example):
+ files = {
+ "pkg/__init__.py": "",
+ "pkg/sub/__init__.py": example,
+ }
+ write_files(files, tmp_path)
+ monkeypatch.chdir(tmp_path)
+ version = expand.read_attr('pkg.sub.VERSION')
+ assert version == '0.1.1'
+ assert not is_static(version)
+
+ def test_import_order(self, tmp_path):
+ """
+ Sometimes the import machinery will import the parent package of a nested
+ module, which triggers side-effects and might create problems (see issue #3176)
+
+ ``read_attr`` should bypass these limitations by resolving modules statically
+ (via ast.literal_eval).
+ """
+ files = {
+ "src/pkg/__init__.py": "from .main import func\nfrom .about import version",
+ "src/pkg/main.py": "import super_complicated_dep\ndef func(): return 42",
+ "src/pkg/about.py": "version = '42'",
+ }
+ write_files(files, tmp_path)
+ attr_desc = "pkg.about.version"
+ package_dir = {"": "src"}
+ # `import super_complicated_dep` should not run, otherwise the build fails
+ assert expand.read_attr(attr_desc, package_dir, tmp_path) == "42"
+
+
+@pytest.mark.parametrize(
+ ("package_dir", "file", "module", "return_value"),
+ [
+ ({"": "src"}, "src/pkg/main.py", "pkg.main", 42),
+ ({"pkg": "lib"}, "lib/main.py", "pkg.main", 13),
+ ({}, "single_module.py", "single_module", 70),
+ ({}, "flat_layout/pkg.py", "flat_layout.pkg", 836),
+ ],
+)
+def test_resolve_class(monkeypatch, tmp_path, package_dir, file, module, return_value):
+ monkeypatch.setattr(sys, "modules", {}) # reproducibility
+ files = {file: f"class Custom:\n def testing(self): return {return_value}"}
+ write_files(files, tmp_path)
+ cls = expand.resolve_class(f"{module}.Custom", package_dir, tmp_path)
+ assert cls().testing() == return_value
+
+
+@pytest.mark.parametrize(
+ ("args", "pkgs"),
+ [
+ ({"where": ["."], "namespaces": False}, {"pkg", "other"}),
+ ({"where": [".", "dir1"], "namespaces": False}, {"pkg", "other", "dir2"}),
+ ({"namespaces": True}, {"pkg", "other", "dir1", "dir1.dir2"}),
+ ({}, {"pkg", "other", "dir1", "dir1.dir2"}), # default value for `namespaces`
+ ],
+)
+def test_find_packages(tmp_path, args, pkgs):
+ files = {
+ "pkg/__init__.py",
+ "other/__init__.py",
+ "dir1/dir2/__init__.py",
+ }
+ write_files({k: "" for k in files}, tmp_path)
+
+ package_dir = {}
+ kwargs = {"root_dir": tmp_path, "fill_package_dir": package_dir, **args}
+ where = kwargs.get("where", ["."])
+ assert set(expand.find_packages(**kwargs)) == pkgs
+ for pkg in pkgs:
+ pkg_path = find_package_path(pkg, package_dir, tmp_path)
+ assert os.path.exists(pkg_path)
+
+ # Make sure the same APIs work outside cwd
+ where = [
+ str((tmp_path / p).resolve()).replace(os.sep, "/") # ensure posix-style paths
+ for p in args.pop("where", ["."])
+ ]
+
+ assert set(expand.find_packages(where=where, **args)) == pkgs
+
+
+@pytest.mark.parametrize(
+ ("files", "where", "expected_package_dir"),
+ [
+ (["pkg1/__init__.py", "pkg1/other.py"], ["."], {}),
+ (["pkg1/__init__.py", "pkg2/__init__.py"], ["."], {}),
+ (["src/pkg1/__init__.py", "src/pkg1/other.py"], ["src"], {"": "src"}),
+ (["src/pkg1/__init__.py", "src/pkg2/__init__.py"], ["src"], {"": "src"}),
+ (
+ ["src1/pkg1/__init__.py", "src2/pkg2/__init__.py"],
+ ["src1", "src2"],
+ {"pkg1": "src1/pkg1", "pkg2": "src2/pkg2"},
+ ),
+ (
+ ["src/pkg1/__init__.py", "pkg2/__init__.py"],
+ ["src", "."],
+ {"pkg1": "src/pkg1"},
+ ),
+ ],
+)
+def test_fill_package_dir(tmp_path, files, where, expected_package_dir):
+ write_files({k: "" for k in files}, tmp_path)
+ pkg_dir = {}
+ kwargs = {"root_dir": tmp_path, "fill_package_dir": pkg_dir, "namespaces": False}
+ pkgs = expand.find_packages(where=where, **kwargs)
+ assert set(pkg_dir.items()) == set(expected_package_dir.items())
+ for pkg in pkgs:
+ pkg_path = find_package_path(pkg, pkg_dir, tmp_path)
+ assert os.path.exists(pkg_path)
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml.py b/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d995d23af12b4f103dbb310635d14687ab1a0be
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml.py
@@ -0,0 +1,398 @@
+import re
+from configparser import ConfigParser
+from inspect import cleandoc
+
+import jaraco.path
+import pytest
+import tomli_w
+from path import Path
+
+import setuptools # noqa: F401 # force distutils.core to be patched
+from setuptools.config.pyprojecttoml import (
+ _ToolsTypoInMetadata,
+ apply_configuration,
+ expand_configuration,
+ read_configuration,
+ validate,
+)
+from setuptools.dist import Distribution
+from setuptools.errors import OptionError
+
+import distutils.core
+
+EXAMPLE = """
+[project]
+name = "myproj"
+keywords = ["some", "key", "words"]
+dynamic = ["version", "readme"]
+requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+dependencies = [
+ 'importlib-metadata>=0.12;python_version<"3.8"',
+ 'importlib-resources>=1.0;python_version<"3.7"',
+ 'pathlib2>=2.3.3,<3;python_version < "3.4" and sys.platform != "win32"',
+]
+
+[project.optional-dependencies]
+docs = [
+ "sphinx>=3",
+ "sphinx-argparse>=0.2.5",
+ "sphinx-rtd-theme>=0.4.3",
+]
+testing = [
+ "pytest>=1",
+ "coverage>=3,<5",
+]
+
+[project.scripts]
+exec = "pkg.__main__:exec"
+
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+package-dir = {"" = "src"}
+zip-safe = true
+platforms = ["any"]
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.setuptools.cmdclass]
+sdist = "pkg.mod.CustomSdist"
+
+[tool.setuptools.dynamic.version]
+attr = "pkg.__version__.VERSION"
+
+[tool.setuptools.dynamic.readme]
+file = ["README.md"]
+content-type = "text/markdown"
+
+[tool.setuptools.package-data]
+"*" = ["*.txt"]
+
+[tool.setuptools.data-files]
+"data" = ["_files/*.txt"]
+
+[tool.distutils.sdist]
+formats = "gztar"
+
+[tool.distutils.bdist_wheel]
+universal = true
+"""
+
+
+def create_example(path, pkg_root):
+ files = {
+ "pyproject.toml": EXAMPLE,
+ "README.md": "hello world",
+ "_files": {
+ "file.txt": "",
+ },
+ }
+ packages = {
+ "pkg": {
+ "__init__.py": "",
+ "mod.py": "class CustomSdist: pass",
+ "__version__.py": "VERSION = (3, 10)",
+ "__main__.py": "def exec(): print('hello')",
+ },
+ }
+
+ assert pkg_root # Meta-test: cannot be empty string.
+
+ if pkg_root == ".":
+ files = {**files, **packages}
+ # skip other files: flat-layout will raise error for multi-package dist
+ else:
+ # Use this opportunity to ensure namespaces are discovered
+ files[pkg_root] = {**packages, "other": {"nested": {"__init__.py": ""}}}
+
+ jaraco.path.build(files, prefix=path)
+
+
+def verify_example(config, path, pkg_root):
+ pyproject = path / "pyproject.toml"
+ pyproject.write_text(tomli_w.dumps(config), encoding="utf-8")
+ expanded = expand_configuration(config, path)
+ expanded_project = expanded["project"]
+ assert read_configuration(pyproject, expand=True) == expanded
+ assert expanded_project["version"] == "3.10"
+ assert expanded_project["readme"]["text"] == "hello world"
+ assert "packages" in expanded["tool"]["setuptools"]
+ if pkg_root == ".":
+ # Auto-discovery will raise error for multi-package dist
+ assert set(expanded["tool"]["setuptools"]["packages"]) == {"pkg"}
+ else:
+ assert set(expanded["tool"]["setuptools"]["packages"]) == {
+ "pkg",
+ "other",
+ "other.nested",
+ }
+ assert expanded["tool"]["setuptools"]["include-package-data"] is True
+ assert "" in expanded["tool"]["setuptools"]["package-data"]
+ assert "*" not in expanded["tool"]["setuptools"]["package-data"]
+ assert expanded["tool"]["setuptools"]["data-files"] == [
+ ("data", ["_files/file.txt"])
+ ]
+
+
+def test_read_configuration(tmp_path):
+ create_example(tmp_path, "src")
+ pyproject = tmp_path / "pyproject.toml"
+
+ config = read_configuration(pyproject, expand=False)
+ assert config["project"].get("version") is None
+ assert config["project"].get("readme") is None
+
+ verify_example(config, tmp_path, "src")
+
+
+@pytest.mark.parametrize(
+ ("pkg_root", "opts"),
+ [
+ (".", {}),
+ ("src", {}),
+ ("lib", {"packages": {"find": {"where": ["lib"]}}}),
+ ],
+)
+def test_discovered_package_dir_with_attr_directive_in_config(tmp_path, pkg_root, opts):
+ create_example(tmp_path, pkg_root)
+
+ pyproject = tmp_path / "pyproject.toml"
+
+ config = read_configuration(pyproject, expand=False)
+ assert config["project"].get("version") is None
+ assert config["project"].get("readme") is None
+ config["tool"]["setuptools"].pop("packages", None)
+ config["tool"]["setuptools"].pop("package-dir", None)
+
+ config["tool"]["setuptools"].update(opts)
+ verify_example(config, tmp_path, pkg_root)
+
+
+ENTRY_POINTS = {
+ "console_scripts": {"a": "mod.a:func"},
+ "gui_scripts": {"b": "mod.b:func"},
+ "other": {"c": "mod.c:func [extra]"},
+}
+
+
+class TestEntryPoints:
+ def write_entry_points(self, tmp_path):
+ entry_points = ConfigParser()
+ entry_points.read_dict(ENTRY_POINTS)
+ with open(tmp_path / "entry-points.txt", "w", encoding="utf-8") as f:
+ entry_points.write(f)
+
+ def pyproject(self, dynamic=None):
+ project = {"dynamic": dynamic or ["scripts", "gui-scripts", "entry-points"]}
+ tool = {"dynamic": {"entry-points": {"file": "entry-points.txt"}}}
+ return {"project": project, "tool": {"setuptools": tool}}
+
+ def test_all_listed_in_dynamic(self, tmp_path):
+ self.write_entry_points(tmp_path)
+ expanded = expand_configuration(self.pyproject(), tmp_path)
+ expanded_project = expanded["project"]
+ assert len(expanded_project["scripts"]) == 1
+ assert expanded_project["scripts"]["a"] == "mod.a:func"
+ assert len(expanded_project["gui-scripts"]) == 1
+ assert expanded_project["gui-scripts"]["b"] == "mod.b:func"
+ assert len(expanded_project["entry-points"]) == 1
+ assert expanded_project["entry-points"]["other"]["c"] == "mod.c:func [extra]"
+
+ @pytest.mark.parametrize("missing_dynamic", ("scripts", "gui-scripts"))
+ def test_scripts_not_listed_in_dynamic(self, tmp_path, missing_dynamic):
+ self.write_entry_points(tmp_path)
+ dynamic = {"scripts", "gui-scripts", "entry-points"} - {missing_dynamic}
+
+ msg = f"defined outside of `pyproject.toml`:.*{missing_dynamic}"
+ with pytest.raises(OptionError, match=re.compile(msg, re.DOTALL)):
+ expand_configuration(self.pyproject(dynamic), tmp_path)
+
+
+class TestClassifiers:
+ def test_dynamic(self, tmp_path):
+ # Let's create a project example that has dynamic classifiers
+ # coming from a txt file.
+ create_example(tmp_path, "src")
+ classifiers = cleandoc(
+ """
+ Framework :: Flask
+ Programming Language :: Haskell
+ """
+ )
+ (tmp_path / "classifiers.txt").write_text(classifiers, encoding="utf-8")
+
+ pyproject = tmp_path / "pyproject.toml"
+ config = read_configuration(pyproject, expand=False)
+ dynamic = config["project"]["dynamic"]
+ config["project"]["dynamic"] = list({*dynamic, "classifiers"})
+ dynamic_config = config["tool"]["setuptools"]["dynamic"]
+ dynamic_config["classifiers"] = {"file": "classifiers.txt"}
+
+ # When the configuration is expanded,
+ # each line of the file should be an different classifier.
+ validate(config, pyproject)
+ expanded = expand_configuration(config, tmp_path)
+
+ assert set(expanded["project"]["classifiers"]) == {
+ "Framework :: Flask",
+ "Programming Language :: Haskell",
+ }
+
+ def test_dynamic_without_config(self, tmp_path):
+ config = """
+ [project]
+ name = "myproj"
+ version = '42'
+ dynamic = ["classifiers"]
+ """
+
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(config), encoding="utf-8")
+ with pytest.raises(OptionError, match="No configuration .* .classifiers."):
+ read_configuration(pyproject)
+
+ def test_dynamic_readme_from_setup_script_args(self, tmp_path):
+ config = """
+ [project]
+ name = "myproj"
+ version = '42'
+ dynamic = ["readme"]
+ """
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(config), encoding="utf-8")
+ dist = Distribution(attrs={"long_description": "42"})
+ # No error should occur because of missing `readme`
+ dist = apply_configuration(dist, pyproject)
+ assert dist.metadata.long_description == "42"
+
+ def test_dynamic_without_file(self, tmp_path):
+ config = """
+ [project]
+ name = "myproj"
+ version = '42'
+ dynamic = ["classifiers"]
+
+ [tool.setuptools.dynamic]
+ classifiers = {file = ["classifiers.txt"]}
+ """
+
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(config), encoding="utf-8")
+ with pytest.warns(UserWarning, match="File .*classifiers.txt. cannot be found"):
+ expanded = read_configuration(pyproject)
+ assert "classifiers" not in expanded["project"]
+
+
+@pytest.mark.parametrize(
+ "example",
+ (
+ """
+ [project]
+ name = "myproj"
+ version = "1.2"
+
+ [my-tool.that-disrespect.pep518]
+ value = 42
+ """,
+ ),
+)
+def test_ignore_unrelated_config(tmp_path, example):
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(example), encoding="utf-8")
+
+ # Make sure no error is raised due to 3rd party configs in pyproject.toml
+ assert read_configuration(pyproject) is not None
+
+
+@pytest.mark.parametrize(
+ ("example", "error_msg"),
+ [
+ (
+ """
+ [project]
+ name = "myproj"
+ version = "1.2"
+ requires = ['pywin32; platform_system=="Windows"' ]
+ """,
+ "configuration error: .project. must not contain ..requires.. properties",
+ ),
+ ],
+)
+def test_invalid_example(tmp_path, example, error_msg):
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(example), encoding="utf-8")
+
+ pattern = re.compile(
+ f"invalid pyproject.toml.*{error_msg}.*", re.MULTILINE | re.DOTALL
+ )
+ with pytest.raises(ValueError, match=pattern):
+ read_configuration(pyproject)
+
+
+@pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))
+def test_empty(tmp_path, config):
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(config, encoding="utf-8")
+
+ # Make sure no error is raised
+ assert read_configuration(pyproject) == {}
+
+
+@pytest.mark.parametrize("config", ("[project]\nname = 'myproj'\nversion='42'\n",))
+def test_include_package_data_by_default(tmp_path, config):
+ """Builds with ``pyproject.toml`` should consider ``include-package-data=True`` as
+ default.
+ """
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(config, encoding="utf-8")
+
+ config = read_configuration(pyproject)
+ assert config["tool"]["setuptools"]["include-package-data"] is True
+
+
+def test_include_package_data_in_setuppy(tmp_path):
+ """Builds with ``pyproject.toml`` should consider ``include_package_data`` set in
+ ``setup.py``.
+
+ See https://github.com/pypa/setuptools/issues/3197#issuecomment-1079023889
+ """
+ files = {
+ "pyproject.toml": "[project]\nname = 'myproj'\nversion='42'\n",
+ "setup.py": "__import__('setuptools').setup(include_package_data=False)",
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ with Path(tmp_path):
+ dist = distutils.core.run_setup("setup.py", {}, stop_after="config")
+
+ assert dist.get_name() == "myproj"
+ assert dist.get_version() == "42"
+ assert dist.include_package_data is False
+
+
+def test_warn_tools_typo(tmp_path):
+ """Test that the common ``tools.setuptools`` typo in ``pyproject.toml`` issues a warning
+
+ See https://github.com/pypa/setuptools/issues/4150
+ """
+ config = """
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "myproj"
+ version = '42'
+
+ [tools.setuptools]
+ packages = ["package"]
+ """
+
+ pyproject = tmp_path / "pyproject.toml"
+ pyproject.write_text(cleandoc(config), encoding="utf-8")
+
+ with pytest.warns(_ToolsTypoInMetadata):
+ read_configuration(pyproject)
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py b/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fc8050743c4b1a8497d5ea20c571b565d074e59
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/test_pyprojecttoml_dynamic_deps.py
@@ -0,0 +1,111 @@
+from inspect import cleandoc
+
+import pytest
+from jaraco import path
+
+from setuptools.config.pyprojecttoml import apply_configuration
+from setuptools.dist import Distribution
+from setuptools.warnings import SetuptoolsWarning
+
+
+def test_dynamic_dependencies(tmp_path):
+ files = {
+ "requirements.txt": "six\n # comment\n",
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "myproj"
+ version = "1.0"
+ dynamic = ["dependencies"]
+
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [tool.setuptools.dynamic.dependencies]
+ file = ["requirements.txt"]
+ """
+ ),
+ }
+ path.build(files, prefix=tmp_path)
+ dist = Distribution()
+ dist = apply_configuration(dist, tmp_path / "pyproject.toml")
+ assert dist.install_requires == ["six"]
+
+
+def test_dynamic_optional_dependencies(tmp_path):
+ files = {
+ "requirements-docs.txt": "sphinx\n # comment\n",
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "myproj"
+ version = "1.0"
+ dynamic = ["optional-dependencies"]
+
+ [tool.setuptools.dynamic.optional-dependencies.docs]
+ file = ["requirements-docs.txt"]
+
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+ """
+ ),
+ }
+ path.build(files, prefix=tmp_path)
+ dist = Distribution()
+ dist = apply_configuration(dist, tmp_path / "pyproject.toml")
+ assert dist.extras_require == {"docs": ["sphinx"]}
+
+
+def test_mixed_dynamic_optional_dependencies(tmp_path):
+ """
+ Test that if PEP 621 was loosened to allow mixing of dynamic and static
+ configurations in the case of fields containing sub-fields (groups),
+ things would work out.
+ """
+ files = {
+ "requirements-images.txt": "pillow~=42.0\n # comment\n",
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "myproj"
+ version = "1.0"
+ dynamic = ["optional-dependencies"]
+
+ [project.optional-dependencies]
+ docs = ["sphinx"]
+
+ [tool.setuptools.dynamic.optional-dependencies.images]
+ file = ["requirements-images.txt"]
+ """
+ ),
+ }
+
+ path.build(files, prefix=tmp_path)
+ pyproject = tmp_path / "pyproject.toml"
+ with pytest.raises(ValueError, match="project.optional-dependencies"):
+ apply_configuration(Distribution(), pyproject)
+
+
+def test_mixed_extras_require_optional_dependencies(tmp_path):
+ files = {
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "myproj"
+ version = "1.0"
+ optional-dependencies.docs = ["sphinx"]
+ """
+ ),
+ }
+
+ path.build(files, prefix=tmp_path)
+ pyproject = tmp_path / "pyproject.toml"
+
+ dist = Distribution({"extras_require": {"hello": ["world"]}})
+
+ with pytest.warns(SetuptoolsWarning, match=".extras_require. overwritten"):
+ dist = apply_configuration(dist, pyproject)
+
+ assert dist.extras_require == {"docs": ["sphinx"]}
diff --git a/lib/python3.12/site-packages/setuptools/tests/config/test_setupcfg.py b/lib/python3.12/site-packages/setuptools/tests/config/test_setupcfg.py
new file mode 100644
index 0000000000000000000000000000000000000000..495337a9a5815e3d2a0aefa584b8ee81f5136d95
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/config/test_setupcfg.py
@@ -0,0 +1,987 @@
+import configparser
+import contextlib
+import inspect
+import re
+import sys
+from pathlib import Path
+from unittest.mock import Mock, patch
+
+import pytest
+from packaging.requirements import InvalidRequirement
+
+from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration
+from setuptools.dist import Distribution, _Distribution
+from setuptools.warnings import SetuptoolsDeprecationWarning
+
+from ..textwrap import DALS
+
+from distutils.errors import DistutilsFileError, DistutilsOptionError
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+class ErrConfigHandler(ConfigHandler[Target]):
+ """Erroneous handler. Fails to implement required methods."""
+
+ section_prefix = "**err**"
+
+
+def make_package_dir(name, base_dir, ns=False):
+ dir_package = base_dir
+ for dir_name in name.split('/'):
+ dir_package = dir_package.mkdir(dir_name)
+ init_file = None
+ if not ns:
+ init_file = dir_package.join('__init__.py')
+ init_file.write('')
+ return dir_package, init_file
+
+
+def fake_env(
+ tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'
+):
+ if setup_py is None:
+ setup_py = 'from setuptools import setup\nsetup()\n'
+
+ tmpdir.join('setup.py').write(setup_py)
+ config = tmpdir.join('setup.cfg')
+ config.write(setup_cfg.encode(encoding), mode='wb')
+
+ package_dir, init_file = make_package_dir(package_path, tmpdir)
+
+ init_file.write(
+ 'VERSION = (1, 2, 3)\n'
+ '\n'
+ 'VERSION_MAJOR = 1'
+ '\n'
+ 'def get_version():\n'
+ ' return [3, 4, 5, "dev"]\n'
+ '\n'
+ )
+
+ return package_dir, config
+
+
+@contextlib.contextmanager
+def get_dist(tmpdir, kwargs_initial=None, parse=True):
+ kwargs_initial = kwargs_initial or {}
+
+ with tmpdir.as_cwd():
+ dist = Distribution(kwargs_initial)
+ dist.script_name = 'setup.py'
+ parse and dist.parse_config_files()
+
+ yield dist
+
+
+def test_parsers_implemented():
+ with pytest.raises(NotImplementedError):
+ handler = ErrConfigHandler(None, {}, False, Mock())
+ handler.parsers
+
+
+class TestConfigurationReader:
+ def test_basic(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'keywords = one, two\n'
+ '\n'
+ '[options]\n'
+ 'scripts = bin/a.py, bin/b.py\n',
+ )
+ config_dict = read_configuration(str(config))
+ assert config_dict['metadata']['version'] == '10.1.1'
+ assert config_dict['metadata']['keywords'] == ['one', 'two']
+ assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']
+
+ def test_no_config(self, tmpdir):
+ with pytest.raises(DistutilsFileError):
+ read_configuration(str(tmpdir.join('setup.cfg')))
+
+ def test_ignore_errors(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\nversion = attr: none.VERSION\nkeywords = one, two\n',
+ )
+ with pytest.raises(ImportError):
+ read_configuration(str(config))
+
+ config_dict = read_configuration(str(config), ignore_option_errors=True)
+
+ assert config_dict['metadata']['keywords'] == ['one', 'two']
+ assert 'version' not in config_dict['metadata']
+
+ config.remove()
+
+
+class TestMetadata:
+ def test_basic(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'description = Some description\n'
+ 'long_description_content_type = text/something\n'
+ 'long_description = file: README\n'
+ 'name = fake_name\n'
+ 'keywords = one, two\n'
+ 'provides = package, package.sub\n'
+ 'license = otherlic\n'
+ 'download_url = http://test.test.com/test/\n'
+ 'maintainer_email = test@test.com\n',
+ )
+
+ tmpdir.join('README').write('readme contents\nline2')
+
+ meta_initial = {
+ # This will be used so `otherlic` won't replace it.
+ 'license': 'BSD 3-Clause License',
+ }
+
+ with get_dist(tmpdir, meta_initial) as dist:
+ metadata = dist.metadata
+
+ assert metadata.version == '10.1.1'
+ assert metadata.description == 'Some description'
+ assert metadata.long_description_content_type == 'text/something'
+ assert metadata.long_description == 'readme contents\nline2'
+ assert metadata.provides == ['package', 'package.sub']
+ assert metadata.license == 'BSD 3-Clause License'
+ assert metadata.name == 'fake_name'
+ assert metadata.keywords == ['one', 'two']
+ assert metadata.download_url == 'http://test.test.com/test/'
+ assert metadata.maintainer_email == 'test@test.com'
+
+ def test_license_cfg(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS(
+ """
+ [metadata]
+ name=foo
+ version=0.0.1
+ license=Apache 2.0
+ """
+ ),
+ )
+
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.name == "foo"
+ assert metadata.version == "0.0.1"
+ assert metadata.license == "Apache 2.0"
+
+ def test_file_mixed(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\nlong_description = file: README.rst, CHANGES.rst\n\n',
+ )
+
+ tmpdir.join('README.rst').write('readme contents\nline2')
+ tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.long_description == (
+ 'readme contents\nline2\nchangelog contents\nand stuff'
+ )
+
+ def test_file_sandboxed(self, tmpdir):
+ tmpdir.ensure("README")
+ project = tmpdir.join('depth1', 'depth2')
+ project.ensure(dir=True)
+ fake_env(project, '[metadata]\nlong_description = file: ../../README\n')
+
+ with get_dist(project, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files() # file: out of sandbox
+
+ def test_aliases(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'author_email = test@test.com\n'
+ 'home_page = http://test.test.com/test/\n'
+ 'summary = Short summary\n'
+ 'platform = a, b\n'
+ 'classifier =\n'
+ ' Framework :: Django\n'
+ ' Programming Language :: Python :: 3.5\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.author_email == 'test@test.com'
+ assert metadata.url == 'http://test.test.com/test/'
+ assert metadata.description == 'Short summary'
+ assert metadata.platforms == ['a', 'b']
+ assert metadata.classifiers == [
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3.5',
+ ]
+
+ def test_multiline(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'name = fake_name\n'
+ 'keywords =\n'
+ ' one\n'
+ ' two\n'
+ 'classifiers =\n'
+ ' Framework :: Django\n'
+ ' Programming Language :: Python :: 3.5\n',
+ )
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.keywords == ['one', 'two']
+ assert metadata.classifiers == [
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3.5',
+ ]
+
+ def test_dict(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'project_urls =\n'
+ ' Link One = https://example.com/one/\n'
+ ' Link Two = https://example.com/two/\n',
+ )
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.project_urls == {
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ }
+
+ def test_version(self, tmpdir):
+ package_dir, config = fake_env(
+ tmpdir, '[metadata]\nversion = attr: fake_package.VERSION\n'
+ )
+
+ sub_a = package_dir.mkdir('subpkg_a')
+ sub_a.join('__init__.py').write('')
+ sub_a.join('mod.py').write('VERSION = (2016, 11, 26)')
+
+ sub_b = package_dir.mkdir('subpkg_b')
+ sub_b.join('__init__.py').write('')
+ sub_b.join('mod.py').write(
+ 'import third_party_module\nVERSION = (2016, 11, 26)'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ config.write('[metadata]\nversion = attr: fake_package.get_version\n')
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '3.4.5.dev'
+
+ config.write('[metadata]\nversion = attr: fake_package.VERSION_MAJOR\n')
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1'
+
+ config.write('[metadata]\nversion = attr: fake_package.subpkg_a.mod.VERSION\n')
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '2016.11.26'
+
+ config.write('[metadata]\nversion = attr: fake_package.subpkg_b.mod.VERSION\n')
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '2016.11.26'
+
+ def test_version_file(self, tmpdir):
+ fake_env(tmpdir, '[metadata]\nversion = file: fake_package/version.txt\n')
+ tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
+ with pytest.raises(DistutilsOptionError):
+ with get_dist(tmpdir) as dist:
+ dist.metadata.version
+
+ def test_version_with_package_dir_simple(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_simple.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' = src\n',
+ package_path='src/fake_package_simple',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ def test_version_with_package_dir_rename(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_rename.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' fake_package_rename = fake_dir\n',
+ package_path='fake_dir',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ def test_version_with_package_dir_complex(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_complex.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' fake_package_complex = src/fake_dir\n',
+ package_path='src/fake_dir',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ def test_unknown_meta_item(self, tmpdir):
+ fake_env(tmpdir, '[metadata]\nname = fake_name\nunknown = some\n')
+ with get_dist(tmpdir, parse=False) as dist:
+ dist.parse_config_files() # Skip unknown.
+
+ def test_usupported_section(self, tmpdir):
+ fake_env(tmpdir, '[metadata.some]\nkey = val\n')
+ with get_dist(tmpdir, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files()
+
+ def test_classifiers(self, tmpdir):
+ expected = set([
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.5',
+ ])
+
+ # From file.
+ _, config = fake_env(tmpdir, '[metadata]\nclassifiers = file: classifiers\n')
+
+ tmpdir.join('classifiers').write(
+ 'Framework :: Django\n'
+ 'Programming Language :: Python :: 3\n'
+ 'Programming Language :: Python :: 3.5\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.metadata.classifiers) == expected
+
+ # From list notation
+ config.write(
+ '[metadata]\n'
+ 'classifiers =\n'
+ ' Framework :: Django\n'
+ ' Programming Language :: Python :: 3\n'
+ ' Programming Language :: Python :: 3.5\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert set(dist.metadata.classifiers) == expected
+
+ def test_interpolation(self, tmpdir):
+ fake_env(tmpdir, '[metadata]\ndescription = %(message)s\n')
+ with pytest.raises(configparser.InterpolationMissingOptionError):
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_1(self, tmpdir):
+ fake_env(tmpdir, '[metadata]\ndescription = éàïôñ\n', encoding='utf-8')
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_3(self, tmpdir):
+ fake_env(tmpdir, '\n# -*- coding: invalid\n')
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_4(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '# -*- coding: utf-8\n[metadata]\ndescription = éàïôñ\n',
+ encoding='utf-8',
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.description == 'éàïôñ'
+
+ def test_not_utf8(self, tmpdir):
+ """
+ Config files encoded not in UTF-8 will fail
+ """
+ fake_env(
+ tmpdir,
+ '# vim: set fileencoding=iso-8859-15 :\n[metadata]\ndescription = éàïôñ\n',
+ encoding='iso-8859-15',
+ )
+ with pytest.raises(UnicodeDecodeError):
+ with get_dist(tmpdir):
+ pass
+
+ @pytest.mark.parametrize(
+ ("error_msg", "config", "invalid"),
+ [
+ (
+ "Invalid dash-separated key 'author-email' in 'metadata' (setup.cfg)",
+ DALS(
+ """
+ [metadata]
+ author-email = test@test.com
+ maintainer_email = foo@foo.com
+ """
+ ),
+ {"author-email": "test@test.com"},
+ ),
+ (
+ "Invalid uppercase key 'Name' in 'metadata' (setup.cfg)",
+ DALS(
+ """
+ [metadata]
+ Name = foo
+ description = Some description
+ """
+ ),
+ {"Name": "foo"},
+ ),
+ ],
+ )
+ def test_invalid_options_previously_deprecated(
+ self, tmpdir, error_msg, config, invalid
+ ):
+ # This test and related methods can be removed when no longer needed.
+ # Deprecation postponed due to push-back from the community in
+ # https://github.com/pypa/setuptools/issues/4910
+ fake_env(tmpdir, config)
+ with pytest.warns(SetuptoolsDeprecationWarning, match=re.escape(error_msg)):
+ dist = get_dist(tmpdir).__enter__()
+
+ tmpdir.join('setup.cfg').remove()
+
+ for field, value in invalid.items():
+ attr = field.replace("-", "_").lower()
+ assert getattr(dist.metadata, attr) == value
+
+
+class TestOptions:
+ def test_basic(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'zip_safe = True\n'
+ 'include_package_data = yes\n'
+ 'package_dir = b=c, =src\n'
+ 'packages = pack_a, pack_b.subpack\n'
+ 'namespace_packages = pack1, pack2\n'
+ 'scripts = bin/one.py, bin/two.py\n'
+ 'eager_resources = bin/one.py, bin/two.py\n'
+ 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n'
+ 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
+ 'dependency_links = http://some.com/here/1, '
+ 'http://some.com/there/2\n'
+ 'python_requires = >=1.0, !=2.8\n'
+ 'py_modules = module1, module2\n',
+ )
+ deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
+ with deprec, get_dist(tmpdir) as dist:
+ assert dist.zip_safe
+ assert dist.include_package_data
+ assert dist.package_dir == {'': 'src', 'b': 'c'}
+ assert dist.packages == ['pack_a', 'pack_b.subpack']
+ assert dist.namespace_packages == ['pack1', 'pack2']
+ assert dist.scripts == ['bin/one.py', 'bin/two.py']
+ assert dist.dependency_links == ([
+ 'http://some.com/here/1',
+ 'http://some.com/there/2',
+ ])
+ assert dist.install_requires == ([
+ 'docutils>=0.3',
+ 'pack==1.1,==1.3',
+ 'hey',
+ ])
+ assert dist.setup_requires == ([
+ 'docutils>=0.3',
+ 'spack ==1.1, ==1.3',
+ 'there',
+ ])
+ assert dist.python_requires == '>=1.0, !=2.8'
+ assert dist.py_modules == ['module1', 'module2']
+
+ def test_multiline(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'package_dir = \n'
+ ' b=c\n'
+ ' =src\n'
+ 'packages = \n'
+ ' pack_a\n'
+ ' pack_b.subpack\n'
+ 'namespace_packages = \n'
+ ' pack1\n'
+ ' pack2\n'
+ 'scripts = \n'
+ ' bin/one.py\n'
+ ' bin/two.py\n'
+ 'eager_resources = \n'
+ ' bin/one.py\n'
+ ' bin/two.py\n'
+ 'install_requires = \n'
+ ' docutils>=0.3\n'
+ ' pack ==1.1, ==1.3\n'
+ ' hey\n'
+ 'setup_requires = \n'
+ ' docutils>=0.3\n'
+ ' spack ==1.1, ==1.3\n'
+ ' there\n'
+ 'dependency_links = \n'
+ ' http://some.com/here/1\n'
+ ' http://some.com/there/2\n',
+ )
+ deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")
+ with deprec, get_dist(tmpdir) as dist:
+ assert dist.package_dir == {'': 'src', 'b': 'c'}
+ assert dist.packages == ['pack_a', 'pack_b.subpack']
+ assert dist.namespace_packages == ['pack1', 'pack2']
+ assert dist.scripts == ['bin/one.py', 'bin/two.py']
+ assert dist.dependency_links == ([
+ 'http://some.com/here/1',
+ 'http://some.com/there/2',
+ ])
+ assert dist.install_requires == ([
+ 'docutils>=0.3',
+ 'pack==1.1,==1.3',
+ 'hey',
+ ])
+ assert dist.setup_requires == ([
+ 'docutils>=0.3',
+ 'spack ==1.1, ==1.3',
+ 'there',
+ ])
+
+ def test_package_dir_fail(self, tmpdir):
+ fake_env(tmpdir, '[options]\npackage_dir = a b\n')
+ with get_dist(tmpdir, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files()
+
+ def test_package_data(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.package_data]\n'
+ '* = *.txt, *.rst\n'
+ 'hello = *.msg\n'
+ '\n'
+ '[options.exclude_package_data]\n'
+ '* = fake1.txt, fake2.txt\n'
+ 'hello = *.dat\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.package_data == {
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ }
+ assert dist.exclude_package_data == {
+ '': ['fake1.txt', 'fake2.txt'],
+ 'hello': ['*.dat'],
+ }
+
+ def test_packages(self, tmpdir):
+ fake_env(tmpdir, '[options]\npackages = find:\n')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.packages == ['fake_package']
+
+ def test_find_directive(self, tmpdir):
+ dir_package, config = fake_env(tmpdir, '[options]\npackages = find:\n')
+
+ make_package_dir('sub_one', dir_package)
+ make_package_dir('sub_two', dir_package)
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == set([
+ 'fake_package',
+ 'fake_package.sub_two',
+ 'fake_package.sub_one',
+ ])
+
+ config.write(
+ '[options]\n'
+ 'packages = find:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'where = .\n'
+ 'include =\n'
+ ' fake_package.sub_one\n'
+ ' two\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.packages == ['fake_package.sub_one']
+
+ config.write(
+ '[options]\n'
+ 'packages = find:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'exclude =\n'
+ ' fake_package.sub_one\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == set(['fake_package', 'fake_package.sub_two'])
+
+ def test_find_namespace_directive(self, tmpdir):
+ dir_package, config = fake_env(
+ tmpdir, '[options]\npackages = find_namespace:\n'
+ )
+
+ make_package_dir('sub_one', dir_package)
+ make_package_dir('sub_two', dir_package, ns=True)
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == {
+ 'fake_package',
+ 'fake_package.sub_two',
+ 'fake_package.sub_one',
+ }
+
+ config.write(
+ '[options]\n'
+ 'packages = find_namespace:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'where = .\n'
+ 'include =\n'
+ ' fake_package.sub_one\n'
+ ' two\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.packages == ['fake_package.sub_one']
+
+ config.write(
+ '[options]\n'
+ 'packages = find_namespace:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'exclude =\n'
+ ' fake_package.sub_one\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == {'fake_package', 'fake_package.sub_two'}
+
+ def test_extras_require(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.extras_require]\n'
+ 'pdf = ReportLab>=1.2; RXP\n'
+ 'rest = \n'
+ ' docutils>=0.3\n'
+ ' pack ==1.1, ==1.3\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.extras_require == {
+ 'pdf': ['ReportLab>=1.2', 'RXP'],
+ 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'],
+ }
+ assert set(dist.metadata.provides_extras) == {'pdf', 'rest'}
+
+ @pytest.mark.parametrize(
+ "config",
+ [
+ "[options.extras_require]\nfoo = bar;python_version<'3'",
+ "[options.extras_require]\nfoo = bar;os_name=='linux'",
+ "[options.extras_require]\nfoo = bar;python_version<'3'\n",
+ "[options.extras_require]\nfoo = bar;os_name=='linux'\n",
+ "[options]\ninstall_requires = bar;python_version<'3'",
+ "[options]\ninstall_requires = bar;os_name=='linux'",
+ "[options]\ninstall_requires = bar;python_version<'3'\n",
+ "[options]\ninstall_requires = bar;os_name=='linux'\n",
+ ],
+ )
+ @pytest.mark.xfail(IS_PYPY, reason="Exceptions missing on PyPy")
+ # TODO: investigate PyPy problem
+ def test_raises_accidental_env_marker_misconfig(self, config, tmpdir):
+ fake_env(tmpdir, config)
+ match = (
+ r"One of the parsed requirements in `(install_requires|extras_require.+)` "
+ "looks like a valid environment marker.*"
+ )
+ with pytest.raises(InvalidRequirement, match=match):
+ with get_dist(tmpdir) as _:
+ pass
+
+ @pytest.mark.parametrize(
+ "config",
+ [
+ "[options.extras_require]\nfoo = bar;python_version<3",
+ "[options.extras_require]\nfoo = bar;python_version<3\n",
+ "[options]\ninstall_requires = bar;python_version<3",
+ "[options]\ninstall_requires = bar;python_version<3\n",
+ ],
+ )
+ @pytest.mark.xfail(IS_PYPY, reason="Warnings missing on PyPy (minor issue)")
+ # TODO: investigate PyPy problem
+ def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):
+ fake_env(tmpdir, config)
+ match = (
+ r"One of the parsed requirements in `(install_requires|extras_require.+)` "
+ "looks like a valid environment marker.*"
+ )
+ with pytest.warns(SetuptoolsDeprecationWarning, match=match):
+ with get_dist(tmpdir) as _:
+ pass
+
+ @pytest.mark.parametrize(
+ "config",
+ [
+ "[options.extras_require]\nfoo =\n bar;python_version<'3'",
+ "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",
+ "[options.extras_require]\nfoo =\n bar;python_version<'3'\n",
+ "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",
+ "[options.extras_require]\nfoo =\n bar\n python_version<3\n",
+ "[options]\ninstall_requires =\n bar;python_version<'3'",
+ "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",
+ "[options]\ninstall_requires =\n bar;python_version<'3'\n",
+ "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",
+ "[options]\ninstall_requires =\n bar\n python_version<3\n",
+ ],
+ )
+ @pytest.mark.filterwarnings("error::setuptools.SetuptoolsDeprecationWarning")
+ def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):
+ fake_env(tmpdir, config)
+ num_warnings = len(recwarn)
+ with get_dist(tmpdir) as _:
+ pass
+ # The examples are valid, no warnings shown
+ assert len(recwarn) == num_warnings
+
+ def test_dash_preserved_extras_require(self, tmpdir):
+ fake_env(tmpdir, '[options.extras_require]\nfoo-a = foo\nfoo_b = test\n')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.extras_require == {'foo-a': ['foo'], 'foo_b': ['test']}
+
+ def test_entry_points(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[options.entry_points]\n'
+ 'group1 = point1 = pack.module:func, '
+ '.point2 = pack.module2:func_rest [rest]\n'
+ 'group2 = point3 = pack.module:func2\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == {
+ 'group1': [
+ 'point1 = pack.module:func',
+ '.point2 = pack.module2:func_rest [rest]',
+ ],
+ 'group2': ['point3 = pack.module:func2'],
+ }
+
+ expected = (
+ '[blogtool.parsers]\n'
+ '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'
+ )
+
+ tmpdir.join('entry_points').write(expected)
+
+ # From file.
+ config.write('[options]\nentry_points = file: entry_points\n')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == expected
+
+ def test_case_sensitive_entry_points(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.entry_points]\n'
+ 'GROUP1 = point1 = pack.module:func, '
+ '.point2 = pack.module2:func_rest [rest]\n'
+ 'group2 = point3 = pack.module:func2\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == {
+ 'GROUP1': [
+ 'point1 = pack.module:func',
+ '.point2 = pack.module2:func_rest [rest]',
+ ],
+ 'group2': ['point3 = pack.module:func2'],
+ }
+
+ def test_data_files(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.data_files]\n'
+ 'cfg =\n'
+ ' a/b.conf\n'
+ ' c/d.conf\n'
+ 'data = e/f.dat, g/h.dat\n',
+ )
+
+ with get_dist(tmpdir) as dist:
+ expected = [
+ ('cfg', ['a/b.conf', 'c/d.conf']),
+ ('data', ['e/f.dat', 'g/h.dat']),
+ ]
+ assert sorted(dist.data_files) == sorted(expected)
+
+ def test_data_files_globby(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.data_files]\n'
+ 'cfg =\n'
+ ' a/b.conf\n'
+ ' c/d.conf\n'
+ 'data = *.dat\n'
+ 'icons = \n'
+ ' *.ico\n'
+ 'audio = \n'
+ ' *.wav\n'
+ ' sounds.db\n',
+ )
+
+ # Create dummy files for glob()'s sake:
+ tmpdir.join('a.dat').write('')
+ tmpdir.join('b.dat').write('')
+ tmpdir.join('c.dat').write('')
+ tmpdir.join('a.ico').write('')
+ tmpdir.join('b.ico').write('')
+ tmpdir.join('c.ico').write('')
+ tmpdir.join('beep.wav').write('')
+ tmpdir.join('boop.wav').write('')
+ tmpdir.join('sounds.db').write('')
+
+ with get_dist(tmpdir) as dist:
+ expected = [
+ ('cfg', ['a/b.conf', 'c/d.conf']),
+ ('data', ['a.dat', 'b.dat', 'c.dat']),
+ ('icons', ['a.ico', 'b.ico', 'c.ico']),
+ ('audio', ['beep.wav', 'boop.wav', 'sounds.db']),
+ ]
+ assert sorted(dist.data_files) == sorted(expected)
+
+ def test_python_requires_simple(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS(
+ """
+ [options]
+ python_requires=>=2.7
+ """
+ ),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_compound(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS(
+ """
+ [options]
+ python_requires=>=2.7,!=3.0.*
+ """
+ ),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_invalid(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS(
+ """
+ [options]
+ python_requires=invalid
+ """
+ ),
+ )
+ with pytest.raises(Exception):
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_cmdclass(self, tmpdir):
+ module_path = Path(tmpdir, "src/custom_build.py") # auto discovery for src
+ module_path.parent.mkdir(parents=True, exist_ok=True)
+ module_path.write_text(
+ "from distutils.core import Command\nclass CustomCmd(Command): pass\n",
+ encoding="utf-8",
+ )
+
+ setup_cfg = """
+ [options]
+ cmdclass =
+ customcmd = custom_build.CustomCmd
+ """
+ fake_env(tmpdir, inspect.cleandoc(setup_cfg))
+
+ with get_dist(tmpdir) as dist:
+ cmdclass = dist.cmdclass['customcmd']
+ assert cmdclass.__name__ == "CustomCmd"
+ assert cmdclass.__module__ == "custom_build"
+ assert module_path.samefile(inspect.getfile(cmdclass))
+
+ def test_requirements_file(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS(
+ """
+ [options]
+ install_requires = file:requirements.txt
+ [options.extras_require]
+ colors = file:requirements-extra.txt
+ """
+ ),
+ )
+
+ tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')
+ tmpdir.join('requirements-extra.txt').write('colorama')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.install_requires == ['docutils>=0.3']
+ assert dist.extras_require == {'colors': ['colorama']}
+
+
+saved_dist_init = _Distribution.__init__
+
+
+class TestExternalSetters:
+ # During creation of the setuptools Distribution() object, we call
+ # the init of the parent distutils Distribution object via
+ # _Distribution.__init__ ().
+ #
+ # It's possible distutils calls out to various keyword
+ # implementations (i.e. distutils.setup_keywords entry points)
+ # that may set a range of variables.
+ #
+ # This wraps distutil's Distribution.__init__ and simulates
+ # pbr or something else setting these values.
+ def _fake_distribution_init(self, dist, attrs):
+ saved_dist_init(dist, attrs)
+ # see self._DISTUTILS_UNSUPPORTED_METADATA
+ dist.metadata.long_description_content_type = 'text/something'
+ # Test overwrite setup() args
+ dist.metadata.project_urls = {
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ }
+
+ @patch.object(_Distribution, '__init__', autospec=True)
+ def test_external_setters(self, mock_parent_init, tmpdir):
+ mock_parent_init.side_effect = self._fake_distribution_init
+
+ dist = Distribution(attrs={'project_urls': {'will_be': 'ignored'}})
+
+ assert dist.metadata.long_description_content_type == 'text/something'
+ assert dist.metadata.project_urls == {
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ }
diff --git a/lib/python3.12/site-packages/setuptools/tests/contexts.py b/lib/python3.12/site-packages/setuptools/tests/contexts.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c931bbd4fd9046702d850a18877622651882d7a
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/contexts.py
@@ -0,0 +1,131 @@
+import contextlib
+import io
+import os
+import shutil
+import site
+import sys
+import tempfile
+
+from filelock import FileLock
+
+
+@contextlib.contextmanager
+def tempdir(cd=lambda dir: None, **kwargs):
+ temp_dir = tempfile.mkdtemp(**kwargs)
+ orig_dir = os.getcwd()
+ try:
+ cd(temp_dir)
+ yield temp_dir
+ finally:
+ cd(orig_dir)
+ shutil.rmtree(temp_dir)
+
+
+@contextlib.contextmanager
+def environment(**replacements):
+ """
+ In a context, patch the environment with replacements. Pass None values
+ to clear the values.
+ """
+ saved = dict((key, os.environ[key]) for key in replacements if key in os.environ)
+
+ # remove values that are null
+ remove = (key for (key, value) in replacements.items() if value is None)
+ for key in list(remove):
+ os.environ.pop(key, None)
+ replacements.pop(key)
+
+ os.environ.update(replacements)
+
+ try:
+ yield saved
+ finally:
+ for key in replacements:
+ os.environ.pop(key, None)
+ os.environ.update(saved)
+
+
+@contextlib.contextmanager
+def quiet():
+ """
+ Redirect stdout/stderr to StringIO objects to prevent console output from
+ distutils commands.
+ """
+
+ old_stdout = sys.stdout
+ old_stderr = sys.stderr
+ new_stdout = sys.stdout = io.StringIO()
+ new_stderr = sys.stderr = io.StringIO()
+ try:
+ yield new_stdout, new_stderr
+ finally:
+ new_stdout.seek(0)
+ new_stderr.seek(0)
+ sys.stdout = old_stdout
+ sys.stderr = old_stderr
+
+
+@contextlib.contextmanager
+def save_user_site_setting():
+ saved = site.ENABLE_USER_SITE
+ try:
+ yield saved
+ finally:
+ site.ENABLE_USER_SITE = saved
+
+
+@contextlib.contextmanager
+def suppress_exceptions(*excs):
+ try:
+ yield
+ except excs:
+ pass
+
+
+def multiproc(request):
+ """
+ Return True if running under xdist and multiple
+ workers are used.
+ """
+ try:
+ worker_id = request.getfixturevalue('worker_id')
+ except Exception:
+ return False
+ return worker_id != 'master'
+
+
+@contextlib.contextmanager
+def session_locked_tmp_dir(request, tmp_path_factory, name):
+ """Uses a file lock to guarantee only one worker can access a temp dir"""
+ # get the temp directory shared by all workers
+ base = tmp_path_factory.getbasetemp()
+ shared_dir = base.parent if multiproc(request) else base
+
+ locked_dir = shared_dir / name
+ with FileLock(locked_dir.with_suffix(".lock")):
+ # ^-- prevent multiple workers to access the directory at once
+ locked_dir.mkdir(exist_ok=True, parents=True)
+ yield locked_dir
+
+
+@contextlib.contextmanager
+def save_paths():
+ """Make sure ``sys.path``, ``sys.meta_path`` and ``sys.path_hooks`` are preserved"""
+ prev = sys.path[:], sys.meta_path[:], sys.path_hooks[:]
+
+ try:
+ yield
+ finally:
+ sys.path, sys.meta_path, sys.path_hooks = prev
+
+
+@contextlib.contextmanager
+def save_sys_modules():
+ """Make sure initial ``sys.modules`` is preserved"""
+ prev_modules = sys.modules
+
+ try:
+ sys.modules = sys.modules.copy()
+ yield
+ finally:
+ sys.modules = prev_modules
diff --git a/lib/python3.12/site-packages/setuptools/tests/environment.py b/lib/python3.12/site-packages/setuptools/tests/environment.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed5499ef7d73762d033a4877bfe586d6c0b82235
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/environment.py
@@ -0,0 +1,95 @@
+import os
+import subprocess
+import sys
+import unicodedata
+from subprocess import PIPE as _PIPE, Popen as _Popen
+
+import jaraco.envs
+
+
+class VirtualEnv(jaraco.envs.VirtualEnv):
+ name = '.env'
+ # Some version of PyPy will import distutils on startup, implicitly
+ # importing setuptools, and thus leading to BackendInvalid errors
+ # when upgrading Setuptools. Bypass this behavior by avoiding the
+ # early availability and need to upgrade.
+ create_opts = ['--no-setuptools']
+
+ def run(self, cmd, *args, **kwargs):
+ cmd = [self.exe(cmd[0])] + cmd[1:]
+ kwargs = {"cwd": self.root, "encoding": "utf-8", **kwargs} # Allow overriding
+ # In some environments (eg. downstream distro packaging), where:
+ # - tox isn't used to run tests and
+ # - PYTHONPATH is set to point to a specific setuptools codebase and
+ # - no custom env is explicitly set by a test
+ # PYTHONPATH will leak into the spawned processes.
+ # In that case tests look for module in the wrong place (on PYTHONPATH).
+ # Unless the test sets its own special env, pass a copy of the existing
+ # environment with removed PYTHONPATH to the subprocesses.
+ if "env" not in kwargs:
+ env = dict(os.environ)
+ if "PYTHONPATH" in env:
+ del env["PYTHONPATH"]
+ kwargs["env"] = env
+ return subprocess.check_output(cmd, *args, **kwargs)
+
+
+def _which_dirs(cmd):
+ result = set()
+ for path in os.environ.get('PATH', '').split(os.pathsep):
+ filename = os.path.join(path, cmd)
+ if os.access(filename, os.X_OK):
+ result.add(path)
+ return result
+
+
+def run_setup_py(cmd, pypath=None, path=None, data_stream=0, env=None):
+ """
+ Execution command for tests, separate from those used by the
+ code directly to prevent accidental behavior issues
+ """
+ if env is None:
+ env = dict()
+ for envname in os.environ:
+ env[envname] = os.environ[envname]
+
+ # override the python path if needed
+ if pypath is not None:
+ env["PYTHONPATH"] = pypath
+
+ # override the execution path if needed
+ if path is not None:
+ env["PATH"] = path
+ if not env.get("PATH", ""):
+ env["PATH"] = _which_dirs("tar").union(_which_dirs("gzip"))
+ env["PATH"] = os.pathsep.join(env["PATH"])
+
+ cmd = [sys.executable, "setup.py"] + list(cmd)
+
+ # https://bugs.python.org/issue8557
+ shell = sys.platform == 'win32'
+
+ try:
+ proc = _Popen(
+ cmd,
+ stdout=_PIPE,
+ stderr=_PIPE,
+ shell=shell,
+ env=env,
+ encoding="utf-8",
+ )
+
+ if isinstance(data_stream, tuple):
+ data_stream = slice(*data_stream)
+ data = proc.communicate()[data_stream]
+ except OSError:
+ return 1, ''
+
+ # decode the console string if needed
+ if hasattr(data, "decode"):
+ # use the default encoding
+ data = data.decode()
+ data = unicodedata.normalize('NFC', data)
+
+ # communicate calls wait()
+ return proc.returncode, data
diff --git a/lib/python3.12/site-packages/setuptools/tests/fixtures.py b/lib/python3.12/site-packages/setuptools/tests/fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..20b31d4681377745bc2ddaeaf5f0074b4050e5ba
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/fixtures.py
@@ -0,0 +1,406 @@
+import contextlib
+import io
+import os
+import subprocess
+import sys
+import tarfile
+import time
+from pathlib import Path
+
+import jaraco.path
+import path
+import pytest
+
+from setuptools._normalization import safer_name
+
+from . import contexts, environment
+from .textwrap import DALS
+
+
+@pytest.fixture
+def user_override(monkeypatch):
+ """
+ Override site.USER_BASE and site.USER_SITE with temporary directories in
+ a context.
+ """
+ with contexts.tempdir() as user_base:
+ monkeypatch.setattr('site.USER_BASE', user_base)
+ with contexts.tempdir() as user_site:
+ monkeypatch.setattr('site.USER_SITE', user_site)
+ with contexts.save_user_site_setting():
+ yield
+
+
+@pytest.fixture
+def tmpdir_cwd(tmpdir):
+ with tmpdir.as_cwd() as orig:
+ yield orig
+
+
+@pytest.fixture(autouse=True, scope="session")
+def workaround_xdist_376(request):
+ """
+ Workaround pytest-dev/pytest-xdist#376
+
+ ``pytest-xdist`` tends to inject '' into ``sys.path``,
+ which may break certain isolation expectations.
+ Remove the entry so the import
+ machinery behaves the same irrespective of xdist.
+ """
+ if not request.config.pluginmanager.has_plugin('xdist'):
+ return
+
+ with contextlib.suppress(ValueError):
+ sys.path.remove('')
+
+
+@pytest.fixture
+def sample_project(tmp_path):
+ """
+ Clone the 'sampleproject' and return a path to it.
+ """
+ cmd = ['git', 'clone', 'https://github.com/pypa/sampleproject']
+ try:
+ subprocess.check_call(cmd, cwd=str(tmp_path))
+ except Exception:
+ pytest.skip("Unable to clone sampleproject")
+ return tmp_path / 'sampleproject'
+
+
+@pytest.fixture
+def sample_project_cwd(sample_project):
+ with path.Path(sample_project):
+ yield
+
+
+# sdist and wheel artifacts should be stable across a round of tests
+# so we can build them once per session and use the files as "readonly"
+
+# In the case of setuptools, building the wheel without sdist may cause
+# it to contain the `build` directory, and therefore create situations with
+# `setuptools/build/lib/build/lib/...`. To avoid that, build both artifacts at once.
+
+
+def _build_distributions(tmp_path_factory, request):
+ with contexts.session_locked_tmp_dir(
+ request, tmp_path_factory, "dist_build"
+ ) as tmp: # pragma: no cover
+ sdist = next(tmp.glob("*.tar.gz"), None)
+ wheel = next(tmp.glob("*.whl"), None)
+ if sdist and wheel:
+ return (sdist, wheel)
+
+ # Sanity check: should not create recursive setuptools/build/lib/build/lib/...
+ assert not Path(request.config.rootdir, "build/lib/build").exists()
+
+ subprocess.check_output([
+ sys.executable,
+ "-m",
+ "build",
+ "--outdir",
+ str(tmp),
+ str(request.config.rootdir),
+ ])
+
+ # Sanity check: should not create recursive setuptools/build/lib/build/lib/...
+ assert not Path(request.config.rootdir, "build/lib/build").exists()
+
+ return next(tmp.glob("*.tar.gz")), next(tmp.glob("*.whl"))
+
+
+@pytest.fixture(scope="session")
+def setuptools_sdist(tmp_path_factory, request):
+ prebuilt = os.getenv("PRE_BUILT_SETUPTOOLS_SDIST")
+ if prebuilt and os.path.exists(prebuilt): # pragma: no cover
+ return Path(prebuilt).resolve()
+
+ sdist, _ = _build_distributions(tmp_path_factory, request)
+ return sdist
+
+
+@pytest.fixture(scope="session")
+def setuptools_wheel(tmp_path_factory, request):
+ prebuilt = os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL")
+ if prebuilt and os.path.exists(prebuilt): # pragma: no cover
+ return Path(prebuilt).resolve()
+
+ _, wheel = _build_distributions(tmp_path_factory, request)
+ return wheel
+
+
+@pytest.fixture
+def venv(tmp_path, setuptools_wheel):
+ """Virtual env with the version of setuptools under test installed"""
+ env = environment.VirtualEnv()
+ env.root = path.Path(tmp_path / 'venv')
+ env.create_opts = ['--no-setuptools', '--wheel=bundle']
+ # TODO: Use `--no-wheel` when setuptools implements its own bdist_wheel
+ env.req = str(setuptools_wheel)
+ # In some environments (eg. downstream distro packaging),
+ # where tox isn't used to run tests and PYTHONPATH is set to point to
+ # a specific setuptools codebase, PYTHONPATH will leak into the spawned
+ # processes.
+ # env.create() should install the just created setuptools
+ # wheel, but it doesn't if it finds another existing matching setuptools
+ # installation present on PYTHONPATH:
+ # `setuptools is already installed with the same version as the provided
+ # wheel. Use --force-reinstall to force an installation of the wheel.`
+ # This prevents leaking PYTHONPATH to the created environment.
+ with contexts.environment(PYTHONPATH=None):
+ return env.create()
+
+
+@pytest.fixture
+def venv_without_setuptools(tmp_path):
+ """Virtual env without any version of setuptools installed"""
+ env = environment.VirtualEnv()
+ env.root = path.Path(tmp_path / 'venv_without_setuptools')
+ env.create_opts = ['--no-setuptools', '--no-wheel']
+ env.ensure_env()
+ return env
+
+
+@pytest.fixture
+def bare_venv(tmp_path):
+ """Virtual env without any common packages installed"""
+ env = environment.VirtualEnv()
+ env.root = path.Path(tmp_path / 'bare_venv')
+ env.create_opts = ['--no-setuptools', '--no-pip', '--no-wheel', '--no-seed']
+ env.ensure_env()
+ return env
+
+
+def make_sdist(dist_path, files):
+ """
+ Create a simple sdist tarball at dist_path, containing the files
+ listed in ``files`` as ``(filename, content)`` tuples.
+ """
+
+ # Distributions with only one file don't play well with pip.
+ assert len(files) > 1
+ with tarfile.open(dist_path, 'w:gz') as dist:
+ for filename, content in files:
+ file_bytes = io.BytesIO(content.encode('utf-8'))
+ file_info = tarfile.TarInfo(name=filename)
+ file_info.size = len(file_bytes.getvalue())
+ file_info.mtime = int(time.time())
+ dist.addfile(file_info, fileobj=file_bytes)
+
+
+def make_trivial_sdist(dist_path, distname, version, setuptools_wheel=None):
+ """
+ Create a simple sdist tarball at dist_path, containing just a simple
+ setup.py.
+
+ If ``setuptools_wheel`` is passed, a ``pyproject.toml`` file will also
+ be generated and the passed value will be used as location for
+ setuptools (as build dependency).
+ """
+ files = [
+ (
+ 'setup.py',
+ DALS(
+ f"""\
+ import setuptools
+ setuptools.setup(
+ name={distname!r},
+ version={version!r}
+ )
+ """
+ ),
+ ),
+ ('setup.cfg', ''),
+ ]
+
+ if setuptools_wheel:
+ files.append((
+ "pyproject.toml",
+ DALS(
+ f"""\
+ [build-system]
+ requires = ["setuptools @ {setuptools_wheel.as_uri()}"]
+ build-backend = "setuptools.build_meta"
+ """
+ ),
+ ))
+
+ make_sdist(dist_path, files)
+
+
+def make_nspkg_sdist(dist_path, distname, version):
+ """
+ Make an sdist tarball with distname and version which also contains one
+ package with the same name as distname. The top-level package is
+ designated a namespace package).
+ """
+ # Assert that the distname contains at least one period
+ assert '.' in distname
+
+ parts = distname.split('.')
+ nspackage = parts[0]
+
+ packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)]
+
+ setup_py = DALS(
+ f"""\
+ import setuptools
+ setuptools.setup(
+ name={distname!r},
+ version={version!r},
+ packages={packages!r},
+ namespace_packages=[{nspackage!r}]
+ )
+ """
+ )
+
+ init = "__import__('pkg_resources').declare_namespace(__name__)"
+
+ files = [('setup.py', setup_py), (os.path.join(nspackage, '__init__.py'), init)]
+ for package in packages[1:]:
+ filename = os.path.join(*(package.split('.') + ['__init__.py']))
+ files.append((filename, ''))
+
+ make_sdist(dist_path, files)
+
+
+def make_python_requires_sdist(dist_path, distname, version, python_requires):
+ make_sdist(
+ dist_path,
+ [
+ (
+ 'setup.py',
+ DALS(
+ """\
+ import setuptools
+ setuptools.setup(
+ name={name!r},
+ version={version!r},
+ python_requires={python_requires!r},
+ )
+ """
+ ).format(
+ name=distname, version=version, python_requires=python_requires
+ ),
+ ),
+ ('setup.cfg', ''),
+ ],
+ )
+
+
+def create_setup_requires_package(
+ path,
+ distname='foobar',
+ version='0.1',
+ make_package=make_trivial_sdist,
+ setup_py_template=None,
+ setup_attrs=None,
+ use_setup_cfg=(),
+):
+ """Creates a source tree under path for a trivial test package that has a
+ single requirement in setup_requires--a tarball for that requirement is
+ also created and added to the dependency_links argument.
+
+ ``distname`` and ``version`` refer to the name/version of the package that
+ the test package requires via ``setup_requires``. The name of the test
+ package itself is just 'test_pkg'.
+ """
+
+ normalized_distname = safer_name(distname)
+ test_setup_attrs = {
+ 'name': 'test_pkg',
+ 'version': '0.0',
+ 'setup_requires': [f'{normalized_distname}=={version}'],
+ 'dependency_links': [os.path.abspath(path)],
+ }
+ if setup_attrs:
+ test_setup_attrs.update(setup_attrs)
+
+ test_pkg = os.path.join(path, 'test_pkg')
+ os.mkdir(test_pkg)
+
+ # setup.cfg
+ if use_setup_cfg:
+ options = []
+ metadata = []
+ for name in use_setup_cfg:
+ value = test_setup_attrs.pop(name)
+ if name in 'name version'.split():
+ section = metadata
+ else:
+ section = options
+ if isinstance(value, (tuple, list)):
+ value = ';'.join(value)
+ section.append(f'{name}: {value}')
+ test_setup_cfg_contents = DALS(
+ """
+ [metadata]
+ {metadata}
+ [options]
+ {options}
+ """
+ ).format(
+ options='\n'.join(options),
+ metadata='\n'.join(metadata),
+ )
+ else:
+ test_setup_cfg_contents = ''
+ with open(os.path.join(test_pkg, 'setup.cfg'), 'w', encoding="utf-8") as f:
+ f.write(test_setup_cfg_contents)
+
+ # setup.py
+ if setup_py_template is None:
+ setup_py_template = DALS(
+ """\
+ import setuptools
+ setuptools.setup(**%r)
+ """
+ )
+ with open(os.path.join(test_pkg, 'setup.py'), 'w', encoding="utf-8") as f:
+ f.write(setup_py_template % test_setup_attrs)
+
+ foobar_path = os.path.join(path, f'{normalized_distname}-{version}.tar.gz')
+ make_package(foobar_path, distname, version)
+
+ return test_pkg
+
+
+@pytest.fixture
+def pbr_package(tmp_path, monkeypatch, venv):
+ files = {
+ "pyproject.toml": DALS(
+ """
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+ """
+ ),
+ "setup.py": DALS(
+ """
+ __import__('setuptools').setup(
+ pbr=True,
+ setup_requires=["pbr"],
+ )
+ """
+ ),
+ "setup.cfg": DALS(
+ """
+ [metadata]
+ name = mypkg
+
+ [files]
+ packages =
+ mypkg
+ """
+ ),
+ "mypkg": {
+ "__init__.py": "",
+ "hello.py": "print('Hello world!')",
+ },
+ "other": {"test.txt": "Another file in here."},
+ }
+ venv.run(["python", "-m", "pip", "install", "pbr"])
+ prefix = tmp_path / 'mypkg'
+ prefix.mkdir()
+ jaraco.path.build(files, prefix=prefix)
+ monkeypatch.setenv('PBR_VERSION', "0.42")
+ return prefix
diff --git a/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/external.html b/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/external.html
new file mode 100644
index 0000000000000000000000000000000000000000..92e4702f634dfb37a404bec3103b76f6afcaa917
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/external.html
@@ -0,0 +1,3 @@
+
+bad old link
+
diff --git a/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html b/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..fefb028bd3ee7d45a414d6e96a7b2a21ffd7eda7
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html
@@ -0,0 +1,4 @@
+
+foobar-0.1.tar.gz
+external homepage
+
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/__init__.py b/lib/python3.12/site-packages/setuptools/tests/integration/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..05416d4339a20aaaf44011b5d1557e8eb695fcaf
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/helpers.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/helpers.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..20fa00f076e5f7ec114eea80e342412500b9642b
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/helpers.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pbr.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pbr.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b3983ec4b938c6e02032b94303d200bbef1d858b
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pbr.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pip_install_sdist.cpython-312.pyc b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pip_install_sdist.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..180c7124c052b5653b990c46462d66e6389bf985
Binary files /dev/null and b/lib/python3.12/site-packages/setuptools/tests/integration/__pycache__/test_pip_install_sdist.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/helpers.py b/lib/python3.12/site-packages/setuptools/tests/integration/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..16b13022913f62ba86df6b2480d22e3119cdb8cc
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/integration/helpers.py
@@ -0,0 +1,80 @@
+"""Reusable functions and classes for different types of integration tests.
+
+For example ``Archive`` can be used to check the contents of distribution built
+with setuptools, and ``run`` will always try to be as verbose as possible to
+facilitate debugging.
+"""
+
+from __future__ import annotations
+
+import os
+import subprocess
+import tarfile
+from collections.abc import Iterator
+from pathlib import Path
+from zipfile import ZipFile, ZipInfo
+
+
+def run(cmd, env=None):
+ r = subprocess.run(
+ cmd,
+ capture_output=True,
+ text=True,
+ encoding="utf-8",
+ env={**os.environ, **(env or {})},
+ # ^-- allow overwriting instead of discarding the current env
+ )
+
+ out = r.stdout + "\n" + r.stderr
+ # pytest omits stdout/err by default, if the test fails they help debugging
+ print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
+ print(f"Command: {cmd}\nreturn code: {r.returncode}\n\n{out}")
+
+ if r.returncode == 0:
+ return out
+ raise subprocess.CalledProcessError(r.returncode, cmd, r.stdout, r.stderr)
+
+
+class Archive:
+ """Compatibility layer for ZipFile/Info and TarFile/Info"""
+
+ def __init__(self, filename) -> None:
+ self._filename = filename
+ if filename.endswith("tar.gz"):
+ self._obj: tarfile.TarFile | ZipFile = tarfile.open(filename, "r:gz")
+ elif filename.endswith("zip"):
+ self._obj = ZipFile(filename)
+ else:
+ raise ValueError(f"{filename} doesn't seem to be a zip or tar.gz")
+
+ def __iter__(self) -> Iterator[ZipInfo] | Iterator[tarfile.TarInfo]:
+ if hasattr(self._obj, "infolist"):
+ return iter(self._obj.infolist())
+ return iter(self._obj)
+
+ def get_name(self, zip_or_tar_info):
+ if hasattr(zip_or_tar_info, "filename"):
+ return zip_or_tar_info.filename
+ return zip_or_tar_info.name
+
+ def get_content(self, zip_or_tar_info):
+ if hasattr(self._obj, "extractfile"):
+ content = self._obj.extractfile(zip_or_tar_info)
+ if content is None:
+ msg = f"Invalid {zip_or_tar_info.name} in {self._filename}"
+ raise ValueError(msg)
+ return str(content.read(), "utf-8")
+ return str(self._obj.read(zip_or_tar_info), "utf-8")
+
+
+def get_sdist_members(sdist_path):
+ with tarfile.open(sdist_path, "r:gz") as tar:
+ files = [Path(f) for f in tar.getnames()]
+ # remove root folder
+ relative_files = ("/".join(f.parts[1:]) for f in files)
+ return {f for f in relative_files if f}
+
+
+def get_wheel_members(wheel_path):
+ with ZipFile(wheel_path) as zipfile:
+ return set(zipfile.namelist())
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/test_pbr.py b/lib/python3.12/site-packages/setuptools/tests/integration/test_pbr.py
new file mode 100644
index 0000000000000000000000000000000000000000..f89e5b8b2151430d0994836dd609f1b75490a336
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/integration/test_pbr.py
@@ -0,0 +1,20 @@
+import subprocess
+
+import pytest
+
+
+@pytest.mark.uses_network
+def test_pbr_integration(pbr_package, venv):
+ """Ensure pbr packages install."""
+ cmd = [
+ 'python',
+ '-m',
+ 'pip',
+ '-v',
+ 'install',
+ '--no-build-isolation',
+ pbr_package,
+ ]
+ venv.run(cmd, stderr=subprocess.STDOUT)
+ out = venv.run(["python", "-c", "import mypkg.hello"])
+ assert "Hello world!" in out
diff --git a/lib/python3.12/site-packages/setuptools/tests/integration/test_pip_install_sdist.py b/lib/python3.12/site-packages/setuptools/tests/integration/test_pip_install_sdist.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e84f218323e6ad67adfbce07cea5a16e91a311c
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/integration/test_pip_install_sdist.py
@@ -0,0 +1,223 @@
+# https://github.com/python/mypy/issues/16936
+# mypy: disable-error-code="has-type"
+"""Integration tests for setuptools that focus on building packages via pip.
+
+The idea behind these tests is not to exhaustively check all the possible
+combinations of packages, operating systems, supporting libraries, etc, but
+rather check a limited number of popular packages and how they interact with
+the exposed public API. This way if any change in API is introduced, we hope to
+identify backward compatibility problems before publishing a release.
+
+The number of tested packages is purposefully kept small, to minimise duration
+and the associated maintenance cost (changes in the way these packages define
+their build process may require changes in the tests).
+"""
+
+import json
+import os
+import shutil
+import sys
+from enum import Enum
+from glob import glob
+from hashlib import md5
+from urllib.request import urlopen
+
+import pytest
+from packaging.requirements import Requirement
+
+from .helpers import Archive, run
+
+pytestmark = pytest.mark.integration
+
+
+(LATEST,) = Enum("v", "LATEST") # type: ignore[misc] # https://github.com/python/mypy/issues/16936
+"""Default version to be checked"""
+# There are positive and negative aspects of checking the latest version of the
+# packages.
+# The main positive aspect is that the latest version might have already
+# removed the use of APIs deprecated in previous releases of setuptools.
+
+
+# Packages to be tested:
+# (Please notice the test environment cannot support EVERY library required for
+# compiling binary extensions. In Ubuntu/Debian nomenclature, we only assume
+# that `build-essential`, `gfortran` and `libopenblas-dev` are installed,
+# due to their relevance to the numerical/scientific programming ecosystem)
+EXAMPLES = [
+ ("pip", LATEST), # just in case...
+ ("pytest", LATEST), # uses setuptools_scm
+ ("mypy", LATEST), # custom build_py + ext_modules
+ # --- Popular packages: https://hugovk.github.io/top-pypi-packages/ ---
+ ("botocore", LATEST),
+ ("kiwisolver", LATEST), # build_ext
+ ("brotli", LATEST), # not in the list but used by urllib3
+ ("pyyaml", LATEST), # cython + custom build_ext + custom distclass
+ ("charset-normalizer", LATEST), # uses mypyc, used by aiohttp
+ ("protobuf", LATEST),
+ # ("requests", LATEST), # XXX: https://github.com/psf/requests/pull/6920
+ ("celery", LATEST),
+ # When adding packages to this list, make sure they expose a `__version__`
+ # attribute, or modify the tests below
+]
+
+
+# Some packages have "optional" dependencies that modify their build behaviour
+# and are not listed in pyproject.toml, others still use `setup_requires`
+EXTRA_BUILD_DEPS = {
+ "pyyaml": ("Cython<3.0",), # constraint to avoid errors
+ "charset-normalizer": ("mypy>=1.4.1",), # no pyproject.toml available
+}
+
+EXTRA_ENV_VARS = {
+ "pyyaml": {"PYYAML_FORCE_CYTHON": "1"},
+ "charset-normalizer": {"CHARSET_NORMALIZER_USE_MYPYC": "1"},
+}
+
+IMPORT_NAME = {
+ "pyyaml": "yaml",
+ "protobuf": "google.protobuf",
+}
+
+
+VIRTUALENV = (sys.executable, "-m", "virtualenv")
+
+
+# By default, pip will try to build packages in isolation (PEP 517), which
+# means it will download the previous stable version of setuptools.
+# `pip` flags can avoid that (the version of setuptools under test
+# should be the one to be used)
+INSTALL_OPTIONS = (
+ "--ignore-installed",
+ "--no-build-isolation",
+ # Omit "--no-binary :all:" the sdist is supplied directly.
+ # Allows dependencies as wheels.
+)
+# The downside of `--no-build-isolation` is that pip will not download build
+# dependencies. The test script will have to also handle that.
+
+
+@pytest.fixture
+def venv_python(tmp_path):
+ run([*VIRTUALENV, str(tmp_path / ".venv")])
+ possible_path = (str(p.parent) for p in tmp_path.glob(".venv/*/python*"))
+ return shutil.which("python", path=os.pathsep.join(possible_path))
+
+
+@pytest.fixture(autouse=True)
+def _prepare(tmp_path, venv_python, monkeypatch):
+ download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path))
+ os.makedirs(download_path, exist_ok=True)
+
+ # Environment vars used for building some of the packages
+ monkeypatch.setenv("USE_MYPYC", "1")
+
+ yield
+
+ # Let's provide the maximum amount of information possible in the case
+ # it is necessary to debug the tests directly from the CI logs.
+ print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
+ print("Temporary directory:")
+ map(print, tmp_path.glob("*"))
+ print("Virtual environment:")
+ run([venv_python, "-m", "pip", "freeze"])
+
+
+@pytest.mark.parametrize(("package", "version"), EXAMPLES)
+@pytest.mark.uses_network
+def test_install_sdist(package, version, tmp_path, venv_python, setuptools_wheel):
+ venv_pip = (venv_python, "-m", "pip")
+ sdist = retrieve_sdist(package, version, tmp_path)
+ deps = build_deps(package, sdist)
+ if deps:
+ print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
+ print("Dependencies:", deps)
+ run([*venv_pip, "install", *deps])
+
+ # Use a virtualenv to simulate PEP 517 isolation
+ # but install fresh setuptools wheel to ensure the version under development
+ env = EXTRA_ENV_VARS.get(package, {})
+ run([*venv_pip, "install", "--force-reinstall", setuptools_wheel])
+ run([*venv_pip, "install", *INSTALL_OPTIONS, sdist], env)
+
+ # Execute a simple script to make sure the package was installed correctly
+ pkg = IMPORT_NAME.get(package, package).replace("-", "_")
+ script = f"import {pkg}; print(getattr({pkg}, '__version__', 0))"
+ run([venv_python, "-c", script])
+
+
+# ---- Helper Functions ----
+
+
+def retrieve_sdist(package, version, tmp_path):
+ """Either use cached sdist file or download it from PyPI"""
+ # `pip download` cannot be used due to
+ # https://github.com/pypa/pip/issues/1884
+ # https://discuss.python.org/t/pep-625-file-name-of-a-source-distribution/4686
+ # We have to find the correct distribution file and download it
+ download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path))
+ dist = retrieve_pypi_sdist_metadata(package, version)
+
+ # Remove old files to prevent cache to grow indefinitely
+ for file in glob(os.path.join(download_path, f"{package}*")):
+ if dist["filename"] != file:
+ os.unlink(file)
+
+ dist_file = os.path.join(download_path, dist["filename"])
+ if not os.path.exists(dist_file):
+ download(dist["url"], dist_file, dist["md5_digest"])
+ return dist_file
+
+
+def retrieve_pypi_sdist_metadata(package, version):
+ # https://warehouse.pypa.io/api-reference/json.html
+ id_ = package if version is LATEST else f"{package}/{version}"
+ with urlopen(f"https://pypi.org/pypi/{id_}/json") as f:
+ metadata = json.load(f)
+
+ if metadata["info"]["yanked"]:
+ raise ValueError(f"Release for {package} {version} was yanked")
+
+ version = metadata["info"]["version"]
+ release = metadata["releases"][version] if version is LATEST else metadata["urls"]
+ (sdist,) = filter(lambda d: d["packagetype"] == "sdist", release)
+ return sdist
+
+
+def download(url, dest, md5_digest):
+ with urlopen(url) as f:
+ data = f.read()
+
+ assert md5(data).hexdigest() == md5_digest
+
+ with open(dest, "wb") as f:
+ f.write(data)
+
+ assert os.path.exists(dest)
+
+
+def build_deps(package, sdist_file):
+ """Find out what are the build dependencies for a package.
+
+ "Manually" install them, since pip will not install build
+ deps with `--no-build-isolation`.
+ """
+ # delay importing, since pytest discovery phase may hit this file from a
+ # testenv without tomli
+ from setuptools.compat.py310 import tomllib
+
+ archive = Archive(sdist_file)
+ info = tomllib.loads(_read_pyproject(archive))
+ deps = info.get("build-system", {}).get("requires", [])
+ deps += EXTRA_BUILD_DEPS.get(package, [])
+ # Remove setuptools from requirements (and deduplicate)
+ requirements = {Requirement(d).name: d for d in deps}
+ return [v for k, v in requirements.items() if k != "setuptools"]
+
+
+def _read_pyproject(archive):
+ contents = (
+ archive.get_content(member)
+ for member in archive
+ if os.path.basename(archive.get_name(member)) == "pyproject.toml"
+ )
+ return next(contents, "")
diff --git a/lib/python3.12/site-packages/setuptools/tests/mod_with_constant.py b/lib/python3.12/site-packages/setuptools/tests/mod_with_constant.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef755dd1c7a8d1f116fe51f1b43315057f03379d
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/mod_with_constant.py
@@ -0,0 +1 @@
+value = 'three, sir!'
diff --git a/lib/python3.12/site-packages/setuptools/tests/namespaces.py b/lib/python3.12/site-packages/setuptools/tests/namespaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..248db98f97951aeeee0222131417e73074cc72d2
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/namespaces.py
@@ -0,0 +1,90 @@
+import ast
+import json
+import textwrap
+from pathlib import Path
+
+
+def iter_namespace_pkgs(namespace):
+ parts = namespace.split(".")
+ for i in range(len(parts)):
+ yield ".".join(parts[: i + 1])
+
+
+def build_namespace_package(tmpdir, name, version="1.0", impl="pkg_resources"):
+ src_dir = tmpdir / name
+ src_dir.mkdir()
+ setup_py = src_dir / 'setup.py'
+ namespace, _, rest = name.rpartition('.')
+ namespaces = list(iter_namespace_pkgs(namespace))
+ setup_args = {
+ "name": name,
+ "version": version,
+ "packages": namespaces,
+ }
+
+ if impl == "pkg_resources":
+ tmpl = '__import__("pkg_resources").declare_namespace(__name__)'
+ setup_args["namespace_packages"] = namespaces
+ elif impl == "pkgutil":
+ tmpl = '__path__ = __import__("pkgutil").extend_path(__path__, __name__)'
+ else:
+ raise ValueError(f"Cannot recognise {impl=} when creating namespaces")
+
+ args = json.dumps(setup_args, indent=4)
+ assert ast.literal_eval(args) # ensure it is valid Python
+
+ script = textwrap.dedent(
+ """\
+ import setuptools
+ args = {args}
+ setuptools.setup(**args)
+ """
+ ).format(args=args)
+ setup_py.write_text(script, encoding='utf-8')
+
+ ns_pkg_dir = Path(src_dir, namespace.replace(".", "/"))
+ ns_pkg_dir.mkdir(parents=True)
+
+ for ns in namespaces:
+ pkg_init = src_dir / ns.replace(".", "/") / '__init__.py'
+ pkg_init.write_text(tmpl, encoding='utf-8')
+
+ pkg_mod = ns_pkg_dir / (rest + '.py')
+ some_functionality = 'name = {rest!r}'.format(**locals())
+ pkg_mod.write_text(some_functionality, encoding='utf-8')
+ return src_dir
+
+
+def build_pep420_namespace_package(tmpdir, name):
+ src_dir = tmpdir / name
+ src_dir.mkdir()
+ pyproject = src_dir / "pyproject.toml"
+ namespace, _, rest = name.rpartition(".")
+ script = f"""\
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "{name}"
+ version = "3.14159"
+ """
+ pyproject.write_text(textwrap.dedent(script), encoding='utf-8')
+ ns_pkg_dir = Path(src_dir, namespace.replace(".", "/"))
+ ns_pkg_dir.mkdir(parents=True)
+ pkg_mod = ns_pkg_dir / (rest + ".py")
+ some_functionality = f"name = {rest!r}"
+ pkg_mod.write_text(some_functionality, encoding='utf-8')
+ return src_dir
+
+
+def make_site_dir(target):
+ """
+ Add a sitecustomize.py module in target to cause
+ target to be added to site dirs such that .pth files
+ are processed there.
+ """
+ sc = target / 'sitecustomize.py'
+ target_str = str(target)
+ tmpl = '__import__("site").addsitedir({target_str!r})'
+ sc.write_text(tmpl.format(**locals()), encoding='utf-8')
diff --git a/lib/python3.12/site-packages/setuptools/tests/script-with-bom.py b/lib/python3.12/site-packages/setuptools/tests/script-with-bom.py
new file mode 100644
index 0000000000000000000000000000000000000000..c074d263c45bcaebe32fdba328d975c73d1ad5ca
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/script-with-bom.py
@@ -0,0 +1 @@
+result = 'passed'
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_archive_util.py b/lib/python3.12/site-packages/setuptools/tests/test_archive_util.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3efc62889994fa68bc9170e8a0e403f48a204e1
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_archive_util.py
@@ -0,0 +1,36 @@
+import io
+import tarfile
+
+import pytest
+
+from setuptools import archive_util
+
+
+@pytest.fixture
+def tarfile_with_unicode(tmpdir):
+ """
+ Create a tarfile containing only a file whose name is
+ a zero byte file called testimäge.png.
+ """
+ tarobj = io.BytesIO()
+
+ with tarfile.open(fileobj=tarobj, mode="w:gz") as tgz:
+ data = b""
+
+ filename = "testimäge.png"
+
+ t = tarfile.TarInfo(filename)
+ t.size = len(data)
+
+ tgz.addfile(t, io.BytesIO(data))
+
+ target = tmpdir / 'unicode-pkg-1.0.tar.gz'
+ with open(str(target), mode='wb') as tf:
+ tf.write(tarobj.getvalue())
+ return str(target)
+
+
+@pytest.mark.xfail(reason="#710 and #712")
+def test_unicode_files(tarfile_with_unicode, tmpdir):
+ target = tmpdir / 'out'
+ archive_util.unpack_archive(tarfile_with_unicode, str(target))
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_bdist_deprecations.py b/lib/python3.12/site-packages/setuptools/tests/test_bdist_deprecations.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9d67b06161a2b36e7b15fab09f797c5c15575c2
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_bdist_deprecations.py
@@ -0,0 +1,28 @@
+"""develop tests"""
+
+import sys
+from unittest import mock
+
+import pytest
+
+from setuptools import SetuptoolsDeprecationWarning
+from setuptools.dist import Distribution
+
+
+@pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
+@pytest.mark.xfail(reason="bdist_rpm is long deprecated, should we remove it? #1988")
+@mock.patch('distutils.command.bdist_rpm.bdist_rpm')
+def test_bdist_rpm_warning(distutils_cmd, tmpdir_cwd):
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['bdist_rpm'],
+ name='foo',
+ py_modules=['hi'],
+ )
+ )
+ dist.parse_command_line()
+ with pytest.warns(SetuptoolsDeprecationWarning):
+ dist.run_commands()
+
+ distutils_cmd.run.assert_called_once()
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_bdist_egg.py b/lib/python3.12/site-packages/setuptools/tests/test_bdist_egg.py
new file mode 100644
index 0000000000000000000000000000000000000000..036167dd951e70ad543775529d5ce3f6d6544c71
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_bdist_egg.py
@@ -0,0 +1,73 @@
+"""develop tests"""
+
+import os
+import re
+import zipfile
+
+import pytest
+
+from setuptools.dist import Distribution
+
+from . import contexts
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(py_modules=['hi'])
+"""
+
+
+@pytest.fixture
+def setup_context(tmpdir):
+ with (tmpdir / 'setup.py').open('w') as f:
+ f.write(SETUP_PY)
+ with (tmpdir / 'hi.py').open('w') as f:
+ f.write('1\n')
+ with tmpdir.as_cwd():
+ yield tmpdir
+
+
+class Test:
+ @pytest.mark.usefixtures("user_override")
+ @pytest.mark.usefixtures("setup_context")
+ def test_bdist_egg(self):
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['bdist_egg'],
+ name='foo',
+ py_modules=['hi'],
+ )
+ )
+ os.makedirs(os.path.join('build', 'src'))
+ with contexts.quiet():
+ dist.parse_command_line()
+ dist.run_commands()
+
+ # let's see if we got our egg link at the right place
+ [content] = os.listdir('dist')
+ assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content)
+
+ @pytest.mark.xfail(
+ os.environ.get('PYTHONDONTWRITEBYTECODE', False),
+ reason="Byte code disabled",
+ )
+ @pytest.mark.usefixtures("user_override")
+ @pytest.mark.usefixtures("setup_context")
+ def test_exclude_source_files(self):
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['bdist_egg', '--exclude-source-files'],
+ py_modules=['hi'],
+ )
+ )
+ with contexts.quiet():
+ dist.parse_command_line()
+ dist.run_commands()
+ [dist_name] = os.listdir('dist')
+ dist_filename = os.path.join('dist', dist_name)
+ zip = zipfile.ZipFile(dist_filename)
+ names = list(zi.filename for zi in zip.filelist)
+ assert 'hi.pyc' in names
+ assert 'hi.py' not in names
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_bdist_wheel.py b/lib/python3.12/site-packages/setuptools/tests/test_bdist_wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..68cc0c4d3662bcf6f0881df54105ebaf38403564
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_bdist_wheel.py
@@ -0,0 +1,708 @@
+from __future__ import annotations
+
+import builtins
+import importlib
+import os.path
+import platform
+import shutil
+import stat
+import struct
+import sys
+import sysconfig
+from contextlib import suppress
+from inspect import cleandoc
+from zipfile import ZipFile
+
+import jaraco.path
+import pytest
+from packaging import tags
+
+import setuptools
+from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag
+from setuptools.dist import Distribution
+from setuptools.warnings import SetuptoolsDeprecationWarning
+
+from distutils.core import run_setup
+
+DEFAULT_FILES = {
+ "dummy_dist-1.0.dist-info/top_level.txt",
+ "dummy_dist-1.0.dist-info/METADATA",
+ "dummy_dist-1.0.dist-info/WHEEL",
+ "dummy_dist-1.0.dist-info/RECORD",
+}
+DEFAULT_LICENSE_FILES = {
+ "LICENSE",
+ "LICENSE.txt",
+ "LICENCE",
+ "LICENCE.txt",
+ "COPYING",
+ "COPYING.md",
+ "NOTICE",
+ "NOTICE.rst",
+ "AUTHORS",
+ "AUTHORS.txt",
+}
+OTHER_IGNORED_FILES = {
+ "LICENSE~",
+ "AUTHORS~",
+}
+SETUPPY_EXAMPLE = """\
+from setuptools import setup
+
+setup(
+ name='dummy_dist',
+ version='1.0',
+)
+"""
+
+
+EXAMPLES = {
+ "dummy-dist": {
+ "setup.py": SETUPPY_EXAMPLE,
+ "licenses_dir": {"DUMMYFILE": ""},
+ **dict.fromkeys(DEFAULT_LICENSE_FILES | OTHER_IGNORED_FILES, ""),
+ },
+ "simple-dist": {
+ "setup.py": cleandoc(
+ """
+ from setuptools import setup
+
+ setup(
+ name="simple.dist",
+ version="0.1",
+ description="A testing distribution \N{SNOWMAN}",
+ extras_require={"voting": ["beaglevote"]},
+ )
+ """
+ ),
+ "simpledist": "",
+ },
+ "complex-dist": {
+ "setup.py": cleandoc(
+ """
+ from setuptools import setup
+
+ setup(
+ name="complex-dist",
+ version="0.1",
+ description="Another testing distribution \N{SNOWMAN}",
+ long_description="Another testing distribution \N{SNOWMAN}",
+ author="Illustrious Author",
+ author_email="illustrious@example.org",
+ url="http://example.org/exemplary",
+ packages=["complexdist"],
+ setup_requires=["setuptools"],
+ install_requires=["quux", "splort"],
+ extras_require={"simple": ["simple.dist"]},
+ entry_points={
+ "console_scripts": [
+ "complex-dist=complexdist:main",
+ "complex-dist2=complexdist:main",
+ ],
+ },
+ )
+ """
+ ),
+ "complexdist": {"__init__.py": "def main(): return"},
+ },
+ "headers-dist": {
+ "setup.py": cleandoc(
+ """
+ from setuptools import setup
+
+ setup(
+ name="headers.dist",
+ version="0.1",
+ description="A distribution with headers",
+ headers=["header.h"],
+ )
+ """
+ ),
+ "headersdist.py": "",
+ "header.h": "",
+ },
+ "commasinfilenames-dist": {
+ "setup.py": cleandoc(
+ """
+ from setuptools import setup
+
+ setup(
+ name="testrepo",
+ version="0.1",
+ packages=["mypackage"],
+ description="A test package with commas in file names",
+ include_package_data=True,
+ package_data={"mypackage.data": ["*"]},
+ )
+ """
+ ),
+ "mypackage": {
+ "__init__.py": "",
+ "data": {"__init__.py": "", "1,2,3.txt": ""},
+ },
+ "testrepo-0.1.0": {
+ "mypackage": {"__init__.py": ""},
+ },
+ },
+ "unicode-dist": {
+ "setup.py": cleandoc(
+ """
+ from setuptools import setup
+
+ setup(
+ name="unicode.dist",
+ version="0.1",
+ description="A testing distribution \N{SNOWMAN}",
+ packages=["unicodedist"],
+ zip_safe=True,
+ )
+ """
+ ),
+ "unicodedist": {"__init__.py": "", "åäö_日本語.py": ""},
+ },
+ "utf8-metadata-dist": {
+ "setup.cfg": cleandoc(
+ """
+ [metadata]
+ name = utf8-metadata-dist
+ version = 42
+ author_email = "John X. Ãørçeč" , Γαμα קּ 東
+ long_description = file: README.rst
+ """
+ ),
+ "README.rst": "UTF-8 描述 説明",
+ },
+ "licenses-dist": {
+ "setup.cfg": cleandoc(
+ """
+ [metadata]
+ name = licenses-dist
+ version = 1.0
+ license_files = **/LICENSE
+ """
+ ),
+ "LICENSE": "",
+ "src": {
+ "vendor": {"LICENSE": ""},
+ },
+ },
+}
+
+
+if sys.platform != "win32":
+ # ABI3 extensions don't really work on Windows
+ EXAMPLES["abi3extension-dist"] = {
+ "setup.py": cleandoc(
+ """
+ from setuptools import Extension, setup
+
+ setup(
+ name="extension.dist",
+ version="0.1",
+ description="A testing distribution \N{SNOWMAN}",
+ ext_modules=[
+ Extension(
+ name="extension", sources=["extension.c"], py_limited_api=True
+ )
+ ],
+ )
+ """
+ ),
+ "setup.cfg": "[bdist_wheel]\npy_limited_api=cp32",
+ "extension.c": "#define Py_LIMITED_API 0x03020000\n#include ",
+ }
+
+
+def bdist_wheel_cmd(**kwargs):
+ """Run command in the same process so that it is easier to collect coverage"""
+ dist_obj = (
+ run_setup("setup.py", stop_after="init")
+ if os.path.exists("setup.py")
+ else Distribution({"script_name": "%%build_meta%%"})
+ )
+ dist_obj.parse_config_files()
+ cmd = bdist_wheel(dist_obj)
+ for attr, value in kwargs.items():
+ setattr(cmd, attr, value)
+ cmd.finalize_options()
+ return cmd
+
+
+def mkexample(tmp_path_factory, name):
+ basedir = tmp_path_factory.mktemp(name)
+ jaraco.path.build(EXAMPLES[name], prefix=str(basedir))
+ return basedir
+
+
+@pytest.fixture(scope="session")
+def wheel_paths(tmp_path_factory):
+ build_base = tmp_path_factory.mktemp("build")
+ dist_dir = tmp_path_factory.mktemp("dist")
+ for name in EXAMPLES:
+ example_dir = mkexample(tmp_path_factory, name)
+ build_dir = build_base / name
+ with jaraco.path.DirectoryStack().context(example_dir):
+ bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
+
+ return sorted(str(fname) for fname in dist_dir.glob("*.whl"))
+
+
+@pytest.fixture
+def dummy_dist(tmp_path_factory):
+ return mkexample(tmp_path_factory, "dummy-dist")
+
+
+@pytest.fixture
+def licenses_dist(tmp_path_factory):
+ return mkexample(tmp_path_factory, "licenses-dist")
+
+
+def test_no_scripts(wheel_paths):
+ """Make sure entry point scripts are not generated."""
+ path = next(path for path in wheel_paths if "complex_dist" in path)
+ for entry in ZipFile(path).infolist():
+ assert ".data/scripts/" not in entry.filename
+
+
+def test_unicode_record(wheel_paths):
+ path = next(path for path in wheel_paths if "unicode_dist" in path)
+ with ZipFile(path) as zf:
+ record = zf.read("unicode_dist-0.1.dist-info/RECORD")
+
+ assert "åäö_日本語.py".encode() in record
+
+
+UTF8_PKG_INFO = """\
+Metadata-Version: 2.1
+Name: helloworld
+Version: 42
+Author-email: "John X. Ãørçeč" , Γαμα קּ 東
+
+
+UTF-8 描述 説明
+"""
+
+
+def test_preserve_unicode_metadata(monkeypatch, tmp_path):
+ monkeypatch.chdir(tmp_path)
+ egginfo = tmp_path / "dummy_dist.egg-info"
+ distinfo = tmp_path / "dummy_dist.dist-info"
+
+ egginfo.mkdir()
+ (egginfo / "PKG-INFO").write_text(UTF8_PKG_INFO, encoding="utf-8")
+ (egginfo / "dependency_links.txt").touch()
+
+ class simpler_bdist_wheel(bdist_wheel):
+ """Avoid messing with setuptools/distutils internals"""
+
+ def __init__(self) -> None:
+ pass
+
+ @property
+ def license_paths(self):
+ return []
+
+ cmd_obj = simpler_bdist_wheel()
+ cmd_obj.egg2dist(egginfo, distinfo)
+
+ metadata = (distinfo / "METADATA").read_text(encoding="utf-8")
+ assert 'Author-email: "John X. Ãørçeč"' in metadata
+ assert "Γαμα קּ 東 " in metadata
+ assert "UTF-8 描述 説明" in metadata
+
+
+def test_licenses_default(dummy_dist, monkeypatch, tmp_path):
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ license_files = {
+ "dummy_dist-1.0.dist-info/licenses/" + fname
+ for fname in DEFAULT_LICENSE_FILES
+ }
+ assert set(wf.namelist()) == DEFAULT_FILES | license_files
+
+
+def test_licenses_deprecated(dummy_dist, monkeypatch, tmp_path):
+ dummy_dist.joinpath("setup.cfg").write_text(
+ "[metadata]\nlicense_file=licenses_dir/DUMMYFILE", encoding="utf-8"
+ )
+ monkeypatch.chdir(dummy_dist)
+
+ bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
+
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ license_files = {"dummy_dist-1.0.dist-info/licenses/licenses_dir/DUMMYFILE"}
+ assert set(wf.namelist()) == DEFAULT_FILES | license_files
+
+
+@pytest.mark.parametrize(
+ ("config_file", "config"),
+ [
+ ("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*\n LICENSE"),
+ ("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*, LICENSE"),
+ (
+ "setup.py",
+ SETUPPY_EXAMPLE.replace(
+ ")", " license_files=['licenses_dir/DUMMYFILE', 'LICENSE'])"
+ ),
+ ),
+ ],
+)
+def test_licenses_override(dummy_dist, monkeypatch, tmp_path, config_file, config):
+ dummy_dist.joinpath(config_file).write_text(config, encoding="utf-8")
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ license_files = {
+ "dummy_dist-1.0.dist-info/licenses/" + fname
+ for fname in {"licenses_dir/DUMMYFILE", "LICENSE"}
+ }
+ assert set(wf.namelist()) == DEFAULT_FILES | license_files
+ metadata = wf.read("dummy_dist-1.0.dist-info/METADATA").decode("utf8")
+ assert "License-File: licenses_dir/DUMMYFILE" in metadata
+ assert "License-File: LICENSE" in metadata
+
+
+def test_licenses_preserve_folder_structure(licenses_dist, monkeypatch, tmp_path):
+ monkeypatch.chdir(licenses_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
+ print(os.listdir("dist"))
+ with ZipFile("dist/licenses_dist-1.0-py3-none-any.whl") as wf:
+ default_files = {name.replace("dummy_", "licenses_") for name in DEFAULT_FILES}
+ license_files = {
+ "licenses_dist-1.0.dist-info/licenses/LICENSE",
+ "licenses_dist-1.0.dist-info/licenses/src/vendor/LICENSE",
+ }
+ assert set(wf.namelist()) == default_files | license_files
+ metadata = wf.read("licenses_dist-1.0.dist-info/METADATA").decode("utf8")
+ assert "License-File: src/vendor/LICENSE" in metadata
+ assert "License-File: LICENSE" in metadata
+
+
+def test_licenses_disabled(dummy_dist, monkeypatch, tmp_path):
+ dummy_dist.joinpath("setup.cfg").write_text(
+ "[metadata]\nlicense_files=\n", encoding="utf-8"
+ )
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ assert set(wf.namelist()) == DEFAULT_FILES
+
+
+def test_build_number(dummy_dist, monkeypatch, tmp_path):
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2").run()
+ with ZipFile("dist/dummy_dist-1.0-2-py3-none-any.whl") as wf:
+ filenames = set(wf.namelist())
+ assert "dummy_dist-1.0.dist-info/RECORD" in filenames
+ assert "dummy_dist-1.0.dist-info/METADATA" in filenames
+
+
+def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path):
+ monkeypatch.chdir(dummy_dist)
+ with pytest.warns(SetuptoolsDeprecationWarning, match=".*universal is deprecated"):
+ bdist_wheel_cmd(bdist_dir=str(tmp_path), universal=True).run()
+
+ # For now we still respect the option
+ assert os.path.exists("dist/dummy_dist-1.0-py2.py3-none-any.whl")
+
+
+EXTENSION_EXAMPLE = """\
+#include
+
+static PyMethodDef methods[] = {
+ { NULL, NULL, 0, NULL }
+};
+
+static struct PyModuleDef module_def = {
+ PyModuleDef_HEAD_INIT,
+ "extension",
+ "Dummy extension module",
+ -1,
+ methods
+};
+
+PyMODINIT_FUNC PyInit_extension(void) {
+ return PyModule_Create(&module_def);
+}
+"""
+EXTENSION_SETUPPY = """\
+from __future__ import annotations
+
+from setuptools import Extension, setup
+
+setup(
+ name="extension.dist",
+ version="0.1",
+ description="A testing distribution \N{SNOWMAN}",
+ ext_modules=[Extension(name="extension", sources=["extension.c"])],
+)
+"""
+
+
+@pytest.mark.filterwarnings(
+ "once:Config variable '.*' is unset.*, Python ABI tag may be incorrect"
+)
+def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory):
+ """Test that building a binary wheel with the limited ABI works."""
+ source_dir = tmp_path_factory.mktemp("extension_dist")
+ (source_dir / "setup.py").write_text(EXTENSION_SETUPPY, encoding="utf-8")
+ (source_dir / "extension.c").write_text(EXTENSION_EXAMPLE, encoding="utf-8")
+ build_dir = tmp_path.joinpath("build")
+ dist_dir = tmp_path.joinpath("dist")
+ monkeypatch.chdir(source_dir)
+ bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
+
+
+def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path):
+ basedir = str(tmp_path.joinpath("dummy"))
+ shutil.copytree(str(dummy_dist), basedir)
+ monkeypatch.chdir(basedir)
+
+ # Make the tree read-only
+ for root, _dirs, files in os.walk(basedir):
+ for fname in files:
+ os.chmod(os.path.join(root, fname), stat.S_IREAD)
+
+ bdist_wheel_cmd().run()
+
+
+@pytest.mark.parametrize(
+ ("option", "compress_type"),
+ list(bdist_wheel.supported_compressions.items()),
+ ids=list(bdist_wheel.supported_compressions),
+)
+def test_compression(dummy_dist, monkeypatch, tmp_path, option, compress_type):
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path), compression=option).run()
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ filenames = set(wf.namelist())
+ assert "dummy_dist-1.0.dist-info/RECORD" in filenames
+ assert "dummy_dist-1.0.dist-info/METADATA" in filenames
+ for zinfo in wf.filelist:
+ assert zinfo.compress_type == compress_type
+
+
+def test_wheelfile_line_endings(wheel_paths):
+ for path in wheel_paths:
+ with ZipFile(path) as wf:
+ wheelfile = next(fn for fn in wf.filelist if fn.filename.endswith("WHEEL"))
+ wheelfile_contents = wf.read(wheelfile)
+ assert b"\r" not in wheelfile_contents
+
+
+def test_unix_epoch_timestamps(dummy_dist, monkeypatch, tmp_path):
+ monkeypatch.setenv("SOURCE_DATE_EPOCH", "0")
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2a").run()
+ with ZipFile("dist/dummy_dist-1.0-2a-py3-none-any.whl") as wf:
+ for zinfo in wf.filelist:
+ assert zinfo.date_time >= (1980, 1, 1, 0, 0, 0) # min epoch is used
+
+
+def test_get_abi_tag_windows(monkeypatch):
+ monkeypatch.setattr(tags, "interpreter_name", lambda: "cp")
+ monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313-win_amd64")
+ assert get_abi_tag() == "cp313"
+ monkeypatch.setattr(sys, "gettotalrefcount", lambda: 1, False)
+ assert get_abi_tag() == "cp313d"
+ monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313t-win_amd64")
+ assert get_abi_tag() == "cp313td"
+ monkeypatch.delattr(sys, "gettotalrefcount")
+ assert get_abi_tag() == "cp313t"
+
+
+def test_get_abi_tag_pypy_old(monkeypatch):
+ monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
+ monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy36-pp73")
+ assert get_abi_tag() == "pypy36_pp73"
+
+
+def test_get_abi_tag_pypy_new(monkeypatch):
+ monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy37-pp73-darwin")
+ monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
+ assert get_abi_tag() == "pypy37_pp73"
+
+
+def test_get_abi_tag_graalpy(monkeypatch):
+ monkeypatch.setattr(
+ sysconfig, "get_config_var", lambda x: "graalpy231-310-native-x86_64-linux"
+ )
+ monkeypatch.setattr(tags, "interpreter_name", lambda: "graalpy")
+ assert get_abi_tag() == "graalpy231_310_native"
+
+
+def test_get_abi_tag_fallback(monkeypatch):
+ monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "unknown-python-310")
+ monkeypatch.setattr(tags, "interpreter_name", lambda: "unknown-python")
+ assert get_abi_tag() == "unknown_python_310"
+
+
+def test_platform_with_space(dummy_dist, monkeypatch):
+ """Ensure building on platforms with a space in the name succeed."""
+ monkeypatch.chdir(dummy_dist)
+ bdist_wheel_cmd(plat_name="isilon onefs").run()
+
+
+def test_data_dir_with_tag_build(monkeypatch, tmp_path):
+ """
+ Setuptools allow authors to set PEP 440's local version segments
+ using ``egg_info.tag_build``. This should be reflected not only in the
+ ``.whl`` file name, but also in the ``.dist-info`` and ``.data`` dirs.
+ See pypa/setuptools#3997.
+ """
+ monkeypatch.chdir(tmp_path)
+ files = {
+ "setup.py": """
+ from setuptools import setup
+ setup(headers=["hello.h"])
+ """,
+ "setup.cfg": """
+ [metadata]
+ name = test
+ version = 1.0
+
+ [options.data_files]
+ hello/world = file.txt
+
+ [egg_info]
+ tag_build = +what
+ tag_date = 0
+ """,
+ "file.txt": "",
+ "hello.h": "",
+ }
+ for file, content in files.items():
+ with open(file, "w", encoding="utf-8") as fh:
+ fh.write(cleandoc(content))
+
+ bdist_wheel_cmd().run()
+
+ # Ensure .whl, .dist-info and .data contain the local segment
+ wheel_path = "dist/test-1.0+what-py3-none-any.whl"
+ assert os.path.exists(wheel_path)
+ entries = set(ZipFile(wheel_path).namelist())
+ for expected in (
+ "test-1.0+what.data/headers/hello.h",
+ "test-1.0+what.data/data/hello/world/file.txt",
+ "test-1.0+what.dist-info/METADATA",
+ "test-1.0+what.dist-info/WHEEL",
+ ):
+ assert expected in entries
+
+ for not_expected in (
+ "test.data/headers/hello.h",
+ "test-1.0.data/data/hello/world/file.txt",
+ "test.dist-info/METADATA",
+ "test-1.0.dist-info/WHEEL",
+ ):
+ assert not_expected not in entries
+
+
+@pytest.mark.parametrize(
+ ("reported", "expected"),
+ [("linux-x86_64", "linux_i686"), ("linux-aarch64", "linux_armv7l")],
+)
+@pytest.mark.skipif(
+ platform.system() != "Linux", reason="Only makes sense to test on Linux"
+)
+def test_platform_linux32(reported, expected, monkeypatch):
+ monkeypatch.setattr(struct, "calcsize", lambda x: 4)
+ dist = setuptools.Distribution()
+ cmd = bdist_wheel(dist)
+ cmd.plat_name = reported
+ cmd.root_is_pure = False
+ _, _, actual = cmd.get_tag()
+ assert actual == expected
+
+
+def test_no_ctypes(monkeypatch) -> None:
+ def _fake_import(name: str, *args, **kwargs):
+ if name == "ctypes":
+ raise ModuleNotFoundError(f"No module named {name}")
+
+ return importlib.__import__(name, *args, **kwargs)
+
+ with suppress(KeyError):
+ monkeypatch.delitem(sys.modules, "wheel.macosx_libfile")
+
+ # Install an importer shim that refuses to load ctypes
+ monkeypatch.setattr(builtins, "__import__", _fake_import)
+ with pytest.raises(ModuleNotFoundError, match="No module named ctypes"):
+ import wheel.macosx_libfile # noqa: F401
+
+ # Unload and reimport the bdist_wheel command module to make sure it won't try to
+ # import ctypes
+ monkeypatch.delitem(sys.modules, "setuptools.command.bdist_wheel")
+
+ import setuptools.command.bdist_wheel # noqa: F401
+
+
+def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path):
+ monkeypatch.chdir(dummy_dist)
+ distinfo = tmp_path / "dummy_dist.dist-info"
+
+ distinfo.mkdir()
+ (distinfo / "METADATA").write_text("name: helloworld", encoding="utf-8")
+
+ # We don't control the metadata. According to PEP-517, "The hook MAY also
+ # create other files inside this directory, and a build frontend MUST
+ # preserve".
+ (distinfo / "FOO").write_text("bar", encoding="utf-8")
+
+ bdist_wheel_cmd(bdist_dir=str(tmp_path), dist_info_dir=str(distinfo)).run()
+ expected = {
+ "dummy_dist-1.0.dist-info/FOO",
+ "dummy_dist-1.0.dist-info/RECORD",
+ }
+ with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
+ files_found = set(wf.namelist())
+ # Check that all expected files are there.
+ assert expected - files_found == set()
+ # Make sure there is no accidental egg-info bleeding into the wheel.
+ assert not [path for path in files_found if 'egg-info' in str(path)]
+
+
+def test_allow_grace_period_parent_directory_license(monkeypatch, tmp_path):
+ # Motivation: https://github.com/pypa/setuptools/issues/4892
+ # TODO: Remove this test after deprecation period is over
+ files = {
+ "LICENSE.txt": "parent license", # <---- the license files are outside
+ "NOTICE.txt": "parent notice",
+ "python": {
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "test-proj"
+ dynamic = ["version"] # <---- testing dynamic will not break
+ [tool.setuptools.dynamic]
+ version.file = "VERSION"
+ """
+ ),
+ "setup.cfg": cleandoc(
+ """
+ [metadata]
+ license_files =
+ ../LICENSE.txt
+ ../NOTICE.txt
+ """
+ ),
+ "VERSION": "42",
+ },
+ }
+ jaraco.path.build(files, prefix=str(tmp_path))
+ monkeypatch.chdir(tmp_path / "python")
+ msg = "Pattern '../.*.txt' cannot contain '..'"
+ with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
+ bdist_wheel_cmd().run()
+ with ZipFile("dist/test_proj-42-py3-none-any.whl") as wf:
+ files_found = set(wf.namelist())
+ expected_files = {
+ "test_proj-42.dist-info/licenses/LICENSE.txt",
+ "test_proj-42.dist-info/licenses/NOTICE.txt",
+ }
+ assert expected_files <= files_found
+
+ metadata = wf.read("test_proj-42.dist-info/METADATA").decode("utf8")
+ assert "License-File: LICENSE.txt" in metadata
+ assert "License-File: NOTICE.txt" in metadata
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_build.py b/lib/python3.12/site-packages/setuptools/tests/test_build.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0f1d9dcf21bafe9dc82a76d373b366ddeecfcec
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_build.py
@@ -0,0 +1,33 @@
+from setuptools import Command
+from setuptools.command.build import build
+from setuptools.dist import Distribution
+
+
+def test_distribution_gives_setuptools_build_obj(tmpdir_cwd):
+ """
+ Check that the setuptools Distribution uses the
+ setuptools specific build object.
+ """
+
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['build'],
+ packages=[],
+ package_data={'': ['path/*']},
+ )
+ )
+ assert isinstance(dist.get_command_obj("build"), build)
+
+
+class Subcommand(Command):
+ """Dummy command to be used in tests"""
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ raise NotImplementedError("just to check if the command runs")
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_build_clib.py b/lib/python3.12/site-packages/setuptools/tests/test_build_clib.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5315df4f6599cd376e628c0d74cb14129cd89b8
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_build_clib.py
@@ -0,0 +1,84 @@
+import random
+from unittest import mock
+
+import pytest
+
+from setuptools.command.build_clib import build_clib
+from setuptools.dist import Distribution
+
+from distutils.errors import DistutilsSetupError
+
+
+class TestBuildCLib:
+ @mock.patch('setuptools.command.build_clib.newer_pairwise_group')
+ def test_build_libraries(self, mock_newer):
+ dist = Distribution()
+ cmd = build_clib(dist)
+
+ # this will be a long section, just making sure all
+ # exceptions are properly raised
+ libs = [('example', {'sources': 'broken.c'})]
+ with pytest.raises(DistutilsSetupError):
+ cmd.build_libraries(libs)
+
+ obj_deps = 'some_string'
+ libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
+ with pytest.raises(DistutilsSetupError):
+ cmd.build_libraries(libs)
+
+ obj_deps = {'': ''}
+ libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
+ with pytest.raises(DistutilsSetupError):
+ cmd.build_libraries(libs)
+
+ obj_deps = {'source.c': ''}
+ libs = [('example', {'sources': ['source.c'], 'obj_deps': obj_deps})]
+ with pytest.raises(DistutilsSetupError):
+ cmd.build_libraries(libs)
+
+ # with that out of the way, let's see if the crude dependency
+ # system works
+ cmd.compiler = mock.MagicMock(spec=cmd.compiler)
+ mock_newer.return_value = ([], [])
+
+ obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
+ libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})]
+
+ cmd.build_libraries(libs)
+ assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0]
+ assert not cmd.compiler.compile.called
+ assert cmd.compiler.create_static_lib.call_count == 1
+
+ # reset the call numbers so we can test again
+ cmd.compiler.reset_mock()
+
+ mock_newer.return_value = '' # anything as long as it's not ([],[])
+ cmd.build_libraries(libs)
+ assert cmd.compiler.compile.call_count == 1
+ assert cmd.compiler.create_static_lib.call_count == 1
+
+ @mock.patch('setuptools.command.build_clib.newer_pairwise_group')
+ def test_build_libraries_reproducible(self, mock_newer):
+ dist = Distribution()
+ cmd = build_clib(dist)
+
+ # with that out of the way, let's see if the crude dependency
+ # system works
+ cmd.compiler = mock.MagicMock(spec=cmd.compiler)
+ mock_newer.return_value = ([], [])
+
+ original_sources = ['a-example.c', 'example.c']
+ sources = original_sources
+
+ obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
+ libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
+
+ cmd.build_libraries(libs)
+ computed_call_args = mock_newer.call_args[0]
+
+ while sources == original_sources:
+ sources = random.sample(original_sources, len(original_sources))
+ libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
+
+ cmd.build_libraries(libs)
+ assert computed_call_args == mock_newer.call_args[0]
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_build_ext.py b/lib/python3.12/site-packages/setuptools/tests/test_build_ext.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7b60ac32fcd6628cf96396a7e5d8fdb50f67c19
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_build_ext.py
@@ -0,0 +1,293 @@
+from __future__ import annotations
+
+import os
+import sys
+from importlib.util import cache_from_source as _compiled_file_name
+
+import pytest
+from jaraco import path
+
+from setuptools.command.build_ext import build_ext, get_abi3_suffix
+from setuptools.dist import Distribution
+from setuptools.errors import CompileError
+from setuptools.extension import Extension
+
+from . import environment
+from .textwrap import DALS
+
+import distutils.command.build_ext as orig
+from distutils.sysconfig import get_config_var
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+class TestBuildExt:
+ def test_get_ext_filename(self):
+ """
+ Setuptools needs to give back the same
+ result as distutils, even if the fullname
+ is not in ext_map.
+ """
+ dist = Distribution()
+ cmd = build_ext(dist)
+ cmd.ext_map['foo/bar'] = ''
+ res = cmd.get_ext_filename('foo')
+ wanted = orig.build_ext.get_ext_filename(cmd, 'foo')
+ assert res == wanted
+
+ def test_abi3_filename(self):
+ """
+ Filename needs to be loadable by several versions
+ of Python 3 if 'is_abi3' is truthy on Extension()
+ """
+ print(get_abi3_suffix())
+
+ extension = Extension('spam.eggs', ['eggs.c'], py_limited_api=True)
+ dist = Distribution(dict(ext_modules=[extension]))
+ cmd = build_ext(dist)
+ cmd.finalize_options()
+ assert 'spam.eggs' in cmd.ext_map
+ res = cmd.get_ext_filename('spam.eggs')
+
+ if not get_abi3_suffix():
+ assert res.endswith(get_config_var('EXT_SUFFIX'))
+ elif sys.platform == 'win32':
+ assert res.endswith('eggs.pyd')
+ else:
+ assert 'abi3' in res
+
+ def test_ext_suffix_override(self):
+ """
+ SETUPTOOLS_EXT_SUFFIX variable always overrides
+ default extension options.
+ """
+ dist = Distribution()
+ cmd = build_ext(dist)
+ cmd.ext_map['for_abi3'] = ext = Extension(
+ 'for_abi3',
+ ['s.c'],
+ # Override shouldn't affect abi3 modules
+ py_limited_api=True,
+ )
+ # Mock value needed to pass tests
+ ext._links_to_dynamic = False
+
+ if not IS_PYPY:
+ expect = cmd.get_ext_filename('for_abi3')
+ else:
+ # PyPy builds do not use ABI3 tag, so they will
+ # also get the overridden suffix.
+ expect = 'for_abi3.test-suffix'
+
+ try:
+ os.environ['SETUPTOOLS_EXT_SUFFIX'] = '.test-suffix'
+ res = cmd.get_ext_filename('normal')
+ assert 'normal.test-suffix' == res
+ res = cmd.get_ext_filename('for_abi3')
+ assert expect == res
+ finally:
+ del os.environ['SETUPTOOLS_EXT_SUFFIX']
+
+ def dist_with_example(self):
+ files = {
+ "src": {"mypkg": {"subpkg": {"ext2.c": ""}}},
+ "c-extensions": {"ext1": {"main.c": ""}},
+ }
+
+ ext1 = Extension("mypkg.ext1", ["c-extensions/ext1/main.c"])
+ ext2 = Extension("mypkg.subpkg.ext2", ["src/mypkg/subpkg/ext2.c"])
+ ext3 = Extension("ext3", ["c-extension/ext3.c"])
+
+ path.build(files)
+ return Distribution({
+ "script_name": "%test%",
+ "ext_modules": [ext1, ext2, ext3],
+ "package_dir": {"": "src"},
+ })
+
+ def test_get_outputs(self, tmpdir_cwd, monkeypatch):
+ monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3') # make test OS-independent
+ monkeypatch.setattr('setuptools.command.build_ext.use_stubs', False)
+ dist = self.dist_with_example()
+
+ # Regular build: get_outputs not empty, but get_output_mappings is empty
+ build_ext = dist.get_command_obj("build_ext")
+ build_ext.editable_mode = False
+ build_ext.ensure_finalized()
+ build_lib = build_ext.build_lib.replace(os.sep, "/")
+ outputs = [x.replace(os.sep, "/") for x in build_ext.get_outputs()]
+ assert outputs == [
+ f"{build_lib}/ext3.mp3",
+ f"{build_lib}/mypkg/ext1.mp3",
+ f"{build_lib}/mypkg/subpkg/ext2.mp3",
+ ]
+ assert build_ext.get_output_mapping() == {}
+
+ # Editable build: get_output_mappings should contain everything in get_outputs
+ dist.reinitialize_command("build_ext")
+ build_ext.editable_mode = True
+ build_ext.ensure_finalized()
+ mapping = {
+ k.replace(os.sep, "/"): v.replace(os.sep, "/")
+ for k, v in build_ext.get_output_mapping().items()
+ }
+ assert mapping == {
+ f"{build_lib}/ext3.mp3": "src/ext3.mp3",
+ f"{build_lib}/mypkg/ext1.mp3": "src/mypkg/ext1.mp3",
+ f"{build_lib}/mypkg/subpkg/ext2.mp3": "src/mypkg/subpkg/ext2.mp3",
+ }
+
+ def test_get_output_mapping_with_stub(self, tmpdir_cwd, monkeypatch):
+ monkeypatch.setenv('SETUPTOOLS_EXT_SUFFIX', '.mp3') # make test OS-independent
+ monkeypatch.setattr('setuptools.command.build_ext.use_stubs', True)
+ dist = self.dist_with_example()
+
+ # Editable build should create compiled stubs (.pyc files only, no .py)
+ build_ext = dist.get_command_obj("build_ext")
+ build_ext.editable_mode = True
+ build_ext.ensure_finalized()
+ for ext in build_ext.extensions:
+ monkeypatch.setattr(ext, "_needs_stub", True)
+
+ build_lib = build_ext.build_lib.replace(os.sep, "/")
+ mapping = {
+ k.replace(os.sep, "/"): v.replace(os.sep, "/")
+ for k, v in build_ext.get_output_mapping().items()
+ }
+
+ def C(file):
+ """Make it possible to do comparisons and tests in a OS-independent way"""
+ return _compiled_file_name(file).replace(os.sep, "/")
+
+ assert mapping == {
+ C(f"{build_lib}/ext3.py"): C("src/ext3.py"),
+ f"{build_lib}/ext3.mp3": "src/ext3.mp3",
+ C(f"{build_lib}/mypkg/ext1.py"): C("src/mypkg/ext1.py"),
+ f"{build_lib}/mypkg/ext1.mp3": "src/mypkg/ext1.mp3",
+ C(f"{build_lib}/mypkg/subpkg/ext2.py"): C("src/mypkg/subpkg/ext2.py"),
+ f"{build_lib}/mypkg/subpkg/ext2.mp3": "src/mypkg/subpkg/ext2.mp3",
+ }
+
+ # Ensure only the compiled stubs are present not the raw .py stub
+ assert f"{build_lib}/mypkg/ext1.py" not in mapping
+ assert f"{build_lib}/mypkg/subpkg/ext2.py" not in mapping
+
+ # Visualize what the cached stub files look like
+ example_stub = C(f"{build_lib}/mypkg/ext1.py")
+ assert example_stub in mapping
+ assert example_stub.startswith(f"{build_lib}/mypkg/__pycache__/ext1")
+ assert example_stub.endswith(".pyc")
+
+
+class TestBuildExtInplace:
+ def get_build_ext_cmd(self, optional: bool, **opts) -> build_ext:
+ files: dict[str, str | dict[str, dict[str, str]]] = {
+ "eggs.c": "#include missingheader.h\n",
+ ".build": {"lib": {}, "tmp": {}},
+ }
+ path.build(files)
+ extension = Extension('spam.eggs', ['eggs.c'], optional=optional)
+ dist = Distribution(dict(ext_modules=[extension]))
+ dist.script_name = 'setup.py'
+ cmd = build_ext(dist)
+ vars(cmd).update(build_lib=".build/lib", build_temp=".build/tmp", **opts)
+ cmd.ensure_finalized()
+ return cmd
+
+ def get_log_messages(self, caplog, capsys):
+ """
+ Historically, distutils "logged" by printing to sys.std*.
+ Later versions adopted the logging framework. Grab
+ messages regardless of how they were captured.
+ """
+ std = capsys.readouterr()
+ return std.out.splitlines() + std.err.splitlines() + caplog.messages
+
+ def test_optional(self, tmpdir_cwd, caplog, capsys):
+ """
+ If optional extensions fail to build, setuptools should show the error
+ in the logs but not fail to build
+ """
+ cmd = self.get_build_ext_cmd(optional=True, inplace=True)
+ cmd.run()
+ assert any(
+ 'build_ext: building extension "spam.eggs" failed'
+ for msg in self.get_log_messages(caplog, capsys)
+ )
+ # No compile error exception should be raised
+
+ def test_non_optional(self, tmpdir_cwd):
+ # Non-optional extensions should raise an exception
+ cmd = self.get_build_ext_cmd(optional=False, inplace=True)
+ with pytest.raises(CompileError):
+ cmd.run()
+
+
+def test_build_ext_config_handling(tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ from setuptools import Extension, setup
+ setup(
+ name='foo',
+ version='0.0.0',
+ ext_modules=[Extension('foo', ['foo.c'])],
+ )
+ """
+ ),
+ 'foo.c': DALS(
+ """
+ #include "Python.h"
+
+ #if PY_MAJOR_VERSION >= 3
+
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "foo",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ };
+
+ #define INITERROR return NULL
+
+ PyMODINIT_FUNC PyInit_foo(void)
+
+ #else
+
+ #define INITERROR return
+
+ void initfoo(void)
+
+ #endif
+ {
+ #if PY_MAJOR_VERSION >= 3
+ PyObject *module = PyModule_Create(&moduledef);
+ #else
+ PyObject *module = Py_InitModule("extension", NULL);
+ #endif
+ if (module == NULL)
+ INITERROR;
+ #if PY_MAJOR_VERSION >= 3
+ return module;
+ #endif
+ }
+ """
+ ),
+ 'setup.cfg': DALS(
+ """
+ [build]
+ build_base = foo_build
+ """
+ ),
+ }
+ path.build(files)
+ code, (stdout, stderr) = environment.run_setup_py(
+ cmd=['build'],
+ data_stream=(0, 2),
+ )
+ assert code == 0, f'\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}'
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_build_meta.py b/lib/python3.12/site-packages/setuptools/tests/test_build_meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cd0a0a8ede8ecb8d0c5ae55c8c3b09559239ed5
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_build_meta.py
@@ -0,0 +1,959 @@
+import contextlib
+import importlib
+import os
+import re
+import shutil
+import signal
+import sys
+import tarfile
+import warnings
+from concurrent import futures
+from pathlib import Path
+from typing import Any, Callable
+from zipfile import ZipFile
+
+import pytest
+from jaraco import path
+from packaging.requirements import Requirement
+
+from setuptools.warnings import SetuptoolsDeprecationWarning
+
+from .textwrap import DALS
+
+SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
+
+
+TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180")) # in seconds
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+pytestmark = pytest.mark.skipif(
+ sys.platform == "win32" and IS_PYPY,
+ reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor "
+ "is flaky and problematic",
+)
+
+
+class BuildBackendBase:
+ def __init__(self, cwd='.', env=None, backend_name='setuptools.build_meta') -> None:
+ self.cwd = cwd
+ self.env = env or {}
+ self.backend_name = backend_name
+
+
+class BuildBackend(BuildBackendBase):
+ """PEP 517 Build Backend"""
+
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self.pool = futures.ProcessPoolExecutor(max_workers=1)
+
+ def __getattr__(self, name: str) -> Callable[..., Any]:
+ """Handles arbitrary function invocations on the build backend."""
+
+ def method(*args, **kw):
+ root = os.path.abspath(self.cwd)
+ caller = BuildBackendCaller(root, self.env, self.backend_name)
+ pid = None
+ try:
+ pid = self.pool.submit(os.getpid).result(TIMEOUT)
+ return self.pool.submit(caller, name, *args, **kw).result(TIMEOUT)
+ except futures.TimeoutError:
+ self.pool.shutdown(wait=False) # doesn't stop already running processes
+ self._kill(pid)
+ pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)")
+ except (futures.process.BrokenProcessPool, MemoryError, OSError):
+ if IS_PYPY:
+ pytest.xfail("PyPy frequently fails tests with ProcessPoolExector")
+ raise
+
+ return method
+
+ def _kill(self, pid):
+ if pid is None:
+ return
+ with contextlib.suppress(ProcessLookupError, OSError):
+ os.kill(pid, signal.SIGTERM if os.name == "nt" else signal.SIGKILL)
+
+
+class BuildBackendCaller(BuildBackendBase):
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+
+ (self.backend_name, _, self.backend_obj) = self.backend_name.partition(':')
+
+ def __call__(self, name, *args, **kw) -> Any:
+ """Handles arbitrary function invocations on the build backend."""
+ os.chdir(self.cwd)
+ os.environ.update(self.env)
+ mod = importlib.import_module(self.backend_name)
+
+ if self.backend_obj:
+ backend = getattr(mod, self.backend_obj)
+ else:
+ backend = mod
+
+ return getattr(backend, name)(*args, **kw)
+
+
+defns = [
+ { # simple setup.py script
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ },
+ { # setup.py that relies on __name__
+ 'setup.py': DALS(
+ """
+ assert __name__ == '__main__'
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ },
+ { # setup.py script that runs arbitrary code
+ 'setup.py': DALS(
+ """
+ variable = True
+ def function():
+ return variable
+ assert variable
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ },
+ { # setup.py script that constructs temp files to be included in the distribution
+ 'setup.py': DALS(
+ """
+ # Some packages construct files on the fly, include them in the package,
+ # and immediately remove them after `setup()` (e.g. pybind11==2.9.1).
+ # Therefore, we cannot use `distutils.core.run_setup(..., stop_after=...)`
+ # to obtain a distribution object first, and then run the distutils
+ # commands later, because these files will be removed in the meantime.
+
+ with open('world.py', 'w', encoding="utf-8") as f:
+ f.write('x = 42')
+
+ try:
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['world'],
+ setup_requires=['six'],
+ )
+ finally:
+ # Some packages will clean temporary files
+ __import__('os').unlink('world.py')
+ """
+ ),
+ },
+ { # setup.cfg only
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ name = foo
+ version = 0.0.0
+
+ [options]
+ py_modules=hello
+ setup_requires=six
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ },
+ { # setup.cfg and setup.py
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ name = foo
+ version = 0.0.0
+
+ [options]
+ py_modules=hello
+ setup_requires=six
+ """
+ ),
+ 'setup.py': "__import__('setuptools').setup()",
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ },
+]
+
+
+class TestBuildMetaBackend:
+ backend_name = 'setuptools.build_meta'
+
+ def get_build_backend(self):
+ return BuildBackend(backend_name=self.backend_name)
+
+ @pytest.fixture(params=defns)
+ def build_backend(self, tmpdir, request):
+ path.build(request.param, prefix=str(tmpdir))
+ with tmpdir.as_cwd():
+ yield self.get_build_backend()
+
+ def test_get_requires_for_build_wheel(self, build_backend):
+ actual = build_backend.get_requires_for_build_wheel()
+ expected = ['six']
+ assert sorted(actual) == sorted(expected)
+
+ def test_get_requires_for_build_sdist(self, build_backend):
+ actual = build_backend.get_requires_for_build_sdist()
+ expected = ['six']
+ assert sorted(actual) == sorted(expected)
+
+ def test_build_wheel(self, build_backend):
+ dist_dir = os.path.abspath('pip-wheel')
+ os.makedirs(dist_dir)
+ wheel_name = build_backend.build_wheel(dist_dir)
+
+ wheel_file = os.path.join(dist_dir, wheel_name)
+ assert os.path.isfile(wheel_file)
+
+ # Temporary files should be removed
+ assert not os.path.isfile('world.py')
+
+ with ZipFile(wheel_file) as zipfile:
+ wheel_contents = set(zipfile.namelist())
+
+ # Each one of the examples have a single module
+ # that should be included in the distribution
+ python_scripts = (f for f in wheel_contents if f.endswith('.py'))
+ modules = [f for f in python_scripts if not f.endswith('setup.py')]
+ assert len(modules) == 1
+
+ @pytest.mark.parametrize('build_type', ('wheel', 'sdist'))
+ def test_build_with_existing_file_present(self, build_type, tmpdir_cwd):
+ # Building a sdist/wheel should still succeed if there's
+ # already a sdist/wheel in the destination directory.
+ files = {
+ 'setup.py': "from setuptools import setup\nsetup()",
+ 'VERSION': "0.0.1",
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ name = foo
+ version = file: VERSION
+ """
+ ),
+ 'pyproject.toml': DALS(
+ """
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+ """
+ ),
+ }
+
+ path.build(files)
+
+ dist_dir = os.path.abspath('preexisting-' + build_type)
+
+ build_backend = self.get_build_backend()
+ build_method = getattr(build_backend, 'build_' + build_type)
+
+ # Build a first sdist/wheel.
+ # Note: this also check the destination directory is
+ # successfully created if it does not exist already.
+ first_result = build_method(dist_dir)
+
+ # Change version.
+ with open("VERSION", "wt", encoding="utf-8") as version_file:
+ version_file.write("0.0.2")
+
+ # Build a *second* sdist/wheel.
+ second_result = build_method(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, first_result))
+ assert first_result != second_result
+
+ # And if rebuilding the exact same sdist/wheel?
+ open(os.path.join(dist_dir, second_result), 'wb').close()
+ third_result = build_method(dist_dir)
+ assert third_result == second_result
+ assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0
+
+ @pytest.mark.parametrize("setup_script", [None, SETUP_SCRIPT_STUB])
+ def test_build_with_pyproject_config(self, tmpdir, setup_script):
+ files = {
+ 'pyproject.toml': DALS(
+ """
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "foo"
+ license = {text = "MIT"}
+ description = "This is a Python package"
+ dynamic = ["version", "readme"]
+ classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers"
+ ]
+ urls = {Homepage = "http://github.com"}
+ dependencies = [
+ "appdirs",
+ ]
+
+ [project.optional-dependencies]
+ all = [
+ "tomli>=1",
+ "pyscaffold>=4,<5",
+ 'importlib; python_version == "2.6"',
+ ]
+
+ [project.scripts]
+ foo = "foo.cli:main"
+
+ [tool.setuptools]
+ zip-safe = false
+ package-dir = {"" = "src"}
+ packages = {find = {where = ["src"]}}
+ license-files = ["LICENSE*"]
+
+ [tool.setuptools.dynamic]
+ version = {attr = "foo.__version__"}
+ readme = {file = "README.rst"}
+
+ [tool.distutils.sdist]
+ formats = "gztar"
+ """
+ ),
+ "MANIFEST.in": DALS(
+ """
+ global-include *.py *.txt
+ global-exclude *.py[cod]
+ """
+ ),
+ "README.rst": "This is a ``README``",
+ "LICENSE.txt": "---- placeholder MIT license ----",
+ "src": {
+ "foo": {
+ "__init__.py": "__version__ = '0.1'",
+ "__init__.pyi": "__version__: str",
+ "cli.py": "def main(): print('hello world')",
+ "data.txt": "def main(): print('hello world')",
+ "py.typed": "",
+ }
+ },
+ }
+ if setup_script:
+ files["setup.py"] = setup_script
+
+ build_backend = self.get_build_backend()
+ with tmpdir.as_cwd():
+ path.build(files)
+ msgs = [
+ "'tool.setuptools.license-files' is deprecated in favor of 'project.license-files'",
+ "`project.license` as a TOML table is deprecated",
+ ]
+ with warnings.catch_warnings():
+ for msg in msgs:
+ warnings.filterwarnings("ignore", msg, SetuptoolsDeprecationWarning)
+ sdist_path = build_backend.build_sdist("temp")
+ wheel_file = build_backend.build_wheel("temp")
+
+ with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
+ sdist_contents = set(tar.getnames())
+
+ with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
+ wheel_contents = set(zipfile.namelist())
+ metadata = str(zipfile.read("foo-0.1.dist-info/METADATA"), "utf-8")
+ license = str(
+ zipfile.read("foo-0.1.dist-info/licenses/LICENSE.txt"), "utf-8"
+ )
+ epoints = str(zipfile.read("foo-0.1.dist-info/entry_points.txt"), "utf-8")
+
+ assert sdist_contents - {"foo-0.1/setup.py"} == {
+ 'foo-0.1',
+ 'foo-0.1/LICENSE.txt',
+ 'foo-0.1/MANIFEST.in',
+ 'foo-0.1/PKG-INFO',
+ 'foo-0.1/README.rst',
+ 'foo-0.1/pyproject.toml',
+ 'foo-0.1/setup.cfg',
+ 'foo-0.1/src',
+ 'foo-0.1/src/foo',
+ 'foo-0.1/src/foo/__init__.py',
+ 'foo-0.1/src/foo/__init__.pyi',
+ 'foo-0.1/src/foo/cli.py',
+ 'foo-0.1/src/foo/data.txt',
+ 'foo-0.1/src/foo/py.typed',
+ 'foo-0.1/src/foo.egg-info',
+ 'foo-0.1/src/foo.egg-info/PKG-INFO',
+ 'foo-0.1/src/foo.egg-info/SOURCES.txt',
+ 'foo-0.1/src/foo.egg-info/dependency_links.txt',
+ 'foo-0.1/src/foo.egg-info/entry_points.txt',
+ 'foo-0.1/src/foo.egg-info/requires.txt',
+ 'foo-0.1/src/foo.egg-info/top_level.txt',
+ 'foo-0.1/src/foo.egg-info/not-zip-safe',
+ }
+ assert wheel_contents == {
+ "foo/__init__.py",
+ "foo/__init__.pyi", # include type information by default
+ "foo/cli.py",
+ "foo/data.txt", # include_package_data defaults to True
+ "foo/py.typed", # include type information by default
+ "foo-0.1.dist-info/licenses/LICENSE.txt",
+ "foo-0.1.dist-info/METADATA",
+ "foo-0.1.dist-info/WHEEL",
+ "foo-0.1.dist-info/entry_points.txt",
+ "foo-0.1.dist-info/top_level.txt",
+ "foo-0.1.dist-info/RECORD",
+ }
+ assert license == "---- placeholder MIT license ----"
+
+ for line in (
+ "Summary: This is a Python package",
+ "License: MIT",
+ "License-File: LICENSE.txt",
+ "Classifier: Intended Audience :: Developers",
+ "Requires-Dist: appdirs",
+ "Requires-Dist: " + str(Requirement('tomli>=1 ; extra == "all"')),
+ "Requires-Dist: "
+ + str(Requirement('importlib; python_version=="2.6" and extra =="all"')),
+ ):
+ assert line in metadata, (line, metadata)
+
+ assert metadata.strip().endswith("This is a ``README``")
+ assert epoints.strip() == "[console_scripts]\nfoo = foo.cli:main"
+
+ def test_static_metadata_in_pyproject_config(self, tmpdir):
+ # Make sure static metadata in pyproject.toml is not overwritten by setup.py
+ # as required by PEP 621
+ files = {
+ 'pyproject.toml': DALS(
+ """
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "foo"
+ description = "This is a Python package"
+ version = "42"
+ dependencies = ["six"]
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='bar',
+ version='13',
+ )
+ """
+ ),
+ }
+ build_backend = self.get_build_backend()
+ with tmpdir.as_cwd():
+ path.build(files)
+ sdist_path = build_backend.build_sdist("temp")
+ wheel_file = build_backend.build_wheel("temp")
+
+ assert (tmpdir / "temp/foo-42.tar.gz").exists()
+ assert (tmpdir / "temp/foo-42-py3-none-any.whl").exists()
+ assert not (tmpdir / "temp/bar-13.tar.gz").exists()
+ assert not (tmpdir / "temp/bar-42.tar.gz").exists()
+ assert not (tmpdir / "temp/foo-13.tar.gz").exists()
+ assert not (tmpdir / "temp/bar-13-py3-none-any.whl").exists()
+ assert not (tmpdir / "temp/bar-42-py3-none-any.whl").exists()
+ assert not (tmpdir / "temp/foo-13-py3-none-any.whl").exists()
+
+ with tarfile.open(os.path.join(tmpdir, "temp", sdist_path)) as tar:
+ pkg_info = str(tar.extractfile('foo-42/PKG-INFO').read(), "utf-8")
+ members = tar.getnames()
+ assert "bar-13/PKG-INFO" not in members
+
+ with ZipFile(os.path.join(tmpdir, "temp", wheel_file)) as zipfile:
+ metadata = str(zipfile.read("foo-42.dist-info/METADATA"), "utf-8")
+ members = zipfile.namelist()
+ assert "bar-13.dist-info/METADATA" not in members
+
+ for file in pkg_info, metadata:
+ for line in ("Name: foo", "Version: 42"):
+ assert line in file
+ for line in ("Name: bar", "Version: 13"):
+ assert line not in file
+
+ def test_build_sdist(self, build_backend):
+ dist_dir = os.path.abspath('pip-sdist')
+ os.makedirs(dist_dir)
+ sdist_name = build_backend.build_sdist(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+ def test_prepare_metadata_for_build_wheel(self, build_backend):
+ dist_dir = os.path.abspath('pip-dist-info')
+ os.makedirs(dist_dir)
+
+ dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
+
+ def test_prepare_metadata_inplace(self, build_backend):
+ """
+ Some users might pass metadata_directory pre-populated with `.tox` or `.venv`.
+ See issue #3523.
+ """
+ for pre_existing in [
+ ".tox/python/lib/python3.10/site-packages/attrs-22.1.0.dist-info",
+ ".tox/python/lib/python3.10/site-packages/autocommand-2.2.1.dist-info",
+ ".nox/python/lib/python3.10/site-packages/build-0.8.0.dist-info",
+ ".venv/python3.10/site-packages/click-8.1.3.dist-info",
+ "venv/python3.10/site-packages/distlib-0.3.5.dist-info",
+ "env/python3.10/site-packages/docutils-0.19.dist-info",
+ ]:
+ os.makedirs(pre_existing, exist_ok=True)
+ dist_info = build_backend.prepare_metadata_for_build_wheel(".")
+ assert os.path.isfile(os.path.join(dist_info, 'METADATA'))
+
+ def test_build_sdist_explicit_dist(self, build_backend):
+ # explicitly specifying the dist folder should work
+ # the folder sdist_directory and the ``--dist-dir`` can be the same
+ dist_dir = os.path.abspath('dist')
+ sdist_name = build_backend.build_sdist(dist_dir)
+ assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+ def test_build_sdist_version_change(self, build_backend):
+ sdist_into_directory = os.path.abspath("out_sdist")
+ os.makedirs(sdist_into_directory)
+
+ sdist_name = build_backend.build_sdist(sdist_into_directory)
+ assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
+
+ # if the setup.py changes subsequent call of the build meta
+ # should still succeed, given the
+ # sdist_directory the frontend specifies is empty
+ setup_loc = os.path.abspath("setup.py")
+ if not os.path.exists(setup_loc):
+ setup_loc = os.path.abspath("setup.cfg")
+
+ with open(setup_loc, 'rt', encoding="utf-8") as file_handler:
+ content = file_handler.read()
+ with open(setup_loc, 'wt', encoding="utf-8") as file_handler:
+ file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'"))
+
+ shutil.rmtree(sdist_into_directory)
+ os.makedirs(sdist_into_directory)
+
+ sdist_name = build_backend.build_sdist("out_sdist")
+ assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+ def test_build_sdist_pyproject_toml_exists(self, tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""
+ ),
+ 'hello.py': '',
+ 'pyproject.toml': DALS(
+ """
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+ """
+ ),
+ }
+ path.build(files)
+ build_backend = self.get_build_backend()
+ targz_path = build_backend.build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert any('pyproject.toml' in name for name in tar.getnames())
+
+ def test_build_sdist_setup_py_exists(self, tmpdir_cwd):
+ # If build_sdist is called from a script other than setup.py,
+ # ensure setup.py is included
+ path.build(defns[0])
+
+ build_backend = self.get_build_backend()
+ targz_path = build_backend.build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert any('setup.py' in name for name in tar.getnames())
+
+ def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
+ # Ensure that MANIFEST.in can exclude setup.py
+ files = {
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""
+ ),
+ 'hello.py': '',
+ 'MANIFEST.in': DALS(
+ """
+ exclude setup.py
+ """
+ ),
+ }
+
+ path.build(files)
+
+ build_backend = self.get_build_backend()
+ targz_path = build_backend.build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert not any('setup.py' in name for name in tar.getnames())
+
+ def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""
+ ),
+ 'hello.py': '',
+ 'setup.cfg': DALS(
+ """
+ [sdist]
+ formats=zip
+ """
+ ),
+ }
+
+ path.build(files)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
+
+ _relative_path_import_files = {
+ 'setup.py': DALS(
+ """
+ __import__('setuptools').setup(
+ name='foo',
+ version=__import__('hello').__version__,
+ py_modules=['hello']
+ )"""
+ ),
+ 'hello.py': '__version__ = "0.0.0"',
+ 'setup.cfg': DALS(
+ """
+ [sdist]
+ formats=zip
+ """
+ ),
+ }
+
+ def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+ path.build(self._relative_path_import_files)
+ build_backend = self.get_build_backend()
+ with pytest.raises(ImportError, match="^No module named 'hello'$"):
+ build_backend.build_sdist("temp")
+
+ _simple_pyproject_example = {
+ "pyproject.toml": DALS(
+ """
+ [project]
+ name = "proj"
+ version = "42"
+ """
+ ),
+ "src": {"proj": {"__init__.py": ""}},
+ }
+
+ def _assert_link_tree(self, parent_dir):
+ """All files in the directory should be either links or hard links"""
+ files = list(Path(parent_dir).glob("**/*"))
+ assert files # Should not be empty
+ for file in files:
+ assert file.is_symlink() or os.stat(file).st_nlink > 0
+
+ def test_editable_without_config_settings(self, tmpdir_cwd):
+ """
+ Sanity check to ensure tests with --mode=strict are different from the ones
+ without --mode.
+
+ --mode=strict should create a local directory with a package tree.
+ The directory should not get created otherwise.
+ """
+ path.build(self._simple_pyproject_example)
+ build_backend = self.get_build_backend()
+ assert not Path("build").exists()
+ build_backend.build_editable("temp")
+ assert not Path("build").exists()
+
+ def test_build_wheel_inplace(self, tmpdir_cwd):
+ config_settings = {"--build-option": ["build_ext", "--inplace"]}
+ path.build(self._simple_pyproject_example)
+ build_backend = self.get_build_backend()
+ assert not Path("build").exists()
+ Path("build").mkdir()
+ build_backend.prepare_metadata_for_build_wheel("build", config_settings)
+ build_backend.build_wheel("build", config_settings)
+ assert Path("build/proj-42-py3-none-any.whl").exists()
+
+ @pytest.mark.parametrize("config_settings", [{"editable-mode": "strict"}])
+ def test_editable_with_config_settings(self, tmpdir_cwd, config_settings):
+ path.build({**self._simple_pyproject_example, '_meta': {}})
+ assert not Path("build").exists()
+ build_backend = self.get_build_backend()
+ build_backend.prepare_metadata_for_build_editable("_meta", config_settings)
+ build_backend.build_editable("temp", config_settings, "_meta")
+ self._assert_link_tree(next(Path("build").glob("__editable__.*")))
+
+ @pytest.mark.parametrize(
+ ("setup_literal", "requirements"),
+ [
+ ("'foo'", ['foo']),
+ ("['foo']", ['foo']),
+ (r"'foo\n'", ['foo']),
+ (r"'foo\n\n'", ['foo']),
+ ("['foo', 'bar']", ['foo', 'bar']),
+ (r"'# Has a comment line\nfoo'", ['foo']),
+ (r"'foo # Has an inline comment'", ['foo']),
+ (r"'foo \\\n >=3.0'", ['foo>=3.0']),
+ (r"'foo\nbar'", ['foo', 'bar']),
+ (r"'foo\nbar\n'", ['foo', 'bar']),
+ (r"['foo\n', 'bar\n']", ['foo', 'bar']),
+ ],
+ )
+ @pytest.mark.parametrize('use_wheel', [True, False])
+ def test_setup_requires(self, setup_literal, requirements, use_wheel, tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ from setuptools import setup
+
+ setup(
+ name="qux",
+ version="0.0.0",
+ py_modules=["hello"],
+ setup_requires={setup_literal},
+ )
+ """
+ ).format(setup_literal=setup_literal),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ }
+
+ path.build(files)
+
+ build_backend = self.get_build_backend()
+
+ if use_wheel:
+ get_requires = build_backend.get_requires_for_build_wheel
+ else:
+ get_requires = build_backend.get_requires_for_build_sdist
+
+ # Ensure that the build requirements are properly parsed
+ expected = sorted(requirements)
+ actual = get_requires()
+
+ assert expected == sorted(actual)
+
+ def test_setup_requires_with_auto_discovery(self, tmpdir_cwd):
+ # Make sure patches introduced to retrieve setup_requires don't accidentally
+ # activate auto-discovery and cause problems due to the incomplete set of
+ # attributes passed to MinimalDistribution
+ files = {
+ 'pyproject.toml': DALS(
+ """
+ [project]
+ name = "proj"
+ version = "42"
+ """
+ ),
+ "setup.py": DALS(
+ """
+ __import__('setuptools').setup(
+ setup_requires=["foo"],
+ py_modules = ["hello", "world"]
+ )
+ """
+ ),
+ 'hello.py': "'hello'",
+ 'world.py': "'world'",
+ }
+ path.build(files)
+ build_backend = self.get_build_backend()
+ setup_requires = build_backend.get_requires_for_build_wheel()
+ assert setup_requires == ["foo"]
+
+ def test_dont_install_setup_requires(self, tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ from setuptools import setup
+
+ setup(
+ name="qux",
+ version="0.0.0",
+ py_modules=["hello"],
+ setup_requires=["does-not-exist >99"],
+ )
+ """
+ ),
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ }
+
+ path.build(files)
+
+ build_backend = self.get_build_backend()
+
+ dist_dir = os.path.abspath('pip-dist-info')
+ os.makedirs(dist_dir)
+
+ # does-not-exist can't be satisfied, so if it attempts to install
+ # setup_requires, it will fail.
+ build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+ _sys_argv_0_passthrough = {
+ 'setup.py': DALS(
+ """
+ import os
+ import sys
+
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ )
+
+ sys_argv = os.path.abspath(sys.argv[0])
+ file_path = os.path.abspath('setup.py')
+ assert sys_argv == file_path
+ """
+ )
+ }
+
+ def test_sys_argv_passthrough(self, tmpdir_cwd):
+ path.build(self._sys_argv_0_passthrough)
+ build_backend = self.get_build_backend()
+ with pytest.raises(AssertionError):
+ build_backend.build_sdist("temp")
+
+ _setup_py_file_abspath = {
+ 'setup.py': DALS(
+ """
+ import os
+ assert os.path.isabs(__file__)
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello'],
+ setup_requires=['six'],
+ )
+ """
+ )
+ }
+
+ def test_setup_py_file_abspath(self, tmpdir_cwd):
+ path.build(self._setup_py_file_abspath)
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
+
+ @pytest.mark.parametrize('build_hook', ('build_sdist', 'build_wheel'))
+ def test_build_with_empty_setuppy(self, build_backend, build_hook):
+ files = {'setup.py': ''}
+ path.build(files)
+
+ msg = re.escape('No distribution was found.')
+ with pytest.raises(ValueError, match=msg):
+ getattr(build_backend, build_hook)("temp")
+
+
+class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
+ backend_name = 'setuptools.build_meta:__legacy__'
+
+ # build_meta_legacy-specific tests
+ def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+ # This must fail in build_meta, but must pass in build_meta_legacy
+ path.build(self._relative_path_import_files)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
+
+ def test_sys_argv_passthrough(self, tmpdir_cwd):
+ path.build(self._sys_argv_0_passthrough)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
+
+
+@pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning")
+def test_sys_exit_0_in_setuppy(monkeypatch, tmp_path):
+ """Setuptools should be resilient to setup.py with ``sys.exit(0)`` (#3973)."""
+ monkeypatch.chdir(tmp_path)
+ setuppy = """
+ import sys, setuptools
+ setuptools.setup(name='foo', version='0.0.0')
+ sys.exit(0)
+ """
+ (tmp_path / "setup.py").write_text(DALS(setuppy), encoding="utf-8")
+ backend = BuildBackend(backend_name="setuptools.build_meta")
+ assert backend.get_requires_for_build_wheel() == []
+
+
+def test_system_exit_in_setuppy(monkeypatch, tmp_path):
+ monkeypatch.chdir(tmp_path)
+ setuppy = "import sys; sys.exit('some error')"
+ (tmp_path / "setup.py").write_text(setuppy, encoding="utf-8")
+ with pytest.raises(SystemExit, match="some error"):
+ backend = BuildBackend(backend_name="setuptools.build_meta")
+ backend.get_requires_for_build_wheel()
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_build_py.py b/lib/python3.12/site-packages/setuptools/tests/test_build_py.py
new file mode 100644
index 0000000000000000000000000000000000000000..78848f718281cce38df5deb82b7afa855c6e0d07
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_build_py.py
@@ -0,0 +1,480 @@
+import os
+import shutil
+import stat
+import warnings
+from pathlib import Path
+from unittest.mock import Mock
+
+import jaraco.path
+import pytest
+
+from setuptools import SetuptoolsDeprecationWarning
+from setuptools.dist import Distribution
+
+from .textwrap import DALS
+
+
+def test_directories_in_package_data_glob(tmpdir_cwd):
+ """
+ Directories matching the glob in package_data should
+ not be included in the package data.
+
+ Regression test for #261.
+ """
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['build_py'],
+ packages=[''],
+ package_data={'': ['path/*']},
+ )
+ )
+ os.makedirs('path/subpath')
+ dist.parse_command_line()
+ dist.run_commands()
+
+
+def test_recursive_in_package_data_glob(tmpdir_cwd):
+ """
+ Files matching recursive globs (**) in package_data should
+ be included in the package data.
+
+ #1806
+ """
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['build_py'],
+ packages=[''],
+ package_data={'': ['path/**/data']},
+ )
+ )
+ os.makedirs('path/subpath/subsubpath')
+ open('path/subpath/subsubpath/data', 'wb').close()
+
+ dist.parse_command_line()
+ dist.run_commands()
+
+ assert stat.S_ISREG(os.stat('build/lib/path/subpath/subsubpath/data').st_mode), (
+ "File is not included"
+ )
+
+
+def test_read_only(tmpdir_cwd):
+ """
+ Ensure read-only flag is not preserved in copy
+ for package modules and package data, as that
+ causes problems with deleting read-only files on
+ Windows.
+
+ #1451
+ """
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['build_py'],
+ packages=['pkg'],
+ package_data={'pkg': ['data.dat']},
+ )
+ )
+ os.makedirs('pkg')
+ open('pkg/__init__.py', 'wb').close()
+ open('pkg/data.dat', 'wb').close()
+ os.chmod('pkg/__init__.py', stat.S_IREAD)
+ os.chmod('pkg/data.dat', stat.S_IREAD)
+ dist.parse_command_line()
+ dist.run_commands()
+ shutil.rmtree('build')
+
+
+@pytest.mark.xfail(
+ 'platform.system() == "Windows"',
+ reason="On Windows, files do not have executable bits",
+ raises=AssertionError,
+ strict=True,
+)
+def test_executable_data(tmpdir_cwd):
+ """
+ Ensure executable bit is preserved in copy for
+ package data, as users rely on it for scripts.
+
+ #2041
+ """
+ dist = Distribution(
+ dict(
+ script_name='setup.py',
+ script_args=['build_py'],
+ packages=['pkg'],
+ package_data={'pkg': ['run-me']},
+ )
+ )
+ os.makedirs('pkg')
+ open('pkg/__init__.py', 'wb').close()
+ open('pkg/run-me', 'wb').close()
+ os.chmod('pkg/run-me', 0o700)
+
+ dist.parse_command_line()
+ dist.run_commands()
+
+ assert os.stat('build/lib/pkg/run-me').st_mode & stat.S_IEXEC, (
+ "Script is not executable"
+ )
+
+
+EXAMPLE_WITH_MANIFEST = {
+ "setup.cfg": DALS(
+ """
+ [metadata]
+ name = mypkg
+ version = 42
+
+ [options]
+ include_package_data = True
+ packages = find:
+
+ [options.packages.find]
+ exclude = *.tests*
+ """
+ ),
+ "mypkg": {
+ "__init__.py": "",
+ "resource_file.txt": "",
+ "tests": {
+ "__init__.py": "",
+ "test_mypkg.py": "",
+ "test_file.txt": "",
+ },
+ },
+ "MANIFEST.in": DALS(
+ """
+ global-include *.py *.txt
+ global-exclude *.py[cod]
+ prune dist
+ prune build
+ prune *.egg-info
+ """
+ ),
+}
+
+
+def test_excluded_subpackages(tmpdir_cwd):
+ jaraco.path.build(EXAMPLE_WITH_MANIFEST)
+ dist = Distribution({"script_name": "%PEP 517%"})
+ dist.parse_config_files()
+
+ build_py = dist.get_command_obj("build_py")
+
+ msg = r"Python recognizes 'mypkg\.tests' as an importable package"
+ with pytest.warns(SetuptoolsDeprecationWarning, match=msg): # noqa: PT031
+ # TODO: To fix #3260 we need some transition period to deprecate the
+ # existing behavior of `include_package_data`. After the transition, we
+ # should remove the warning and fix the behavior.
+
+ if os.getenv("SETUPTOOLS_USE_DISTUTILS") == "stdlib":
+ # pytest.warns reset the warning filter temporarily
+ # https://github.com/pytest-dev/pytest/issues/4011#issuecomment-423494810
+ warnings.filterwarnings(
+ "ignore",
+ "'encoding' argument not specified",
+ module="distutils.text_file",
+ # This warning is already fixed in pypa/distutils but not in stdlib
+ )
+
+ build_py.finalize_options()
+ build_py.run()
+
+ build_dir = Path(dist.get_command_obj("build_py").build_lib)
+ assert (build_dir / "mypkg/__init__.py").exists()
+ assert (build_dir / "mypkg/resource_file.txt").exists()
+
+ # Setuptools is configured to ignore `mypkg.tests`, therefore the following
+ # files/dirs should not be included in the distribution.
+ for f in [
+ "mypkg/tests/__init__.py",
+ "mypkg/tests/test_mypkg.py",
+ "mypkg/tests/test_file.txt",
+ "mypkg/tests",
+ ]:
+ with pytest.raises(AssertionError):
+ # TODO: Enforce the following assertion once #3260 is fixed
+ # (remove context manager and the following xfail).
+ assert not (build_dir / f).exists()
+
+ pytest.xfail("#3260")
+
+
+@pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning")
+def test_existing_egg_info(tmpdir_cwd, monkeypatch):
+ """When provided with the ``existing_egg_info_dir`` attribute, build_py should not
+ attempt to run egg_info again.
+ """
+ # == Pre-condition ==
+ # Generate an egg-info dir
+ jaraco.path.build(EXAMPLE_WITH_MANIFEST)
+ dist = Distribution({"script_name": "%PEP 517%"})
+ dist.parse_config_files()
+ assert dist.include_package_data
+
+ egg_info = dist.get_command_obj("egg_info")
+ dist.run_command("egg_info")
+ egg_info_dir = next(Path(egg_info.egg_base).glob("*.egg-info"))
+ assert egg_info_dir.is_dir()
+
+ # == Setup ==
+ build_py = dist.get_command_obj("build_py")
+ build_py.finalize_options()
+ egg_info = dist.get_command_obj("egg_info")
+ egg_info_run = Mock(side_effect=egg_info.run)
+ monkeypatch.setattr(egg_info, "run", egg_info_run)
+
+ # == Remove caches ==
+ # egg_info is called when build_py looks for data_files, which gets cached.
+ # We need to ensure it is not cached yet, otherwise it may impact on the tests
+ build_py.__dict__.pop('data_files', None)
+ dist.reinitialize_command(egg_info)
+
+ # == Sanity check ==
+ # Ensure that if existing_egg_info is not given, build_py attempts to run egg_info
+ build_py.existing_egg_info_dir = None
+ build_py.run()
+ egg_info_run.assert_called()
+
+ # == Remove caches ==
+ egg_info_run.reset_mock()
+ build_py.__dict__.pop('data_files', None)
+ dist.reinitialize_command(egg_info)
+
+ # == Actual test ==
+ # Ensure that if existing_egg_info_dir is given, egg_info doesn't run
+ build_py.existing_egg_info_dir = egg_info_dir
+ build_py.run()
+ egg_info_run.assert_not_called()
+ assert build_py.data_files
+
+ # Make sure the list of outputs is actually OK
+ outputs = map(lambda x: x.replace(os.sep, "/"), build_py.get_outputs())
+ assert outputs
+ example = str(Path(build_py.build_lib, "mypkg/__init__.py")).replace(os.sep, "/")
+ assert example in outputs
+
+
+EXAMPLE_ARBITRARY_MAPPING = {
+ "pyproject.toml": DALS(
+ """
+ [project]
+ name = "mypkg"
+ version = "42"
+
+ [tool.setuptools]
+ packages = ["mypkg", "mypkg.sub1", "mypkg.sub2", "mypkg.sub2.nested"]
+
+ [tool.setuptools.package-dir]
+ "" = "src"
+ "mypkg.sub2" = "src/mypkg/_sub2"
+ "mypkg.sub2.nested" = "other"
+ """
+ ),
+ "src": {
+ "mypkg": {
+ "__init__.py": "",
+ "resource_file.txt": "",
+ "sub1": {
+ "__init__.py": "",
+ "mod1.py": "",
+ },
+ "_sub2": {
+ "mod2.py": "",
+ },
+ },
+ },
+ "other": {
+ "__init__.py": "",
+ "mod3.py": "",
+ },
+ "MANIFEST.in": DALS(
+ """
+ global-include *.py *.txt
+ global-exclude *.py[cod]
+ """
+ ),
+}
+
+
+def test_get_outputs(tmpdir_cwd):
+ jaraco.path.build(EXAMPLE_ARBITRARY_MAPPING)
+ dist = Distribution({"script_name": "%test%"})
+ dist.parse_config_files()
+
+ build_py = dist.get_command_obj("build_py")
+ build_py.editable_mode = True
+ build_py.ensure_finalized()
+ build_lib = build_py.build_lib.replace(os.sep, "/")
+ outputs = {x.replace(os.sep, "/") for x in build_py.get_outputs()}
+ assert outputs == {
+ f"{build_lib}/mypkg/__init__.py",
+ f"{build_lib}/mypkg/resource_file.txt",
+ f"{build_lib}/mypkg/sub1/__init__.py",
+ f"{build_lib}/mypkg/sub1/mod1.py",
+ f"{build_lib}/mypkg/sub2/mod2.py",
+ f"{build_lib}/mypkg/sub2/nested/__init__.py",
+ f"{build_lib}/mypkg/sub2/nested/mod3.py",
+ }
+ mapping = {
+ k.replace(os.sep, "/"): v.replace(os.sep, "/")
+ for k, v in build_py.get_output_mapping().items()
+ }
+ assert mapping == {
+ f"{build_lib}/mypkg/__init__.py": "src/mypkg/__init__.py",
+ f"{build_lib}/mypkg/resource_file.txt": "src/mypkg/resource_file.txt",
+ f"{build_lib}/mypkg/sub1/__init__.py": "src/mypkg/sub1/__init__.py",
+ f"{build_lib}/mypkg/sub1/mod1.py": "src/mypkg/sub1/mod1.py",
+ f"{build_lib}/mypkg/sub2/mod2.py": "src/mypkg/_sub2/mod2.py",
+ f"{build_lib}/mypkg/sub2/nested/__init__.py": "other/__init__.py",
+ f"{build_lib}/mypkg/sub2/nested/mod3.py": "other/mod3.py",
+ }
+
+
+class TestTypeInfoFiles:
+ PYPROJECTS = {
+ "default_pyproject": DALS(
+ """
+ [project]
+ name = "foo"
+ version = "1"
+ """
+ ),
+ "dont_include_package_data": DALS(
+ """
+ [project]
+ name = "foo"
+ version = "1"
+
+ [tool.setuptools]
+ include-package-data = false
+ """
+ ),
+ "exclude_type_info": DALS(
+ """
+ [project]
+ name = "foo"
+ version = "1"
+
+ [tool.setuptools]
+ include-package-data = false
+
+ [tool.setuptools.exclude-package-data]
+ "*" = ["py.typed", "*.pyi"]
+ """
+ ),
+ }
+
+ EXAMPLES = {
+ "simple_namespace": {
+ "directory_structure": {
+ "foo": {
+ "bar.pyi": "",
+ "py.typed": "",
+ "__init__.py": "",
+ }
+ },
+ "expected_type_files": {"foo/bar.pyi", "foo/py.typed"},
+ },
+ "nested_inside_namespace": {
+ "directory_structure": {
+ "foo": {
+ "bar": {
+ "py.typed": "",
+ "mod.pyi": "",
+ }
+ }
+ },
+ "expected_type_files": {"foo/bar/mod.pyi", "foo/bar/py.typed"},
+ },
+ "namespace_nested_inside_regular": {
+ "directory_structure": {
+ "foo": {
+ "namespace": {
+ "foo.pyi": "",
+ },
+ "__init__.pyi": "",
+ "py.typed": "",
+ }
+ },
+ "expected_type_files": {
+ "foo/namespace/foo.pyi",
+ "foo/__init__.pyi",
+ "foo/py.typed",
+ },
+ },
+ }
+
+ @pytest.mark.parametrize(
+ "pyproject",
+ [
+ "default_pyproject",
+ pytest.param(
+ "dont_include_package_data",
+ marks=pytest.mark.xfail(reason="pypa/setuptools#4350"),
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example):
+ structure = {
+ **self.EXAMPLES[example]["directory_structure"],
+ "pyproject.toml": self.PYPROJECTS[pyproject],
+ }
+ expected_type_files = self.EXAMPLES[example]["expected_type_files"]
+ jaraco.path.build(structure)
+
+ build_py = get_finalized_build_py()
+ outputs = get_outputs(build_py)
+ assert expected_type_files <= outputs
+
+ @pytest.mark.parametrize("pyproject", ["exclude_type_info"])
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_type_files_can_be_excluded(self, tmpdir_cwd, pyproject, example):
+ structure = {
+ **self.EXAMPLES[example]["directory_structure"],
+ "pyproject.toml": self.PYPROJECTS[pyproject],
+ }
+ expected_type_files = self.EXAMPLES[example]["expected_type_files"]
+ jaraco.path.build(structure)
+
+ build_py = get_finalized_build_py()
+ outputs = get_outputs(build_py)
+ assert expected_type_files.isdisjoint(outputs)
+
+ def test_stub_only_package(self, tmpdir_cwd):
+ structure = {
+ "pyproject.toml": DALS(
+ """
+ [project]
+ name = "foo-stubs"
+ version = "1"
+ """
+ ),
+ "foo-stubs": {"__init__.pyi": "", "bar.pyi": ""},
+ }
+ expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"}
+ jaraco.path.build(structure)
+
+ build_py = get_finalized_build_py()
+ outputs = get_outputs(build_py)
+ assert expected_type_files <= outputs
+
+
+def get_finalized_build_py(script_name="%build_py-test%"):
+ dist = Distribution({"script_name": script_name})
+ dist.parse_config_files()
+ build_py = dist.get_command_obj("build_py")
+ build_py.finalize_options()
+ return build_py
+
+
+def get_outputs(build_py):
+ build_dir = Path(build_py.build_lib)
+ return {
+ os.path.relpath(x, build_dir).replace(os.sep, "/")
+ for x in build_py.get_outputs()
+ }
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_config_discovery.py b/lib/python3.12/site-packages/setuptools/tests/test_config_discovery.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5df8203cdb6f9129a65d0c503c4f51e21315b1f
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_config_discovery.py
@@ -0,0 +1,647 @@
+import os
+import sys
+from configparser import ConfigParser
+from itertools import product
+from typing import cast
+
+import jaraco.path
+import pytest
+from path import Path
+
+import setuptools # noqa: F401 # force distutils.core to be patched
+from setuptools.command.sdist import sdist
+from setuptools.discovery import find_package_path, find_parent_package
+from setuptools.dist import Distribution
+from setuptools.errors import PackageDiscoveryError
+
+from .contexts import quiet
+from .integration.helpers import get_sdist_members, get_wheel_members, run
+from .textwrap import DALS
+
+import distutils.core
+
+
+class TestFindParentPackage:
+ def test_single_package(self, tmp_path):
+ # find_parent_package should find a non-namespace parent package
+ (tmp_path / "src/namespace/pkg/nested").mkdir(exist_ok=True, parents=True)
+ (tmp_path / "src/namespace/pkg/nested/__init__.py").touch()
+ (tmp_path / "src/namespace/pkg/__init__.py").touch()
+ packages = ["namespace", "namespace.pkg", "namespace.pkg.nested"]
+ assert find_parent_package(packages, {"": "src"}, tmp_path) == "namespace.pkg"
+
+ def test_multiple_toplevel(self, tmp_path):
+ # find_parent_package should return null if the given list of packages does not
+ # have a single parent package
+ multiple = ["pkg", "pkg1", "pkg2"]
+ for name in multiple:
+ (tmp_path / f"src/{name}").mkdir(exist_ok=True, parents=True)
+ (tmp_path / f"src/{name}/__init__.py").touch()
+ assert find_parent_package(multiple, {"": "src"}, tmp_path) is None
+
+
+class TestDiscoverPackagesAndPyModules:
+ """Make sure discovered values for ``packages`` and ``py_modules`` work
+ similarly to explicit configuration for the simple scenarios.
+ """
+
+ OPTIONS = {
+ # Different options according to the circumstance being tested
+ "explicit-src": {"package_dir": {"": "src"}, "packages": ["pkg"]},
+ "variation-lib": {
+ "package_dir": {"": "lib"}, # variation of the source-layout
+ },
+ "explicit-flat": {"packages": ["pkg"]},
+ "explicit-single_module": {"py_modules": ["pkg"]},
+ "explicit-namespace": {"packages": ["ns", "ns.pkg"]},
+ "automatic-src": {},
+ "automatic-flat": {},
+ "automatic-single_module": {},
+ "automatic-namespace": {},
+ }
+ FILES = {
+ "src": ["src/pkg/__init__.py", "src/pkg/main.py"],
+ "lib": ["lib/pkg/__init__.py", "lib/pkg/main.py"],
+ "flat": ["pkg/__init__.py", "pkg/main.py"],
+ "single_module": ["pkg.py"],
+ "namespace": ["ns/pkg/__init__.py"],
+ }
+
+ def _get_info(self, circumstance):
+ _, _, layout = circumstance.partition("-")
+ files = self.FILES[layout]
+ options = self.OPTIONS[circumstance]
+ return files, options
+
+ @pytest.mark.parametrize("circumstance", OPTIONS.keys())
+ def test_sdist_filelist(self, tmp_path, circumstance):
+ files, options = self._get_info(circumstance)
+ _populate_project_dir(tmp_path, files, options)
+
+ _, cmd = _run_sdist_programatically(tmp_path, options)
+
+ manifest = [f.replace(os.sep, "/") for f in cmd.filelist.files]
+ for file in files:
+ assert any(f.endswith(file) for f in manifest)
+
+ @pytest.mark.parametrize("circumstance", OPTIONS.keys())
+ def test_project(self, tmp_path, circumstance):
+ files, options = self._get_info(circumstance)
+ _populate_project_dir(tmp_path, files, options)
+
+ # Simulate a pre-existing `build` directory
+ (tmp_path / "build").mkdir()
+ (tmp_path / "build/lib").mkdir()
+ (tmp_path / "build/bdist.linux-x86_64").mkdir()
+ (tmp_path / "build/bdist.linux-x86_64/file.py").touch()
+ (tmp_path / "build/lib/__init__.py").touch()
+ (tmp_path / "build/lib/file.py").touch()
+ (tmp_path / "dist").mkdir()
+ (tmp_path / "dist/file.py").touch()
+
+ _run_build(tmp_path)
+
+ sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
+ print("~~~~~ sdist_members ~~~~~")
+ print('\n'.join(sdist_files))
+ assert sdist_files >= set(files)
+
+ wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
+ print("~~~~~ wheel_members ~~~~~")
+ print('\n'.join(wheel_files))
+ orig_files = {f.replace("src/", "").replace("lib/", "") for f in files}
+ assert wheel_files >= orig_files
+
+ # Make sure build files are not included by mistake
+ for file in wheel_files:
+ assert "build" not in files
+ assert "dist" not in files
+
+ PURPOSEFULLY_EMPY = {
+ "setup.cfg": DALS(
+ """
+ [metadata]
+ name = myproj
+ version = 0.0.0
+
+ [options]
+ {param} =
+ """
+ ),
+ "setup.py": DALS(
+ """
+ __import__('setuptools').setup(
+ name="myproj",
+ version="0.0.0",
+ {param}=[]
+ )
+ """
+ ),
+ "pyproject.toml": DALS(
+ """
+ [build-system]
+ requires = []
+ build-backend = 'setuptools.build_meta'
+
+ [project]
+ name = "myproj"
+ version = "0.0.0"
+
+ [tool.setuptools]
+ {param} = []
+ """
+ ),
+ "template-pyproject.toml": DALS(
+ """
+ [build-system]
+ requires = []
+ build-backend = 'setuptools.build_meta'
+ """
+ ),
+ }
+
+ @pytest.mark.parametrize(
+ ("config_file", "param", "circumstance"),
+ product(
+ ["setup.cfg", "setup.py", "pyproject.toml"],
+ ["packages", "py_modules"],
+ FILES.keys(),
+ ),
+ )
+ def test_purposefully_empty(self, tmp_path, config_file, param, circumstance):
+ files = self.FILES[circumstance] + ["mod.py", "other.py", "src/pkg/__init__.py"]
+ _populate_project_dir(tmp_path, files, {})
+
+ if config_file == "pyproject.toml":
+ template_param = param.replace("_", "-")
+ else:
+ # Make sure build works with or without setup.cfg
+ pyproject = self.PURPOSEFULLY_EMPY["template-pyproject.toml"]
+ (tmp_path / "pyproject.toml").write_text(pyproject, encoding="utf-8")
+ template_param = param
+
+ config = self.PURPOSEFULLY_EMPY[config_file].format(param=template_param)
+ (tmp_path / config_file).write_text(config, encoding="utf-8")
+
+ dist = _get_dist(tmp_path, {})
+ # When either parameter package or py_modules is an empty list,
+ # then there should be no discovery
+ assert getattr(dist, param) == []
+ other = {"py_modules": "packages", "packages": "py_modules"}[param]
+ assert getattr(dist, other) is None
+
+ @pytest.mark.parametrize(
+ ("extra_files", "pkgs"),
+ [
+ (["venv/bin/simulate_venv"], {"pkg"}),
+ (["pkg-stubs/__init__.pyi"], {"pkg", "pkg-stubs"}),
+ (["other-stubs/__init__.pyi"], {"pkg", "other-stubs"}),
+ (
+ # Type stubs can also be namespaced
+ ["namespace-stubs/pkg/__init__.pyi"],
+ {"pkg", "namespace-stubs", "namespace-stubs.pkg"},
+ ),
+ (
+ # Just the top-level package can have `-stubs`, ignore nested ones
+ ["namespace-stubs/pkg-stubs/__init__.pyi"],
+ {"pkg", "namespace-stubs"},
+ ),
+ (["_hidden/file.py"], {"pkg"}),
+ (["news/finalize.py"], {"pkg"}),
+ ],
+ )
+ def test_flat_layout_with_extra_files(self, tmp_path, extra_files, pkgs):
+ files = self.FILES["flat"] + extra_files
+ _populate_project_dir(tmp_path, files, {})
+ dist = _get_dist(tmp_path, {})
+ assert set(dist.packages) == pkgs
+
+ @pytest.mark.parametrize(
+ "extra_files",
+ [
+ ["other/__init__.py"],
+ ["other/finalize.py"],
+ ],
+ )
+ def test_flat_layout_with_dangerous_extra_files(self, tmp_path, extra_files):
+ files = self.FILES["flat"] + extra_files
+ _populate_project_dir(tmp_path, files, {})
+ with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
+ _get_dist(tmp_path, {})
+
+ def test_flat_layout_with_single_module(self, tmp_path):
+ files = self.FILES["single_module"] + ["invalid-module-name.py"]
+ _populate_project_dir(tmp_path, files, {})
+ dist = _get_dist(tmp_path, {})
+ assert set(dist.py_modules) == {"pkg"}
+
+ def test_flat_layout_with_multiple_modules(self, tmp_path):
+ files = self.FILES["single_module"] + ["valid_module_name.py"]
+ _populate_project_dir(tmp_path, files, {})
+ with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
+ _get_dist(tmp_path, {})
+
+ def test_py_modules_when_wheel_dir_is_cwd(self, tmp_path):
+ """Regression for issue 3692"""
+ from setuptools import build_meta
+
+ pyproject = '[project]\nname = "test"\nversion = "1"'
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
+ (tmp_path / "foo.py").touch()
+ with jaraco.path.DirectoryStack().context(tmp_path):
+ build_meta.build_wheel(".")
+ # Ensure py_modules are found
+ wheel_files = get_wheel_members(next(tmp_path.glob("*.whl")))
+ assert "foo.py" in wheel_files
+
+
+class TestNoConfig:
+ DEFAULT_VERSION = "0.0.0" # Default version given by setuptools
+
+ EXAMPLES = {
+ "pkg1": ["src/pkg1.py"],
+ "pkg2": ["src/pkg2/__init__.py"],
+ "pkg3": ["src/pkg3/__init__.py", "src/pkg3-stubs/__init__.py"],
+ "pkg4": ["pkg4/__init__.py", "pkg4-stubs/__init__.py"],
+ "ns.nested.pkg1": ["src/ns/nested/pkg1/__init__.py"],
+ "ns.nested.pkg2": ["ns/nested/pkg2/__init__.py"],
+ }
+
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_discover_name(self, tmp_path, example):
+ _populate_project_dir(tmp_path, self.EXAMPLES[example], {})
+ dist = _get_dist(tmp_path, {})
+ assert dist.get_name() == example
+
+ def test_build_with_discovered_name(self, tmp_path):
+ files = ["src/ns/nested/pkg/__init__.py"]
+ _populate_project_dir(tmp_path, files, {})
+ _run_build(tmp_path, "--sdist")
+ # Expected distribution file
+ dist_file = tmp_path / f"dist/ns_nested_pkg-{self.DEFAULT_VERSION}.tar.gz"
+ assert dist_file.is_file()
+
+
+class TestWithAttrDirective:
+ @pytest.mark.parametrize(
+ ("folder", "opts"),
+ [
+ ("src", {}),
+ ("lib", {"packages": "find:", "packages.find": {"where": "lib"}}),
+ ],
+ )
+ def test_setupcfg_metadata(self, tmp_path, folder, opts):
+ files = [f"{folder}/pkg/__init__.py", "setup.cfg"]
+ _populate_project_dir(tmp_path, files, opts)
+
+ config = (tmp_path / "setup.cfg").read_text(encoding="utf-8")
+ overwrite = {
+ folder: {"pkg": {"__init__.py": "version = 42"}},
+ "setup.cfg": "[metadata]\nversion = attr: pkg.version\n" + config,
+ }
+ jaraco.path.build(overwrite, prefix=tmp_path)
+
+ dist = _get_dist(tmp_path, {})
+ assert dist.get_name() == "pkg"
+ assert dist.get_version() == "42"
+ assert dist.package_dir
+ package_path = find_package_path("pkg", dist.package_dir, tmp_path)
+ assert os.path.exists(package_path)
+ assert folder in Path(package_path).parts()
+
+ _run_build(tmp_path, "--sdist")
+ dist_file = tmp_path / "dist/pkg-42.tar.gz"
+ assert dist_file.is_file()
+
+ def test_pyproject_metadata(self, tmp_path):
+ _populate_project_dir(tmp_path, ["src/pkg/__init__.py"], {})
+
+ overwrite = {
+ "src": {"pkg": {"__init__.py": "version = 42"}},
+ "pyproject.toml": (
+ "[project]\nname = 'pkg'\ndynamic = ['version']\n"
+ "[tool.setuptools.dynamic]\nversion = {attr = 'pkg.version'}\n"
+ ),
+ }
+ jaraco.path.build(overwrite, prefix=tmp_path)
+
+ dist = _get_dist(tmp_path, {})
+ assert dist.get_version() == "42"
+ assert dist.package_dir == {"": "src"}
+
+
+class TestWithCExtension:
+ def _simulate_package_with_extension(self, tmp_path):
+ # This example is based on: https://github.com/nucleic/kiwi/tree/1.4.0
+ files = [
+ "benchmarks/file.py",
+ "docs/Makefile",
+ "docs/requirements.txt",
+ "docs/source/conf.py",
+ "proj/header.h",
+ "proj/file.py",
+ "py/proj.cpp",
+ "py/other.cpp",
+ "py/file.py",
+ "py/py.typed",
+ "py/tests/test_proj.py",
+ "README.rst",
+ ]
+ _populate_project_dir(tmp_path, files, {})
+
+ setup_script = """
+ from setuptools import Extension, setup
+
+ ext_modules = [
+ Extension(
+ "proj",
+ ["py/proj.cpp", "py/other.cpp"],
+ include_dirs=["."],
+ language="c++",
+ ),
+ ]
+ setup(ext_modules=ext_modules)
+ """
+ (tmp_path / "setup.py").write_text(DALS(setup_script), encoding="utf-8")
+
+ def test_skip_discovery_with_setupcfg_metadata(self, tmp_path):
+ """Ensure that auto-discovery is not triggered when the project is based on
+ C-extensions only, for backward compatibility.
+ """
+ self._simulate_package_with_extension(tmp_path)
+
+ pyproject = """
+ [build-system]
+ requires = []
+ build-backend = 'setuptools.build_meta'
+ """
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
+
+ setupcfg = """
+ [metadata]
+ name = proj
+ version = 42
+ """
+ (tmp_path / "setup.cfg").write_text(DALS(setupcfg), encoding="utf-8")
+
+ dist = _get_dist(tmp_path, {})
+ assert dist.get_name() == "proj"
+ assert dist.get_version() == "42"
+ assert dist.py_modules is None
+ assert dist.packages is None
+ assert len(dist.ext_modules) == 1
+ assert dist.ext_modules[0].name == "proj"
+
+ def test_dont_skip_discovery_with_pyproject_metadata(self, tmp_path):
+ """When opting-in to pyproject.toml metadata, auto-discovery will be active if
+ the package lists C-extensions, but does not configure py-modules or packages.
+
+ This way we ensure users with complex package layouts that would lead to the
+ discovery of multiple top-level modules/packages see errors and are forced to
+ explicitly set ``packages`` or ``py-modules``.
+ """
+ self._simulate_package_with_extension(tmp_path)
+
+ pyproject = """
+ [project]
+ name = 'proj'
+ version = '42'
+ """
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
+ with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
+ _get_dist(tmp_path, {})
+
+
+class TestWithPackageData:
+ def _simulate_package_with_data_files(self, tmp_path, src_root):
+ files = [
+ f"{src_root}/proj/__init__.py",
+ f"{src_root}/proj/file1.txt",
+ f"{src_root}/proj/nested/file2.txt",
+ ]
+ _populate_project_dir(tmp_path, files, {})
+
+ manifest = """
+ global-include *.py *.txt
+ """
+ (tmp_path / "MANIFEST.in").write_text(DALS(manifest), encoding="utf-8")
+
+ EXAMPLE_SETUPCFG = """
+ [metadata]
+ name = proj
+ version = 42
+
+ [options]
+ include_package_data = True
+ """
+ EXAMPLE_PYPROJECT = """
+ [project]
+ name = "proj"
+ version = "42"
+ """
+
+ PYPROJECT_PACKAGE_DIR = """
+ [tool.setuptools]
+ package-dir = {"" = "src"}
+ """
+
+ @pytest.mark.parametrize(
+ ("src_root", "files"),
+ [
+ (".", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
+ (".", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
+ ("src", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
+ ("src", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
+ (
+ "src",
+ {
+ "setup.cfg": DALS(EXAMPLE_SETUPCFG)
+ + DALS(
+ """
+ packages = find:
+ package_dir =
+ =src
+
+ [options.packages.find]
+ where = src
+ """
+ )
+ },
+ ),
+ (
+ "src",
+ {
+ "pyproject.toml": DALS(EXAMPLE_PYPROJECT)
+ + DALS(
+ """
+ [tool.setuptools]
+ package-dir = {"" = "src"}
+ """
+ )
+ },
+ ),
+ ],
+ )
+ def test_include_package_data(self, tmp_path, src_root, files):
+ """
+ Make sure auto-discovery does not affect package include_package_data.
+ See issue #3196.
+ """
+ jaraco.path.build(files, prefix=str(tmp_path))
+ self._simulate_package_with_data_files(tmp_path, src_root)
+
+ expected = {
+ os.path.normpath(f"{src_root}/proj/file1.txt").replace(os.sep, "/"),
+ os.path.normpath(f"{src_root}/proj/nested/file2.txt").replace(os.sep, "/"),
+ }
+
+ _run_build(tmp_path)
+
+ sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
+ print("~~~~~ sdist_members ~~~~~")
+ print('\n'.join(sdist_files))
+ assert sdist_files >= expected
+
+ wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
+ print("~~~~~ wheel_members ~~~~~")
+ print('\n'.join(wheel_files))
+ orig_files = {f.replace("src/", "").replace("lib/", "") for f in expected}
+ assert wheel_files >= orig_files
+
+
+def test_compatible_with_numpy_configuration(tmp_path):
+ files = [
+ "dir1/__init__.py",
+ "dir2/__init__.py",
+ "file.py",
+ ]
+ _populate_project_dir(tmp_path, files, {})
+ dist = Distribution({})
+ dist.configuration = object()
+ dist.set_defaults()
+ assert dist.py_modules is None
+ assert dist.packages is None
+
+
+def test_name_discovery_doesnt_break_cli(tmpdir_cwd):
+ jaraco.path.build({"pkg.py": ""})
+ dist = Distribution({})
+ dist.script_args = ["--name"]
+ dist.set_defaults()
+ dist.parse_command_line() # <-- no exception should be raised here.
+ assert dist.get_name() == "pkg"
+
+
+def test_preserve_explicit_name_with_dynamic_version(tmpdir_cwd, monkeypatch):
+ """According to #3545 it seems that ``name`` discovery is running,
+ even when the project already explicitly sets it.
+ This seems to be related to parsing of dynamic versions (via ``attr`` directive),
+ which requires the auto-discovery of ``package_dir``.
+ """
+ files = {
+ "src": {
+ "pkg": {"__init__.py": "__version__ = 42\n"},
+ },
+ "pyproject.toml": DALS(
+ """
+ [project]
+ name = "myproj" # purposefully different from package name
+ dynamic = ["version"]
+ [tool.setuptools.dynamic]
+ version = {"attr" = "pkg.__version__"}
+ """
+ ),
+ }
+ jaraco.path.build(files)
+ dist = Distribution({})
+ orig_analyse_name = dist.set_defaults.analyse_name
+
+ def spy_analyse_name():
+ # We can check if name discovery was triggered by ensuring the original
+ # name remains instead of the package name.
+ orig_analyse_name()
+ assert dist.get_name() == "myproj"
+
+ monkeypatch.setattr(dist.set_defaults, "analyse_name", spy_analyse_name)
+ dist.parse_config_files()
+ assert dist.get_version() == "42"
+ assert set(dist.packages) == {"pkg"}
+
+
+def _populate_project_dir(root, files, options):
+ # NOTE: Currently pypa/build will refuse to build the project if no
+ # `pyproject.toml` or `setup.py` is found. So it is impossible to do
+ # completely "config-less" projects.
+ basic = {
+ "setup.py": "import setuptools\nsetuptools.setup()",
+ "README.md": "# Example Package",
+ "LICENSE": "Copyright (c) 2018",
+ }
+ jaraco.path.build(basic, prefix=root)
+ _write_setupcfg(root, options)
+ paths = (root / f for f in files)
+ for path in paths:
+ path.parent.mkdir(exist_ok=True, parents=True)
+ path.touch()
+
+
+def _write_setupcfg(root, options):
+ if not options:
+ print("~~~~~ **NO** setup.cfg ~~~~~")
+ return
+ setupcfg = ConfigParser()
+ setupcfg.add_section("options")
+ for key, value in options.items():
+ if key == "packages.find":
+ setupcfg.add_section(f"options.{key}")
+ setupcfg[f"options.{key}"].update(value)
+ elif isinstance(value, list):
+ setupcfg["options"][key] = ", ".join(value)
+ elif isinstance(value, dict):
+ str_value = "\n".join(f"\t{k} = {v}" for k, v in value.items())
+ setupcfg["options"][key] = "\n" + str_value
+ else:
+ setupcfg["options"][key] = str(value)
+ with open(root / "setup.cfg", "w", encoding="utf-8") as f:
+ setupcfg.write(f)
+ print("~~~~~ setup.cfg ~~~~~")
+ print((root / "setup.cfg").read_text(encoding="utf-8"))
+
+
+def _run_build(path, *flags):
+ cmd = [sys.executable, "-m", "build", "--no-isolation", *flags, str(path)]
+ return run(cmd, env={'DISTUTILS_DEBUG': ''})
+
+
+def _get_dist(dist_path, attrs):
+ root = "/".join(os.path.split(dist_path)) # POSIX-style
+
+ script = dist_path / 'setup.py'
+ if script.exists():
+ with Path(dist_path):
+ dist = cast(
+ Distribution,
+ distutils.core.run_setup("setup.py", {}, stop_after="init"),
+ )
+ else:
+ dist = Distribution(attrs)
+
+ dist.src_root = root
+ dist.script_name = "setup.py"
+ with Path(dist_path):
+ dist.parse_config_files()
+
+ dist.set_defaults()
+ return dist
+
+
+def _run_sdist_programatically(dist_path, attrs):
+ dist = _get_dist(dist_path, attrs)
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ assert cmd.distribution.packages or cmd.distribution.py_modules
+
+ with quiet(), Path(dist_path):
+ cmd.run()
+
+ return dist, cmd
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_core_metadata.py b/lib/python3.12/site-packages/setuptools/tests/test_core_metadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d925111fa6e611ae919df8bb30bcda0f248c5b6
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_core_metadata.py
@@ -0,0 +1,622 @@
+from __future__ import annotations
+
+import functools
+import importlib
+import io
+from email import message_from_string
+from email.generator import Generator
+from email.message import EmailMessage, Message
+from email.parser import Parser
+from email.policy import EmailPolicy
+from inspect import cleandoc
+from pathlib import Path
+from unittest.mock import Mock
+
+import jaraco.path
+import pytest
+from packaging.metadata import Metadata
+from packaging.requirements import Requirement
+
+from setuptools import _reqs, sic
+from setuptools._core_metadata import rfc822_escape, rfc822_unescape
+from setuptools.command.egg_info import egg_info, write_requirements
+from setuptools.config import expand, setupcfg
+from setuptools.dist import Distribution
+
+from .config.downloads import retrieve_file, urls_from_file
+
+EXAMPLE_BASE_INFO = dict(
+ name="package",
+ version="0.0.1",
+ author="Foo Bar",
+ author_email="foo@bar.net",
+ long_description="Long\ndescription",
+ description="Short description",
+ keywords=["one", "two"],
+)
+
+
+@pytest.mark.parametrize(
+ ("content", "result"),
+ (
+ pytest.param(
+ "Just a single line",
+ None,
+ id="single_line",
+ ),
+ pytest.param(
+ "Multiline\nText\nwithout\nextra indents\n",
+ None,
+ id="multiline",
+ ),
+ pytest.param(
+ "Multiline\n With\n\nadditional\n indentation",
+ None,
+ id="multiline_with_indentation",
+ ),
+ pytest.param(
+ " Leading whitespace",
+ "Leading whitespace",
+ id="remove_leading_whitespace",
+ ),
+ pytest.param(
+ " Leading whitespace\nIn\n Multiline comment",
+ "Leading whitespace\nIn\n Multiline comment",
+ id="remove_leading_whitespace_multiline",
+ ),
+ ),
+)
+def test_rfc822_unescape(content, result):
+ assert (result or content) == rfc822_unescape(rfc822_escape(content))
+
+
+def __read_test_cases():
+ base = EXAMPLE_BASE_INFO
+
+ params = functools.partial(dict, base)
+
+ return [
+ ('Metadata version 1.0', params()),
+ (
+ 'Metadata Version 1.0: Short long description',
+ params(
+ long_description='Short long description',
+ ),
+ ),
+ (
+ 'Metadata version 1.1: Classifiers',
+ params(
+ classifiers=[
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.7',
+ 'License :: OSI Approved :: MIT License',
+ ],
+ ),
+ ),
+ (
+ 'Metadata version 1.1: Download URL',
+ params(
+ download_url='https://example.com',
+ ),
+ ),
+ (
+ 'Metadata Version 1.2: Requires-Python',
+ params(
+ python_requires='>=3.7',
+ ),
+ ),
+ pytest.param(
+ 'Metadata Version 1.2: Project-Url',
+ params(project_urls=dict(Foo='https://example.bar')),
+ marks=pytest.mark.xfail(
+ reason="Issue #1578: project_urls not read",
+ ),
+ ),
+ (
+ 'Metadata Version 2.1: Long Description Content Type',
+ params(
+ long_description_content_type='text/x-rst; charset=UTF-8',
+ ),
+ ),
+ (
+ 'License',
+ params(
+ license='MIT',
+ ),
+ ),
+ (
+ 'License multiline',
+ params(
+ license='This is a long license \nover multiple lines',
+ ),
+ ),
+ pytest.param(
+ 'Metadata Version 2.1: Provides Extra',
+ params(provides_extras=['foo', 'bar']),
+ marks=pytest.mark.xfail(reason="provides_extras not read"),
+ ),
+ (
+ 'Missing author',
+ dict(
+ name='foo',
+ version='1.0.0',
+ author_email='snorri@sturluson.name',
+ ),
+ ),
+ (
+ 'Missing author e-mail',
+ dict(
+ name='foo',
+ version='1.0.0',
+ author='Snorri Sturluson',
+ ),
+ ),
+ (
+ 'Missing author and e-mail',
+ dict(
+ name='foo',
+ version='1.0.0',
+ ),
+ ),
+ (
+ 'Bypass normalized version',
+ dict(
+ name='foo',
+ version=sic('1.0.0a'),
+ ),
+ ),
+ ]
+
+
+@pytest.mark.parametrize(("name", "attrs"), __read_test_cases())
+def test_read_metadata(name, attrs):
+ dist = Distribution(attrs)
+ metadata_out = dist.metadata
+ dist_class = metadata_out.__class__
+
+ # Write to PKG_INFO and then load into a new metadata object
+ PKG_INFO = io.StringIO()
+
+ metadata_out.write_pkg_file(PKG_INFO)
+ PKG_INFO.seek(0)
+ pkg_info = PKG_INFO.read()
+ assert _valid_metadata(pkg_info)
+
+ PKG_INFO.seek(0)
+ metadata_in = dist_class()
+ metadata_in.read_pkg_file(PKG_INFO)
+
+ tested_attrs = [
+ ('name', dist_class.get_name),
+ ('version', dist_class.get_version),
+ ('author', dist_class.get_contact),
+ ('author_email', dist_class.get_contact_email),
+ ('metadata_version', dist_class.get_metadata_version),
+ ('provides', dist_class.get_provides),
+ ('description', dist_class.get_description),
+ ('long_description', dist_class.get_long_description),
+ ('download_url', dist_class.get_download_url),
+ ('keywords', dist_class.get_keywords),
+ ('platforms', dist_class.get_platforms),
+ ('obsoletes', dist_class.get_obsoletes),
+ ('requires', dist_class.get_requires),
+ ('classifiers', dist_class.get_classifiers),
+ ('project_urls', lambda s: getattr(s, 'project_urls', {})),
+ ('provides_extras', lambda s: getattr(s, 'provides_extras', {})),
+ ]
+
+ for attr, getter in tested_attrs:
+ assert getter(metadata_in) == getter(metadata_out)
+
+
+def __maintainer_test_cases():
+ attrs = {"name": "package", "version": "1.0", "description": "xxx"}
+
+ def merge_dicts(d1, d2):
+ d1 = d1.copy()
+ d1.update(d2)
+
+ return d1
+
+ return [
+ ('No author, no maintainer', attrs.copy()),
+ (
+ 'Author (no e-mail), no maintainer',
+ merge_dicts(attrs, {'author': 'Author Name'}),
+ ),
+ (
+ 'Author (e-mail), no maintainer',
+ merge_dicts(
+ attrs, {'author': 'Author Name', 'author_email': 'author@name.com'}
+ ),
+ ),
+ (
+ 'No author, maintainer (no e-mail)',
+ merge_dicts(attrs, {'maintainer': 'Maintainer Name'}),
+ ),
+ (
+ 'No author, maintainer (e-mail)',
+ merge_dicts(
+ attrs,
+ {
+ 'maintainer': 'Maintainer Name',
+ 'maintainer_email': 'maintainer@name.com',
+ },
+ ),
+ ),
+ (
+ 'Author (no e-mail), Maintainer (no-email)',
+ merge_dicts(
+ attrs, {'author': 'Author Name', 'maintainer': 'Maintainer Name'}
+ ),
+ ),
+ (
+ 'Author (e-mail), Maintainer (e-mail)',
+ merge_dicts(
+ attrs,
+ {
+ 'author': 'Author Name',
+ 'author_email': 'author@name.com',
+ 'maintainer': 'Maintainer Name',
+ 'maintainer_email': 'maintainer@name.com',
+ },
+ ),
+ ),
+ (
+ 'No author (e-mail), no maintainer (e-mail)',
+ merge_dicts(
+ attrs,
+ {
+ 'author_email': 'author@name.com',
+ 'maintainer_email': 'maintainer@name.com',
+ },
+ ),
+ ),
+ ('Author unicode', merge_dicts(attrs, {'author': '鉄沢寛'})),
+ ('Maintainer unicode', merge_dicts(attrs, {'maintainer': 'Jan Łukasiewicz'})),
+ ]
+
+
+@pytest.mark.parametrize(("name", "attrs"), __maintainer_test_cases())
+def test_maintainer_author(name, attrs, tmpdir):
+ tested_keys = {
+ 'author': 'Author',
+ 'author_email': 'Author-email',
+ 'maintainer': 'Maintainer',
+ 'maintainer_email': 'Maintainer-email',
+ }
+
+ # Generate a PKG-INFO file
+ dist = Distribution(attrs)
+ fn = tmpdir.mkdir('pkg_info')
+ fn_s = str(fn)
+
+ dist.metadata.write_pkg_info(fn_s)
+
+ with open(str(fn.join('PKG-INFO')), 'r', encoding='utf-8') as f:
+ pkg_info = f.read()
+
+ assert _valid_metadata(pkg_info)
+
+ # Drop blank lines and strip lines from default description
+ raw_pkg_lines = pkg_info.splitlines()
+ pkg_lines = list(filter(None, raw_pkg_lines[:-2]))
+
+ pkg_lines_set = set(pkg_lines)
+
+ # Duplicate lines should not be generated
+ assert len(pkg_lines) == len(pkg_lines_set)
+
+ for fkey, dkey in tested_keys.items():
+ val = attrs.get(dkey, None)
+ if val is None:
+ for line in pkg_lines:
+ assert not line.startswith(fkey + ':')
+ else:
+ line = f'{fkey}: {val}'
+ assert line in pkg_lines_set
+
+
+class TestParityWithMetadataFromPyPaWheel:
+ def base_example(self):
+ attrs = dict(
+ **EXAMPLE_BASE_INFO,
+ # Example with complex requirement definition
+ python_requires=">=3.8",
+ install_requires="""
+ packaging==23.2
+ more-itertools==8.8.0; extra == "other"
+ jaraco.text==3.7.0
+ importlib-resources==5.10.2; python_version<"3.8"
+ importlib-metadata==6.0.0 ; python_version<"3.8"
+ colorama>=0.4.4; sys_platform == "win32"
+ """,
+ extras_require={
+ "testing": """
+ pytest >= 6
+ pytest-checkdocs >= 2.4
+ tomli ; \\
+ # Using stdlib when possible
+ python_version < "3.11"
+ ini2toml[lite]>=0.9
+ """,
+ "other": [],
+ },
+ )
+ # Generate a PKG-INFO file using setuptools
+ return Distribution(attrs)
+
+ def test_requires_dist(self, tmp_path):
+ dist = self.base_example()
+ pkg_info = _get_pkginfo(dist)
+ assert _valid_metadata(pkg_info)
+
+ # Ensure Requires-Dist is present
+ expected = [
+ 'Metadata-Version:',
+ 'Requires-Python: >=3.8',
+ 'Provides-Extra: other',
+ 'Provides-Extra: testing',
+ 'Requires-Dist: tomli; python_version < "3.11" and extra == "testing"',
+ 'Requires-Dist: more-itertools==8.8.0; extra == "other"',
+ 'Requires-Dist: ini2toml[lite]>=0.9; extra == "testing"',
+ ]
+ for line in expected:
+ assert line in pkg_info
+
+ HERE = Path(__file__).parent
+ EXAMPLES_FILE = HERE / "config/setupcfg_examples.txt"
+
+ @pytest.fixture(params=[None, *urls_from_file(EXAMPLES_FILE)])
+ def dist(self, request, monkeypatch, tmp_path):
+ """Example of distribution with arbitrary configuration"""
+ monkeypatch.chdir(tmp_path)
+ monkeypatch.setattr(expand, "read_attr", Mock(return_value="0.42"))
+ monkeypatch.setattr(expand, "read_files", Mock(return_value="hello world"))
+ monkeypatch.setattr(
+ Distribution, "_finalize_license_files", Mock(return_value=None)
+ )
+ if request.param is None:
+ yield self.base_example()
+ else:
+ # Real-world usage
+ config = retrieve_file(request.param)
+ yield setupcfg.apply_configuration(Distribution({}), config)
+
+ @pytest.mark.uses_network
+ def test_equivalent_output(self, tmp_path, dist):
+ """Ensure output from setuptools is equivalent to the one from `pypa/wheel`"""
+ # Generate a METADATA file using pypa/wheel for comparison
+ wheel_metadata = importlib.import_module("wheel.metadata")
+ pkginfo_to_metadata = getattr(wheel_metadata, "pkginfo_to_metadata", None)
+
+ if pkginfo_to_metadata is None: # pragma: nocover
+ pytest.xfail(
+ "wheel.metadata.pkginfo_to_metadata is undefined, "
+ "(this is likely to be caused by API changes in pypa/wheel"
+ )
+
+ # Generate an simplified "egg-info" dir for pypa/wheel to convert
+ pkg_info = _get_pkginfo(dist)
+ egg_info_dir = tmp_path / "pkg.egg-info"
+ egg_info_dir.mkdir(parents=True)
+ (egg_info_dir / "PKG-INFO").write_text(pkg_info, encoding="utf-8")
+ write_requirements(egg_info(dist), egg_info_dir, egg_info_dir / "requires.txt")
+
+ # Get pypa/wheel generated METADATA but normalize requirements formatting
+ metadata_msg = pkginfo_to_metadata(egg_info_dir, egg_info_dir / "PKG-INFO")
+ metadata_str = _normalize_metadata(metadata_msg)
+ pkg_info_msg = message_from_string(pkg_info)
+ pkg_info_str = _normalize_metadata(pkg_info_msg)
+
+ # Compare setuptools PKG-INFO x pypa/wheel METADATA
+ assert metadata_str == pkg_info_str
+
+ # Make sure it parses/serializes well in pypa/wheel
+ _assert_roundtrip_message(pkg_info)
+
+
+class TestPEP643:
+ STATIC_CONFIG = {
+ "setup.cfg": cleandoc(
+ """
+ [metadata]
+ name = package
+ version = 0.0.1
+ author = Foo Bar
+ author_email = foo@bar.net
+ long_description = Long
+ description
+ description = Short description
+ keywords = one, two
+ platforms = abcd
+ [options]
+ install_requires = requests
+ """
+ ),
+ "pyproject.toml": cleandoc(
+ """
+ [project]
+ name = "package"
+ version = "0.0.1"
+ authors = [
+ {name = "Foo Bar", email = "foo@bar.net"}
+ ]
+ description = "Short description"
+ readme = {text = "Long\\ndescription", content-type = "text/plain"}
+ keywords = ["one", "two"]
+ dependencies = ["requests"]
+ license = "AGPL-3.0-or-later"
+ [tool.setuptools]
+ provides = ["abcd"]
+ obsoletes = ["abcd"]
+ """
+ ),
+ }
+
+ @pytest.mark.parametrize("file", STATIC_CONFIG.keys())
+ def test_static_config_has_no_dynamic(self, file, tmpdir_cwd):
+ Path(file).write_text(self.STATIC_CONFIG[file], encoding="utf-8")
+ metadata = _get_metadata()
+ assert metadata.get_all("Dynamic") is None
+ assert metadata.get_all("dynamic") is None
+
+ @pytest.mark.parametrize("file", STATIC_CONFIG.keys())
+ @pytest.mark.parametrize(
+ "fields",
+ [
+ # Single dynamic field
+ {"requires-python": ("python_requires", ">=3.12")},
+ {"author-email": ("author_email", "snoopy@peanuts.com")},
+ {"keywords": ("keywords", ["hello", "world"])},
+ {"platform": ("platforms", ["abcd"])},
+ # Multiple dynamic fields
+ {
+ "summary": ("description", "hello world"),
+ "description": ("long_description", "bla bla bla bla"),
+ "requires-dist": ("install_requires", ["hello-world"]),
+ },
+ ],
+ )
+ def test_modified_fields_marked_as_dynamic(self, file, fields, tmpdir_cwd):
+ # We start with a static config
+ Path(file).write_text(self.STATIC_CONFIG[file], encoding="utf-8")
+ dist = _makedist()
+
+ # ... but then we simulate the effects of a plugin modifying the distribution
+ for attr, value in fields.values():
+ # `dist` and `dist.metadata` are complicated...
+ # Some attributes work when set on `dist`, others on `dist.metadata`...
+ # Here we set in both just in case (this also avoids calling `_finalize_*`)
+ setattr(dist, attr, value)
+ setattr(dist.metadata, attr, value)
+
+ # Then we should be able to list the modified fields as Dynamic
+ metadata = _get_metadata(dist)
+ assert set(metadata.get_all("Dynamic")) == set(fields)
+
+ @pytest.mark.parametrize(
+ "extra_toml",
+ [
+ "# Let setuptools autofill license-files",
+ "license-files = ['LICENSE*', 'AUTHORS*', 'NOTICE']",
+ ],
+ )
+ def test_license_files_dynamic(self, extra_toml, tmpdir_cwd):
+ # For simplicity (and for the time being) setuptools is not making
+ # any special handling to guarantee `License-File` is considered static.
+ # Instead we rely in the fact that, although suboptimal, it is OK to have
+ # it as dynamics, as per:
+ # https://github.com/pypa/setuptools/issues/4629#issuecomment-2331233677
+ files = {
+ "pyproject.toml": self.STATIC_CONFIG["pyproject.toml"].replace(
+ 'license = "AGPL-3.0-or-later"',
+ f"dynamic = ['license']\n{extra_toml}",
+ ),
+ "LICENSE.md": "--- mock license ---",
+ "NOTICE": "--- mock notice ---",
+ "AUTHORS.txt": "--- me ---",
+ }
+ # Sanity checks:
+ assert extra_toml in files["pyproject.toml"]
+ assert 'license = "AGPL-3.0-or-later"' not in extra_toml
+
+ jaraco.path.build(files)
+ dist = _makedist(license_expression="AGPL-3.0-or-later")
+ metadata = _get_metadata(dist)
+ assert set(metadata.get_all("Dynamic")) == {
+ 'license-file',
+ 'license-expression',
+ }
+ assert metadata.get("License-Expression") == "AGPL-3.0-or-later"
+ assert set(metadata.get_all("License-File")) == {
+ "NOTICE",
+ "AUTHORS.txt",
+ "LICENSE.md",
+ }
+
+
+def _makedist(**attrs):
+ dist = Distribution(attrs)
+ dist.parse_config_files()
+ return dist
+
+
+def _assert_roundtrip_message(metadata: str) -> None:
+ """Emulate the way wheel.bdist_wheel parses and regenerates the message,
+ then ensures the metadata generated by setuptools is compatible.
+ """
+ with io.StringIO(metadata) as buffer:
+ msg = Parser(EmailMessage).parse(buffer)
+
+ serialization_policy = EmailPolicy(
+ utf8=True,
+ mangle_from_=False,
+ max_line_length=0,
+ )
+ with io.BytesIO() as buffer:
+ out = io.TextIOWrapper(buffer, encoding="utf-8")
+ Generator(out, policy=serialization_policy).flatten(msg)
+ out.flush()
+ regenerated = buffer.getvalue()
+
+ raw_metadata = bytes(metadata, "utf-8")
+ # Normalise newlines to avoid test errors on Windows:
+ raw_metadata = b"\n".join(raw_metadata.splitlines())
+ regenerated = b"\n".join(regenerated.splitlines())
+ assert regenerated == raw_metadata
+
+
+def _normalize_metadata(msg: Message) -> str:
+ """Allow equivalent metadata to be compared directly"""
+ # The main challenge regards the requirements and extras.
+ # Both setuptools and wheel already apply some level of normalization
+ # but they differ regarding which character is chosen, according to the
+ # following spec it should be "-":
+ # https://packaging.python.org/en/latest/specifications/name-normalization/
+
+ # Related issues:
+ # https://github.com/pypa/packaging/issues/845
+ # https://github.com/pypa/packaging/issues/644#issuecomment-2429813968
+
+ extras = {x.replace("_", "-"): x for x in msg.get_all("Provides-Extra", [])}
+ reqs = [
+ _normalize_req(req, extras)
+ for req in _reqs.parse(msg.get_all("Requires-Dist", []))
+ ]
+ del msg["Requires-Dist"]
+ del msg["Provides-Extra"]
+
+ # Ensure consistent ord
+ for req in sorted(reqs):
+ msg["Requires-Dist"] = req
+ for extra in sorted(extras):
+ msg["Provides-Extra"] = extra
+
+ # TODO: Handle lack of PEP 643 implementation in pypa/wheel?
+ del msg["Metadata-Version"]
+
+ return msg.as_string()
+
+
+def _normalize_req(req: Requirement, extras: dict[str, str]) -> str:
+ """Allow equivalent requirement objects to be compared directly"""
+ as_str = str(req).replace(req.name, req.name.replace("_", "-"))
+ for norm, orig in extras.items():
+ as_str = as_str.replace(orig, norm)
+ return as_str
+
+
+def _get_pkginfo(dist: Distribution):
+ with io.StringIO() as fp:
+ dist.metadata.write_pkg_file(fp)
+ return fp.getvalue()
+
+
+def _get_metadata(dist: Distribution | None = None):
+ return message_from_string(_get_pkginfo(dist or _makedist()))
+
+
+def _valid_metadata(text: str) -> bool:
+ metadata = Metadata.from_email(text, validate=True) # can raise exceptions
+ return metadata is not None
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_depends.py b/lib/python3.12/site-packages/setuptools/tests/test_depends.py
new file mode 100644
index 0000000000000000000000000000000000000000..1714c041f7a23e1ecbfc3245bf964f75c13734ca
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_depends.py
@@ -0,0 +1,15 @@
+import sys
+
+from setuptools import depends
+
+
+class TestGetModuleConstant:
+ def test_basic(self):
+ """
+ Invoke get_module_constant on a module in
+ the test package.
+ """
+ mod_name = 'setuptools.tests.mod_with_constant'
+ val = depends.get_module_constant(mod_name, 'value')
+ assert val == 'three, sir!'
+ assert 'setuptools.tests.mod_with_constant' not in sys.modules
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_develop.py b/lib/python3.12/site-packages/setuptools/tests/test_develop.py
new file mode 100644
index 0000000000000000000000000000000000000000..354c51fc3c7888de2161122f6f3cafa55b181594
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_develop.py
@@ -0,0 +1,112 @@
+"""develop tests"""
+
+import os
+import platform
+import subprocess
+import sys
+
+import pytest
+
+from setuptools._path import paths_on_pythonpath
+
+from . import contexts, namespaces
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo',
+ packages=['foo'],
+)
+"""
+
+INIT_PY = """print "foo"
+"""
+
+
+@pytest.fixture
+def temp_user(monkeypatch):
+ with contexts.tempdir() as user_base:
+ with contexts.tempdir() as user_site:
+ monkeypatch.setattr('site.USER_BASE', user_base)
+ monkeypatch.setattr('site.USER_SITE', user_site)
+ yield
+
+
+@pytest.fixture
+def test_env(tmpdir, temp_user):
+ target = tmpdir
+ foo = target.mkdir('foo')
+ setup = target / 'setup.py'
+ if setup.isfile():
+ raise ValueError(dir(target))
+ with setup.open('w') as f:
+ f.write(SETUP_PY)
+ init = foo / '__init__.py'
+ with init.open('w') as f:
+ f.write(INIT_PY)
+ with target.as_cwd():
+ yield target
+
+
+class TestNamespaces:
+ @staticmethod
+ def install_develop(src_dir, target):
+ develop_cmd = [
+ sys.executable,
+ 'setup.py',
+ 'develop',
+ '--install-dir',
+ str(target),
+ ]
+ with src_dir.as_cwd():
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(develop_cmd)
+
+ @pytest.mark.skipif(
+ bool(os.environ.get("APPVEYOR")),
+ reason="https://github.com/pypa/setuptools/issues/851",
+ )
+ @pytest.mark.skipif(
+ platform.python_implementation() == 'PyPy',
+ reason="https://github.com/pypa/setuptools/issues/1202",
+ )
+ @pytest.mark.uses_network
+ def test_namespace_package_importable(self, tmpdir):
+ """
+ Installing two packages sharing the same namespace, one installed
+ naturally using pip or `--single-version-externally-managed`
+ and the other installed using `develop` should leave the namespace
+ in tact and both packages reachable by import.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
+ target = tmpdir / 'packages'
+ # use pip to install to the target directory
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip',
+ 'install',
+ str(pkg_A),
+ '-t',
+ str(target),
+ ]
+ subprocess.check_call(install_cmd)
+ self.install_develop(pkg_B, target)
+ namespaces.make_site_dir(target)
+ try_import = [
+ sys.executable,
+ '-c',
+ 'import myns.pkgA; import myns.pkgB',
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(try_import)
+
+ # additionally ensure that pkg_resources import works
+ pkg_resources_imp = [
+ sys.executable,
+ '-c',
+ 'import pkg_resources',
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(pkg_resources_imp)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_dist.py b/lib/python3.12/site-packages/setuptools/tests/test_dist.py
new file mode 100644
index 0000000000000000000000000000000000000000..9685dcd7cbbeceb1e35d69de30b9e2fdc3f06227
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_dist.py
@@ -0,0 +1,280 @@
+import os
+import re
+import urllib.parse
+import urllib.request
+
+import pytest
+
+from setuptools import Distribution
+from setuptools.dist import check_package_data, check_specifier
+
+from .fixtures import make_trivial_sdist
+from .test_find_packages import ensure_files
+from .textwrap import DALS
+
+from distutils.errors import DistutilsSetupError
+
+
+def test_dist_fetch_build_egg(tmpdir, setuptools_wheel):
+ """
+ Check multiple calls to `Distribution.fetch_build_egg` work as expected.
+ """
+ index = tmpdir.mkdir('index')
+ index_url = urllib.parse.urljoin('file://', urllib.request.pathname2url(str(index)))
+
+ def sdist_with_index(distname, version):
+ dist_dir = index.mkdir(distname)
+ dist_sdist = f'{distname}-{version}.tar.gz'
+ make_trivial_sdist(
+ str(dist_dir.join(dist_sdist)), distname, version, setuptools_wheel
+ )
+ with dist_dir.join('index.html').open('w') as fp:
+ fp.write(
+ DALS(
+ """
+
+ {dist_sdist}
+
+ """
+ ).format(dist_sdist=dist_sdist)
+ )
+
+ sdist_with_index('barbazquux', '3.2.0')
+ sdist_with_index('barbazquux-runner', '2.11.1')
+ with tmpdir.join('setup.cfg').open('w') as fp:
+ fp.write(
+ DALS(
+ """
+ [easy_install]
+ index_url = {index_url}
+ """
+ ).format(index_url=index_url)
+ )
+ reqs = """
+ barbazquux-runner
+ barbazquux
+ """.split()
+ with tmpdir.as_cwd():
+ dist = Distribution()
+ dist.parse_config_files()
+ resolved_dists = [dist.fetch_build_egg(r) for r in reqs]
+ assert [dist.name for dist in resolved_dists if dist] == reqs
+
+
+EXAMPLE_BASE_INFO = dict(
+ name="package",
+ version="0.0.1",
+ author="Foo Bar",
+ author_email="foo@bar.net",
+ long_description="Long\ndescription",
+ description="Short description",
+ keywords=["one", "two"],
+)
+
+
+def test_provides_extras_deterministic_order():
+ attrs = dict(extras_require=dict(a=['foo'], b=['bar']))
+ dist = Distribution(attrs)
+ assert list(dist.metadata.provides_extras) == ['a', 'b']
+ attrs['extras_require'] = dict(reversed(attrs['extras_require'].items()))
+ dist = Distribution(attrs)
+ assert list(dist.metadata.provides_extras) == ['b', 'a']
+
+
+CHECK_PACKAGE_DATA_TESTS = (
+ # Valid.
+ (
+ {
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ },
+ None,
+ ),
+ # Not a dictionary.
+ (
+ (
+ ('', ['*.txt', '*.rst']),
+ ('hello', ['*.msg']),
+ ),
+ (
+ "'package_data' must be a dictionary mapping package"
+ " names to lists of string wildcard patterns"
+ ),
+ ),
+ # Invalid key type.
+ (
+ {
+ 400: ['*.txt', '*.rst'],
+ },
+ ("keys of 'package_data' dict must be strings (got 400)"),
+ ),
+ # Invalid value type.
+ (
+ {
+ 'hello': '*.msg',
+ },
+ (
+ "\"values of 'package_data' dict\" must be of type "
+ " (got '*.msg')"
+ ),
+ ),
+ # Invalid value type (generators are single use)
+ (
+ {
+ 'hello': (x for x in "generator"),
+ },
+ (
+ "\"values of 'package_data' dict\" must be of type "
+ " (got =3.0, !=3.1'}
+ dist = Distribution(attrs)
+ check_specifier(dist, attrs, attrs['python_requires'])
+
+ attrs = {'name': 'foo', 'python_requires': ['>=3.0', '!=3.1']}
+ dist = Distribution(attrs)
+ check_specifier(dist, attrs, attrs['python_requires'])
+
+ # invalid specifier value
+ attrs = {'name': 'foo', 'python_requires': '>=invalid-version'}
+ with pytest.raises(DistutilsSetupError):
+ dist = Distribution(attrs)
+
+
+def test_metadata_name():
+ with pytest.raises(DistutilsSetupError, match='missing.*name'):
+ Distribution()._validate_metadata()
+
+
+@pytest.mark.parametrize(
+ ('dist_name', 'py_module'),
+ [
+ ("my.pkg", "my_pkg"),
+ ("my-pkg", "my_pkg"),
+ ("my_pkg", "my_pkg"),
+ ("pkg", "pkg"),
+ ],
+)
+def test_dist_default_py_modules(tmp_path, dist_name, py_module):
+ (tmp_path / f"{py_module}.py").touch()
+
+ (tmp_path / "setup.py").touch()
+ (tmp_path / "noxfile.py").touch()
+ # ^-- make sure common tool files are ignored
+
+ attrs = {**EXAMPLE_BASE_INFO, "name": dist_name, "src_root": str(tmp_path)}
+ # Find `py_modules` corresponding to dist_name if not given
+ dist = Distribution(attrs)
+ dist.set_defaults()
+ assert dist.py_modules == [py_module]
+ # When `py_modules` is given, don't do anything
+ dist = Distribution({**attrs, "py_modules": ["explicity_py_module"]})
+ dist.set_defaults()
+ assert dist.py_modules == ["explicity_py_module"]
+ # When `packages` is given, don't do anything
+ dist = Distribution({**attrs, "packages": ["explicity_package"]})
+ dist.set_defaults()
+ assert not dist.py_modules
+
+
+@pytest.mark.parametrize(
+ ('dist_name', 'package_dir', 'package_files', 'packages'),
+ [
+ ("my.pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
+ ("my-pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
+ ("my_pkg", None, ["my_pkg/__init__.py", "my_pkg/mod.py"], ["my_pkg"]),
+ ("my.pkg", None, ["my/pkg/__init__.py"], ["my", "my.pkg"]),
+ (
+ "my_pkg",
+ None,
+ ["src/my_pkg/__init__.py", "src/my_pkg2/__init__.py"],
+ ["my_pkg", "my_pkg2"],
+ ),
+ (
+ "my_pkg",
+ {"pkg": "lib", "pkg2": "lib2"},
+ ["lib/__init__.py", "lib/nested/__init__.pyt", "lib2/__init__.py"],
+ ["pkg", "pkg.nested", "pkg2"],
+ ),
+ ],
+)
+def test_dist_default_packages(
+ tmp_path, dist_name, package_dir, package_files, packages
+):
+ ensure_files(tmp_path, package_files)
+
+ (tmp_path / "setup.py").touch()
+ (tmp_path / "noxfile.py").touch()
+ # ^-- should not be included by default
+
+ attrs = {
+ **EXAMPLE_BASE_INFO,
+ "name": dist_name,
+ "src_root": str(tmp_path),
+ "package_dir": package_dir,
+ }
+ # Find `packages` either corresponding to dist_name or inside src
+ dist = Distribution(attrs)
+ dist.set_defaults()
+ assert not dist.py_modules
+ assert not dist.py_modules
+ assert set(dist.packages) == set(packages)
+ # When `py_modules` is given, don't do anything
+ dist = Distribution({**attrs, "py_modules": ["explicit_py_module"]})
+ dist.set_defaults()
+ assert not dist.packages
+ assert set(dist.py_modules) == {"explicit_py_module"}
+ # When `packages` is given, don't do anything
+ dist = Distribution({**attrs, "packages": ["explicit_package"]})
+ dist.set_defaults()
+ assert not dist.py_modules
+ assert set(dist.packages) == {"explicit_package"}
+
+
+@pytest.mark.parametrize(
+ ('dist_name', 'package_dir', 'package_files'),
+ [
+ ("my.pkg.nested", None, ["my/pkg/nested/__init__.py"]),
+ ("my.pkg", None, ["my/pkg/__init__.py", "my/pkg/file.py"]),
+ ("my_pkg", None, ["my_pkg.py"]),
+ ("my_pkg", None, ["my_pkg/__init__.py", "my_pkg/nested/__init__.py"]),
+ ("my_pkg", None, ["src/my_pkg/__init__.py", "src/my_pkg/nested/__init__.py"]),
+ (
+ "my_pkg",
+ {"my_pkg": "lib", "my_pkg.lib2": "lib2"},
+ ["lib/__init__.py", "lib/nested/__init__.pyt", "lib2/__init__.py"],
+ ),
+ # Should not try to guess a name from multiple py_modules/packages
+ ("UNKNOWN", None, ["src/mod1.py", "src/mod2.py"]),
+ ("UNKNOWN", None, ["src/pkg1/__ini__.py", "src/pkg2/__init__.py"]),
+ ],
+)
+def test_dist_default_name(tmp_path, dist_name, package_dir, package_files):
+ """Make sure dist.name is discovered from packages/py_modules"""
+ ensure_files(tmp_path, package_files)
+ attrs = {
+ **EXAMPLE_BASE_INFO,
+ "src_root": "/".join(os.path.split(tmp_path)), # POSIX-style
+ "package_dir": package_dir,
+ }
+ del attrs["name"]
+
+ dist = Distribution(attrs)
+ dist.set_defaults()
+ assert dist.py_modules or dist.packages
+ assert dist.get_name() == dist_name
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_dist_info.py b/lib/python3.12/site-packages/setuptools/tests/test_dist_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..f65d0afbe46299fa816b2c64dc538dbd66880dab
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_dist_info.py
@@ -0,0 +1,147 @@
+"""Test .dist-info style distributions."""
+
+import pathlib
+import re
+import shutil
+import subprocess
+import sys
+from functools import partial
+
+import pytest
+
+from setuptools.archive_util import unpack_archive
+
+from .textwrap import DALS
+
+read = partial(pathlib.Path.read_text, encoding="utf-8")
+
+
+class TestDistInfo:
+ def test_invalid_version(self, tmp_path):
+ """
+ Supplying an invalid version crashes dist_info.
+ """
+ config = "[metadata]\nname=proj\nversion=42\n[egg_info]\ntag_build=invalid!!!\n"
+ (tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
+ msg = re.compile("invalid version", re.MULTILINE | re.IGNORECASE)
+ proc = run_command_inner("dist_info", cwd=tmp_path, check=False)
+ assert proc.returncode
+ assert msg.search(proc.stdout)
+ assert not list(tmp_path.glob("*.dist-info"))
+
+ def test_tag_arguments(self, tmp_path):
+ config = """
+ [metadata]
+ name=proj
+ version=42
+ [egg_info]
+ tag_date=1
+ tag_build=.post
+ """
+ (tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
+
+ print(run_command("dist_info", "--no-date", cwd=tmp_path))
+ dist_info = next(tmp_path.glob("*.dist-info"))
+ assert dist_info.name.startswith("proj-42")
+ shutil.rmtree(dist_info)
+
+ print(run_command("dist_info", "--tag-build", ".a", cwd=tmp_path))
+ dist_info = next(tmp_path.glob("*.dist-info"))
+ assert dist_info.name.startswith("proj-42a")
+
+ @pytest.mark.parametrize("keep_egg_info", (False, True))
+ def test_output_dir(self, tmp_path, keep_egg_info):
+ config = "[metadata]\nname=proj\nversion=42\n"
+ (tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
+ out = tmp_path / "__out"
+ out.mkdir()
+ opts = ["--keep-egg-info"] if keep_egg_info else []
+ run_command("dist_info", "--output-dir", out, *opts, cwd=tmp_path)
+ assert len(list(out.glob("*.dist-info"))) == 1
+ assert len(list(tmp_path.glob("*.dist-info"))) == 0
+ expected_egg_info = int(keep_egg_info)
+ assert len(list(out.glob("*.egg-info"))) == expected_egg_info
+ assert len(list(tmp_path.glob("*.egg-info"))) == 0
+ assert len(list(out.glob("*.__bkp__"))) == 0
+ assert len(list(tmp_path.glob("*.__bkp__"))) == 0
+
+
+class TestWheelCompatibility:
+ """Make sure the .dist-info directory produced with the ``dist_info`` command
+ is the same as the one produced by ``bdist_wheel``.
+ """
+
+ SETUPCFG = DALS(
+ """
+ [metadata]
+ name = {name}
+ version = {version}
+
+ [options]
+ install_requires =
+ foo>=12; sys_platform != "linux"
+
+ [options.extras_require]
+ test = pytest
+
+ [options.entry_points]
+ console_scripts =
+ executable-name = my_package.module:function
+ discover =
+ myproj = my_package.other_module:function
+ """
+ )
+
+ EGG_INFO_OPTS = [
+ # Related: #3088 #2872
+ ("", ""),
+ (".post", "[egg_info]\ntag_build = post\n"),
+ (".post", "[egg_info]\ntag_build = .post\n"),
+ (".post", "[egg_info]\ntag_build = post\ntag_date = 1\n"),
+ (".dev", "[egg_info]\ntag_build = .dev\n"),
+ (".dev", "[egg_info]\ntag_build = .dev\ntag_date = 1\n"),
+ ("a1", "[egg_info]\ntag_build = .a1\n"),
+ ("+local", "[egg_info]\ntag_build = +local\n"),
+ ]
+
+ @pytest.mark.parametrize("name", "my-proj my_proj my.proj My.Proj".split())
+ @pytest.mark.parametrize("version", ["0.42.13"])
+ @pytest.mark.parametrize(("suffix", "cfg"), EGG_INFO_OPTS)
+ def test_dist_info_is_the_same_as_in_wheel(
+ self, name, version, tmp_path, suffix, cfg
+ ):
+ config = self.SETUPCFG.format(name=name, version=version) + cfg
+
+ for i in "dir_wheel", "dir_dist":
+ (tmp_path / i).mkdir()
+ (tmp_path / i / "setup.cfg").write_text(config, encoding="utf-8")
+
+ run_command("bdist_wheel", cwd=tmp_path / "dir_wheel")
+ wheel = next(tmp_path.glob("dir_wheel/dist/*.whl"))
+ unpack_archive(wheel, tmp_path / "unpack")
+ wheel_dist_info = next(tmp_path.glob("unpack/*.dist-info"))
+
+ run_command("dist_info", cwd=tmp_path / "dir_dist")
+ dist_info = next(tmp_path.glob("dir_dist/*.dist-info"))
+
+ assert dist_info.name == wheel_dist_info.name
+ assert dist_info.name.startswith(f"my_proj-{version}{suffix}")
+ for file in "METADATA", "entry_points.txt":
+ assert read(dist_info / file) == read(wheel_dist_info / file)
+
+
+def run_command_inner(*cmd, **kwargs):
+ opts = {
+ "stderr": subprocess.STDOUT,
+ "stdout": subprocess.PIPE,
+ "text": True,
+ "encoding": "utf-8",
+ "check": True,
+ **kwargs,
+ }
+ cmd = [sys.executable, "-c", "__import__('setuptools').setup()", *map(str, cmd)]
+ return subprocess.run(cmd, **opts)
+
+
+def run_command(*args, **kwargs):
+ return run_command_inner(*args, **kwargs).stdout
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_distutils_adoption.py b/lib/python3.12/site-packages/setuptools/tests/test_distutils_adoption.py
new file mode 100644
index 0000000000000000000000000000000000000000..f99a58849950029f53322c19cde1c171fe26622c
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_distutils_adoption.py
@@ -0,0 +1,198 @@
+import os
+import platform
+import sys
+import textwrap
+
+import pytest
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+_TEXT_KWARGS = {"text": True, "encoding": "utf-8"} # For subprocess.run
+
+
+def win_sr(env):
+ """
+ On Windows, SYSTEMROOT must be present to avoid
+
+ > Fatal Python error: _Py_HashRandomization_Init: failed to
+ > get random numbers to initialize Python
+ """
+ if env and platform.system() == 'Windows':
+ env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
+ return env
+
+
+def find_distutils(venv, imports='distutils', env=None, **kwargs):
+ py_cmd = 'import {imports}; print(distutils.__file__)'.format(**locals())
+ cmd = ['python', '-c', py_cmd]
+ return venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS, **kwargs)
+
+
+def count_meta_path(venv, env=None):
+ py_cmd = textwrap.dedent(
+ """
+ import sys
+ is_distutils = lambda finder: finder.__class__.__name__ == "DistutilsMetaFinder"
+ print(len(list(filter(is_distutils, sys.meta_path))))
+ """
+ )
+ cmd = ['python', '-c', py_cmd]
+ return int(venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS))
+
+
+skip_without_stdlib_distutils = pytest.mark.skipif(
+ sys.version_info >= (3, 12),
+ reason='stdlib distutils is removed from Python 3.12+',
+)
+
+
+@skip_without_stdlib_distutils
+def test_distutils_stdlib(venv):
+ """
+ Ensure stdlib distutils is used when appropriate.
+ """
+ env = dict(SETUPTOOLS_USE_DISTUTILS='stdlib')
+ assert venv.name not in find_distutils(venv, env=env).split(os.sep)
+ assert count_meta_path(venv, env=env) == 0
+
+
+def test_distutils_local_with_setuptools(venv):
+ """
+ Ensure local distutils is used when appropriate.
+ """
+ env = dict(SETUPTOOLS_USE_DISTUTILS='local')
+ loc = find_distutils(venv, imports='setuptools, distutils', env=env)
+ assert venv.name in loc.split(os.sep)
+ assert count_meta_path(venv, env=env) <= 1
+
+
+@pytest.mark.xfail('IS_PYPY', reason='pypy imports distutils on startup')
+def test_distutils_local(venv):
+ """
+ Even without importing, the setuptools-local copy of distutils is
+ preferred.
+ """
+ env = dict(SETUPTOOLS_USE_DISTUTILS='local')
+ assert venv.name in find_distutils(venv, env=env).split(os.sep)
+ assert count_meta_path(venv, env=env) <= 1
+
+
+def test_pip_import(venv):
+ """
+ Ensure pip can be imported.
+ Regression test for #3002.
+ """
+ cmd = ['python', '-c', 'import pip']
+ venv.run(cmd, **_TEXT_KWARGS)
+
+
+def test_distutils_has_origin():
+ """
+ Distutils module spec should have an origin. #2990.
+ """
+ assert __import__('distutils').__spec__.origin
+
+
+ENSURE_IMPORTS_ARE_NOT_DUPLICATED = r"""
+# Depending on the importlib machinery and _distutils_hack, some imports are
+# duplicated resulting in different module objects being loaded, which prevents
+# patches as shown in #3042.
+# This script provides a way of verifying if this duplication is happening.
+
+from distutils import cmd
+import distutils.command.sdist as sdist
+
+# import last to prevent caching
+from distutils import {imported_module}
+
+for mod in (cmd, sdist):
+ assert mod.{imported_module} == {imported_module}, (
+ f"\n{{mod.dir_util}}\n!=\n{{{imported_module}}}"
+ )
+
+print("success")
+"""
+
+
+@pytest.mark.usefixtures("tmpdir_cwd")
+@pytest.mark.parametrize(
+ ('distutils_version', 'imported_module'),
+ [
+ pytest.param("stdlib", "dir_util", marks=skip_without_stdlib_distutils),
+ pytest.param("stdlib", "file_util", marks=skip_without_stdlib_distutils),
+ pytest.param("stdlib", "archive_util", marks=skip_without_stdlib_distutils),
+ ("local", "dir_util"),
+ ("local", "file_util"),
+ ("local", "archive_util"),
+ ],
+)
+def test_modules_are_not_duplicated_on_import(distutils_version, imported_module, venv):
+ env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
+ script = ENSURE_IMPORTS_ARE_NOT_DUPLICATED.format(imported_module=imported_module)
+ cmd = ['python', '-c', script]
+ output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
+ assert output == "success"
+
+
+ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED = r"""
+import types
+import distutils.dist as dist
+from distutils import log
+if isinstance(dist.log, types.ModuleType):
+ assert dist.log == log, f"\n{dist.log}\n!=\n{log}"
+print("success")
+"""
+
+
+@pytest.mark.usefixtures("tmpdir_cwd")
+@pytest.mark.parametrize(
+ "distutils_version",
+ [
+ "local",
+ pytest.param("stdlib", marks=skip_without_stdlib_distutils),
+ ],
+)
+def test_log_module_is_not_duplicated_on_import(distutils_version, venv):
+ env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
+ cmd = ['python', '-c', ENSURE_LOG_IMPORT_IS_NOT_DUPLICATED]
+ output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
+ assert output == "success"
+
+
+ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY = r"""
+from setuptools.modified import newer
+from {imported_module}.errors import DistutilsError
+
+# Can't use pytest.raises in this context
+try:
+ newer("", "")
+except DistutilsError:
+ print("success")
+else:
+ raise AssertionError("Expected to raise")
+"""
+
+
+@pytest.mark.usefixtures("tmpdir_cwd")
+@pytest.mark.parametrize(
+ ('distutils_version', 'imported_module'),
+ [
+ ("local", "distutils"),
+ # Unfortunately we still get ._distutils.errors.DistutilsError with SETUPTOOLS_USE_DISTUTILS=stdlib
+ # But that's a deprecated use-case we don't mind not fully supporting in newer code
+ pytest.param(
+ "stdlib", "setuptools._distutils", marks=skip_without_stdlib_distutils
+ ),
+ ],
+)
+def test_consistent_error_from_modified_py(distutils_version, imported_module, venv):
+ env = dict(SETUPTOOLS_USE_DISTUTILS=distutils_version)
+ cmd = [
+ 'python',
+ '-c',
+ ENSURE_CONSISTENT_ERROR_FROM_MODIFIED_PY.format(
+ imported_module=imported_module
+ ),
+ ]
+ output = venv.run(cmd, env=win_sr(env), **_TEXT_KWARGS).strip()
+ assert output == "success"
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_editable_install.py b/lib/python3.12/site-packages/setuptools/tests/test_editable_install.py
new file mode 100644
index 0000000000000000000000000000000000000000..225fc6a2f501e46e6b8f0c0010bf332dd1e59c54
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_editable_install.py
@@ -0,0 +1,1263 @@
+from __future__ import annotations
+
+import os
+import platform
+import stat
+import subprocess
+import sys
+from copy import deepcopy
+from importlib import import_module
+from importlib.machinery import EXTENSION_SUFFIXES
+from pathlib import Path
+from textwrap import dedent
+from typing import Any
+from unittest.mock import Mock
+from uuid import uuid4
+
+import jaraco.envs
+import jaraco.path
+import pytest
+from path import Path as _Path
+
+from setuptools._importlib import resources as importlib_resources
+from setuptools.command.editable_wheel import (
+ _encode_pth,
+ _find_namespaces,
+ _find_package_roots,
+ _find_virtual_namespaces,
+ _finder_template,
+ _LinkTree,
+ _TopLevelFinder,
+ editable_wheel,
+)
+from setuptools.dist import Distribution
+from setuptools.extension import Extension
+from setuptools.warnings import SetuptoolsDeprecationWarning
+
+from . import contexts, namespaces
+
+from distutils.core import run_setup
+
+
+@pytest.fixture(params=["strict", "lenient"])
+def editable_opts(request):
+ if request.param == "strict":
+ return ["--config-settings", "editable-mode=strict"]
+ return []
+
+
+EXAMPLE = {
+ 'pyproject.toml': dedent(
+ """\
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "mypkg"
+ version = "3.14159"
+ license = {text = "MIT"}
+ description = "This is a Python package"
+ dynamic = ["readme"]
+ classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers"
+ ]
+ urls = {Homepage = "https://github.com"}
+
+ [tool.setuptools]
+ package-dir = {"" = "src"}
+ packages = {find = {where = ["src"]}}
+ license-files = ["LICENSE*"]
+
+ [tool.setuptools.dynamic]
+ readme = {file = "README.rst"}
+
+ [tool.distutils.egg_info]
+ tag-build = ".post0"
+ """
+ ),
+ "MANIFEST.in": dedent(
+ """\
+ global-include *.py *.txt
+ global-exclude *.py[cod]
+ prune dist
+ prune build
+ """
+ ).strip(),
+ "README.rst": "This is a ``README``",
+ "LICENSE.txt": "---- placeholder MIT license ----",
+ "src": {
+ "mypkg": {
+ "__init__.py": dedent(
+ """\
+ import sys
+ from importlib.metadata import PackageNotFoundError, version
+
+ try:
+ __version__ = version(__name__)
+ except PackageNotFoundError:
+ __version__ = "unknown"
+ """
+ ),
+ "__main__.py": dedent(
+ """\
+ from importlib.resources import read_text
+ from . import __version__, __name__ as parent
+ from .mod import x
+
+ data = read_text(parent, "data.txt")
+ print(__version__, data, x)
+ """
+ ),
+ "mod.py": "x = ''",
+ "data.txt": "Hello World",
+ }
+ },
+}
+
+
+SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
+
+
+@pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
+@pytest.mark.parametrize(
+ "files",
+ [
+ {**EXAMPLE, "setup.py": SETUP_SCRIPT_STUB},
+ EXAMPLE, # No setup.py script
+ ],
+)
+def test_editable_with_pyproject(tmp_path, venv, files, editable_opts):
+ project = tmp_path / "mypkg"
+ project.mkdir()
+ jaraco.path.build(files, prefix=project)
+
+ cmd = [
+ "python",
+ "-m",
+ "pip",
+ "install",
+ "--no-build-isolation", # required to force current version of setuptools
+ "-e",
+ str(project),
+ *editable_opts,
+ ]
+ print(venv.run(cmd))
+
+ cmd = ["python", "-m", "mypkg"]
+ assert venv.run(cmd).strip() == "3.14159.post0 Hello World"
+
+ (project / "src/mypkg/data.txt").write_text("foobar", encoding="utf-8")
+ (project / "src/mypkg/mod.py").write_text("x = 42", encoding="utf-8")
+ assert venv.run(cmd).strip() == "3.14159.post0 foobar 42"
+
+
+def test_editable_with_flat_layout(tmp_path, venv, editable_opts):
+ files = {
+ "mypkg": {
+ "pyproject.toml": dedent(
+ """\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "mypkg"
+ version = "3.14159"
+
+ [tool.setuptools]
+ packages = ["pkg"]
+ py-modules = ["mod"]
+ """
+ ),
+ "pkg": {"__init__.py": "a = 4"},
+ "mod.py": "b = 2",
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ project = tmp_path / "mypkg"
+
+ cmd = [
+ "python",
+ "-m",
+ "pip",
+ "install",
+ "--no-build-isolation", # required to force current version of setuptools
+ "-e",
+ str(project),
+ *editable_opts,
+ ]
+ print(venv.run(cmd))
+ cmd = ["python", "-c", "import pkg, mod; print(pkg.a, mod.b)"]
+ assert venv.run(cmd).strip() == "4 2"
+
+
+def test_editable_with_single_module(tmp_path, venv, editable_opts):
+ files = {
+ "mypkg": {
+ "pyproject.toml": dedent(
+ """\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "mod"
+ version = "3.14159"
+
+ [tool.setuptools]
+ py-modules = ["mod"]
+ """
+ ),
+ "mod.py": "b = 2",
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ project = tmp_path / "mypkg"
+
+ cmd = [
+ "python",
+ "-m",
+ "pip",
+ "install",
+ "--no-build-isolation", # required to force current version of setuptools
+ "-e",
+ str(project),
+ *editable_opts,
+ ]
+ print(venv.run(cmd))
+ cmd = ["python", "-c", "import mod; print(mod.b)"]
+ assert venv.run(cmd).strip() == "2"
+
+
+class TestLegacyNamespaces:
+ # legacy => pkg_resources.declare_namespace(...) + setup(namespace_packages=...)
+
+ def test_nspkg_file_is_unique(self, tmp_path, monkeypatch):
+ deprecation = pytest.warns(
+ SetuptoolsDeprecationWarning, match=".*namespace_packages parameter.*"
+ )
+ installation_dir = tmp_path / ".installation_dir"
+ installation_dir.mkdir()
+ examples = (
+ "myns.pkgA",
+ "myns.pkgB",
+ "myns.n.pkgA",
+ "myns.n.pkgB",
+ )
+
+ for name in examples:
+ pkg = namespaces.build_namespace_package(tmp_path, name, version="42")
+ with deprecation, monkeypatch.context() as ctx:
+ ctx.chdir(pkg)
+ dist = run_setup("setup.py", stop_after="config")
+ cmd = editable_wheel(dist)
+ cmd.finalize_options()
+ editable_name = cmd.get_finalized_command("dist_info").name
+ cmd._install_namespaces(installation_dir, editable_name)
+
+ files = list(installation_dir.glob("*-nspkg.pth"))
+ assert len(files) == len(examples)
+
+ @pytest.mark.parametrize(
+ "impl",
+ (
+ "pkg_resources",
+ # "pkgutil", => does not work
+ ),
+ )
+ @pytest.mark.parametrize("ns", ("myns.n",))
+ def test_namespace_package_importable(
+ self, venv, tmp_path, ns, impl, editable_opts
+ ):
+ """
+ Installing two packages sharing the same namespace, one installed
+ naturally using pip or `--single-version-externally-managed`
+ and the other installed in editable mode should leave the namespace
+ intact and both packages reachable by import.
+ (Ported from test_develop).
+ """
+ build_system = """\
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+ """
+ pkg_A = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgA", impl=impl)
+ pkg_B = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgB", impl=impl)
+ (pkg_A / "pyproject.toml").write_text(build_system, encoding="utf-8")
+ (pkg_B / "pyproject.toml").write_text(build_system, encoding="utf-8")
+ # use pip to install to the target directory
+ opts = editable_opts[:]
+ opts.append("--no-build-isolation") # force current version of setuptools
+ venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
+ venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
+ venv.run(["python", "-c", f"import {ns}.pkgA; import {ns}.pkgB"])
+ # additionally ensure that pkg_resources import works
+ venv.run(["python", "-c", "import pkg_resources"])
+
+
+class TestPep420Namespaces:
+ def test_namespace_package_importable(self, venv, tmp_path, editable_opts):
+ """
+ Installing two packages sharing the same namespace, one installed
+ normally using pip and the other installed in editable mode
+ should allow importing both packages.
+ """
+ pkg_A = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgA')
+ pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
+ # use pip to install to the target directory
+ opts = editable_opts[:]
+ opts.append("--no-build-isolation") # force current version of setuptools
+ venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
+ venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
+ venv.run(["python", "-c", "import myns.n.pkgA; import myns.n.pkgB"])
+
+ def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_opts):
+ """Currently users can create a namespace by tweaking `package_dir`"""
+ files = {
+ "pkgA": {
+ "pyproject.toml": dedent(
+ """\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "pkgA"
+ version = "3.14159"
+
+ [tool.setuptools]
+ package-dir = {"myns.n.pkgA" = "src"}
+ """
+ ),
+ "src": {"__init__.py": "a = 1"},
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ pkg_A = tmp_path / "pkgA"
+ pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
+ pkg_C = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgC')
+
+ # use pip to install to the target directory
+ opts = editable_opts[:]
+ opts.append("--no-build-isolation") # force current version of setuptools
+ venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
+ venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
+ venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])
+ venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"])
+
+ def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path):
+ """Sometimes users might specify an ``include`` pattern that ignores parent
+ packages. In a normal installation this would ignore all modules inside the
+ parent packages, and make them namespaces (reported in issue #3504),
+ so the editable mode should preserve this behaviour.
+ """
+ files = {
+ "pkgA": {
+ "pyproject.toml": dedent(
+ """\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "pkgA"
+ version = "3.14159"
+
+ [tool.setuptools]
+ packages.find.include = ["mypkg.*"]
+ """
+ ),
+ "mypkg": {
+ "__init__.py": "",
+ "other.py": "b = 1",
+ "n": {
+ "__init__.py": "",
+ "pkgA.py": "a = 1",
+ },
+ },
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ pkg_A = tmp_path / "pkgA"
+
+ # use pip to install to the target directory
+ opts = ["--no-build-isolation"] # force current version of setuptools
+ venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts])
+ out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"])
+ assert out.strip() == "1"
+ cmd = """\
+ try:
+ import mypkg.other
+ except ImportError:
+ print("mypkg.other not defined")
+ """
+ out = venv.run(["python", "-c", dedent(cmd)])
+ assert "mypkg.other not defined" in out
+
+
+def test_editable_with_prefix(tmp_path, sample_project, editable_opts):
+ """
+ Editable install to a prefix should be discoverable.
+ """
+ prefix = tmp_path / 'prefix'
+
+ # figure out where pip will likely install the package
+ site_packages_all = [
+ prefix / Path(path).relative_to(sys.prefix)
+ for path in sys.path
+ if 'site-packages' in path and path.startswith(sys.prefix)
+ ]
+
+ for sp in site_packages_all:
+ sp.mkdir(parents=True)
+
+ # install workaround
+ _addsitedirs(site_packages_all)
+
+ env = dict(os.environ, PYTHONPATH=os.pathsep.join(map(str, site_packages_all)))
+ cmd = [
+ sys.executable,
+ '-m',
+ 'pip',
+ 'install',
+ '--editable',
+ str(sample_project),
+ '--prefix',
+ str(prefix),
+ '--no-build-isolation',
+ *editable_opts,
+ ]
+ subprocess.check_call(cmd, env=env)
+
+ # now run 'sample' with the prefix on the PYTHONPATH
+ bin = 'Scripts' if platform.system() == 'Windows' else 'bin'
+ exe = prefix / bin / 'sample'
+ subprocess.check_call([exe], env=env)
+
+
+class TestFinderTemplate:
+ """This test focus in getting a particular implementation detail right.
+ If at some point in time the implementation is changed for something different,
+ this test can be modified or even excluded.
+ """
+
+ def install_finder(self, finder):
+ loc = {}
+ exec(finder, loc, loc)
+ loc["install"]()
+
+ def test_packages(self, tmp_path):
+ files = {
+ "src1": {
+ "pkg1": {
+ "__init__.py": "",
+ "subpkg": {"mod1.py": "a = 42"},
+ },
+ },
+ "src2": {"mod2.py": "a = 43"},
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {
+ "pkg1": str(tmp_path / "src1/pkg1"),
+ "mod2": str(tmp_path / "src2/mod2"),
+ }
+ template = _finder_template(str(uuid4()), mapping, {})
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in ("pkg1", "pkg1.subpkg", "pkg1.subpkg.mod1", "mod2"):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+ mod1 = import_module("pkg1.subpkg.mod1")
+ mod2 = import_module("mod2")
+ subpkg = import_module("pkg1.subpkg")
+
+ assert mod1.a == 42
+ assert mod2.a == 43
+ expected = str((tmp_path / "src1/pkg1/subpkg").resolve())
+ assert_path(subpkg, expected)
+
+ def test_namespace(self, tmp_path):
+ files = {"pkg": {"__init__.py": "a = 13", "text.txt": "abc"}}
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {"ns.othername": str(tmp_path / "pkg")}
+ namespaces = {"ns": []}
+
+ template = _finder_template(str(uuid4()), mapping, namespaces)
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in ("ns", "ns.othername"):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+ pkg = import_module("ns.othername")
+ text = importlib_resources.files(pkg) / "text.txt"
+
+ expected = str((tmp_path / "pkg").resolve())
+ assert_path(pkg, expected)
+ assert pkg.a == 13
+
+ # Make sure resources can also be found
+ assert text.read_text(encoding="utf-8") == "abc"
+
+ def test_combine_namespaces(self, tmp_path):
+ files = {
+ "src1": {"ns": {"pkg1": {"__init__.py": "a = 13"}}},
+ "src2": {"ns": {"mod2.py": "b = 37"}},
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {
+ "ns.pkgA": str(tmp_path / "src1/ns/pkg1"),
+ "ns": str(tmp_path / "src2/ns"),
+ }
+ namespaces_ = {"ns": [str(tmp_path / "src1"), str(tmp_path / "src2")]}
+ template = _finder_template(str(uuid4()), mapping, namespaces_)
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in ("ns", "ns.pkgA", "ns.mod2"):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+ pkgA = import_module("ns.pkgA")
+ mod2 = import_module("ns.mod2")
+
+ expected = str((tmp_path / "src1/ns/pkg1").resolve())
+ assert_path(pkgA, expected)
+ assert pkgA.a == 13
+ assert mod2.b == 37
+
+ def test_combine_namespaces_nested(self, tmp_path):
+ """
+ Users may attempt to combine namespace packages in a nested way via
+ ``package_dir`` as shown in pypa/setuptools#4248.
+ """
+
+ files = {
+ "src": {"my_package": {"my_module.py": "a = 13"}},
+ "src2": {"my_package2": {"my_module2.py": "b = 37"}},
+ }
+
+ stack = jaraco.path.DirectoryStack()
+ with stack.context(tmp_path):
+ jaraco.path.build(files)
+ attrs = {
+ "script_name": "%PEP 517%",
+ "package_dir": {
+ "different_name": "src/my_package",
+ "different_name.subpkg": "src2/my_package2",
+ },
+ "packages": ["different_name", "different_name.subpkg"],
+ }
+ dist = Distribution(attrs)
+ finder = _TopLevelFinder(dist, str(uuid4()))
+ code = next(v for k, v in finder.get_implementation() if k.endswith(".py"))
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in attrs["packages"]:
+ sys.modules.pop(mod, None)
+
+ self.install_finder(code)
+ mod1 = import_module("different_name.my_module")
+ mod2 = import_module("different_name.subpkg.my_module2")
+
+ expected = str((tmp_path / "src/my_package/my_module.py").resolve())
+ assert str(Path(mod1.__file__).resolve()) == expected
+
+ expected = str((tmp_path / "src2/my_package2/my_module2.py").resolve())
+ assert str(Path(mod2.__file__).resolve()) == expected
+
+ assert mod1.a == 13
+ assert mod2.b == 37
+
+ def test_dynamic_path_computation(self, tmp_path):
+ # Follows the example in PEP 420
+ files = {
+ "project1": {"parent": {"child": {"one.py": "x = 1"}}},
+ "project2": {"parent": {"child": {"two.py": "x = 2"}}},
+ "project3": {"parent": {"child": {"three.py": "x = 3"}}},
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ mapping = {}
+ namespaces_ = {"parent": [str(tmp_path / "project1/parent")]}
+ template = _finder_template(str(uuid4()), mapping, namespaces_)
+
+ mods = (f"parent.child.{name}" for name in ("one", "two", "three"))
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in ("parent", "parent.child", "parent.child", *mods):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+
+ one = import_module("parent.child.one")
+ assert one.x == 1
+
+ with pytest.raises(ImportError):
+ import_module("parent.child.two")
+
+ sys.path.append(str(tmp_path / "project2"))
+ two = import_module("parent.child.two")
+ assert two.x == 2
+
+ with pytest.raises(ImportError):
+ import_module("parent.child.three")
+
+ sys.path.append(str(tmp_path / "project3"))
+ three = import_module("parent.child.three")
+ assert three.x == 3
+
+ def test_no_recursion(self, tmp_path):
+ # See issue #3550
+ files = {
+ "pkg": {
+ "__init__.py": "from . import pkg",
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {
+ "pkg": str(tmp_path / "pkg"),
+ }
+ template = _finder_template(str(uuid4()), mapping, {})
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ sys.modules.pop("pkg", None)
+
+ self.install_finder(template)
+ with pytest.raises(ImportError, match="pkg"):
+ import_module("pkg")
+
+ def test_similar_name(self, tmp_path):
+ files = {
+ "foo": {
+ "__init__.py": "",
+ "bar": {
+ "__init__.py": "",
+ },
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {
+ "foo": str(tmp_path / "foo"),
+ }
+ template = _finder_template(str(uuid4()), mapping, {})
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ sys.modules.pop("foo", None)
+ sys.modules.pop("foo.bar", None)
+
+ self.install_finder(template)
+ with pytest.raises(ImportError, match="foobar"):
+ import_module("foobar")
+
+ def test_case_sensitivity(self, tmp_path):
+ files = {
+ "foo": {
+ "__init__.py": "",
+ "lowercase.py": "x = 1",
+ "bar": {
+ "__init__.py": "",
+ "lowercase.py": "x = 2",
+ },
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ mapping = {
+ "foo": str(tmp_path / "foo"),
+ }
+ template = _finder_template(str(uuid4()), mapping, {})
+ with contexts.save_paths(), contexts.save_sys_modules():
+ sys.modules.pop("foo", None)
+
+ self.install_finder(template)
+ with pytest.raises(ImportError, match="'FOO'"):
+ import_module("FOO")
+
+ with pytest.raises(ImportError, match="'foo\\.LOWERCASE'"):
+ import_module("foo.LOWERCASE")
+
+ with pytest.raises(ImportError, match="'foo\\.bar\\.Lowercase'"):
+ import_module("foo.bar.Lowercase")
+
+ with pytest.raises(ImportError, match="'foo\\.BAR'"):
+ import_module("foo.BAR.lowercase")
+
+ with pytest.raises(ImportError, match="'FOO'"):
+ import_module("FOO.bar.lowercase")
+
+ mod = import_module("foo.lowercase")
+ assert mod.x == 1
+
+ mod = import_module("foo.bar.lowercase")
+ assert mod.x == 2
+
+ def test_namespace_case_sensitivity(self, tmp_path):
+ files = {
+ "pkg": {
+ "__init__.py": "a = 13",
+ "foo": {
+ "__init__.py": "b = 37",
+ "bar.py": "c = 42",
+ },
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {"ns.othername": str(tmp_path / "pkg")}
+ namespaces = {"ns": []}
+
+ template = _finder_template(str(uuid4()), mapping, namespaces)
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in ("ns", "ns.othername"):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+ pkg = import_module("ns.othername")
+ expected = str((tmp_path / "pkg").resolve())
+ assert_path(pkg, expected)
+ assert pkg.a == 13
+
+ foo = import_module("ns.othername.foo")
+ assert foo.b == 37
+
+ bar = import_module("ns.othername.foo.bar")
+ assert bar.c == 42
+
+ with pytest.raises(ImportError, match="'NS'"):
+ import_module("NS.othername.foo")
+
+ with pytest.raises(ImportError, match="'ns\\.othername\\.FOO\\'"):
+ import_module("ns.othername.FOO")
+
+ with pytest.raises(ImportError, match="'ns\\.othername\\.foo\\.BAR\\'"):
+ import_module("ns.othername.foo.BAR")
+
+ def test_intermediate_packages(self, tmp_path):
+ """
+ The finder should not import ``fullname`` if the intermediate segments
+ don't exist (see pypa/setuptools#4019).
+ """
+ files = {
+ "src": {
+ "mypkg": {
+ "__init__.py": "",
+ "config.py": "a = 13",
+ "helloworld.py": "b = 13",
+ "components": {
+ "config.py": "a = 37",
+ },
+ },
+ }
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+
+ mapping = {"mypkg": str(tmp_path / "src/mypkg")}
+ template = _finder_template(str(uuid4()), mapping, {})
+
+ with contexts.save_paths(), contexts.save_sys_modules():
+ for mod in (
+ "mypkg",
+ "mypkg.config",
+ "mypkg.helloworld",
+ "mypkg.components",
+ "mypkg.components.config",
+ "mypkg.components.helloworld",
+ ):
+ sys.modules.pop(mod, None)
+
+ self.install_finder(template)
+
+ config = import_module("mypkg.components.config")
+ assert config.a == 37
+
+ helloworld = import_module("mypkg.helloworld")
+ assert helloworld.b == 13
+
+ with pytest.raises(ImportError):
+ import_module("mypkg.components.helloworld")
+
+
+def test_pkg_roots(tmp_path):
+ """This test focus in getting a particular implementation detail right.
+ If at some point in time the implementation is changed for something different,
+ this test can be modified or even excluded.
+ """
+ files = {
+ "a": {"b": {"__init__.py": "ab = 1"}, "__init__.py": "a = 1"},
+ "d": {"__init__.py": "d = 1", "e": {"__init__.py": "de = 1"}},
+ "f": {"g": {"h": {"__init__.py": "fgh = 1"}}},
+ "other": {"__init__.py": "abc = 1"},
+ "another": {"__init__.py": "abcxyz = 1"},
+ "yet_another": {"__init__.py": "mnopq = 1"},
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ package_dir = {
+ "a.b.c": "other",
+ "a.b.c.x.y.z": "another",
+ "m.n.o.p.q": "yet_another",
+ }
+ packages = [
+ "a",
+ "a.b",
+ "a.b.c",
+ "a.b.c.x.y",
+ "a.b.c.x.y.z",
+ "d",
+ "d.e",
+ "f",
+ "f.g",
+ "f.g.h",
+ "m.n.o.p.q",
+ ]
+ roots = _find_package_roots(packages, package_dir, tmp_path)
+ assert roots == {
+ "a": str(tmp_path / "a"),
+ "a.b.c": str(tmp_path / "other"),
+ "a.b.c.x.y.z": str(tmp_path / "another"),
+ "d": str(tmp_path / "d"),
+ "f": str(tmp_path / "f"),
+ "m.n.o.p.q": str(tmp_path / "yet_another"),
+ }
+
+ ns = set(dict(_find_namespaces(packages, roots)))
+ assert ns == {"f", "f.g"}
+
+ ns = set(_find_virtual_namespaces(roots))
+ assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}
+
+
+class TestOverallBehaviour:
+ PYPROJECT = """\
+ [build-system]
+ requires = ["setuptools"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "mypkg"
+ version = "3.14159"
+ """
+
+ # Any: Would need a TypedDict. Keep it simple for tests
+ FLAT_LAYOUT: dict[str, Any] = {
+ "pyproject.toml": dedent(PYPROJECT),
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ "otherfile.py": "",
+ "mypkg": {
+ "__init__.py": "",
+ "mod1.py": "var = 42",
+ "subpackage": {
+ "__init__.py": "",
+ "mod2.py": "var = 13",
+ "resource_file.txt": "resource 39",
+ },
+ },
+ }
+
+ EXAMPLES = {
+ "flat-layout": FLAT_LAYOUT,
+ "src-layout": {
+ "pyproject.toml": dedent(PYPROJECT),
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ "otherfile.py": "",
+ "src": {"mypkg": FLAT_LAYOUT["mypkg"]},
+ },
+ "custom-layout": {
+ "pyproject.toml": dedent(PYPROJECT)
+ + dedent(
+ """\
+ [tool.setuptools]
+ packages = ["mypkg", "mypkg.subpackage"]
+
+ [tool.setuptools.package-dir]
+ "mypkg.subpackage" = "other"
+ """
+ ),
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ "otherfile.py": "",
+ "mypkg": {
+ "__init__.py": "",
+ "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
+ },
+ "other": FLAT_LAYOUT["mypkg"]["subpackage"],
+ },
+ "namespace": {
+ "pyproject.toml": dedent(PYPROJECT),
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ "otherfile.py": "",
+ "src": {
+ "mypkg": {
+ "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
+ "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"],
+ },
+ },
+ },
+ }
+
+ @pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
+ @pytest.mark.parametrize("layout", EXAMPLES.keys())
+ def test_editable_install(self, tmp_path, venv, layout, editable_opts):
+ project, _ = install_project(
+ "mypkg", venv, tmp_path, self.EXAMPLES[layout], *editable_opts
+ )
+
+ # Ensure stray files are not importable
+ cmd_import_error = """\
+ try:
+ import otherfile
+ except ImportError as ex:
+ print(ex)
+ """
+ out = venv.run(["python", "-c", dedent(cmd_import_error)])
+ assert "No module named 'otherfile'" in out
+
+ # Ensure the modules are importable
+ cmd_get_vars = """\
+ import mypkg, mypkg.mod1, mypkg.subpackage.mod2
+ print(mypkg.mod1.var, mypkg.subpackage.mod2.var)
+ """
+ out = venv.run(["python", "-c", dedent(cmd_get_vars)])
+ assert "42 13" in out
+
+ # Ensure resources are reachable
+ cmd_get_resource = """\
+ import mypkg.subpackage
+ from setuptools._importlib import resources as importlib_resources
+ text = importlib_resources.files(mypkg.subpackage) / "resource_file.txt"
+ print(text.read_text(encoding="utf-8"))
+ """
+ out = venv.run(["python", "-c", dedent(cmd_get_resource)])
+ assert "resource 39" in out
+
+ # Ensure files are editable
+ mod1 = next(project.glob("**/mod1.py"))
+ mod2 = next(project.glob("**/mod2.py"))
+ resource_file = next(project.glob("**/resource_file.txt"))
+
+ mod1.write_text("var = 17", encoding="utf-8")
+ mod2.write_text("var = 781", encoding="utf-8")
+ resource_file.write_text("resource 374", encoding="utf-8")
+
+ out = venv.run(["python", "-c", dedent(cmd_get_vars)])
+ assert "42 13" not in out
+ assert "17 781" in out
+
+ out = venv.run(["python", "-c", dedent(cmd_get_resource)])
+ assert "resource 39" not in out
+ assert "resource 374" in out
+
+
+class TestLinkTree:
+ FILES = deepcopy(TestOverallBehaviour.EXAMPLES["src-layout"])
+ FILES["pyproject.toml"] += dedent(
+ """\
+ [tool.setuptools]
+ # Temporary workaround: both `include-package-data` and `package-data` configs
+ # can be removed after #3260 is fixed.
+ include-package-data = false
+ package-data = {"*" = ["*.txt"]}
+
+ [tool.setuptools.packages.find]
+ where = ["src"]
+ exclude = ["*.subpackage*"]
+ """
+ )
+ FILES["src"]["mypkg"]["resource.not_in_manifest"] = "abc"
+
+ def test_generated_tree(self, tmp_path):
+ jaraco.path.build(self.FILES, prefix=tmp_path)
+
+ with _Path(tmp_path):
+ name = "mypkg-3.14159"
+ dist = Distribution({"script_name": "%PEP 517%"})
+ dist.parse_config_files()
+
+ wheel = Mock()
+ aux = tmp_path / ".aux"
+ build = tmp_path / ".build"
+ aux.mkdir()
+ build.mkdir()
+
+ build_py = dist.get_command_obj("build_py")
+ build_py.editable_mode = True
+ build_py.build_lib = str(build)
+ build_py.ensure_finalized()
+ outputs = build_py.get_outputs()
+ output_mapping = build_py.get_output_mapping()
+
+ make_tree = _LinkTree(dist, name, aux, build)
+ make_tree(wheel, outputs, output_mapping)
+
+ mod1 = next(aux.glob("**/mod1.py"))
+ expected = tmp_path / "src/mypkg/mod1.py"
+ assert_link_to(mod1, expected)
+
+ assert next(aux.glob("**/subpackage"), None) is None
+ assert next(aux.glob("**/mod2.py"), None) is None
+ assert next(aux.glob("**/resource_file.txt"), None) is None
+
+ assert next(aux.glob("**/resource.not_in_manifest"), None) is None
+
+ def test_strict_install(self, tmp_path, venv):
+ opts = ["--config-settings", "editable-mode=strict"]
+ install_project("mypkg", venv, tmp_path, self.FILES, *opts)
+
+ out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
+ assert "42" in out
+
+ # Ensure packages excluded from distribution are not importable
+ cmd_import_error = """\
+ try:
+ from mypkg import subpackage
+ except ImportError as ex:
+ print(ex)
+ """
+ out = venv.run(["python", "-c", dedent(cmd_import_error)])
+ assert "cannot import name 'subpackage'" in out
+
+ # Ensure resource files excluded from distribution are not reachable
+ cmd_get_resource = """\
+ import mypkg
+ from setuptools._importlib import resources as importlib_resources
+ try:
+ text = importlib_resources.files(mypkg) / "resource.not_in_manifest"
+ print(text.read_text(encoding="utf-8"))
+ except FileNotFoundError as ex:
+ print(ex)
+ """
+ out = venv.run(["python", "-c", dedent(cmd_get_resource)])
+ assert "No such file or directory" in out
+ assert "resource.not_in_manifest" in out
+
+
+@pytest.mark.filterwarnings("ignore:.*compat.*:setuptools.SetuptoolsDeprecationWarning")
+def test_compat_install(tmp_path, venv):
+ # TODO: Remove `compat` after Dec/2022.
+ opts = ["--config-settings", "editable-mode=compat"]
+ files = TestOverallBehaviour.EXAMPLES["custom-layout"]
+ install_project("mypkg", venv, tmp_path, files, *opts)
+
+ out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
+ assert "42" in out
+
+ expected_path = comparable_path(str(tmp_path))
+
+ # Compatible behaviour will make spurious modules and excluded
+ # files importable directly from the original path
+ for cmd in (
+ "import otherfile; print(otherfile)",
+ "import other; print(other)",
+ "import mypkg; print(mypkg)",
+ ):
+ out = comparable_path(venv.run(["python", "-c", cmd]))
+ assert expected_path in out
+
+ # Compatible behaviour will not consider custom mappings
+ cmd = """\
+ try:
+ from mypkg import subpackage;
+ except ImportError as ex:
+ print(ex)
+ """
+ out = venv.run(["python", "-c", dedent(cmd)])
+ assert "cannot import name 'subpackage'" in out
+
+
+@pytest.mark.uses_network
+def test_pbr_integration(pbr_package, venv, editable_opts):
+ """Ensure editable installs work with pbr, issue #3500"""
+ cmd = [
+ 'python',
+ '-m',
+ 'pip',
+ '-v',
+ 'install',
+ '--editable',
+ pbr_package,
+ *editable_opts,
+ ]
+ venv.run(cmd, stderr=subprocess.STDOUT)
+ out = venv.run(["python", "-c", "import mypkg.hello"])
+ assert "Hello world!" in out
+
+
+class TestCustomBuildPy:
+ """
+ Issue #3501 indicates that some plugins/customizations might rely on:
+
+ 1. ``build_py`` not running
+ 2. ``build_py`` always copying files to ``build_lib``
+
+ During the transition period setuptools should prevent potential errors from
+ happening due to those assumptions.
+ """
+
+ # TODO: Remove tests after _run_build_steps is removed.
+
+ FILES = {
+ **TestOverallBehaviour.EXAMPLES["flat-layout"],
+ "setup.py": dedent(
+ """\
+ import pathlib
+ from setuptools import setup
+ from setuptools.command.build_py import build_py as orig
+
+ class my_build_py(orig):
+ def run(self):
+ super().run()
+ raise ValueError("TEST_RAISE")
+
+ setup(cmdclass={"build_py": my_build_py})
+ """
+ ),
+ }
+
+ def test_safeguarded_from_errors(self, tmp_path, venv):
+ """Ensure that errors in custom build_py are reported as warnings"""
+ # Warnings should show up
+ _, out = install_project("mypkg", venv, tmp_path, self.FILES)
+ assert "SetuptoolsDeprecationWarning" in out
+ assert "ValueError: TEST_RAISE" in out
+ # but installation should be successful
+ out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
+ assert "42" in out
+
+
+class TestCustomBuildWheel:
+ def install_custom_build_wheel(self, dist):
+ bdist_wheel_cls = dist.get_command_class("bdist_wheel")
+
+ class MyBdistWheel(bdist_wheel_cls):
+ def get_tag(self):
+ # In issue #3513, we can see that some extensions may try to access
+ # the `plat_name` property in bdist_wheel
+ if self.plat_name.startswith("macosx-"):
+ _ = "macOS platform"
+ return super().get_tag()
+
+ dist.cmdclass["bdist_wheel"] = MyBdistWheel
+
+ def test_access_plat_name(self, tmpdir_cwd):
+ # Even when a custom bdist_wheel tries to access plat_name the build should
+ # be successful
+ jaraco.path.build({"module.py": "x = 42"})
+ dist = Distribution()
+ dist.script_name = "setup.py"
+ dist.set_defaults()
+ self.install_custom_build_wheel(dist)
+ cmd = editable_wheel(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ wheel_file = str(next(Path().glob('dist/*.whl')))
+ assert "editable" in wheel_file
+
+
+class TestCustomBuildExt:
+ def install_custom_build_ext_distutils(self, dist):
+ from distutils.command.build_ext import build_ext as build_ext_cls
+
+ class MyBuildExt(build_ext_cls):
+ pass
+
+ dist.cmdclass["build_ext"] = MyBuildExt
+
+ @pytest.mark.skipif(
+ sys.platform != "linux", reason="compilers may fail without correct setup"
+ )
+ def test_distutils_leave_inplace_files(self, tmpdir_cwd):
+ jaraco.path.build({"module.c": ""})
+ attrs = {
+ "ext_modules": [Extension("module", ["module.c"])],
+ }
+ dist = Distribution(attrs)
+ dist.script_name = "setup.py"
+ dist.set_defaults()
+ self.install_custom_build_ext_distutils(dist)
+ cmd = editable_wheel(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ wheel_file = str(next(Path().glob('dist/*.whl')))
+ assert "editable" in wheel_file
+ files = [p for p in Path().glob("module.*") if p.suffix != ".c"]
+ assert len(files) == 1
+ name = files[0].name
+ assert any(name.endswith(ext) for ext in EXTENSION_SUFFIXES)
+
+
+def test_debugging_tips(tmpdir_cwd, monkeypatch):
+ """Make sure to display useful debugging tips to the user."""
+ jaraco.path.build({"module.py": "x = 42"})
+ dist = Distribution()
+ dist.script_name = "setup.py"
+ dist.set_defaults()
+ cmd = editable_wheel(dist)
+ cmd.ensure_finalized()
+
+ SimulatedErr = type("SimulatedErr", (Exception,), {})
+ simulated_failure = Mock(side_effect=SimulatedErr())
+ monkeypatch.setattr(cmd, "get_finalized_command", simulated_failure)
+
+ with pytest.raises(SimulatedErr) as ctx:
+ cmd.run()
+ assert any('debugging-tips' in note for note in ctx.value.__notes__)
+
+
+@pytest.mark.filterwarnings("error")
+def test_encode_pth():
+ """Ensure _encode_pth function does not produce encoding warnings"""
+ content = _encode_pth("tkmilan_ç_utf8") # no warnings (would be turned into errors)
+ assert isinstance(content, bytes)
+
+
+def install_project(name, venv, tmp_path, files, *opts):
+ project = tmp_path / name
+ project.mkdir()
+ jaraco.path.build(files, prefix=project)
+ opts = [*opts, "--no-build-isolation"] # force current version of setuptools
+ out = venv.run(
+ ["python", "-m", "pip", "-v", "install", "-e", str(project), *opts],
+ stderr=subprocess.STDOUT,
+ )
+ return project, out
+
+
+def _addsitedirs(new_dirs):
+ """To use this function, it is necessary to insert new_dir in front of sys.path.
+ The Python process will try to import a ``sitecustomize`` module on startup.
+ If we manipulate sys.path/PYTHONPATH, we can force it to run our code,
+ which invokes ``addsitedir`` and ensure ``.pth`` files are loaded.
+ """
+ content = '\n'.join(
+ ("import site",)
+ + tuple(f"site.addsitedir({os.fspath(new_dir)!r})" for new_dir in new_dirs)
+ )
+ (new_dirs[0] / "sitecustomize.py").write_text(content, encoding="utf-8")
+
+
+# ---- Assertion Helpers ----
+
+
+def assert_path(pkg, expected):
+ # __path__ is not guaranteed to exist, so we have to account for that
+ if pkg.__path__:
+ path = next(iter(pkg.__path__), None)
+ if path:
+ assert str(Path(path).resolve()) == expected
+
+
+def assert_link_to(file: Path, other: Path) -> None:
+ if file.is_symlink():
+ assert str(file.resolve()) == str(other.resolve())
+ else:
+ file_stat = file.stat()
+ other_stat = other.stat()
+ assert file_stat[stat.ST_INO] == other_stat[stat.ST_INO]
+ assert file_stat[stat.ST_DEV] == other_stat[stat.ST_DEV]
+
+
+def comparable_path(str_with_path: str) -> str:
+ return str_with_path.lower().replace(os.sep, "/").replace("//", "/")
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_egg_info.py b/lib/python3.12/site-packages/setuptools/tests/test_egg_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..3653be096f11b77c71f58679d6e4a108903668a5
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_egg_info.py
@@ -0,0 +1,1306 @@
+from __future__ import annotations
+
+import ast
+import glob
+import os
+import re
+import stat
+import sys
+import time
+from pathlib import Path
+from unittest import mock
+
+import pytest
+from jaraco import path
+
+from setuptools import errors
+from setuptools.command.egg_info import egg_info, manifest_maker, write_entries
+from setuptools.dist import Distribution
+
+from . import contexts, environment
+from .textwrap import DALS
+
+
+class Environment(str):
+ pass
+
+
+@pytest.fixture
+def env():
+ with contexts.tempdir(prefix='setuptools-test.') as env_dir:
+ env = Environment(env_dir)
+ os.chmod(env_dir, stat.S_IRWXU)
+ subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
+ env.paths = dict((dirname, os.path.join(env_dir, dirname)) for dirname in subs)
+ list(map(os.mkdir, env.paths.values()))
+ path.build({
+ env.paths['home']: {
+ '.pydistutils.cfg': DALS(
+ """
+ [egg_info]
+ egg-base = {egg-base}
+ """.format(**env.paths)
+ )
+ }
+ })
+ yield env
+
+
+class TestEggInfo:
+ setup_script = DALS(
+ """
+ from setuptools import setup
+
+ setup(
+ name='foo',
+ py_modules=['hello'],
+ entry_points={'console_scripts': ['hi = hello.run']},
+ zip_safe=False,
+ )
+ """
+ )
+
+ def _create_project(self):
+ path.build({
+ 'setup.py': self.setup_script,
+ 'hello.py': DALS(
+ """
+ def run():
+ print('hello')
+ """
+ ),
+ })
+
+ @staticmethod
+ def _extract_mv_version(pkg_info_lines: list[str]) -> tuple[int, int]:
+ version_str = pkg_info_lines[0].split(' ')[1]
+ major, minor = map(int, version_str.split('.')[:2])
+ return major, minor
+
+ def test_egg_info_save_version_info_setup_empty(self, tmpdir_cwd, env):
+ """
+ When the egg_info section is empty or not present, running
+ save_version_info should add the settings to the setup.cfg
+ in a deterministic order.
+ """
+ setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
+ dist = Distribution()
+ ei = egg_info(dist)
+ ei.initialize_options()
+ ei.save_version_info(setup_cfg)
+
+ with open(setup_cfg, 'r', encoding="utf-8") as f:
+ content = f.read()
+
+ assert '[egg_info]' in content
+ assert 'tag_build =' in content
+ assert 'tag_date = 0' in content
+
+ expected_order = (
+ 'tag_build',
+ 'tag_date',
+ )
+
+ self._validate_content_order(content, expected_order)
+
+ @staticmethod
+ def _validate_content_order(content, expected):
+ """
+ Assert that the strings in expected appear in content
+ in order.
+ """
+ pattern = '.*'.join(expected)
+ flags = re.MULTILINE | re.DOTALL
+ assert re.search(pattern, content, flags)
+
+ def test_egg_info_save_version_info_setup_defaults(self, tmpdir_cwd, env):
+ """
+ When running save_version_info on an existing setup.cfg
+ with the 'default' values present from a previous run,
+ the file should remain unchanged.
+ """
+ setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
+ path.build({
+ setup_cfg: DALS(
+ """
+ [egg_info]
+ tag_build =
+ tag_date = 0
+ """
+ ),
+ })
+ dist = Distribution()
+ ei = egg_info(dist)
+ ei.initialize_options()
+ ei.save_version_info(setup_cfg)
+
+ with open(setup_cfg, 'r', encoding="utf-8") as f:
+ content = f.read()
+
+ assert '[egg_info]' in content
+ assert 'tag_build =' in content
+ assert 'tag_date = 0' in content
+
+ expected_order = (
+ 'tag_build',
+ 'tag_date',
+ )
+
+ self._validate_content_order(content, expected_order)
+
+ def test_expected_files_produced(self, tmpdir_cwd, env):
+ self._create_project()
+
+ self._run_egg_info_command(tmpdir_cwd, env)
+ actual = os.listdir('foo.egg-info')
+
+ expected = [
+ 'PKG-INFO',
+ 'SOURCES.txt',
+ 'dependency_links.txt',
+ 'entry_points.txt',
+ 'not-zip-safe',
+ 'top_level.txt',
+ ]
+ assert sorted(actual) == expected
+
+ def test_handling_utime_error(self, tmpdir_cwd, env):
+ dist = Distribution()
+ ei = egg_info(dist)
+ utime_patch = mock.patch('os.utime', side_effect=OSError("TEST"))
+ mkpath_patch = mock.patch(
+ 'setuptools.command.egg_info.egg_info.mkpath', return_val=None
+ )
+
+ with utime_patch, mkpath_patch:
+ import distutils.errors
+
+ msg = r"Cannot update time stamp of directory 'None'"
+ with pytest.raises(distutils.errors.DistutilsFileError, match=msg):
+ ei.run()
+
+ def test_license_is_a_string(self, tmpdir_cwd, env):
+ setup_config = DALS(
+ """
+ [metadata]
+ name=foo
+ version=0.0.1
+ license=file:MIT
+ """
+ )
+
+ setup_script = DALS(
+ """
+ from setuptools import setup
+
+ setup()
+ """
+ )
+
+ path.build({
+ 'setup.py': setup_script,
+ 'setup.cfg': setup_config,
+ })
+
+ # This command should fail with a ValueError, but because it's
+ # currently configured to use a subprocess, the actual traceback
+ # object is lost and we need to parse it from stderr
+ with pytest.raises(AssertionError) as exc:
+ self._run_egg_info_command(tmpdir_cwd, env)
+
+ # The only argument to the assertion error should be a traceback
+ # containing a ValueError
+ assert 'ValueError' in exc.value.args[0]
+
+ def test_rebuilt(self, tmpdir_cwd, env):
+ """Ensure timestamps are updated when the command is re-run."""
+ self._create_project()
+
+ self._run_egg_info_command(tmpdir_cwd, env)
+ timestamp_a = os.path.getmtime('foo.egg-info')
+
+ # arbitrary sleep just to handle *really* fast systems
+ time.sleep(0.001)
+
+ self._run_egg_info_command(tmpdir_cwd, env)
+ timestamp_b = os.path.getmtime('foo.egg-info')
+
+ assert timestamp_a != timestamp_b
+
+ def test_manifest_template_is_read(self, tmpdir_cwd, env):
+ self._create_project()
+ path.build({
+ 'MANIFEST.in': DALS(
+ """
+ recursive-include docs *.rst
+ """
+ ),
+ 'docs': {
+ 'usage.rst': "Run 'hi'",
+ },
+ })
+ self._run_egg_info_command(tmpdir_cwd, env)
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt')
+ with open(sources_txt, encoding="utf-8") as f:
+ assert 'docs/usage.rst' in f.read().split('\n')
+
+ def _setup_script_with_requires(self, requires, use_setup_cfg=False):
+ setup_script = DALS(
+ """
+ from setuptools import setup
+
+ setup(name='foo', zip_safe=False, %s)
+ """
+ ) % ('' if use_setup_cfg else requires)
+ setup_config = requires if use_setup_cfg else ''
+ path.build({
+ 'setup.py': setup_script,
+ 'setup.cfg': setup_config,
+ })
+
+ mismatch_marker = f"python_version<'{sys.version_info[0]}'"
+ # Alternate equivalent syntax.
+ mismatch_marker_alternate = f'python_version < "{sys.version_info[0]}"'
+ invalid_marker = "<=>++"
+
+ class RequiresTestHelper:
+ @staticmethod
+ def parametrize(*test_list, **format_dict):
+ idlist = []
+ argvalues = []
+ for test in test_list:
+ test_params = test.lstrip().split('\n\n', 3)
+ name_kwargs = test_params.pop(0).split('\n')
+ if len(name_kwargs) > 1:
+ val = name_kwargs[1].strip()
+ install_cmd_kwargs = ast.literal_eval(val)
+ else:
+ install_cmd_kwargs = {}
+ name = name_kwargs[0].strip()
+ setup_py_requires, setup_cfg_requires, expected_requires = [
+ DALS(a).format(**format_dict) for a in test_params
+ ]
+ for id_, requires, use_cfg in (
+ (name, setup_py_requires, False),
+ (name + '_in_setup_cfg', setup_cfg_requires, True),
+ ):
+ idlist.append(id_)
+ marks = ()
+ if requires.startswith('@xfail\n'):
+ requires = requires[7:]
+ marks = pytest.mark.xfail
+ argvalues.append(
+ pytest.param(
+ requires,
+ use_cfg,
+ expected_requires,
+ install_cmd_kwargs,
+ marks=marks,
+ )
+ )
+ return pytest.mark.parametrize(
+ (
+ "requires",
+ "use_setup_cfg",
+ "expected_requires",
+ "install_cmd_kwargs",
+ ),
+ argvalues,
+ ids=idlist,
+ )
+
+ @RequiresTestHelper.parametrize(
+ # Format of a test:
+ #
+ # id
+ # install_cmd_kwargs [optional]
+ #
+ # requires block (when used in setup.py)
+ #
+ # requires block (when used in setup.cfg)
+ #
+ # expected contents of requires.txt
+ """
+ install_requires_deterministic
+
+ install_requires=["wheel>=0.5", "pytest"]
+
+ [options]
+ install_requires =
+ wheel>=0.5
+ pytest
+
+ wheel>=0.5
+ pytest
+ """,
+ """
+ install_requires_ordered
+
+ install_requires=["pytest>=3.0.2,!=10.9999"]
+
+ [options]
+ install_requires =
+ pytest>=3.0.2,!=10.9999
+
+ pytest!=10.9999,>=3.0.2
+ """,
+ """
+ install_requires_with_marker
+
+ install_requires=["barbazquux;{mismatch_marker}"],
+
+ [options]
+ install_requires =
+ barbazquux; {mismatch_marker}
+
+ [:{mismatch_marker_alternate}]
+ barbazquux
+ """,
+ """
+ install_requires_with_extra
+ {'cmd': ['egg_info']}
+
+ install_requires=["barbazquux [test]"],
+
+ [options]
+ install_requires =
+ barbazquux [test]
+
+ barbazquux[test]
+ """,
+ """
+ install_requires_with_extra_and_marker
+
+ install_requires=["barbazquux [test]; {mismatch_marker}"],
+
+ [options]
+ install_requires =
+ barbazquux [test]; {mismatch_marker}
+
+ [:{mismatch_marker_alternate}]
+ barbazquux[test]
+ """,
+ """
+ setup_requires_with_markers
+
+ setup_requires=["barbazquux;{mismatch_marker}"],
+
+ [options]
+ setup_requires =
+ barbazquux; {mismatch_marker}
+
+ """,
+ """
+ extras_require_with_extra
+ {'cmd': ['egg_info']}
+
+ extras_require={{"extra": ["barbazquux [test]"]}},
+
+ [options.extras_require]
+ extra = barbazquux [test]
+
+ [extra]
+ barbazquux[test]
+ """,
+ """
+ extras_require_with_extra_and_marker_in_req
+
+ extras_require={{"extra": ["barbazquux [test]; {mismatch_marker}"]}},
+
+ [options.extras_require]
+ extra =
+ barbazquux [test]; {mismatch_marker}
+
+ [extra]
+
+ [extra:{mismatch_marker_alternate}]
+ barbazquux[test]
+ """,
+ # FIXME: ConfigParser does not allow : in key names!
+ """
+ extras_require_with_marker
+
+ extras_require={{":{mismatch_marker}": ["barbazquux"]}},
+
+ @xfail
+ [options.extras_require]
+ :{mismatch_marker} = barbazquux
+
+ [:{mismatch_marker}]
+ barbazquux
+ """,
+ """
+ extras_require_with_marker_in_req
+
+ extras_require={{"extra": ["barbazquux; {mismatch_marker}"]}},
+
+ [options.extras_require]
+ extra =
+ barbazquux; {mismatch_marker}
+
+ [extra]
+
+ [extra:{mismatch_marker_alternate}]
+ barbazquux
+ """,
+ """
+ extras_require_with_empty_section
+
+ extras_require={{"empty": []}},
+
+ [options.extras_require]
+ empty =
+
+ [empty]
+ """,
+ # Format arguments.
+ invalid_marker=invalid_marker,
+ mismatch_marker=mismatch_marker,
+ mismatch_marker_alternate=mismatch_marker_alternate,
+ )
+ def test_requires(
+ self,
+ tmpdir_cwd,
+ env,
+ requires,
+ use_setup_cfg,
+ expected_requires,
+ install_cmd_kwargs,
+ ):
+ self._setup_script_with_requires(requires, use_setup_cfg)
+ self._run_egg_info_command(tmpdir_cwd, env, **install_cmd_kwargs)
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ requires_txt = os.path.join(egg_info_dir, 'requires.txt')
+ if os.path.exists(requires_txt):
+ with open(requires_txt, encoding="utf-8") as fp:
+ install_requires = fp.read()
+ else:
+ install_requires = ''
+ assert install_requires.lstrip() == expected_requires
+ assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+
+ def test_install_requires_unordered_disallowed(self, tmpdir_cwd, env):
+ """
+ Packages that pass unordered install_requires sequences
+ should be rejected as they produce non-deterministic
+ builds. See #458.
+ """
+ req = 'install_requires={"fake-factory==0.5.2", "pytz"}'
+ self._setup_script_with_requires(req)
+ with pytest.raises(AssertionError):
+ self._run_egg_info_command(tmpdir_cwd, env)
+
+ def test_extras_require_with_invalid_marker(self, tmpdir_cwd, env):
+ tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
+ req = tmpl.format(marker=self.invalid_marker)
+ self._setup_script_with_requires(req)
+ with pytest.raises(AssertionError):
+ self._run_egg_info_command(tmpdir_cwd, env)
+ assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+
+ def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
+ tmpl = 'extras_require={{"extra": ["barbazquux; {marker}"]}},'
+ req = tmpl.format(marker=self.invalid_marker)
+ self._setup_script_with_requires(req)
+ with pytest.raises(AssertionError):
+ self._run_egg_info_command(tmpdir_cwd, env)
+ assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+
+ def test_provides_extra(self, tmpdir_cwd, env):
+ self._setup_script_with_requires('extras_require={"foobar": ["barbazquux"]},')
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ assert 'Provides-Extra: foobar' in pkg_info_lines
+ assert 'Metadata-Version: 2.4' in pkg_info_lines
+
+ def test_doesnt_provides_extra(self, tmpdir_cwd, env):
+ self._setup_script_with_requires(
+ """install_requires=["spam ; python_version<'3.6'"]"""
+ )
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_text = fp.read()
+ assert 'Provides-Extra:' not in pkg_info_text
+
+ @pytest.mark.parametrize(
+ ('files', 'license_in_sources'),
+ [
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE
+ """
+ ),
+ 'LICENSE': "Test license",
+ },
+ True,
+ ), # with license
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = INVALID_LICENSE
+ """
+ ),
+ 'LICENSE': "Test license",
+ },
+ False,
+ ), # with an invalid license
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ """
+ ),
+ 'LICENSE': "Test license",
+ },
+ True,
+ ), # no license_file attribute, LICENSE auto-included
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE
+ """
+ ),
+ 'MANIFEST.in': "exclude LICENSE",
+ 'LICENSE': "Test license",
+ },
+ True,
+ ), # manifest is overwritten by license_file
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICEN[CS]E*
+ """
+ ),
+ 'LICENSE': "Test license",
+ },
+ True,
+ id="glob_pattern",
+ ),
+ ],
+ )
+ def test_setup_cfg_license_file(self, tmpdir_cwd, env, files, license_in_sources):
+ self._create_project()
+ path.build(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
+
+ if license_in_sources:
+ assert 'LICENSE' in sources_text
+ else:
+ assert 'LICENSE' not in sources_text
+ # for invalid license test
+ assert 'INVALID_LICENSE' not in sources_text
+
+ @pytest.mark.parametrize(
+ ('files', 'incl_licenses', 'excl_licenses'),
+ [
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ [],
+ ), # with licenses
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files = LICENSE-ABC, LICENSE-XYZ
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ [],
+ ), # with commas
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ ['LICENSE-ABC'],
+ ['LICENSE-XYZ'],
+ ), # with one license
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ [],
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ ), # empty
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files = LICENSE-XYZ
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ ['LICENSE-XYZ'],
+ ['LICENSE-ABC'],
+ ), # on same line
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ INVALID_LICENSE
+ """
+ ),
+ 'LICENSE-ABC': "Test license",
+ },
+ ['LICENSE-ABC'],
+ ['INVALID_LICENSE'],
+ ), # with an invalid license
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ """
+ ),
+ 'LICENSE': "Test license",
+ },
+ ['LICENSE'],
+ [],
+ ), # no license_files attribute, LICENSE auto-included
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files = LICENSE
+ """
+ ),
+ 'MANIFEST.in': "exclude LICENSE",
+ 'LICENSE': "Test license",
+ },
+ ['LICENSE'],
+ [],
+ ), # manifest is overwritten by license_files
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """
+ ),
+ 'MANIFEST.in': "exclude LICENSE-XYZ",
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ # manifest is overwritten by license_files
+ },
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ [],
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': "",
+ 'LICENSE-ABC': "ABC license",
+ 'COPYING-ABC': "ABC copying",
+ 'NOTICE-ABC': "ABC notice",
+ 'AUTHORS-ABC': "ABC authors",
+ 'LICENCE-XYZ': "XYZ license",
+ 'LICENSE': "License",
+ 'INVALID-LICENSE': "Invalid license",
+ },
+ [
+ 'LICENSE-ABC',
+ 'COPYING-ABC',
+ 'NOTICE-ABC',
+ 'AUTHORS-ABC',
+ 'LICENCE-XYZ',
+ 'LICENSE',
+ ],
+ ['INVALID-LICENSE'],
+ # ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
+ id="default_glob_patterns",
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE*
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-XYZ': "XYZ notice",
+ },
+ ['LICENSE-ABC'],
+ ['NOTICE-XYZ'],
+ id="no_default_glob_patterns",
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE*
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ },
+ ['LICENSE-ABC'],
+ [],
+ id="files_only_added_once",
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_files = **/LICENSE
+ """
+ ),
+ 'LICENSE': "ABC license",
+ 'LICENSE-OTHER': "Don't include",
+ 'vendor': {'LICENSE': "Vendor license"},
+ },
+ ['LICENSE', 'vendor/LICENSE'],
+ ['LICENSE-OTHER'],
+ id="recursive_glob",
+ ),
+ ],
+ )
+ def test_setup_cfg_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses
+ ):
+ self._create_project()
+ path.build(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
+ sources_lines = [line.strip() for line in sources_text.splitlines()]
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
+ @pytest.mark.parametrize(
+ ('files', 'incl_licenses', 'excl_licenses'),
+ [
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file =
+ license_files =
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ [],
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ ), # both empty
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license",
+ # license_file is still singular
+ },
+ [],
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ ),
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license",
+ },
+ ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
+ [],
+ ), # combined
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ LICENSE-PQR
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license",
+ # duplicate license
+ },
+ ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
+ [],
+ ),
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license",
+ # combined subset
+ },
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ ['LICENSE-PQR'],
+ ),
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """
+ ),
+ 'LICENSE-PQR': "Test license",
+ # with invalid licenses
+ },
+ ['LICENSE-PQR'],
+ ['LICENSE-ABC', 'LICENSE-XYZ'],
+ ),
+ (
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-PQR
+ LICENSE-XYZ
+ """
+ ),
+ 'MANIFEST.in': "exclude LICENSE-ABC\nexclude LICENSE-PQR",
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license",
+ # manifest is overwritten
+ },
+ ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'],
+ [],
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE*
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-XYZ': "XYZ notice",
+ },
+ ['LICENSE-ABC'],
+ ['NOTICE-XYZ'],
+ id="no_default_glob_patterns",
+ ),
+ pytest.param(
+ {
+ 'setup.cfg': DALS(
+ """
+ [metadata]
+ license_file = LICENSE*
+ license_files =
+ NOTICE*
+ """
+ ),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-ABC': "ABC notice",
+ 'AUTHORS-ABC': "ABC authors",
+ },
+ ['LICENSE-ABC', 'NOTICE-ABC'],
+ ['AUTHORS-ABC'],
+ id="combined_glob_patterrns",
+ ),
+ ],
+ )
+ def test_setup_cfg_license_file_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses
+ ):
+ self._create_project()
+ path.build(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ sources_text = Path(egg_info_dir, "SOURCES.txt").read_text(encoding="utf-8")
+ sources_lines = [line.strip() for line in sources_text.splitlines()]
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
+ def test_license_file_attr_pkg_info(self, tmpdir_cwd, env):
+ """All matched license files should have a corresponding License-File."""
+ self._create_project()
+ path.build({
+ "setup.cfg": DALS(
+ """
+ [metadata]
+ license_files =
+ NOTICE*
+ LICENSE*
+ **/LICENSE
+ """
+ ),
+ "LICENSE-ABC": "ABC license",
+ "LICENSE-XYZ": "XYZ license",
+ "NOTICE": "included",
+ "IGNORE": "not include",
+ "vendor": {'LICENSE': "Vendor license"},
+ })
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ license_file_lines = [
+ line for line in pkg_info_lines if line.startswith('License-File:')
+ ]
+
+ # Only 'NOTICE', LICENSE-ABC', and 'LICENSE-XYZ' should have been matched
+ # Also assert that order from license_files is keeped
+ assert len(license_file_lines) == 4
+ assert "License-File: NOTICE" == license_file_lines[0]
+ assert "License-File: LICENSE-ABC" in license_file_lines[1:]
+ assert "License-File: LICENSE-XYZ" in license_file_lines[1:]
+ assert "License-File: vendor/LICENSE" in license_file_lines[3]
+
+ def test_metadata_version(self, tmpdir_cwd, env):
+ """Make sure latest metadata version is used by default."""
+ self._setup_script_with_requires("")
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ # Update metadata version if changed
+ assert self._extract_mv_version(pkg_info_lines) == (2, 4)
+
+ def test_long_description_content_type(self, tmpdir_cwd, env):
+ # Test that specifying a `long_description_content_type` keyword arg to
+ # the `setup` function results in writing a `Description-Content-Type`
+ # line to the `PKG-INFO` file in the `.egg-info`
+ # directory.
+ # `Description-Content-Type` is described at
+ # https://github.com/pypa/python-packaging-user-guide/pull/258
+
+ self._setup_script_with_requires(
+ """long_description_content_type='text/markdown',"""
+ )
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ expected_line = 'Description-Content-Type: text/markdown'
+ assert expected_line in pkg_info_lines
+ assert 'Metadata-Version: 2.4' in pkg_info_lines
+
+ def test_long_description(self, tmpdir_cwd, env):
+ # Test that specifying `long_description` and `long_description_content_type`
+ # keyword args to the `setup` function results in writing
+ # the description in the message payload of the `PKG-INFO` file
+ # in the `.egg-info` directory.
+ self._setup_script_with_requires(
+ "long_description='This is a long description\\nover multiple lines',"
+ "long_description_content_type='text/markdown',"
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ assert 'Metadata-Version: 2.4' in pkg_info_lines
+ assert '' == pkg_info_lines[-1] # last line should be empty
+ long_desc_lines = pkg_info_lines[pkg_info_lines.index('') :]
+ assert 'This is a long description' in long_desc_lines
+ assert 'over multiple lines' in long_desc_lines
+
+ def test_project_urls(self, tmpdir_cwd, env):
+ # Test that specifying a `project_urls` dict to the `setup`
+ # function results in writing multiple `Project-URL` lines to
+ # the `PKG-INFO` file in the `.egg-info`
+ # directory.
+ # `Project-URL` is described at https://packaging.python.org
+ # /specifications/core-metadata/#project-url-multiple-use
+
+ self._setup_script_with_requires(
+ """project_urls={
+ 'Link One': 'https://example.com/one/',
+ 'Link Two': 'https://example.com/two/',
+ },"""
+ )
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ expected_line = 'Project-URL: Link One, https://example.com/one/'
+ assert expected_line in pkg_info_lines
+ expected_line = 'Project-URL: Link Two, https://example.com/two/'
+ assert expected_line in pkg_info_lines
+ assert self._extract_mv_version(pkg_info_lines) >= (1, 2)
+
+ def test_license(self, tmpdir_cwd, env):
+ """Test single line license."""
+ self._setup_script_with_requires("license='MIT',")
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ assert 'License: MIT' in pkg_info_lines
+
+ def test_license_escape(self, tmpdir_cwd, env):
+ """Test license is escaped correctly if longer than one line."""
+ self._setup_script_with_requires(
+ "license='This is a long license text \\nover multiple lines',"
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+
+ assert 'License: This is a long license text ' in pkg_info_lines
+ assert ' over multiple lines' in pkg_info_lines
+ assert 'text \n over multiple' in '\n'.join(pkg_info_lines)
+
+ def test_python_requires_egg_info(self, tmpdir_cwd, env):
+ self._setup_script_with_requires("""python_requires='>=2.7.12',""")
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ assert 'Requires-Python: >=2.7.12' in pkg_info_lines
+ assert self._extract_mv_version(pkg_info_lines) >= (1, 2)
+
+ def test_manifest_maker_warning_suppression(self):
+ fixtures = [
+ "standard file not found: should have one of foo.py, bar.py",
+ "standard file 'setup.py' not found",
+ ]
+
+ for msg in fixtures:
+ assert manifest_maker._should_suppress_warning(msg)
+
+ def test_egg_info_includes_setup_py(self, tmpdir_cwd):
+ self._create_project()
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.script_name = "non_setup.py"
+ egg_info_instance = egg_info(dist)
+ egg_info_instance.finalize_options()
+ egg_info_instance.run()
+
+ assert 'setup.py' in egg_info_instance.filelist.files
+
+ with open(egg_info_instance.egg_info + "/SOURCES.txt", encoding="utf-8") as f:
+ sources = f.read().split('\n')
+ assert 'setup.py' in sources
+
+ def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
+ environ = os.environ.copy().update(
+ HOME=env.paths['home'],
+ )
+ if cmd is None:
+ cmd = [
+ 'egg_info',
+ ]
+ code, data = environment.run_setup_py(
+ cmd=cmd,
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+ data_stream=1,
+ env=environ,
+ )
+ assert not code, data
+
+ if output:
+ assert output in data
+
+ def test_egg_info_tag_only_once(self, tmpdir_cwd, env):
+ self._create_project()
+ path.build({
+ 'setup.cfg': DALS(
+ """
+ [egg_info]
+ tag_build = dev
+ tag_date = 0
+ tag_svn_revision = 0
+ """
+ ),
+ })
+ self._run_egg_info_command(tmpdir_cwd, env)
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+ with open(os.path.join(egg_info_dir, 'PKG-INFO'), encoding="utf-8") as fp:
+ pkg_info_lines = fp.read().split('\n')
+ assert 'Version: 0.0.0.dev0' in pkg_info_lines
+
+
+class TestWriteEntries:
+ def test_invalid_entry_point(self, tmpdir_cwd, env):
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.entry_points = {"foo": "foo = invalid-identifier:foo"}
+ cmd = dist.get_command_obj("egg_info")
+ expected_msg = r"(Invalid object reference|Problems to parse)"
+ with pytest.raises((errors.OptionError, ValueError), match=expected_msg) as ex:
+ write_entries(cmd, "entry_points", "entry_points.txt")
+ assert "ensure entry-point follows the spec" in ex.value.args[0]
+ assert "invalid-identifier" in str(ex.value)
+
+ def test_valid_entry_point(self, tmpdir_cwd, env):
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.entry_points = {
+ "abc": "foo = bar:baz",
+ "def": ["faa = bor:boz"],
+ }
+ cmd = dist.get_command_obj("egg_info")
+ write_entries(cmd, "entry_points", "entry_points.txt")
+ content = Path("entry_points.txt").read_text(encoding="utf-8")
+ assert "[abc]\nfoo = bar:baz\n" in content
+ assert "[def]\nfaa = bor:boz\n" in content
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_extern.py b/lib/python3.12/site-packages/setuptools/tests/test_extern.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7eb3c62c190dacdd4a054d2934962be2f4ee860
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_extern.py
@@ -0,0 +1,15 @@
+import importlib
+import pickle
+
+import packaging
+
+from setuptools import Distribution
+
+
+def test_reimport_extern():
+ packaging2 = importlib.import_module(packaging.__name__)
+ assert packaging is packaging2
+
+
+def test_distribution_picklable():
+ pickle.loads(pickle.dumps(Distribution()))
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_find_packages.py b/lib/python3.12/site-packages/setuptools/tests/test_find_packages.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fd9f8f6637d13cb898fcd446a6b090323d02014
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_find_packages.py
@@ -0,0 +1,218 @@
+"""Tests for automatic package discovery"""
+
+import os
+import shutil
+import tempfile
+
+import pytest
+
+from setuptools import find_namespace_packages, find_packages
+from setuptools.discovery import FlatLayoutPackageFinder
+
+from .compat.py39 import os_helper
+
+
+class TestFindPackages:
+ def setup_method(self, method):
+ self.dist_dir = tempfile.mkdtemp()
+ self._make_pkg_structure()
+
+ def teardown_method(self, method):
+ shutil.rmtree(self.dist_dir)
+
+ def _make_pkg_structure(self):
+ """Make basic package structure.
+
+ dist/
+ docs/
+ conf.py
+ pkg/
+ __pycache__/
+ nspkg/
+ mod.py
+ subpkg/
+ assets/
+ asset
+ __init__.py
+ setup.py
+
+ """
+ self.docs_dir = self._mkdir('docs', self.dist_dir)
+ self._touch('conf.py', self.docs_dir)
+ self.pkg_dir = self._mkdir('pkg', self.dist_dir)
+ self._mkdir('__pycache__', self.pkg_dir)
+ self.ns_pkg_dir = self._mkdir('nspkg', self.pkg_dir)
+ self._touch('mod.py', self.ns_pkg_dir)
+ self.sub_pkg_dir = self._mkdir('subpkg', self.pkg_dir)
+ self.asset_dir = self._mkdir('assets', self.sub_pkg_dir)
+ self._touch('asset', self.asset_dir)
+ self._touch('__init__.py', self.sub_pkg_dir)
+ self._touch('setup.py', self.dist_dir)
+
+ def _mkdir(self, path, parent_dir=None):
+ if parent_dir:
+ path = os.path.join(parent_dir, path)
+ os.mkdir(path)
+ return path
+
+ def _touch(self, path, dir_=None):
+ if dir_:
+ path = os.path.join(dir_, path)
+ open(path, 'wb').close()
+ return path
+
+ def test_regular_package(self):
+ self._touch('__init__.py', self.pkg_dir)
+ packages = find_packages(self.dist_dir)
+ assert packages == ['pkg', 'pkg.subpkg']
+
+ def test_exclude(self):
+ self._touch('__init__.py', self.pkg_dir)
+ packages = find_packages(self.dist_dir, exclude=('pkg.*',))
+ assert packages == ['pkg']
+
+ def test_exclude_recursive(self):
+ """
+ Excluding a parent package should not exclude child packages as well.
+ """
+ self._touch('__init__.py', self.pkg_dir)
+ self._touch('__init__.py', self.sub_pkg_dir)
+ packages = find_packages(self.dist_dir, exclude=('pkg',))
+ assert packages == ['pkg.subpkg']
+
+ def test_include_excludes_other(self):
+ """
+ If include is specified, other packages should be excluded.
+ """
+ self._touch('__init__.py', self.pkg_dir)
+ alt_dir = self._mkdir('other_pkg', self.dist_dir)
+ self._touch('__init__.py', alt_dir)
+ packages = find_packages(self.dist_dir, include=['other_pkg'])
+ assert packages == ['other_pkg']
+
+ def test_dir_with_dot_is_skipped(self):
+ shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
+ data_dir = self._mkdir('some.data', self.pkg_dir)
+ self._touch('__init__.py', data_dir)
+ self._touch('file.dat', data_dir)
+ packages = find_packages(self.dist_dir)
+ assert 'pkg.some.data' not in packages
+
+ def test_dir_with_packages_in_subdir_is_excluded(self):
+ """
+ Ensure that a package in a non-package such as build/pkg/__init__.py
+ is excluded.
+ """
+ build_dir = self._mkdir('build', self.dist_dir)
+ build_pkg_dir = self._mkdir('pkg', build_dir)
+ self._touch('__init__.py', build_pkg_dir)
+ packages = find_packages(self.dist_dir)
+ assert 'build.pkg' not in packages
+
+ @pytest.mark.skipif(not os_helper.can_symlink(), reason='Symlink support required')
+ def test_symlinked_packages_are_included(self):
+ """
+ A symbolically-linked directory should be treated like any other
+ directory when matched as a package.
+
+ Create a link from lpkg -> pkg.
+ """
+ self._touch('__init__.py', self.pkg_dir)
+ linked_pkg = os.path.join(self.dist_dir, 'lpkg')
+ os.symlink('pkg', linked_pkg)
+ assert os.path.isdir(linked_pkg)
+ packages = find_packages(self.dist_dir)
+ assert 'lpkg' in packages
+
+ def _assert_packages(self, actual, expected):
+ assert set(actual) == set(expected)
+
+ def test_pep420_ns_package(self):
+ packages = find_namespace_packages(
+ self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']
+ )
+ self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
+
+ def test_pep420_ns_package_no_includes(self):
+ packages = find_namespace_packages(self.dist_dir, exclude=['pkg.subpkg.assets'])
+ self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
+
+ def test_pep420_ns_package_no_includes_or_excludes(self):
+ packages = find_namespace_packages(self.dist_dir)
+ expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
+ self._assert_packages(packages, expected)
+
+ def test_regular_package_with_nested_pep420_ns_packages(self):
+ self._touch('__init__.py', self.pkg_dir)
+ packages = find_namespace_packages(
+ self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']
+ )
+ self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
+
+ def test_pep420_ns_package_no_non_package_dirs(self):
+ shutil.rmtree(self.docs_dir)
+ shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
+ packages = find_namespace_packages(self.dist_dir)
+ self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
+
+
+class TestFlatLayoutPackageFinder:
+ EXAMPLES = {
+ "hidden-folders": (
+ [".pkg/__init__.py", "pkg/__init__.py", "pkg/nested/file.txt"],
+ ["pkg", "pkg.nested"],
+ ),
+ "private-packages": (
+ ["_pkg/__init__.py", "pkg/_private/__init__.py"],
+ ["pkg", "pkg._private"],
+ ),
+ "invalid-name": (
+ ["invalid-pkg/__init__.py", "other.pkg/__init__.py", "yet,another/file.py"],
+ [],
+ ),
+ "docs": (["pkg/__init__.py", "docs/conf.py", "docs/readme.rst"], ["pkg"]),
+ "tests": (
+ ["pkg/__init__.py", "tests/test_pkg.py", "tests/__init__.py"],
+ ["pkg"],
+ ),
+ "examples": (
+ [
+ "pkg/__init__.py",
+ "examples/__init__.py",
+ "examples/file.py",
+ "example/other_file.py",
+ # Sub-packages should always be fine
+ "pkg/example/__init__.py",
+ "pkg/examples/__init__.py",
+ ],
+ ["pkg", "pkg.examples", "pkg.example"],
+ ),
+ "tool-specific": (
+ [
+ "htmlcov/index.html",
+ "pkg/__init__.py",
+ "tasks/__init__.py",
+ "tasks/subpackage/__init__.py",
+ "fabfile/__init__.py",
+ "fabfile/subpackage/__init__.py",
+ # Sub-packages should always be fine
+ "pkg/tasks/__init__.py",
+ "pkg/fabfile/__init__.py",
+ ],
+ ["pkg", "pkg.tasks", "pkg.fabfile"],
+ ),
+ }
+
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_unwanted_directories_not_included(self, tmp_path, example):
+ files, expected_packages = self.EXAMPLES[example]
+ ensure_files(tmp_path, files)
+ found_packages = FlatLayoutPackageFinder.find(str(tmp_path))
+ assert set(found_packages) == set(expected_packages)
+
+
+def ensure_files(root_path, files):
+ for file in files:
+ path = root_path / file
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.touch()
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_find_py_modules.py b/lib/python3.12/site-packages/setuptools/tests/test_find_py_modules.py
new file mode 100644
index 0000000000000000000000000000000000000000..8034b544294e5d30274bac82f24f93613120a0d4
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_find_py_modules.py
@@ -0,0 +1,73 @@
+"""Tests for automatic discovery of modules"""
+
+import os
+
+import pytest
+
+from setuptools.discovery import FlatLayoutModuleFinder, ModuleFinder
+
+from .compat.py39 import os_helper
+from .test_find_packages import ensure_files
+
+
+class TestModuleFinder:
+ def find(self, path, *args, **kwargs):
+ return set(ModuleFinder.find(str(path), *args, **kwargs))
+
+ EXAMPLES = {
+ # circumstance: (files, kwargs, expected_modules)
+ "simple_folder": (
+ ["file.py", "other.py"],
+ {}, # kwargs
+ ["file", "other"],
+ ),
+ "exclude": (
+ ["file.py", "other.py"],
+ {"exclude": ["f*"]},
+ ["other"],
+ ),
+ "include": (
+ ["file.py", "fole.py", "other.py"],
+ {"include": ["f*"], "exclude": ["fo*"]},
+ ["file"],
+ ),
+ "invalid-name": (["my-file.py", "other.file.py"], {}, []),
+ }
+
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_finder(self, tmp_path, example):
+ files, kwargs, expected_modules = self.EXAMPLES[example]
+ ensure_files(tmp_path, files)
+ assert self.find(tmp_path, **kwargs) == set(expected_modules)
+
+ @pytest.mark.skipif(not os_helper.can_symlink(), reason='Symlink support required')
+ def test_symlinked_packages_are_included(self, tmp_path):
+ src = "_myfiles/file.py"
+ ensure_files(tmp_path, [src])
+ os.symlink(tmp_path / src, tmp_path / "link.py")
+ assert self.find(tmp_path) == {"link"}
+
+
+class TestFlatLayoutModuleFinder:
+ def find(self, path, *args, **kwargs):
+ return set(FlatLayoutModuleFinder.find(str(path)))
+
+ EXAMPLES = {
+ # circumstance: (files, expected_modules)
+ "hidden-files": ([".module.py"], []),
+ "private-modules": (["_module.py"], []),
+ "common-names": (
+ ["setup.py", "conftest.py", "test.py", "tests.py", "example.py", "mod.py"],
+ ["mod"],
+ ),
+ "tool-specific": (
+ ["tasks.py", "fabfile.py", "noxfile.py", "dodo.py", "manage.py", "mod.py"],
+ ["mod"],
+ ),
+ }
+
+ @pytest.mark.parametrize("example", EXAMPLES.keys())
+ def test_unwanted_files_not_included(self, tmp_path, example):
+ files, expected_modules = self.EXAMPLES[example]
+ ensure_files(tmp_path, files)
+ assert self.find(tmp_path) == set(expected_modules)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_glob.py b/lib/python3.12/site-packages/setuptools/tests/test_glob.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d225a44610163c7d56d65b07c06f0f598ccfe84
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_glob.py
@@ -0,0 +1,45 @@
+import pytest
+from jaraco import path
+
+from setuptools.glob import glob
+
+
+@pytest.mark.parametrize(
+ ('tree', 'pattern', 'matches'),
+ (
+ ('', b'', []),
+ ('', '', []),
+ (
+ """
+ appveyor.yml
+ CHANGES.rst
+ LICENSE
+ MANIFEST.in
+ pyproject.toml
+ README.rst
+ setup.cfg
+ setup.py
+ """,
+ '*.rst',
+ ('CHANGES.rst', 'README.rst'),
+ ),
+ (
+ """
+ appveyor.yml
+ CHANGES.rst
+ LICENSE
+ MANIFEST.in
+ pyproject.toml
+ README.rst
+ setup.cfg
+ setup.py
+ """,
+ b'*.rst',
+ (b'CHANGES.rst', b'README.rst'),
+ ),
+ ),
+)
+def test_glob(monkeypatch, tmpdir, tree, pattern, matches):
+ monkeypatch.chdir(tmpdir)
+ path.build({name: '' for name in tree.split()})
+ assert list(sorted(glob(pattern))) == list(sorted(matches))
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_install_scripts.py b/lib/python3.12/site-packages/setuptools/tests/test_install_scripts.py
new file mode 100644
index 0000000000000000000000000000000000000000..e62a6b7f318df2da0cf29e53c41f74e5525e78ac
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_install_scripts.py
@@ -0,0 +1,89 @@
+"""install_scripts tests"""
+
+import sys
+
+import pytest
+
+from setuptools.command.install_scripts import install_scripts
+from setuptools.dist import Distribution
+
+from . import contexts
+
+
+class TestInstallScripts:
+ settings = dict(
+ name='foo',
+ entry_points={'console_scripts': ['foo=foo:foo']},
+ version='0.0',
+ )
+ unix_exe = '/usr/dummy-test-path/local/bin/python'
+ unix_spaces_exe = '/usr/bin/env dummy-test-python'
+ win32_exe = 'C:\\Dummy Test Path\\Program Files\\Python 3.6\\python.exe'
+
+ def _run_install_scripts(self, install_dir, executable=None):
+ dist = Distribution(self.settings)
+ dist.script_name = 'setup.py'
+ cmd = install_scripts(dist)
+ cmd.install_dir = install_dir
+ if executable is not None:
+ bs = cmd.get_finalized_command('build_scripts')
+ bs.executable = executable
+ cmd.ensure_finalized()
+ with contexts.quiet():
+ cmd.run()
+
+ @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
+ def test_sys_executable_escaping_unix(self, tmpdir, monkeypatch):
+ """
+ Ensure that shebang is not quoted on Unix when getting the Python exe
+ from sys.executable.
+ """
+ expected = f'#!{self.unix_exe}\n'
+ monkeypatch.setattr('sys.executable', self.unix_exe)
+ with tmpdir.as_cwd():
+ self._run_install_scripts(str(tmpdir))
+ with open(str(tmpdir.join('foo')), 'r', encoding="utf-8") as f:
+ actual = f.readline()
+ assert actual == expected
+
+ @pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
+ def test_sys_executable_escaping_win32(self, tmpdir, monkeypatch):
+ """
+ Ensure that shebang is quoted on Windows when getting the Python exe
+ from sys.executable and it contains a space.
+ """
+ expected = f'#!"{self.win32_exe}"\n'
+ monkeypatch.setattr('sys.executable', self.win32_exe)
+ with tmpdir.as_cwd():
+ self._run_install_scripts(str(tmpdir))
+ with open(str(tmpdir.join('foo-script.py')), 'r', encoding="utf-8") as f:
+ actual = f.readline()
+ assert actual == expected
+
+ @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
+ def test_executable_with_spaces_escaping_unix(self, tmpdir):
+ """
+ Ensure that shebang on Unix is not quoted, even when
+ a value with spaces
+ is specified using --executable.
+ """
+ expected = f'#!{self.unix_spaces_exe}\n'
+ with tmpdir.as_cwd():
+ self._run_install_scripts(str(tmpdir), self.unix_spaces_exe)
+ with open(str(tmpdir.join('foo')), 'r', encoding="utf-8") as f:
+ actual = f.readline()
+ assert actual == expected
+
+ @pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
+ def test_executable_arg_escaping_win32(self, tmpdir):
+ """
+ Ensure that shebang on Windows is quoted when
+ getting a path with spaces
+ from --executable, that is itself properly quoted.
+ """
+ expected = f'#!"{self.win32_exe}"\n'
+ with tmpdir.as_cwd():
+ self._run_install_scripts(str(tmpdir), '"' + self.win32_exe + '"')
+ with open(str(tmpdir.join('foo-script.py')), 'r', encoding="utf-8") as f:
+ actual = f.readline()
+ assert actual == expected
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_logging.py b/lib/python3.12/site-packages/setuptools/tests/test_logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea58001e93d8e4bcbd50bfd49303324d71858d16
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_logging.py
@@ -0,0 +1,76 @@
+import functools
+import inspect
+import logging
+import sys
+
+import pytest
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+setup_py = """\
+from setuptools import setup
+
+setup(
+ name="test_logging",
+ version="0.0"
+)
+"""
+
+
+@pytest.mark.parametrize(
+ ('flag', 'expected_level'), [("--dry-run", "INFO"), ("--verbose", "DEBUG")]
+)
+def test_verbosity_level(tmp_path, monkeypatch, flag, expected_level):
+ """Make sure the correct verbosity level is set (issue #3038)"""
+ import setuptools # noqa: F401 # import setuptools to monkeypatch distutils
+
+ import distutils # <- load distutils after all the patches take place
+
+ logger = logging.Logger(__name__)
+ monkeypatch.setattr(logging, "root", logger)
+ unset_log_level = logger.getEffectiveLevel()
+ assert logging.getLevelName(unset_log_level) == "NOTSET"
+
+ setup_script = tmp_path / "setup.py"
+ setup_script.write_text(setup_py, encoding="utf-8")
+ dist = distutils.core.run_setup(setup_script, stop_after="init")
+ dist.script_args = [flag, "sdist"]
+ dist.parse_command_line() # <- where the log level is set
+ log_level = logger.getEffectiveLevel()
+ log_level_name = logging.getLevelName(log_level)
+ assert log_level_name == expected_level
+
+
+def flaky_on_pypy(func):
+ @functools.wraps(func)
+ def _func():
+ try:
+ func()
+ except AssertionError: # pragma: no cover
+ if IS_PYPY:
+ msg = "Flaky monkeypatch on PyPy (#4124)"
+ pytest.xfail(f"{msg}. Original discussion in #3707, #3709.")
+ raise
+
+ return _func
+
+
+@flaky_on_pypy
+def test_patching_does_not_cause_problems():
+ # Ensure `dist.log` is only patched if necessary
+
+ import _distutils_hack
+
+ import setuptools.logging
+
+ from distutils import dist
+
+ setuptools.logging.configure()
+
+ if _distutils_hack.enabled():
+ # Modern logging infra, no problematic patching.
+ assert dist.__file__ is None or "setuptools" in dist.__file__
+ assert isinstance(dist.log, logging.Logger)
+ else:
+ assert inspect.ismodule(dist.log)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_manifest.py b/lib/python3.12/site-packages/setuptools/tests/test_manifest.py
new file mode 100644
index 0000000000000000000000000000000000000000..903a528db0cc2bba27bbcef24aa1c59dc4156528
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_manifest.py
@@ -0,0 +1,622 @@
+"""sdist tests"""
+
+from __future__ import annotations
+
+import contextlib
+import io
+import itertools
+import logging
+import os
+import shutil
+import sys
+import tempfile
+
+import pytest
+
+from setuptools.command.egg_info import FileList, egg_info, translate_pattern
+from setuptools.dist import Distribution
+from setuptools.tests.textwrap import DALS
+
+from distutils import log
+from distutils.errors import DistutilsTemplateError
+
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+def make_local_path(s):
+ """Converts '/' in a string to os.sep"""
+ return s.replace('/', os.sep)
+
+
+SETUP_ATTRS = {
+ 'name': 'app',
+ 'version': '0.0',
+ 'packages': ['app'],
+}
+
+SETUP_PY = f"""\
+from setuptools import setup
+
+setup(**{SETUP_ATTRS!r})
+"""
+
+
+@contextlib.contextmanager
+def quiet():
+ old_stdout, old_stderr = sys.stdout, sys.stderr
+ sys.stdout, sys.stderr = io.StringIO(), io.StringIO()
+ try:
+ yield
+ finally:
+ sys.stdout, sys.stderr = old_stdout, old_stderr
+
+
+def touch(filename):
+ open(filename, 'wb').close()
+
+
+# The set of files always in the manifest, including all files in the
+# .egg-info directory
+default_files = frozenset(
+ map(
+ make_local_path,
+ [
+ 'README.rst',
+ 'MANIFEST.in',
+ 'setup.py',
+ 'app.egg-info/PKG-INFO',
+ 'app.egg-info/SOURCES.txt',
+ 'app.egg-info/dependency_links.txt',
+ 'app.egg-info/top_level.txt',
+ 'app/__init__.py',
+ ],
+ )
+)
+
+
+translate_specs: list[tuple[str, list[str], list[str]]] = [
+ ('foo', ['foo'], ['bar', 'foobar']),
+ ('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']),
+ # Glob matching
+ ('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']),
+ ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
+ ('*/*.py', ['bin/start.py'], []),
+ ('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']),
+ # Globstars change what they mean depending upon where they are
+ (
+ 'foo/**/bar',
+ ['foo/bing/bar', 'foo/bing/bang/bar', 'foo/bar'],
+ ['foo/abar'],
+ ),
+ (
+ 'foo/**',
+ ['foo/bar/bing.py', 'foo/x'],
+ ['/foo/x'],
+ ),
+ (
+ '**',
+ ['x', 'abc/xyz', '@nything'],
+ [],
+ ),
+ # Character classes
+ (
+ 'pre[one]post',
+ ['preopost', 'prenpost', 'preepost'],
+ ['prepost', 'preonepost'],
+ ),
+ (
+ 'hello[!one]world',
+ ['helloxworld', 'helloyworld'],
+ ['hellooworld', 'helloworld', 'hellooneworld'],
+ ),
+ (
+ '[]one].txt',
+ ['o.txt', '].txt', 'e.txt'],
+ ['one].txt'],
+ ),
+ (
+ 'foo[!]one]bar',
+ ['fooybar'],
+ ['foo]bar', 'fooobar', 'fooebar'],
+ ),
+]
+"""
+A spec of inputs for 'translate_pattern' and matches and mismatches
+for that input.
+"""
+
+match_params = itertools.chain.from_iterable(
+ zip(itertools.repeat(pattern), matches)
+ for pattern, matches, mismatches in translate_specs
+)
+
+
+@pytest.fixture(params=match_params)
+def pattern_match(request):
+ return map(make_local_path, request.param)
+
+
+mismatch_params = itertools.chain.from_iterable(
+ zip(itertools.repeat(pattern), mismatches)
+ for pattern, matches, mismatches in translate_specs
+)
+
+
+@pytest.fixture(params=mismatch_params)
+def pattern_mismatch(request):
+ return map(make_local_path, request.param)
+
+
+def test_translated_pattern_match(pattern_match):
+ pattern, target = pattern_match
+ assert translate_pattern(pattern).match(target)
+
+
+def test_translated_pattern_mismatch(pattern_mismatch):
+ pattern, target = pattern_mismatch
+ assert not translate_pattern(pattern).match(target)
+
+
+class TempDirTestCase:
+ def setup_method(self, method):
+ self.temp_dir = tempfile.mkdtemp()
+ self.old_cwd = os.getcwd()
+ os.chdir(self.temp_dir)
+
+ def teardown_method(self, method):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.temp_dir)
+
+
+class TestManifestTest(TempDirTestCase):
+ def setup_method(self, method):
+ super().setup_method(method)
+
+ f = open(os.path.join(self.temp_dir, 'setup.py'), 'w', encoding="utf-8")
+ f.write(SETUP_PY)
+ f.close()
+ """
+ Create a file tree like:
+ - LICENSE
+ - README.rst
+ - testing.rst
+ - .hidden.rst
+ - app/
+ - __init__.py
+ - a.txt
+ - b.txt
+ - c.rst
+ - static/
+ - app.js
+ - app.js.map
+ - app.css
+ - app.css.map
+ """
+
+ for fname in ['README.rst', '.hidden.rst', 'testing.rst', 'LICENSE']:
+ touch(os.path.join(self.temp_dir, fname))
+
+ # Set up the rest of the test package
+ test_pkg = os.path.join(self.temp_dir, 'app')
+ os.mkdir(test_pkg)
+ for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']:
+ touch(os.path.join(test_pkg, fname))
+
+ # Some compiled front-end assets to include
+ static = os.path.join(test_pkg, 'static')
+ os.mkdir(static)
+ for fname in ['app.js', 'app.js.map', 'app.css', 'app.css.map']:
+ touch(os.path.join(static, fname))
+
+ def make_manifest(self, contents):
+ """Write a MANIFEST.in."""
+ manifest = os.path.join(self.temp_dir, 'MANIFEST.in')
+ with open(manifest, 'w', encoding="utf-8") as f:
+ f.write(DALS(contents))
+
+ def get_files(self):
+ """Run egg_info and get all the files to include, as a set"""
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = egg_info(dist)
+ cmd.ensure_finalized()
+
+ cmd.run()
+
+ return set(cmd.filelist.files)
+
+ def test_no_manifest(self):
+ """Check a missing MANIFEST.in includes only the standard files."""
+ assert (default_files - set(['MANIFEST.in'])) == self.get_files()
+
+ def test_empty_files(self):
+ """Check an empty MANIFEST.in includes only the standard files."""
+ self.make_manifest("")
+ assert default_files == self.get_files()
+
+ def test_include(self):
+ """Include extra rst files in the project root."""
+ self.make_manifest("include *.rst")
+ files = default_files | set(['testing.rst', '.hidden.rst'])
+ assert files == self.get_files()
+
+ def test_exclude(self):
+ """Include everything in app/ except the text files"""
+ ml = make_local_path
+ self.make_manifest(
+ """
+ include app/*
+ exclude app/*.txt
+ """
+ )
+ files = default_files | set([ml('app/c.rst')])
+ assert files == self.get_files()
+
+ def test_include_multiple(self):
+ """Include with multiple patterns."""
+ ml = make_local_path
+ self.make_manifest("include app/*.txt app/static/*")
+ files = default_files | set([
+ ml('app/a.txt'),
+ ml('app/b.txt'),
+ ml('app/static/app.js'),
+ ml('app/static/app.js.map'),
+ ml('app/static/app.css'),
+ ml('app/static/app.css.map'),
+ ])
+ assert files == self.get_files()
+
+ def test_graft(self):
+ """Include the whole app/static/ directory."""
+ ml = make_local_path
+ self.make_manifest("graft app/static")
+ files = default_files | set([
+ ml('app/static/app.js'),
+ ml('app/static/app.js.map'),
+ ml('app/static/app.css'),
+ ml('app/static/app.css.map'),
+ ])
+ assert files == self.get_files()
+
+ def test_graft_glob_syntax(self):
+ """Include the whole app/static/ directory."""
+ ml = make_local_path
+ self.make_manifest("graft */static")
+ files = default_files | set([
+ ml('app/static/app.js'),
+ ml('app/static/app.js.map'),
+ ml('app/static/app.css'),
+ ml('app/static/app.css.map'),
+ ])
+ assert files == self.get_files()
+
+ def test_graft_global_exclude(self):
+ """Exclude all *.map files in the project."""
+ ml = make_local_path
+ self.make_manifest(
+ """
+ graft app/static
+ global-exclude *.map
+ """
+ )
+ files = default_files | set([ml('app/static/app.js'), ml('app/static/app.css')])
+ assert files == self.get_files()
+
+ def test_global_include(self):
+ """Include all *.rst, *.js, and *.css files in the whole tree."""
+ ml = make_local_path
+ self.make_manifest(
+ """
+ global-include *.rst *.js *.css
+ """
+ )
+ files = default_files | set([
+ '.hidden.rst',
+ 'testing.rst',
+ ml('app/c.rst'),
+ ml('app/static/app.js'),
+ ml('app/static/app.css'),
+ ])
+ assert files == self.get_files()
+
+ def test_graft_prune(self):
+ """Include all files in app/, except for the whole app/static/ dir."""
+ ml = make_local_path
+ self.make_manifest(
+ """
+ graft app
+ prune app/static
+ """
+ )
+ files = default_files | set([ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')])
+ assert files == self.get_files()
+
+
+class TestFileListTest(TempDirTestCase):
+ """
+ A copy of the relevant bits of distutils/tests/test_filelist.py,
+ to ensure setuptools' version of FileList keeps parity with distutils.
+ """
+
+ @pytest.fixture(autouse=os.getenv("SETUPTOOLS_USE_DISTUTILS") == "stdlib")
+ def _compat_record_logs(self, monkeypatch, caplog):
+ """Account for stdlib compatibility"""
+
+ def _log(_logger, level, msg, args):
+ exc = sys.exc_info()
+ rec = logging.LogRecord("distutils", level, "", 0, msg, args, exc)
+ caplog.records.append(rec)
+
+ monkeypatch.setattr(log.Log, "_log", _log)
+
+ def get_records(self, caplog, *levels):
+ return [r for r in caplog.records if r.levelno in levels]
+
+ def assertNoWarnings(self, caplog):
+ assert self.get_records(caplog, log.WARN) == []
+ caplog.clear()
+
+ def assertWarnings(self, caplog):
+ if IS_PYPY and not caplog.records:
+ pytest.xfail("caplog checks may not work well in PyPy")
+ else:
+ assert len(self.get_records(caplog, log.WARN)) > 0
+ caplog.clear()
+
+ def make_files(self, files):
+ for file in files:
+ file = os.path.join(self.temp_dir, file)
+ dirname, _basename = os.path.split(file)
+ os.makedirs(dirname, exist_ok=True)
+ touch(file)
+
+ def test_process_template_line(self):
+ # testing all MANIFEST.in template patterns
+ file_list = FileList()
+ ml = make_local_path
+
+ # simulated file list
+ self.make_files([
+ 'foo.tmp',
+ 'ok',
+ 'xo',
+ 'four.txt',
+ 'buildout.cfg',
+ # filelist does not filter out VCS directories,
+ # it's sdist that does
+ ml('.hg/last-message.txt'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
+ ml('global/files.x'),
+ ml('global/here.tmp'),
+ ml('f/o/f.oo'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('dir3/ok'),
+ ml('dir3/sub/ok.txt'),
+ ])
+
+ MANIFEST_IN = DALS(
+ """\
+ include ok
+ include xo
+ exclude xo
+ include foo.tmp
+ include buildout.cfg
+ global-include *.x
+ global-include *.txt
+ global-exclude *.tmp
+ recursive-include f *.oo
+ recursive-exclude global *.x
+ graft dir
+ prune dir3
+ """
+ )
+
+ for line in MANIFEST_IN.split('\n'):
+ if not line:
+ continue
+ file_list.process_template_line(line)
+
+ wanted = [
+ 'buildout.cfg',
+ 'four.txt',
+ 'ok',
+ ml('.hg/last-message.txt'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('f/o/f.oo'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
+ ]
+
+ file_list.sort()
+ assert file_list.files == wanted
+
+ def test_exclude_pattern(self):
+ # return False if no match
+ file_list = FileList()
+ assert not file_list.exclude_pattern('*.py')
+
+ # return True if files match
+ file_list = FileList()
+ file_list.files = ['a.py', 'b.py']
+ assert file_list.exclude_pattern('*.py')
+
+ # test excludes
+ file_list = FileList()
+ file_list.files = ['a.py', 'a.txt']
+ file_list.exclude_pattern('*.py')
+ file_list.sort()
+ assert file_list.files == ['a.txt']
+
+ def test_include_pattern(self):
+ # return False if no match
+ file_list = FileList()
+ self.make_files([])
+ assert not file_list.include_pattern('*.py')
+
+ # return True if files match
+ file_list = FileList()
+ self.make_files(['a.py', 'b.txt'])
+ assert file_list.include_pattern('*.py')
+
+ # test * matches all files
+ file_list = FileList()
+ self.make_files(['a.py', 'b.txt'])
+ file_list.include_pattern('*')
+ file_list.sort()
+ assert file_list.files == ['a.py', 'b.txt']
+
+ def test_process_template_line_invalid(self):
+ # invalid lines
+ file_list = FileList()
+ for action in (
+ 'include',
+ 'exclude',
+ 'global-include',
+ 'global-exclude',
+ 'recursive-include',
+ 'recursive-exclude',
+ 'graft',
+ 'prune',
+ 'blarg',
+ ):
+ with pytest.raises(DistutilsTemplateError):
+ file_list.process_template_line(action)
+
+ def test_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # include
+ file_list = FileList()
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
+
+ file_list.process_template_line('include *.py')
+ file_list.sort()
+ assert file_list.files == ['a.py']
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('include *.rb')
+ file_list.sort()
+ assert file_list.files == ['a.py']
+ self.assertWarnings(caplog)
+
+ def test_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # exclude
+ file_list = FileList()
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
+
+ file_list.process_template_line('exclude *.py')
+ file_list.sort()
+ assert file_list.files == ['b.txt', ml('d/c.py')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('exclude *.rb')
+ file_list.sort()
+ assert file_list.files == ['b.txt', ml('d/c.py')]
+ self.assertWarnings(caplog)
+
+ def test_global_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # global-include
+ file_list = FileList()
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
+
+ file_list.process_template_line('global-include *.py')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('d/c.py')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('global-include *.rb')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('d/c.py')]
+ self.assertWarnings(caplog)
+
+ def test_global_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # global-exclude
+ file_list = FileList()
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
+
+ file_list.process_template_line('global-exclude *.py')
+ file_list.sort()
+ assert file_list.files == ['b.txt']
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('global-exclude *.rb')
+ file_list.sort()
+ assert file_list.files == ['b.txt']
+ self.assertWarnings(caplog)
+
+ def test_recursive_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # recursive-include
+ file_list = FileList()
+ self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')])
+
+ file_list.process_template_line('recursive-include d *.py')
+ file_list.sort()
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('recursive-include e *.py')
+ file_list.sort()
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
+ self.assertWarnings(caplog)
+
+ def test_recursive_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # recursive-exclude
+ file_list = FileList()
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]
+
+ file_list.process_template_line('recursive-exclude d *.py')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('d/c.txt')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('recursive-exclude e *.py')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('d/c.txt')]
+ self.assertWarnings(caplog)
+
+ def test_graft(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # graft
+ file_list = FileList()
+ self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')])
+
+ file_list.process_template_line('graft d')
+ file_list.sort()
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('graft e')
+ file_list.sort()
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
+ self.assertWarnings(caplog)
+
+ def test_prune(self, caplog):
+ caplog.set_level(logging.DEBUG)
+ ml = make_local_path
+ # prune
+ file_list = FileList()
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]
+
+ file_list.process_template_line('prune d')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('f/f.py')]
+ self.assertNoWarnings(caplog)
+
+ file_list.process_template_line('prune e')
+ file_list.sort()
+ assert file_list.files == ['a.py', ml('f/f.py')]
+ self.assertWarnings(caplog)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_namespaces.py b/lib/python3.12/site-packages/setuptools/tests/test_namespaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0f4120bf7900b2118cc066034e036ab7af1798b
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_namespaces.py
@@ -0,0 +1,138 @@
+import subprocess
+import sys
+
+from setuptools._path import paths_on_pythonpath
+
+from . import namespaces
+
+
+class TestNamespaces:
+ def test_mixed_site_and_non_site(self, tmpdir):
+ """
+ Installing two packages sharing the same namespace, one installed
+ to a site dir and the other installed just to a path on PYTHONPATH
+ should leave the namespace in tact and both packages reachable by
+ import.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
+ site_packages = tmpdir / 'site-packages'
+ path_packages = tmpdir / 'path-packages'
+ targets = site_packages, path_packages
+ # use pip to install to the target directory
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip.__main__',
+ 'install',
+ str(pkg_A),
+ '-t',
+ str(site_packages),
+ ]
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(site_packages)
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip.__main__',
+ 'install',
+ str(pkg_B),
+ '-t',
+ str(path_packages),
+ ]
+ subprocess.check_call(install_cmd)
+ try_import = [
+ sys.executable,
+ '-c',
+ 'import myns.pkgA; import myns.pkgB',
+ ]
+ with paths_on_pythonpath(map(str, targets)):
+ subprocess.check_call(try_import)
+
+ def test_pkg_resources_import(self, tmpdir):
+ """
+ Ensure that a namespace package doesn't break on import
+ of pkg_resources.
+ """
+ pkg = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ target = tmpdir / 'packages'
+ target.mkdir()
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip',
+ 'install',
+ '-t',
+ str(target),
+ str(pkg),
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(target)
+ try_import = [
+ sys.executable,
+ '-c',
+ 'import pkg_resources',
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(try_import)
+
+ def test_namespace_package_installed_and_cwd(self, tmpdir):
+ """
+ Installing a namespace packages but also having it in the current
+ working directory, only one version should take precedence.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ target = tmpdir / 'packages'
+ # use pip to install to the target directory
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip.__main__',
+ 'install',
+ str(pkg_A),
+ '-t',
+ str(target),
+ ]
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(target)
+
+ # ensure that package imports and pkg_resources imports
+ pkg_resources_imp = [
+ sys.executable,
+ '-c',
+ 'import pkg_resources; import myns.pkgA',
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(pkg_resources_imp, cwd=str(pkg_A))
+
+ def test_packages_in_the_same_namespace_installed_and_cwd(self, tmpdir):
+ """
+ Installing one namespace package and also have another in the same
+ namespace in the current working directory, both of them must be
+ importable.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
+ target = tmpdir / 'packages'
+ # use pip to install to the target directory
+ install_cmd = [
+ sys.executable,
+ '-m',
+ 'pip.__main__',
+ 'install',
+ str(pkg_A),
+ '-t',
+ str(target),
+ ]
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(target)
+
+ # ensure that all packages import and pkg_resources imports
+ pkg_resources_imp = [
+ sys.executable,
+ '-c',
+ 'import pkg_resources; import myns.pkgA; import myns.pkgB',
+ ]
+ with paths_on_pythonpath([str(target)]):
+ subprocess.check_call(pkg_resources_imp, cwd=str(pkg_B))
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_scripts.py b/lib/python3.12/site-packages/setuptools/tests/test_scripts.py
new file mode 100644
index 0000000000000000000000000000000000000000..8641f7b639161525e2fff3100744d7fa55d9d718
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_scripts.py
@@ -0,0 +1,12 @@
+from setuptools import _scripts
+
+
+class TestWindowsScriptWriter:
+ def test_header(self):
+ hdr = _scripts.WindowsScriptWriter.get_header('')
+ assert hdr.startswith('#!')
+ assert hdr.endswith('\n')
+ hdr = hdr.lstrip('#!')
+ hdr = hdr.rstrip('\n')
+ # header should not start with an escaped quote
+ assert not hdr.startswith('\\"')
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_sdist.py b/lib/python3.12/site-packages/setuptools/tests/test_sdist.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b435fe111346d026ba20bbbf68994282e8b64c7
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_sdist.py
@@ -0,0 +1,980 @@
+"""sdist tests"""
+
+import contextlib
+import io
+import logging
+import os
+import pathlib
+import sys
+import tarfile
+import tempfile
+import unicodedata
+from inspect import cleandoc
+from pathlib import Path
+from unittest import mock
+
+import jaraco.path
+import pytest
+
+from setuptools import Command, SetuptoolsDeprecationWarning
+from setuptools._importlib import metadata
+from setuptools.command.egg_info import manifest_maker
+from setuptools.command.sdist import sdist
+from setuptools.dist import Distribution
+from setuptools.extension import Extension
+from setuptools.tests import fail_on_ascii
+
+from .text import Filenames
+
+import distutils
+from distutils.core import run_setup
+
+SETUP_ATTRS = {
+ 'name': 'sdist_test',
+ 'version': '0.0',
+ 'packages': ['sdist_test'],
+ 'package_data': {'sdist_test': ['*.txt']},
+ 'data_files': [("data", [os.path.join("d", "e.dat")])],
+}
+
+SETUP_PY = f"""\
+from setuptools import setup
+
+setup(**{SETUP_ATTRS!r})
+"""
+
+EXTENSION = Extension(
+ name="sdist_test.f",
+ sources=[os.path.join("sdist_test", "f.c")],
+ depends=[os.path.join("sdist_test", "f.h")],
+)
+EXTENSION_SOURCES = EXTENSION.sources + EXTENSION.depends
+
+
+@contextlib.contextmanager
+def quiet():
+ old_stdout, old_stderr = sys.stdout, sys.stderr
+ sys.stdout, sys.stderr = io.StringIO(), io.StringIO()
+ try:
+ yield
+ finally:
+ sys.stdout, sys.stderr = old_stdout, old_stderr
+
+
+# Convert to POSIX path
+def posix(path):
+ if not isinstance(path, str):
+ return path.replace(os.sep.encode('ascii'), b'/')
+ else:
+ return path.replace(os.sep, '/')
+
+
+# HFS Plus uses decomposed UTF-8
+def decompose(path):
+ if isinstance(path, str):
+ return unicodedata.normalize('NFD', path)
+ try:
+ path = path.decode('utf-8')
+ path = unicodedata.normalize('NFD', path)
+ path = path.encode('utf-8')
+ except UnicodeError:
+ pass # Not UTF-8
+ return path
+
+
+def read_all_bytes(filename):
+ with open(filename, 'rb') as fp:
+ return fp.read()
+
+
+def latin1_fail():
+ try:
+ desc, filename = tempfile.mkstemp(suffix=Filenames.latin_1)
+ os.close(desc)
+ os.remove(filename)
+ except Exception:
+ return True
+
+
+fail_on_latin1_encoded_filenames = pytest.mark.xfail(
+ latin1_fail(),
+ reason="System does not support latin-1 filenames",
+)
+
+
+skip_under_xdist = pytest.mark.skipif(
+ "os.environ.get('PYTEST_XDIST_WORKER')",
+ reason="pytest-dev/pytest-xdist#843",
+)
+skip_under_stdlib_distutils = pytest.mark.skipif(
+ not distutils.__package__.startswith('setuptools'),
+ reason="the test is not supported with stdlib distutils",
+)
+
+
+def touch(path):
+ open(path, 'wb').close()
+ return path
+
+
+def symlink_or_skip_test(src, dst):
+ try:
+ os.symlink(src, dst)
+ except (OSError, NotImplementedError):
+ pytest.skip("symlink not supported in OS")
+ return None
+ return dst
+
+
+class TestSdistTest:
+ @pytest.fixture(autouse=True)
+ def source_dir(self, tmpdir):
+ tmpdir = tmpdir / "project_root"
+ tmpdir.mkdir()
+
+ (tmpdir / 'setup.py').write_text(SETUP_PY, encoding='utf-8')
+
+ # Set up the rest of the test package
+ test_pkg = tmpdir / 'sdist_test'
+ test_pkg.mkdir()
+ data_folder = tmpdir / 'd'
+ data_folder.mkdir()
+ # *.rst was not included in package_data, so c.rst should not be
+ # automatically added to the manifest when not under version control
+ for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']:
+ touch(test_pkg / fname)
+ touch(data_folder / 'e.dat')
+ # C sources are not included by default, but they will be,
+ # if an extension module uses them as sources or depends
+ for fname in EXTENSION_SOURCES:
+ touch(tmpdir / fname)
+
+ with tmpdir.as_cwd():
+ yield tmpdir
+
+ def assert_package_data_in_manifest(self, cmd):
+ manifest = cmd.filelist.files
+ assert os.path.join('sdist_test', 'a.txt') in manifest
+ assert os.path.join('sdist_test', 'b.txt') in manifest
+ assert os.path.join('sdist_test', 'c.rst') not in manifest
+ assert os.path.join('d', 'e.dat') in manifest
+
+ def setup_with_extension(self):
+ setup_attrs = {**SETUP_ATTRS, 'ext_modules': [EXTENSION]}
+
+ dist = Distribution(setup_attrs)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ return cmd
+
+ def test_package_data_in_sdist(self):
+ """Regression test for pull request #4: ensures that files listed in
+ package_data are included in the manifest even if they're not added to
+ version control.
+ """
+
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ self.assert_package_data_in_manifest(cmd)
+
+ def test_package_data_and_include_package_data_in_sdist(self):
+ """
+ Ensure package_data and include_package_data work
+ together.
+ """
+ setup_attrs = {**SETUP_ATTRS, 'include_package_data': True}
+ assert setup_attrs['package_data']
+
+ dist = Distribution(setup_attrs)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ self.assert_package_data_in_manifest(cmd)
+
+ def test_extension_sources_in_sdist(self):
+ """
+ Ensure that the files listed in Extension.sources and Extension.depends
+ are automatically included in the manifest.
+ """
+ cmd = self.setup_with_extension()
+ self.assert_package_data_in_manifest(cmd)
+ manifest = cmd.filelist.files
+ for path in EXTENSION_SOURCES:
+ assert path in manifest
+
+ def test_missing_extension_sources(self):
+ """
+ Similar to test_extension_sources_in_sdist but the referenced files don't exist.
+ Missing files should not be included in distribution (with no error raised).
+ """
+ for path in EXTENSION_SOURCES:
+ os.remove(path)
+
+ cmd = self.setup_with_extension()
+ self.assert_package_data_in_manifest(cmd)
+ manifest = cmd.filelist.files
+ for path in EXTENSION_SOURCES:
+ assert path not in manifest
+
+ def test_symlinked_extension_sources(self):
+ """
+ Similar to test_extension_sources_in_sdist but the referenced files are
+ instead symbolic links to project-local files. Referenced file paths
+ should be included. Symlink targets themselves should NOT be included.
+ """
+ symlinked = []
+ for path in EXTENSION_SOURCES:
+ base, ext = os.path.splitext(path)
+ target = base + "_target." + ext
+
+ os.rename(path, target)
+ symlink_or_skip_test(os.path.basename(target), path)
+ symlinked.append(target)
+
+ cmd = self.setup_with_extension()
+ self.assert_package_data_in_manifest(cmd)
+ manifest = cmd.filelist.files
+ for path in EXTENSION_SOURCES:
+ assert path in manifest
+ for path in symlinked:
+ assert path not in manifest
+
+ _INVALID_PATHS = {
+ "must be relative": lambda: os.path.abspath(os.path.join("sdist_test", "f.h")),
+ "can't have `..` segments": lambda: os.path.join(
+ "sdist_test", "..", "sdist_test", "f.h"
+ ),
+ "doesn't exist": lambda: os.path.join(
+ "sdist_test", "this_file_does_not_exist.h"
+ ),
+ "must be inside the project root": lambda: symlink_or_skip_test(
+ touch(os.path.join("..", "outside_of_project_root.h")),
+ "symlink.h",
+ ),
+ }
+
+ @skip_under_stdlib_distutils
+ @pytest.mark.parametrize("reason", _INVALID_PATHS.keys())
+ def test_invalid_extension_depends(self, reason, caplog):
+ """
+ Due to backwards compatibility reasons, `Extension.depends` should accept
+ invalid/weird paths, but then ignore them when building a sdist.
+
+ This test verifies that the source distribution is still built
+ successfully with such paths, but that instead of adding these paths to
+ the manifest, we emit an informational message, notifying the user that
+ the invalid path won't be automatically included.
+ """
+ invalid_path = self._INVALID_PATHS[reason]()
+ extension = Extension(
+ name="sdist_test.f",
+ sources=[],
+ depends=[invalid_path],
+ )
+ setup_attrs = {**SETUP_ATTRS, 'ext_modules': [extension]}
+
+ dist = Distribution(setup_attrs)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet(), caplog.at_level(logging.INFO):
+ cmd.run()
+
+ self.assert_package_data_in_manifest(cmd)
+ manifest = cmd.filelist.files
+ assert invalid_path not in manifest
+
+ expected_message = [
+ message
+ for (logger, level, message) in caplog.record_tuples
+ if (
+ logger == "root" #
+ and level == logging.INFO #
+ and invalid_path in message #
+ )
+ ]
+ assert len(expected_message) == 1
+ (expected_message,) = expected_message
+ assert reason in expected_message
+
+ def test_custom_build_py(self):
+ """
+ Ensure projects defining custom build_py don't break
+ when creating sdists (issue #2849)
+ """
+ from distutils.command.build_py import build_py as OrigBuildPy
+
+ using_custom_command_guard = mock.Mock()
+
+ class CustomBuildPy(OrigBuildPy):
+ """
+ Some projects have custom commands inheriting from `distutils`
+ """
+
+ def get_data_files(self):
+ using_custom_command_guard()
+ return super().get_data_files()
+
+ setup_attrs = {**SETUP_ATTRS, 'include_package_data': True}
+ assert setup_attrs['package_data']
+
+ dist = Distribution(setup_attrs)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Make sure we use the custom command
+ cmd.cmdclass = {'build_py': CustomBuildPy}
+ cmd.distribution.cmdclass = {'build_py': CustomBuildPy}
+ assert cmd.distribution.get_command_class('build_py') == CustomBuildPy
+
+ msg = "setuptools instead of distutils"
+ with quiet(), pytest.warns(SetuptoolsDeprecationWarning, match=msg):
+ cmd.run()
+
+ using_custom_command_guard.assert_called()
+ self.assert_package_data_in_manifest(cmd)
+
+ def test_setup_py_exists(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' in manifest
+
+ def test_setup_py_missing(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ if os.path.exists("setup.py"):
+ os.remove("setup.py")
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
+ def test_setup_py_excluded(self):
+ with open("MANIFEST.in", "w", encoding="utf-8") as manifest_file:
+ manifest_file.write("exclude setup.py")
+
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
+ def test_defaults_case_sensitivity(self, source_dir):
+ """
+ Make sure default files (README.*, etc.) are added in a case-sensitive
+ way to avoid problems with packages built on Windows.
+ """
+
+ touch(source_dir / 'readme.rst')
+ touch(source_dir / 'SETUP.cfg')
+
+ dist = Distribution(SETUP_ATTRS)
+ # the extension deliberately capitalized for this test
+ # to make sure the actual filename (not capitalized) gets added
+ # to the manifest
+ dist.script_name = 'setup.PY'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ # lowercase all names so we can test in a
+ # case-insensitive way to make sure the files
+ # are not included.
+ manifest = map(lambda x: x.lower(), cmd.filelist.files)
+ assert 'readme.rst' not in manifest, manifest
+ assert 'setup.py' not in manifest, manifest
+ assert 'setup.cfg' not in manifest, manifest
+
+ def test_exclude_dev_only_cache_folders(self, source_dir):
+ included = {
+ # Emulate problem in https://github.com/pypa/setuptools/issues/4601
+ "MANIFEST.in": (
+ "global-include LICEN[CS]E* COPYING* NOTICE* AUTHORS*\n"
+ "global-include *.txt\n"
+ ),
+ # For the sake of being conservative and limiting unforeseen side-effects
+ # we just exclude dev-only cache folders at the root of the repository:
+ "test/.venv/lib/python3.9/site-packages/bar-2.dist-info/AUTHORS.rst": "",
+ "src/.nox/py/lib/python3.12/site-packages/bar-2.dist-info/COPYING.txt": "",
+ "doc/.tox/default/lib/python3.11/site-packages/foo-4.dist-info/LICENSE": "",
+ # Let's test against false positives with similarly named files:
+ ".venv-requirements.txt": "",
+ ".tox-coveragerc.txt": "",
+ ".noxy/coveragerc.txt": "",
+ }
+
+ excluded = {
+ # .tox/.nox/.venv are well-know folders present at the root of Python repos
+ # and therefore should be excluded
+ ".tox/release/lib/python3.11/site-packages/foo-4.dist-info/LICENSE": "",
+ ".nox/py/lib/python3.12/site-packages/bar-2.dist-info/COPYING.txt": "",
+ ".venv/lib/python3.9/site-packages/bar-2.dist-info/AUTHORS.rst": "",
+ }
+
+ for file, content in {**excluded, **included}.items():
+ Path(source_dir, file).parent.mkdir(parents=True, exist_ok=True)
+ Path(source_dir, file).write_text(content, encoding="utf-8")
+
+ cmd = self.setup_with_extension()
+ self.assert_package_data_in_manifest(cmd)
+ manifest = {f.replace(os.sep, '/') for f in cmd.filelist.files}
+ for path in excluded:
+ assert os.path.exists(path)
+ assert path not in manifest, (path, manifest)
+ for path in included:
+ assert os.path.exists(path)
+ assert path in manifest, (path, manifest)
+
+ @fail_on_ascii
+ def test_manifest_is_written_with_utf8_encoding(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ # UTF-8 filename
+ filename = os.path.join('sdist_test', 'smörbröd.py')
+
+ # Must create the file or it will get stripped.
+ touch(filename)
+
+ # Add UTF-8 filename and write manifest
+ with quiet():
+ mm.run()
+ mm.filelist.append(filename)
+ mm.write_manifest()
+
+ contents = read_all_bytes(mm.manifest)
+
+ # The manifest should be UTF-8 encoded
+ u_contents = contents.decode('UTF-8')
+
+ # The manifest should contain the UTF-8 filename
+ assert posix(filename) in u_contents
+
+ @fail_on_ascii
+ def test_write_manifest_allows_utf8_filenames(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
+
+ # Must touch the file or risk removal
+ touch(filename)
+
+ # Add filename and write manifest
+ with quiet():
+ mm.run()
+ u_filename = filename.decode('utf-8')
+ mm.filelist.files.append(u_filename)
+ # Re-write manifest
+ mm.write_manifest()
+
+ contents = read_all_bytes(mm.manifest)
+
+ # The manifest should be UTF-8 encoded
+ contents.decode('UTF-8')
+
+ # The manifest should contain the UTF-8 filename
+ assert posix(filename) in contents
+
+ # The filelist should have been updated as well
+ assert u_filename in mm.filelist.files
+
+ @skip_under_xdist
+ def test_write_manifest_skips_non_utf8_filenames(self):
+ """
+ Files that cannot be encoded to UTF-8 (specifically, those that
+ weren't originally successfully decoded and have surrogate
+ escapes) should be omitted from the manifest.
+ See https://bitbucket.org/tarek/distribute/issue/303 for history.
+ """
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ # Latin-1 filename
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
+
+ # Add filename with surrogates and write manifest
+ with quiet():
+ mm.run()
+ u_filename = filename.decode('utf-8', 'surrogateescape')
+ mm.filelist.append(u_filename)
+ # Re-write manifest
+ mm.write_manifest()
+
+ contents = read_all_bytes(mm.manifest)
+
+ # The manifest should be UTF-8 encoded
+ contents.decode('UTF-8')
+
+ # The Latin-1 filename should have been skipped
+ assert posix(filename) not in contents
+
+ # The filelist should have been updated as well
+ assert u_filename not in mm.filelist.files
+
+ @fail_on_ascii
+ def test_manifest_is_read_with_utf8_encoding(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Create manifest
+ with quiet():
+ cmd.run()
+
+ # Add UTF-8 filename to manifest
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
+ cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ manifest = open(cmd.manifest, 'ab')
+ manifest.write(b'\n' + filename)
+ manifest.close()
+
+ # The file must exist to be included in the filelist
+ touch(filename)
+
+ # Re-read manifest
+ cmd.filelist.files = []
+ with quiet():
+ cmd.read_manifest()
+
+ # The filelist should contain the UTF-8 filename
+ filename = filename.decode('utf-8')
+ assert filename in cmd.filelist.files
+
+ @fail_on_latin1_encoded_filenames
+ def test_read_manifest_skips_non_utf8_filenames(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Create manifest
+ with quiet():
+ cmd.run()
+
+ # Add Latin-1 filename to manifest
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
+ cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ manifest = open(cmd.manifest, 'ab')
+ manifest.write(b'\n' + filename)
+ manifest.close()
+
+ # The file must exist to be included in the filelist
+ touch(filename)
+
+ # Re-read manifest
+ cmd.filelist.files = []
+ with quiet():
+ cmd.read_manifest()
+
+ # The Latin-1 filename should have been skipped
+ filename = filename.decode('latin-1')
+ assert filename not in cmd.filelist.files
+
+ @fail_on_ascii
+ @fail_on_latin1_encoded_filenames
+ def test_sdist_with_utf8_encoded_filename(self):
+ # Test for #303.
+ dist = Distribution(self.make_strings(SETUP_ATTRS))
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ filename = os.path.join(b'sdist_test', Filenames.utf_8)
+ touch(filename)
+
+ with quiet():
+ cmd.run()
+
+ if sys.platform == 'darwin':
+ filename = decompose(filename)
+
+ fs_enc = sys.getfilesystemencoding()
+
+ if sys.platform == 'win32':
+ if fs_enc == 'cp1252':
+ # Python mangles the UTF-8 filename
+ filename = filename.decode('cp1252')
+ assert filename in cmd.filelist.files
+ else:
+ filename = filename.decode('mbcs')
+ assert filename in cmd.filelist.files
+ else:
+ filename = filename.decode('utf-8')
+ assert filename in cmd.filelist.files
+
+ @classmethod
+ def make_strings(cls, item):
+ if isinstance(item, dict):
+ return {key: cls.make_strings(value) for key, value in item.items()}
+ if isinstance(item, list):
+ return list(map(cls.make_strings, item))
+ return str(item)
+
+ @fail_on_latin1_encoded_filenames
+ @skip_under_xdist
+ def test_sdist_with_latin1_encoded_filename(self):
+ # Test for #303.
+ dist = Distribution(self.make_strings(SETUP_ATTRS))
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Latin-1 filename
+ filename = os.path.join(b'sdist_test', Filenames.latin_1)
+ touch(filename)
+ assert os.path.isfile(filename)
+
+ with quiet():
+ cmd.run()
+
+ # not all windows systems have a default FS encoding of cp1252
+ if sys.platform == 'win32':
+ # Latin-1 is similar to Windows-1252 however
+ # on mbcs filesys it is not in latin-1 encoding
+ fs_enc = sys.getfilesystemencoding()
+ if fs_enc != 'mbcs':
+ fs_enc = 'latin-1'
+ filename = filename.decode(fs_enc)
+
+ assert filename in cmd.filelist.files
+ else:
+ # The Latin-1 filename should have been skipped
+ filename = filename.decode('latin-1')
+ assert filename not in cmd.filelist.files
+
+ _EXAMPLE_DIRECTIVES = {
+ "setup.cfg - long_description and version": """
+ [metadata]
+ name = testing
+ version = file: src/VERSION.txt
+ license_files = DOWHATYOUWANT
+ long_description = file: README.rst, USAGE.rst
+ """,
+ "pyproject.toml - static readme/license files and dynamic version": """
+ [project]
+ name = "testing"
+ readme = "USAGE.rst"
+ license-files = ["DOWHATYOUWANT"]
+ dynamic = ["version"]
+ [tool.setuptools.dynamic]
+ version = {file = ["src/VERSION.txt"]}
+ """,
+ "pyproject.toml - directive with str instead of list": """
+ [project]
+ name = "testing"
+ readme = "USAGE.rst"
+ license-files = ["DOWHATYOUWANT"]
+ dynamic = ["version"]
+ [tool.setuptools.dynamic]
+ version = {file = "src/VERSION.txt"}
+ """,
+ "pyproject.toml - deprecated license table with file entry": """
+ [project]
+ name = "testing"
+ readme = "USAGE.rst"
+ license = {file = "DOWHATYOUWANT"}
+ dynamic = ["version"]
+ [tool.setuptools.dynamic]
+ version = {file = "src/VERSION.txt"}
+ """,
+ }
+
+ @pytest.mark.parametrize("config", _EXAMPLE_DIRECTIVES.keys())
+ @pytest.mark.filterwarnings(
+ "ignore:.project.license. as a TOML table is deprecated"
+ )
+ def test_add_files_referenced_by_config_directives(self, source_dir, config):
+ config_file, _, _ = config.partition(" - ")
+ config_text = self._EXAMPLE_DIRECTIVES[config]
+ (source_dir / 'src').mkdir()
+ (source_dir / 'src/VERSION.txt').write_text("0.42", encoding="utf-8")
+ (source_dir / 'README.rst').write_text("hello world!", encoding="utf-8")
+ (source_dir / 'USAGE.rst').write_text("hello world!", encoding="utf-8")
+ (source_dir / 'DOWHATYOUWANT').write_text("hello world!", encoding="utf-8")
+ (source_dir / config_file).write_text(config_text, encoding="utf-8")
+
+ dist = Distribution({"packages": []})
+ dist.script_name = 'setup.py'
+ dist.parse_config_files()
+
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+
+ assert (
+ 'src/VERSION.txt' in cmd.filelist.files
+ or 'src\\VERSION.txt' in cmd.filelist.files
+ )
+ assert 'USAGE.rst' in cmd.filelist.files
+ assert 'DOWHATYOUWANT' in cmd.filelist.files
+ assert '/' not in cmd.filelist.files
+ assert '\\' not in cmd.filelist.files
+
+ def test_pyproject_toml_in_sdist(self, source_dir):
+ """
+ Check if pyproject.toml is included in source distribution if present
+ """
+ touch(source_dir / 'pyproject.toml')
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+ manifest = cmd.filelist.files
+ assert 'pyproject.toml' in manifest
+
+ def test_pyproject_toml_excluded(self, source_dir):
+ """
+ Check that pyproject.toml can excluded even if present
+ """
+ touch(source_dir / 'pyproject.toml')
+ with open('MANIFEST.in', 'w', encoding="utf-8") as mts:
+ print('exclude pyproject.toml', file=mts)
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+ manifest = cmd.filelist.files
+ assert 'pyproject.toml' not in manifest
+
+ def test_build_subcommand_source_files(self, source_dir):
+ touch(source_dir / '.myfile~')
+
+ # Sanity check: without custom commands file list should not be affected
+ dist = Distribution({**SETUP_ATTRS, "script_name": "setup.py"})
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+ manifest = cmd.filelist.files
+ assert '.myfile~' not in manifest
+
+ # Test: custom command should be able to augment file list
+ dist = Distribution({**SETUP_ATTRS, "script_name": "setup.py"})
+ build = dist.get_command_obj("build")
+ build.sub_commands = [*build.sub_commands, ("build_custom", None)]
+
+ class build_custom(Command):
+ def initialize_options(self): ...
+
+ def finalize_options(self): ...
+
+ def run(self): ...
+
+ def get_source_files(self):
+ return ['.myfile~']
+
+ dist.cmdclass.update(build_custom=build_custom)
+
+ cmd = sdist(dist)
+ cmd.use_defaults = True
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+ manifest = cmd.filelist.files
+ assert '.myfile~' in manifest
+
+ @pytest.mark.skipif("os.environ.get('SETUPTOOLS_USE_DISTUTILS') == 'stdlib'")
+ def test_build_base_pathlib(self, source_dir):
+ """
+ Ensure if build_base is a pathlib.Path, the build still succeeds.
+ """
+ dist = Distribution({
+ **SETUP_ATTRS,
+ "script_name": "setup.py",
+ "options": {"build": {"build_base": pathlib.Path('build')}},
+ })
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+ with quiet():
+ cmd.run()
+
+
+def test_default_revctrl():
+ """
+ When _default_revctrl was removed from the `setuptools.command.sdist`
+ module in 10.0, it broke some systems which keep an old install of
+ setuptools (Distribute) around. Those old versions require that the
+ setuptools package continue to implement that interface, so this
+ function provides that interface, stubbed. See #320 for details.
+
+ This interface must be maintained until Ubuntu 12.04 is no longer
+ supported (by Setuptools).
+ """
+ (ep,) = metadata.EntryPoints._from_text(
+ """
+ [setuptools.file_finders]
+ svn_cvs = setuptools.command.sdist:_default_revctrl
+ """
+ )
+ res = ep.load()
+ assert hasattr(res, '__iter__')
+
+
+class TestRegressions:
+ """
+ Can be removed/changed if the project decides to change how it handles symlinks
+ or external files.
+ """
+
+ @staticmethod
+ def files_for_symlink_in_extension_depends(tmp_path, dep_path):
+ return {
+ "external": {
+ "dir": {"file.h": ""},
+ },
+ "project": {
+ "setup.py": cleandoc(
+ f"""
+ from setuptools import Extension, setup
+ setup(
+ name="myproj",
+ version="42",
+ ext_modules=[
+ Extension(
+ "hello", sources=["hello.pyx"],
+ depends=[{dep_path!r}]
+ )
+ ],
+ )
+ """
+ ),
+ "hello.pyx": "",
+ "MANIFEST.in": "global-include *.h",
+ },
+ }
+
+ @pytest.mark.parametrize(
+ "dep_path", ("myheaders/dir/file.h", "myheaders/dir/../dir/file.h")
+ )
+ def test_symlink_in_extension_depends(self, monkeypatch, tmp_path, dep_path):
+ # Given a project with a symlinked dir and a "depends" targeting that dir
+ files = self.files_for_symlink_in_extension_depends(tmp_path, dep_path)
+ jaraco.path.build(files, prefix=str(tmp_path))
+ symlink_or_skip_test(tmp_path / "external", tmp_path / "project/myheaders")
+
+ # When `sdist` runs, there should be no error
+ members = run_sdist(monkeypatch, tmp_path / "project")
+ # and the sdist should contain the symlinked files
+ for expected in (
+ "myproj-42/hello.pyx",
+ "myproj-42/myheaders/dir/file.h",
+ ):
+ assert expected in members
+
+ @staticmethod
+ def files_for_external_path_in_extension_depends(tmp_path, dep_path):
+ head, _, tail = dep_path.partition("$tmp_path$/")
+ dep_path = tmp_path / tail if tail else head
+
+ return {
+ "external": {
+ "dir": {"file.h": ""},
+ },
+ "project": {
+ "setup.py": cleandoc(
+ f"""
+ from setuptools import Extension, setup
+ setup(
+ name="myproj",
+ version="42",
+ ext_modules=[
+ Extension(
+ "hello", sources=["hello.pyx"],
+ depends=[{str(dep_path)!r}]
+ )
+ ],
+ )
+ """
+ ),
+ "hello.pyx": "",
+ "MANIFEST.in": "global-include *.h",
+ },
+ }
+
+ @pytest.mark.parametrize(
+ "dep_path", ("$tmp_path$/external/dir/file.h", "../external/dir/file.h")
+ )
+ def test_external_path_in_extension_depends(self, monkeypatch, tmp_path, dep_path):
+ # Given a project with a "depends" targeting an external dir
+ files = self.files_for_external_path_in_extension_depends(tmp_path, dep_path)
+ jaraco.path.build(files, prefix=str(tmp_path))
+ # When `sdist` runs, there should be no error
+ members = run_sdist(monkeypatch, tmp_path / "project")
+ # and the sdist should not contain the external file
+ for name in members:
+ assert "file.h" not in name
+
+
+def run_sdist(monkeypatch, project):
+ """Given a project directory, run the sdist and return its contents"""
+ monkeypatch.chdir(project)
+ with quiet():
+ run_setup("setup.py", ["sdist"])
+
+ archive = next((project / "dist").glob("*.tar.gz"))
+ with tarfile.open(str(archive)) as tar:
+ return set(tar.getnames())
+
+
+def test_sanity_check_setuptools_own_sdist(setuptools_sdist):
+ with tarfile.open(setuptools_sdist) as tar:
+ files = tar.getnames()
+
+ # setuptools sdist should not include the .tox folder
+ tox_files = [name for name in files if ".tox" in name]
+ assert len(tox_files) == 0, f"not empty {tox_files}"
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_setopt.py b/lib/python3.12/site-packages/setuptools/tests/test_setopt.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccf25618a5d6e255ad0be0fbed51fc29c179dcfe
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_setopt.py
@@ -0,0 +1,40 @@
+import configparser
+
+from setuptools.command import setopt
+
+
+class TestEdit:
+ @staticmethod
+ def parse_config(filename):
+ parser = configparser.ConfigParser()
+ with open(filename, encoding='utf-8') as reader:
+ parser.read_file(reader)
+ return parser
+
+ @staticmethod
+ def write_text(file, content):
+ with open(file, 'wb') as strm:
+ strm.write(content.encode('utf-8'))
+
+ def test_utf8_encoding_retained(self, tmpdir):
+ """
+ When editing a file, non-ASCII characters encoded in
+ UTF-8 should be retained.
+ """
+ config = tmpdir.join('setup.cfg')
+ self.write_text(str(config), '[names]\njaraco=джарако')
+ setopt.edit_config(str(config), dict(names=dict(other='yes')))
+ parser = self.parse_config(str(config))
+ assert parser.get('names', 'jaraco') == 'джарако'
+ assert parser.get('names', 'other') == 'yes'
+
+ def test_case_retained(self, tmpdir):
+ """
+ When editing a file, case of keys should be retained.
+ """
+ config = tmpdir.join('setup.cfg')
+ self.write_text(str(config), '[names]\nFoO=bAr')
+ setopt.edit_config(str(config), dict(names=dict(oTher='yes')))
+ actual = config.read_text(encoding='ascii')
+ assert 'FoO' in actual
+ assert 'oTher' in actual
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_setuptools.py b/lib/python3.12/site-packages/setuptools/tests/test_setuptools.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d56e1a8a4ebc5c7aaeb9902ef9972f7de97ecbf
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_setuptools.py
@@ -0,0 +1,290 @@
+"""Tests for the 'setuptools' package"""
+
+import os
+import re
+import sys
+from zipfile import ZipFile
+
+import pytest
+from packaging.version import Version
+
+import setuptools
+import setuptools.depends as dep
+import setuptools.dist
+from setuptools.depends import Require
+
+import distutils.cmd
+import distutils.core
+from distutils.core import Extension
+from distutils.errors import DistutilsSetupError
+
+
+@pytest.fixture(autouse=True)
+def isolated_dir(tmpdir_cwd):
+ return
+
+
+def makeSetup(**args):
+ """Return distribution from 'setup(**args)', without executing commands"""
+
+ distutils.core._setup_stop_after = "commandline"
+
+ # Don't let system command line leak into tests!
+ args.setdefault('script_args', ['install'])
+
+ try:
+ return setuptools.setup(**args)
+ finally:
+ distutils.core._setup_stop_after = None
+
+
+needs_bytecode = pytest.mark.skipif(
+ not hasattr(dep, 'get_module_constant'),
+ reason="bytecode support not available",
+)
+
+
+class TestDepends:
+ def testExtractConst(self):
+ if not hasattr(dep, 'extract_constant'):
+ # skip on non-bytecode platforms
+ return
+
+ def f1():
+ global x, y, z
+ x = "test"
+ y = z # pyright: ignore[reportUnboundVariable] # Explicitly testing for this runtime issue
+
+ fc = f1.__code__
+
+ # unrecognized name
+ assert dep.extract_constant(fc, 'q', -1) is None
+
+ # constant assigned
+ assert dep.extract_constant(fc, 'x', -1) == "test"
+
+ # expression assigned
+ assert dep.extract_constant(fc, 'y', -1) == -1
+
+ # recognized name, not assigned
+ assert dep.extract_constant(fc, 'z', -1) is None
+
+ def testFindModule(self):
+ with pytest.raises(ImportError):
+ dep.find_module('no-such.-thing')
+ with pytest.raises(ImportError):
+ dep.find_module('setuptools.non-existent')
+ f, _p, _i = dep.find_module('setuptools.tests')
+ f.close()
+
+ @needs_bytecode
+ def testModuleExtract(self):
+ from json import __version__
+
+ assert dep.get_module_constant('json', '__version__') == __version__
+ assert dep.get_module_constant('sys', 'version') == sys.version
+ assert (
+ dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__')
+ == __doc__
+ )
+
+ @needs_bytecode
+ def testRequire(self):
+ req = Require('Json', '1.0.3', 'json')
+
+ assert req.name == 'Json'
+ assert req.module == 'json'
+ assert req.requested_version == Version('1.0.3')
+ assert req.attribute == '__version__'
+ assert req.full_name() == 'Json-1.0.3'
+
+ from json import __version__
+
+ assert str(req.get_version()) == __version__
+ assert req.version_ok('1.0.9')
+ assert not req.version_ok('0.9.1')
+ assert not req.version_ok('unknown')
+
+ assert req.is_present()
+ assert req.is_current()
+
+ req = Require('Do-what-I-mean', '1.0', 'd-w-i-m')
+ assert not req.is_present()
+ assert not req.is_current()
+
+ @needs_bytecode
+ def test_require_present(self):
+ # In #1896, this test was failing for months with the only
+ # complaint coming from test runners (not end users).
+ # TODO: Evaluate if this code is needed at all.
+ req = Require('Tests', None, 'tests', homepage="http://example.com")
+ assert req.format is None
+ assert req.attribute is None
+ assert req.requested_version is None
+ assert req.full_name() == 'Tests'
+ assert req.homepage == 'http://example.com'
+
+ from setuptools.tests import __path__
+
+ paths = [os.path.dirname(p) for p in __path__]
+ assert req.is_present(paths)
+ assert req.is_current(paths)
+
+
+class TestDistro:
+ def setup_method(self, method):
+ self.e1 = Extension('bar.ext', ['bar.c'])
+ self.e2 = Extension('c.y', ['y.c'])
+
+ self.dist = makeSetup(
+ packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
+ py_modules=['b.d', 'x'],
+ ext_modules=(self.e1, self.e2),
+ package_dir={},
+ )
+
+ def testDistroType(self):
+ assert isinstance(self.dist, setuptools.dist.Distribution)
+
+ def testExcludePackage(self):
+ self.dist.exclude_package('a')
+ assert self.dist.packages == ['b', 'c']
+
+ self.dist.exclude_package('b')
+ assert self.dist.packages == ['c']
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1, self.e2]
+
+ self.dist.exclude_package('c')
+ assert self.dist.packages == []
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1]
+
+ # test removals from unspecified options
+ makeSetup().exclude_package('x')
+
+ def testIncludeExclude(self):
+ # remove an extension
+ self.dist.exclude(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2]
+
+ # add it back in
+ self.dist.include(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2, self.e1]
+
+ # should not add duplicate
+ self.dist.include(ext_modules=[self.e1])
+ assert self.dist.ext_modules == [self.e2, self.e1]
+
+ def testExcludePackages(self):
+ self.dist.exclude(packages=['c', 'b', 'a'])
+ assert self.dist.packages == []
+ assert self.dist.py_modules == ['x']
+ assert self.dist.ext_modules == [self.e1]
+
+ def testEmpty(self):
+ dist = makeSetup()
+ dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+ dist = makeSetup()
+ dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+
+ def testContents(self):
+ assert self.dist.has_contents_for('a')
+ self.dist.exclude_package('a')
+ assert not self.dist.has_contents_for('a')
+
+ assert self.dist.has_contents_for('b')
+ self.dist.exclude_package('b')
+ assert not self.dist.has_contents_for('b')
+
+ assert self.dist.has_contents_for('c')
+ self.dist.exclude_package('c')
+ assert not self.dist.has_contents_for('c')
+
+ def testInvalidIncludeExclude(self):
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(nonexistent_option='x')
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(nonexistent_option='x')
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(packages={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(packages={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(ext_modules={'x': 'y'})
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(ext_modules={'x': 'y'})
+
+ with pytest.raises(DistutilsSetupError):
+ self.dist.include(package_dir=['q'])
+ with pytest.raises(DistutilsSetupError):
+ self.dist.exclude(package_dir=['q'])
+
+
+@pytest.fixture
+def example_source(tmpdir):
+ tmpdir.mkdir('foo')
+ (tmpdir / 'foo/bar.py').write('')
+ (tmpdir / 'readme.txt').write('')
+ return tmpdir
+
+
+def test_findall(example_source):
+ found = list(setuptools.findall(str(example_source)))
+ expected = ['readme.txt', 'foo/bar.py']
+ expected = [example_source.join(fn) for fn in expected]
+ assert found == expected
+
+
+def test_findall_curdir(example_source):
+ with example_source.as_cwd():
+ found = list(setuptools.findall())
+ expected = ['readme.txt', os.path.join('foo', 'bar.py')]
+ assert found == expected
+
+
+@pytest.fixture
+def can_symlink(tmpdir):
+ """
+ Skip if cannot create a symbolic link
+ """
+ link_fn = 'link'
+ target_fn = 'target'
+ try:
+ os.symlink(target_fn, link_fn)
+ except (OSError, NotImplementedError, AttributeError):
+ pytest.skip("Cannot create symbolic links")
+ os.remove(link_fn)
+
+
+@pytest.mark.usefixtures("can_symlink")
+def test_findall_missing_symlink(tmpdir):
+ with tmpdir.as_cwd():
+ os.symlink('foo', 'bar')
+ found = list(setuptools.findall())
+ assert found == []
+
+
+@pytest.mark.xfail(reason="unable to exclude tests; #4475 #3260")
+def test_its_own_wheel_does_not_contain_tests(setuptools_wheel):
+ with ZipFile(setuptools_wheel) as zipfile:
+ contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
+
+ for member in contents:
+ assert '/tests/' not in member
+
+
+def test_wheel_includes_cli_scripts(setuptools_wheel):
+ with ZipFile(setuptools_wheel) as zipfile:
+ contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
+
+ assert any('cli-64.exe' in member for member in contents)
+
+
+def test_wheel_includes_vendored_metadata(setuptools_wheel):
+ with ZipFile(setuptools_wheel) as zipfile:
+ contents = [f.replace(os.sep, '/') for f in zipfile.namelist()]
+
+ assert any(
+ re.search(r'_vendor/.*\.dist-info/METADATA', member) for member in contents
+ )
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_shutil_wrapper.py b/lib/python3.12/site-packages/setuptools/tests/test_shutil_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..74ff7e9a896328a3d57ca3639658e3b9d538585f
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_shutil_wrapper.py
@@ -0,0 +1,23 @@
+import stat
+import sys
+from unittest.mock import Mock
+
+from setuptools import _shutil
+
+
+def test_rmtree_readonly(monkeypatch, tmp_path):
+ """Verify onerr works as expected"""
+
+ tmp_dir = tmp_path / "with_readonly"
+ tmp_dir.mkdir()
+ some_file = tmp_dir.joinpath("file.txt")
+ some_file.touch()
+ some_file.chmod(stat.S_IREAD)
+
+ expected_count = 1 if sys.platform.startswith("win") else 0
+ chmod_fn = Mock(wraps=_shutil.attempt_chmod_verbose)
+ monkeypatch.setattr(_shutil, "attempt_chmod_verbose", chmod_fn)
+
+ _shutil.rmtree(tmp_dir)
+ assert chmod_fn.call_count == expected_count
+ assert not tmp_dir.is_dir()
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_unicode_utils.py b/lib/python3.12/site-packages/setuptools/tests/test_unicode_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a24a9bd5305d1de7c1c925466bb7d222c85864a7
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_unicode_utils.py
@@ -0,0 +1,10 @@
+from setuptools import unicode_utils
+
+
+def test_filesys_decode_fs_encoding_is_None(monkeypatch):
+ """
+ Test filesys_decode does not raise TypeError when
+ getfilesystemencoding returns None.
+ """
+ monkeypatch.setattr('sys.getfilesystemencoding', lambda: None)
+ unicode_utils.filesys_decode(b'test')
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_virtualenv.py b/lib/python3.12/site-packages/setuptools/tests/test_virtualenv.py
new file mode 100644
index 0000000000000000000000000000000000000000..b02949baf9cef8eb4df9c697add28f79a93b64d4
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_virtualenv.py
@@ -0,0 +1,113 @@
+import os
+import subprocess
+import sys
+from urllib.error import URLError
+from urllib.request import urlopen
+
+import pytest
+
+
+@pytest.fixture(autouse=True)
+def pytest_virtualenv_works(venv):
+ """
+ pytest_virtualenv may not work. if it doesn't, skip these
+ tests. See #1284.
+ """
+ venv_prefix = venv.run(["python", "-c", "import sys; print(sys.prefix)"]).strip()
+ if venv_prefix == sys.prefix:
+ pytest.skip("virtualenv is broken (see pypa/setuptools#1284)")
+
+
+def test_clean_env_install(venv_without_setuptools, setuptools_wheel):
+ """
+ Check setuptools can be installed in a clean environment.
+ """
+ cmd = ["python", "-m", "pip", "install", str(setuptools_wheel)]
+ venv_without_setuptools.run(cmd)
+
+
+def access_pypi():
+ # Detect if tests are being run without connectivity
+ if not os.environ.get('NETWORK_REQUIRED', False): # pragma: nocover
+ try:
+ urlopen('https://pypi.org', timeout=1)
+ except URLError:
+ # No network, disable most of these tests
+ return False
+
+ return True
+
+
+@pytest.mark.skipif(
+ 'platform.python_implementation() == "PyPy"',
+ reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995",
+)
+@pytest.mark.skipif(not access_pypi(), reason="no network")
+# ^-- Even when it is not necessary to install a different version of `pip`
+# the build process will still try to download `wheel`, see #3147 and #2986.
+@pytest.mark.parametrize(
+ 'pip_version',
+ [
+ None,
+ pytest.param(
+ 'pip<20.1',
+ marks=pytest.mark.xfail(
+ 'sys.version_info >= (3, 12)',
+ reason="pip 23.1.2 required for Python 3.12 and later",
+ ),
+ ),
+ pytest.param(
+ 'pip<21',
+ marks=pytest.mark.xfail(
+ 'sys.version_info >= (3, 12)',
+ reason="pip 23.1.2 required for Python 3.12 and later",
+ ),
+ ),
+ pytest.param(
+ 'pip<22',
+ marks=pytest.mark.xfail(
+ 'sys.version_info >= (3, 12)',
+ reason="pip 23.1.2 required for Python 3.12 and later",
+ ),
+ ),
+ pytest.param(
+ 'pip<23',
+ marks=pytest.mark.xfail(
+ 'sys.version_info >= (3, 12)',
+ reason="pip 23.1.2 required for Python 3.12 and later",
+ ),
+ ),
+ pytest.param(
+ 'https://github.com/pypa/pip/archive/main.zip',
+ marks=pytest.mark.xfail(reason='#2975'),
+ ),
+ ],
+)
+def test_pip_upgrade_from_source(
+ pip_version, venv_without_setuptools, setuptools_wheel, setuptools_sdist
+):
+ """
+ Check pip can upgrade setuptools from source.
+ """
+ # Install pip/wheel, in a venv without setuptools (as it
+ # should not be needed for bootstrapping from source)
+ venv = venv_without_setuptools
+ venv.run(["pip", "install", "-U", "wheel"])
+ if pip_version is not None:
+ venv.run(["python", "-m", "pip", "install", "-U", pip_version, "--retries=1"])
+ with pytest.raises(subprocess.CalledProcessError):
+ # Meta-test to make sure setuptools is not installed
+ venv.run(["python", "-c", "import setuptools"])
+
+ # Then install from wheel.
+ venv.run(["pip", "install", str(setuptools_wheel)])
+ # And finally try to upgrade from source.
+ venv.run(["pip", "install", "--no-cache-dir", "--upgrade", str(setuptools_sdist)])
+
+
+def test_no_missing_dependencies(bare_venv, request):
+ """
+ Quick and dirty test to ensure all external dependencies are vendored.
+ """
+ setuptools_dir = request.config.rootdir
+ bare_venv.run(['python', 'setup.py', '--help'], cwd=setuptools_dir)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_warnings.py b/lib/python3.12/site-packages/setuptools/tests/test_warnings.py
new file mode 100644
index 0000000000000000000000000000000000000000..41193d4f717344546f8c70ad18b268a04740129b
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_warnings.py
@@ -0,0 +1,106 @@
+from inspect import cleandoc
+
+import pytest
+
+from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
+
+_EXAMPLES = {
+ "default": dict(
+ args=("Hello {x}", "\n\t{target} {v:.1f}"),
+ kwargs={"x": 5, "v": 3, "target": "World"},
+ expected="""
+ Hello 5
+ !!
+
+ ********************************************************************************
+ World 3.0
+ ********************************************************************************
+
+ !!
+ """,
+ ),
+ "futue_due_date": dict(
+ args=("Summary", "Lorem ipsum"),
+ kwargs={"due_date": (9999, 11, 22)},
+ expected="""
+ Summary
+ !!
+
+ ********************************************************************************
+ Lorem ipsum
+
+ By 9999-Nov-22, you need to update your project and remove deprecated calls
+ or your builds will no longer be supported.
+ ********************************************************************************
+
+ !!
+ """,
+ ),
+ "past_due_date_with_docs": dict(
+ args=("Summary", "Lorem ipsum"),
+ kwargs={"due_date": (2000, 11, 22), "see_docs": "some_page.html"},
+ expected="""
+ Summary
+ !!
+
+ ********************************************************************************
+ Lorem ipsum
+
+ This deprecation is overdue, please update your project and remove deprecated
+ calls to avoid build errors in the future.
+
+ See https://setuptools.pypa.io/en/latest/some_page.html for details.
+ ********************************************************************************
+
+ !!
+ """,
+ ),
+}
+
+
+@pytest.mark.parametrize("example_name", _EXAMPLES.keys())
+def test_formatting(monkeypatch, example_name):
+ """
+ It should automatically handle indentation, interpolation and things like due date.
+ """
+ args = _EXAMPLES[example_name]["args"]
+ kwargs = _EXAMPLES[example_name]["kwargs"]
+ expected = _EXAMPLES[example_name]["expected"]
+
+ monkeypatch.setenv("SETUPTOOLS_ENFORCE_DEPRECATION", "false")
+ with pytest.warns(SetuptoolsWarning) as warn_info:
+ SetuptoolsWarning.emit(*args, **kwargs)
+ assert _get_message(warn_info) == cleandoc(expected)
+
+
+def test_due_date_enforcement(monkeypatch):
+ class _MyDeprecation(SetuptoolsDeprecationWarning):
+ _SUMMARY = "Summary"
+ _DETAILS = "Lorem ipsum"
+ _DUE_DATE = (2000, 11, 22)
+ _SEE_DOCS = "some_page.html"
+
+ monkeypatch.setenv("SETUPTOOLS_ENFORCE_DEPRECATION", "true")
+ with pytest.raises(SetuptoolsDeprecationWarning) as exc_info:
+ _MyDeprecation.emit()
+
+ expected = """
+ Summary
+ !!
+
+ ********************************************************************************
+ Lorem ipsum
+
+ This deprecation is overdue, please update your project and remove deprecated
+ calls to avoid build errors in the future.
+
+ See https://setuptools.pypa.io/en/latest/some_page.html for details.
+ ********************************************************************************
+
+ !!
+ """
+ assert str(exc_info.value) == cleandoc(expected)
+
+
+def _get_message(warn_info):
+ return next(warn.message.args[0] for warn in warn_info)
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_wheel.py b/lib/python3.12/site-packages/setuptools/tests/test_wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3b215a4744c8118f2667c8a6cc1e1a2ebdce138
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_wheel.py
@@ -0,0 +1,690 @@
+"""wheel tests"""
+
+from __future__ import annotations
+
+import contextlib
+import glob
+import inspect
+import os
+import pathlib
+import stat
+import subprocess
+import sys
+import sysconfig
+import zipfile
+from typing import Any
+
+import pytest
+from jaraco import path
+from packaging.tags import parse_tag
+
+from setuptools._importlib import metadata
+from setuptools.wheel import Wheel
+
+from .contexts import tempdir
+from .textwrap import DALS
+
+from distutils.sysconfig import get_config_var
+from distutils.util import get_platform
+
+WHEEL_INFO_TESTS = (
+ ('invalid.whl', ValueError),
+ (
+ 'simplewheel-2.0-1-py2.py3-none-any.whl',
+ {
+ 'project_name': 'simplewheel',
+ 'version': '2.0',
+ 'build': '1',
+ 'py_version': 'py2.py3',
+ 'abi': 'none',
+ 'platform': 'any',
+ },
+ ),
+ (
+ 'simple.dist-0.1-py2.py3-none-any.whl',
+ {
+ 'project_name': 'simple.dist',
+ 'version': '0.1',
+ 'build': None,
+ 'py_version': 'py2.py3',
+ 'abi': 'none',
+ 'platform': 'any',
+ },
+ ),
+ (
+ 'example_pkg_a-1-py3-none-any.whl',
+ {
+ 'project_name': 'example_pkg_a',
+ 'version': '1',
+ 'build': None,
+ 'py_version': 'py3',
+ 'abi': 'none',
+ 'platform': 'any',
+ },
+ ),
+ (
+ 'PyQt5-5.9-5.9.1-cp35.cp36.cp37-abi3-manylinux1_x86_64.whl',
+ {
+ 'project_name': 'PyQt5',
+ 'version': '5.9',
+ 'build': '5.9.1',
+ 'py_version': 'cp35.cp36.cp37',
+ 'abi': 'abi3',
+ 'platform': 'manylinux1_x86_64',
+ },
+ ),
+)
+
+
+@pytest.mark.parametrize(
+ ('filename', 'info'), WHEEL_INFO_TESTS, ids=[t[0] for t in WHEEL_INFO_TESTS]
+)
+def test_wheel_info(filename, info):
+ if inspect.isclass(info):
+ with pytest.raises(info):
+ Wheel(filename)
+ return
+ w = Wheel(filename)
+ assert {k: getattr(w, k) for k in info.keys()} == info
+
+
+@contextlib.contextmanager
+def build_wheel(extra_file_defs=None, **kwargs):
+ file_defs = {
+ 'setup.py': (
+ DALS(
+ """
+ # -*- coding: utf-8 -*-
+ from setuptools import setup
+ import setuptools
+ setup(**%r)
+ """
+ )
+ % kwargs
+ ).encode('utf-8'),
+ }
+ if extra_file_defs:
+ file_defs.update(extra_file_defs)
+ with tempdir() as source_dir:
+ path.build(file_defs, source_dir)
+ subprocess.check_call(
+ (sys.executable, 'setup.py', '-q', 'bdist_wheel'), cwd=source_dir
+ )
+ yield glob.glob(os.path.join(source_dir, 'dist', '*.whl'))[0]
+
+
+def tree_set(root):
+ return {
+ os.path.join(os.path.relpath(dirpath, root), filename)
+ for dirpath, dirnames, filenames in os.walk(root)
+ for filename in filenames
+ }
+
+
+def flatten_tree(tree):
+ """Flatten nested dicts and lists into a full list of paths"""
+ output = set()
+ for node, contents in tree.items():
+ if isinstance(contents, dict):
+ contents = flatten_tree(contents)
+
+ for elem in contents:
+ if isinstance(elem, dict):
+ output |= {os.path.join(node, val) for val in flatten_tree(elem)}
+ else:
+ output.add(os.path.join(node, elem))
+ return output
+
+
+def format_install_tree(tree):
+ return {
+ x.format(
+ py_version=sysconfig.get_python_version(),
+ platform=get_platform(),
+ shlib_ext=get_config_var('EXT_SUFFIX') or get_config_var('SO'),
+ )
+ for x in tree
+ }
+
+
+def _check_wheel_install(
+ filename, install_dir, install_tree_includes, project_name, version, requires_txt
+):
+ w = Wheel(filename)
+ egg_path = os.path.join(install_dir, w.egg_name())
+ w.install_as_egg(egg_path)
+ if install_tree_includes is not None:
+ install_tree = format_install_tree(install_tree_includes)
+ exp = tree_set(install_dir)
+ assert install_tree.issubset(exp), install_tree - exp
+
+ (dist,) = metadata.Distribution.discover(path=[egg_path])
+
+ # pyright is nitpicky; fine to assume dist.metadata.__getitem__ will fail or return None
+ # (https://github.com/pypa/setuptools/pull/5006#issuecomment-2894774288)
+ assert dist.metadata['Name'] == project_name # pyright: ignore # noqa: PGH003
+ assert dist.metadata['Version'] == version # pyright: ignore # noqa: PGH003
+ assert dist.read_text('requires.txt') == requires_txt
+
+
+class Record:
+ def __init__(self, id, **kwargs) -> None:
+ self._id = id
+ self._fields = kwargs
+
+ def __repr__(self) -> str:
+ return f'{self._id}(**{self._fields!r})'
+
+
+# Using Any to avoid possible type union issues later in test
+# making a TypedDict is not worth in a test and anonymous/inline TypedDict are experimental
+# https://github.com/python/mypy/issues/9884
+WHEEL_INSTALL_TESTS: tuple[dict[str, Any], ...] = (
+ dict(
+ id='basic',
+ file_defs={'foo': {'__init__.py': ''}},
+ setup_kwargs=dict(
+ packages=['foo'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': ['PKG-INFO', 'RECORD', 'WHEEL', 'top_level.txt'],
+ 'foo': ['__init__.py'],
+ }
+ }),
+ ),
+ dict(
+ id='utf-8',
+ setup_kwargs=dict(
+ description='Description accentuée',
+ ),
+ ),
+ dict(
+ id='data',
+ file_defs={
+ 'data.txt': DALS(
+ """
+ Some data...
+ """
+ ),
+ },
+ setup_kwargs=dict(
+ data_files=[('data_dir', ['data.txt'])],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': ['PKG-INFO', 'RECORD', 'WHEEL', 'top_level.txt'],
+ 'data_dir': ['data.txt'],
+ }
+ }),
+ ),
+ dict(
+ id='extension',
+ file_defs={
+ 'extension.c': DALS(
+ """
+ #include "Python.h"
+
+ #if PY_MAJOR_VERSION >= 3
+
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "extension",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ };
+
+ #define INITERROR return NULL
+
+ PyMODINIT_FUNC PyInit_extension(void)
+
+ #else
+
+ #define INITERROR return
+
+ void initextension(void)
+
+ #endif
+ {
+ #if PY_MAJOR_VERSION >= 3
+ PyObject *module = PyModule_Create(&moduledef);
+ #else
+ PyObject *module = Py_InitModule("extension", NULL);
+ #endif
+ if (module == NULL)
+ INITERROR;
+ #if PY_MAJOR_VERSION >= 3
+ return module;
+ #endif
+ }
+ """
+ ),
+ },
+ setup_kwargs=dict(
+ ext_modules=[
+ Record(
+ 'setuptools.Extension', name='extension', sources=['extension.c']
+ )
+ ],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}-{platform}.egg': [
+ 'extension{shlib_ext}',
+ {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'top_level.txt',
+ ]
+ },
+ ]
+ }),
+ ),
+ dict(
+ id='header',
+ file_defs={
+ 'header.h': DALS(
+ """
+ """
+ ),
+ },
+ setup_kwargs=dict(
+ headers=['header.h'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': [
+ 'header.h',
+ {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'top_level.txt',
+ ]
+ },
+ ]
+ }),
+ ),
+ dict(
+ id='script',
+ file_defs={
+ 'script.py': DALS(
+ """
+ #/usr/bin/python
+ print('hello world!')
+ """
+ ),
+ 'script.sh': DALS(
+ """
+ #/bin/sh
+ echo 'hello world!'
+ """
+ ),
+ },
+ setup_kwargs=dict(
+ scripts=['script.py', 'script.sh'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'top_level.txt',
+ {'scripts': ['script.py', 'script.sh']},
+ ]
+ }
+ }),
+ ),
+ dict(
+ id='requires1',
+ install_requires='foobar==2.0',
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'requires.txt',
+ 'top_level.txt',
+ ]
+ }
+ }),
+ requires_txt=DALS(
+ """
+ foobar==2.0
+ """
+ ),
+ ),
+ dict(
+ id='requires2',
+ install_requires=f"""
+ bar
+ foo<=2.0; {sys.platform!r} in sys_platform
+ """,
+ requires_txt=DALS(
+ """
+ bar
+ foo<=2.0
+ """
+ ),
+ ),
+ dict(
+ id='requires3',
+ install_requires=f"""
+ bar; {sys.platform!r} != sys_platform
+ """,
+ ),
+ dict(
+ id='requires4',
+ install_requires="""
+ foo
+ """,
+ extras_require={
+ 'extra': 'foobar>3',
+ },
+ requires_txt=DALS(
+ """
+ foo
+
+ [extra]
+ foobar>3
+ """
+ ),
+ ),
+ dict(
+ id='requires5',
+ extras_require={
+ 'extra': f'foobar; {sys.platform!r} != sys_platform',
+ },
+ requires_txt='\n'
+ + DALS(
+ """
+ [extra]
+ """
+ ),
+ ),
+ dict(
+ id='requires_ensure_order',
+ install_requires="""
+ foo
+ bar
+ baz
+ qux
+ """,
+ extras_require={
+ 'extra': """
+ foobar>3
+ barbaz>4
+ bazqux>5
+ quxzap>6
+ """,
+ },
+ requires_txt=DALS(
+ """
+ foo
+ bar
+ baz
+ qux
+
+ [extra]
+ foobar>3
+ barbaz>4
+ bazqux>5
+ quxzap>6
+ """
+ ),
+ ),
+ dict(
+ id='namespace_package',
+ file_defs={
+ 'foo': {
+ 'bar': {'__init__.py': ''},
+ },
+ },
+ setup_kwargs=dict(
+ namespace_packages=['foo'],
+ packages=['foo.bar'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': [
+ 'foo-1.0-py{py_version}-nspkg.pth',
+ {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'namespace_packages.txt',
+ 'top_level.txt',
+ ]
+ },
+ {
+ 'foo': [
+ '__init__.py',
+ {'bar': ['__init__.py']},
+ ]
+ },
+ ]
+ }),
+ ),
+ dict(
+ id='empty_namespace_package',
+ file_defs={
+ 'foobar': {
+ '__init__.py': (
+ "__import__('pkg_resources').declare_namespace(__name__)"
+ )
+ },
+ },
+ setup_kwargs=dict(
+ namespace_packages=['foobar'],
+ packages=['foobar'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': [
+ 'foo-1.0-py{py_version}-nspkg.pth',
+ {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'namespace_packages.txt',
+ 'top_level.txt',
+ ]
+ },
+ {
+ 'foobar': [
+ '__init__.py',
+ ]
+ },
+ ]
+ }),
+ ),
+ dict(
+ id='data_in_package',
+ file_defs={
+ 'foo': {
+ '__init__.py': '',
+ 'data_dir': {
+ 'data.txt': DALS(
+ """
+ Some data...
+ """
+ ),
+ },
+ }
+ },
+ setup_kwargs=dict(
+ packages=['foo'],
+ data_files=[('foo/data_dir', ['foo/data_dir/data.txt'])],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'top_level.txt',
+ ],
+ 'foo': [
+ '__init__.py',
+ {
+ 'data_dir': [
+ 'data.txt',
+ ]
+ },
+ ],
+ }
+ }),
+ ),
+)
+
+
+@pytest.mark.parametrize(
+ 'params',
+ WHEEL_INSTALL_TESTS,
+ ids=[params['id'] for params in WHEEL_INSTALL_TESTS],
+)
+def test_wheel_install(params):
+ project_name = params.get('name', 'foo')
+ version = params.get('version', '1.0')
+ install_requires = params.get('install_requires', [])
+ extras_require = params.get('extras_require', {})
+ requires_txt = params.get('requires_txt', None)
+ install_tree = params.get('install_tree')
+ file_defs = params.get('file_defs', {})
+ setup_kwargs = params.get('setup_kwargs', {})
+ with (
+ build_wheel(
+ name=project_name,
+ version=version,
+ install_requires=install_requires,
+ extras_require=extras_require,
+ extra_file_defs=file_defs,
+ **setup_kwargs,
+ ) as filename,
+ tempdir() as install_dir,
+ ):
+ _check_wheel_install(
+ filename, install_dir, install_tree, project_name, version, requires_txt
+ )
+
+
+def test_wheel_no_dist_dir():
+ project_name = 'nodistinfo'
+ version = '1.0'
+ wheel_name = f'{project_name}-{version}-py2.py3-none-any.whl'
+ with tempdir() as source_dir:
+ wheel_path = os.path.join(source_dir, wheel_name)
+ # create an empty zip file
+ zipfile.ZipFile(wheel_path, 'w').close()
+ with tempdir() as install_dir:
+ with pytest.raises(ValueError):
+ _check_wheel_install(
+ wheel_path, install_dir, None, project_name, version, None
+ )
+
+
+def test_wheel_is_compatible(monkeypatch):
+ def sys_tags():
+ return {
+ (t.interpreter, t.abi, t.platform)
+ for t in parse_tag('cp36-cp36m-manylinux1_x86_64')
+ }
+
+ monkeypatch.setattr('setuptools.wheel._get_supported_tags', sys_tags)
+ assert Wheel('onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible()
+
+
+def test_wheel_mode():
+ @contextlib.contextmanager
+ def build_wheel(extra_file_defs=None, **kwargs):
+ file_defs = {
+ 'setup.py': (
+ DALS(
+ """
+ # -*- coding: utf-8 -*-
+ from setuptools import setup
+ import setuptools
+ setup(**%r)
+ """
+ )
+ % kwargs
+ ).encode('utf-8'),
+ }
+ if extra_file_defs:
+ file_defs.update(extra_file_defs)
+ with tempdir() as source_dir:
+ path.build(file_defs, source_dir)
+ runsh = pathlib.Path(source_dir) / "script.sh"
+ os.chmod(runsh, 0o777)
+ subprocess.check_call(
+ (sys.executable, 'setup.py', '-q', 'bdist_wheel'), cwd=source_dir
+ )
+ yield glob.glob(os.path.join(source_dir, 'dist', '*.whl'))[0]
+
+ params = dict(
+ id='script',
+ file_defs={
+ 'script.py': DALS(
+ """
+ #/usr/bin/python
+ print('hello world!')
+ """
+ ),
+ 'script.sh': DALS(
+ """
+ #/bin/sh
+ echo 'hello world!'
+ """
+ ),
+ },
+ setup_kwargs=dict(
+ scripts=['script.py', 'script.sh'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': {
+ 'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'top_level.txt',
+ {'scripts': ['script.py', 'script.sh']},
+ ]
+ }
+ }),
+ )
+
+ project_name = params.get('name', 'foo')
+ version = params.get('version', '1.0')
+ install_tree = params.get('install_tree')
+ file_defs = params.get('file_defs', {})
+ setup_kwargs = params.get('setup_kwargs', {})
+
+ with (
+ build_wheel(
+ name=project_name,
+ version=version,
+ install_requires=[],
+ extras_require={},
+ extra_file_defs=file_defs,
+ **setup_kwargs,
+ ) as filename,
+ tempdir() as install_dir,
+ ):
+ _check_wheel_install(
+ filename, install_dir, install_tree, project_name, version, None
+ )
+ w = Wheel(filename)
+ base = pathlib.Path(install_dir) / w.egg_name()
+ script_sh = base / "EGG-INFO" / "scripts" / "script.sh"
+ assert script_sh.exists()
+ if sys.platform != 'win32':
+ # Editable file mode has no effect on Windows
+ assert oct(stat.S_IMODE(script_sh.stat().st_mode)) == "0o777"
diff --git a/lib/python3.12/site-packages/setuptools/tests/test_windows_wrappers.py b/lib/python3.12/site-packages/setuptools/tests/test_windows_wrappers.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f990eb1c3cc67c61ea17f791430651e91040f18
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/test_windows_wrappers.py
@@ -0,0 +1,258 @@
+"""
+Python Script Wrapper for Windows
+=================================
+
+setuptools includes wrappers for Python scripts that allows them to be
+executed like regular windows programs. There are 2 wrappers, one
+for command-line programs, cli.exe, and one for graphical programs,
+gui.exe. These programs are almost identical, function pretty much
+the same way, and are generated from the same source file. The
+wrapper programs are used by copying them to the directory containing
+the script they are to wrap and with the same name as the script they
+are to wrap.
+"""
+
+import pathlib
+import platform
+import subprocess
+import sys
+import textwrap
+
+import pytest
+
+from setuptools._importlib import resources
+
+pytestmark = pytest.mark.skipif(sys.platform != 'win32', reason="Windows only")
+
+
+class WrapperTester:
+ @classmethod
+ def prep_script(cls, template):
+ python_exe = subprocess.list2cmdline([sys.executable])
+ return template % locals()
+
+ @classmethod
+ def create_script(cls, tmpdir):
+ """
+ Create a simple script, foo-script.py
+
+ Note that the script starts with a Unix-style '#!' line saying which
+ Python executable to run. The wrapper will use this line to find the
+ correct Python executable.
+ """
+
+ script = cls.prep_script(cls.script_tmpl)
+
+ with (tmpdir / cls.script_name).open('w') as f:
+ f.write(script)
+
+ # also copy cli.exe to the sample directory
+ with (tmpdir / cls.wrapper_name).open('wb') as f:
+ w = resources.files('setuptools').joinpath(cls.wrapper_source).read_bytes()
+ f.write(w)
+
+
+def win_launcher_exe(prefix):
+ """A simple routine to select launcher script based on platform."""
+ assert prefix in ('cli', 'gui')
+ if platform.machine() == "ARM64":
+ return f"{prefix}-arm64.exe"
+ else:
+ return f"{prefix}-32.exe"
+
+
+class TestCLI(WrapperTester):
+ script_name = 'foo-script.py'
+ wrapper_name = 'foo.exe'
+ wrapper_source = win_launcher_exe('cli')
+
+ script_tmpl = textwrap.dedent(
+ """
+ #!%(python_exe)s
+ import sys
+ input = repr(sys.stdin.read())
+ print(sys.argv[0][-14:])
+ print(sys.argv[1:])
+ print(input)
+ if __debug__:
+ print('non-optimized')
+ """
+ ).lstrip()
+
+ def test_basic(self, tmpdir):
+ """
+ When the copy of cli.exe, foo.exe in this example, runs, it examines
+ the path name it was run with and computes a Python script path name
+ by removing the '.exe' suffix and adding the '-script.py' suffix. (For
+ GUI programs, the suffix '-script.pyw' is added.) This is why we
+ named out script the way we did. Now we can run out script by running
+ the wrapper:
+
+ This example was a little pathological in that it exercised windows
+ (MS C runtime) quoting rules:
+
+ - Strings containing spaces are surrounded by double quotes.
+
+ - Double quotes in strings need to be escaped by preceding them with
+ back slashes.
+
+ - One or more backslashes preceding double quotes need to be escaped
+ by preceding each of them with back slashes.
+ """
+ self.create_script(tmpdir)
+ cmd = [
+ str(tmpdir / 'foo.exe'),
+ 'arg1',
+ 'arg 2',
+ 'arg "2\\"',
+ 'arg 4\\',
+ 'arg5 a\\\\b',
+ ]
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ text=True,
+ encoding="utf-8",
+ )
+ stdout, _stderr = proc.communicate('hello\nworld\n')
+ actual = stdout.replace('\r\n', '\n')
+ expected = textwrap.dedent(
+ r"""
+ \foo-script.py
+ ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ 'hello\nworld\n'
+ non-optimized
+ """
+ ).lstrip()
+ assert actual == expected
+
+ def test_symlink(self, tmpdir):
+ """
+ Ensure that symlink for the foo.exe is working correctly.
+ """
+ script_dir = tmpdir / "script_dir"
+ script_dir.mkdir()
+ self.create_script(script_dir)
+ symlink = pathlib.Path(tmpdir / "foo.exe")
+ symlink.symlink_to(script_dir / "foo.exe")
+
+ cmd = [
+ str(tmpdir / 'foo.exe'),
+ 'arg1',
+ 'arg 2',
+ 'arg "2\\"',
+ 'arg 4\\',
+ 'arg5 a\\\\b',
+ ]
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ text=True,
+ encoding="utf-8",
+ )
+ stdout, _stderr = proc.communicate('hello\nworld\n')
+ actual = stdout.replace('\r\n', '\n')
+ expected = textwrap.dedent(
+ r"""
+ \foo-script.py
+ ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ 'hello\nworld\n'
+ non-optimized
+ """
+ ).lstrip()
+ assert actual == expected
+
+ def test_with_options(self, tmpdir):
+ """
+ Specifying Python Command-line Options
+ --------------------------------------
+
+ You can specify a single argument on the '#!' line. This can be used
+ to specify Python options like -O, to run in optimized mode or -i
+ to start the interactive interpreter. You can combine multiple
+ options as usual. For example, to run in optimized mode and
+ enter the interpreter after running the script, you could use -Oi:
+ """
+ self.create_script(tmpdir)
+ tmpl = textwrap.dedent(
+ """
+ #!%(python_exe)s -Oi
+ import sys
+ input = repr(sys.stdin.read())
+ print(sys.argv[0][-14:])
+ print(sys.argv[1:])
+ print(input)
+ if __debug__:
+ print('non-optimized')
+ sys.ps1 = '---'
+ """
+ ).lstrip()
+ with (tmpdir / 'foo-script.py').open('w') as f:
+ f.write(self.prep_script(tmpl))
+ cmd = [str(tmpdir / 'foo.exe')]
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ encoding="utf-8",
+ )
+ stdout, _stderr = proc.communicate()
+ actual = stdout.replace('\r\n', '\n')
+ expected = textwrap.dedent(
+ r"""
+ \foo-script.py
+ []
+ ''
+ ---
+ """
+ ).lstrip()
+ assert actual == expected
+
+
+class TestGUI(WrapperTester):
+ """
+ Testing the GUI Version
+ -----------------------
+ """
+
+ script_name = 'bar-script.pyw'
+ wrapper_source = win_launcher_exe('gui')
+ wrapper_name = 'bar.exe'
+
+ script_tmpl = textwrap.dedent(
+ """
+ #!%(python_exe)s
+ import sys
+ f = open(sys.argv[1], 'wb')
+ bytes_written = f.write(repr(sys.argv[2]).encode('utf-8'))
+ f.close()
+ """
+ ).strip()
+
+ def test_basic(self, tmpdir):
+ """Test the GUI version with the simple script, bar-script.py"""
+ self.create_script(tmpdir)
+
+ cmd = [
+ str(tmpdir / 'bar.exe'),
+ str(tmpdir / 'test_output.txt'),
+ 'Test Argument',
+ ]
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ encoding="utf-8",
+ )
+ stdout, stderr = proc.communicate()
+ assert not stdout
+ assert not stderr
+ with (tmpdir / 'test_output.txt').open('rb') as f_out:
+ actual = f_out.read().decode('ascii')
+ assert actual == repr('Test Argument')
diff --git a/lib/python3.12/site-packages/setuptools/tests/text.py b/lib/python3.12/site-packages/setuptools/tests/text.py
new file mode 100644
index 0000000000000000000000000000000000000000..e05cc633ede9e5ce4f74b66a7bf76327c2000caa
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/text.py
@@ -0,0 +1,4 @@
+class Filenames:
+ unicode = 'smörbröd.py'
+ latin_1 = unicode.encode('latin-1')
+ utf_8 = unicode.encode('utf-8')
diff --git a/lib/python3.12/site-packages/setuptools/tests/textwrap.py b/lib/python3.12/site-packages/setuptools/tests/textwrap.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e39618dca4ad6c3f0d4c8cb20af59ab85fb0eba
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/tests/textwrap.py
@@ -0,0 +1,6 @@
+import textwrap
+
+
+def DALS(s):
+ "dedent and left-strip"
+ return textwrap.dedent(s).lstrip()
diff --git a/lib/python3.12/site-packages/setuptools/unicode_utils.py b/lib/python3.12/site-packages/setuptools/unicode_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..f502f5b089619eafd28e2c7a61967e34e16920e5
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/unicode_utils.py
@@ -0,0 +1,102 @@
+import sys
+import unicodedata
+from configparser import RawConfigParser
+
+from .compat import py39
+from .warnings import SetuptoolsDeprecationWarning
+
+
+# HFS Plus uses decomposed UTF-8
+def decompose(path):
+ if isinstance(path, str):
+ return unicodedata.normalize('NFD', path)
+ try:
+ path = path.decode('utf-8')
+ path = unicodedata.normalize('NFD', path)
+ path = path.encode('utf-8')
+ except UnicodeError:
+ pass # Not UTF-8
+ return path
+
+
+def filesys_decode(path):
+ """
+ Ensure that the given path is decoded,
+ ``None`` when no expected encoding works
+ """
+
+ if isinstance(path, str):
+ return path
+
+ fs_enc = sys.getfilesystemencoding() or 'utf-8'
+ candidates = fs_enc, 'utf-8'
+
+ for enc in candidates:
+ try:
+ return path.decode(enc)
+ except UnicodeDecodeError:
+ continue
+
+ return None
+
+
+def try_encode(string, enc):
+ "turn unicode encoding into a functional routine"
+ try:
+ return string.encode(enc)
+ except UnicodeEncodeError:
+ return None
+
+
+def _read_utf8_with_fallback(file: str, fallback_encoding=py39.LOCALE_ENCODING) -> str:
+ """
+ First try to read the file with UTF-8, if there is an error fallback to a
+ different encoding ("locale" by default). Returns the content of the file.
+ Also useful when reading files that might have been produced by an older version of
+ setuptools.
+ """
+ try:
+ with open(file, "r", encoding="utf-8") as f:
+ return f.read()
+ except UnicodeDecodeError: # pragma: no cover
+ _Utf8EncodingNeeded.emit(file=file, fallback_encoding=fallback_encoding)
+ with open(file, "r", encoding=fallback_encoding) as f:
+ return f.read()
+
+
+def _cfg_read_utf8_with_fallback(
+ cfg: RawConfigParser, file: str, fallback_encoding=py39.LOCALE_ENCODING
+) -> None:
+ """Same idea as :func:`_read_utf8_with_fallback`, but for the
+ :meth:`RawConfigParser.read` method.
+
+ This method may call ``cfg.clear()``.
+ """
+ try:
+ cfg.read(file, encoding="utf-8")
+ except UnicodeDecodeError: # pragma: no cover
+ _Utf8EncodingNeeded.emit(file=file, fallback_encoding=fallback_encoding)
+ cfg.clear()
+ cfg.read(file, encoding=fallback_encoding)
+
+
+class _Utf8EncodingNeeded(SetuptoolsDeprecationWarning):
+ _SUMMARY = """
+ `encoding="utf-8"` fails with {file!r}, trying `encoding={fallback_encoding!r}`.
+ """
+
+ _DETAILS = """
+ Fallback behavior for UTF-8 is considered **deprecated** and future versions of
+ `setuptools` may not implement it.
+
+ Please encode {file!r} with "utf-8" to ensure future builds will succeed.
+
+ If this file was produced by `setuptools` itself, cleaning up the cached files
+ and re-building/re-installing the package with a newer version of `setuptools`
+ (e.g. by updating `build-system.requires` in its `pyproject.toml`)
+ might solve the problem.
+ """
+ # TODO: Add a deadline?
+ # Will we be able to remove this?
+ # The question comes to mind mainly because of sdists that have been produced
+ # by old versions of setuptools and published to PyPI...
diff --git a/lib/python3.12/site-packages/setuptools/version.py b/lib/python3.12/site-packages/setuptools/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec253c414474677d3a5977511cfe901bfb786740
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/version.py
@@ -0,0 +1,6 @@
+from ._importlib import metadata
+
+try:
+ __version__ = metadata.version('setuptools') or '0.dev0+unknown'
+except Exception:
+ __version__ = '0.dev0+unknown'
diff --git a/lib/python3.12/site-packages/setuptools/warnings.py b/lib/python3.12/site-packages/setuptools/warnings.py
new file mode 100644
index 0000000000000000000000000000000000000000..96467787c237846bfbacf2d44eb833be0a88b633
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/warnings.py
@@ -0,0 +1,110 @@
+"""Provide basic warnings used by setuptools modules.
+
+Using custom classes (other than ``UserWarning``) allow users to set
+``PYTHONWARNINGS`` filters to run tests and prepare for upcoming changes in
+setuptools.
+"""
+
+from __future__ import annotations
+
+import os
+import warnings
+from datetime import date
+from inspect import cleandoc
+from textwrap import indent
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+_DueDate: TypeAlias = tuple[int, int, int] # time tuple
+_INDENT = 8 * " "
+_TEMPLATE = f"""{80 * '*'}\n{{details}}\n{80 * '*'}"""
+
+
+class SetuptoolsWarning(UserWarning):
+ """Base class in ``setuptools`` warning hierarchy."""
+
+ @classmethod
+ def emit(
+ cls,
+ summary: str | None = None,
+ details: str | None = None,
+ due_date: _DueDate | None = None,
+ see_docs: str | None = None,
+ see_url: str | None = None,
+ stacklevel: int = 2,
+ **kwargs,
+ ) -> None:
+ """Private: reserved for ``setuptools`` internal use only"""
+ # Default values:
+ summary_ = summary or getattr(cls, "_SUMMARY", None) or ""
+ details_ = details or getattr(cls, "_DETAILS", None) or ""
+ due_date = due_date or getattr(cls, "_DUE_DATE", None)
+ docs_ref = see_docs or getattr(cls, "_SEE_DOCS", None)
+ docs_url = docs_ref and f"https://setuptools.pypa.io/en/latest/{docs_ref}"
+ see_url = see_url or getattr(cls, "_SEE_URL", None)
+ due = date(*due_date) if due_date else None
+
+ text = cls._format(summary_, details_, due, see_url or docs_url, kwargs)
+ if due and due < date.today() and _should_enforce():
+ raise cls(text)
+ warnings.warn(text, cls, stacklevel=stacklevel + 1)
+
+ @classmethod
+ def _format(
+ cls,
+ summary: str,
+ details: str,
+ due_date: date | None = None,
+ see_url: str | None = None,
+ format_args: dict | None = None,
+ ) -> str:
+ """Private: reserved for ``setuptools`` internal use only"""
+ today = date.today()
+ summary = cleandoc(summary).format_map(format_args or {})
+ possible_parts = [
+ cleandoc(details).format_map(format_args or {}),
+ (
+ f"\nBy {due_date:%Y-%b-%d}, you need to update your project and remove "
+ "deprecated calls\nor your builds will no longer be supported."
+ if due_date and due_date > today
+ else None
+ ),
+ (
+ "\nThis deprecation is overdue, please update your project and remove "
+ "deprecated\ncalls to avoid build errors in the future."
+ if due_date and due_date < today
+ else None
+ ),
+ (f"\nSee {see_url} for details." if see_url else None),
+ ]
+ parts = [x for x in possible_parts if x]
+ if parts:
+ body = indent(_TEMPLATE.format(details="\n".join(parts)), _INDENT)
+ return "\n".join([summary, "!!\n", body, "\n!!"])
+ return summary
+
+
+class InformationOnly(SetuptoolsWarning):
+ """Currently there is no clear way of displaying messages to the users
+ that use the setuptools backend directly via ``pip``.
+ The only thing that might work is a warning, although it is not the
+ most appropriate tool for the job...
+
+ See pypa/packaging-problems#558.
+ """
+
+
+class SetuptoolsDeprecationWarning(SetuptoolsWarning):
+ """
+ Base class for warning deprecations in ``setuptools``
+
+ This class is not derived from ``DeprecationWarning``, and as such is
+ visible by default.
+ """
+
+
+def _should_enforce():
+ enforce = os.getenv("SETUPTOOLS_ENFORCE_DEPRECATION", "false").lower()
+ return enforce in ("true", "on", "ok", "1")
diff --git a/lib/python3.12/site-packages/setuptools/wheel.py b/lib/python3.12/site-packages/setuptools/wheel.py
new file mode 100644
index 0000000000000000000000000000000000000000..93663031541eba1b459f44bd81bc418c9e40decc
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/wheel.py
@@ -0,0 +1,262 @@
+"""Wheels support."""
+
+import contextlib
+import email
+import functools
+import itertools
+import os
+import posixpath
+import re
+import zipfile
+from collections.abc import Iterator
+
+from packaging.requirements import Requirement
+from packaging.tags import sys_tags
+from packaging.utils import canonicalize_name
+from packaging.version import Version as parse_version
+
+import setuptools
+from setuptools.archive_util import _unpack_zipfile_obj
+from setuptools.command.egg_info import _egg_basename, write_requirements
+
+from ._discovery import extras_from_deps
+from ._importlib import metadata
+from .unicode_utils import _read_utf8_with_fallback
+
+from distutils.util import get_platform
+
+WHEEL_NAME = re.compile(
+ r"""^(?P.+?)-(?P\d.*?)
+ ((-(?P\d.*?))?-(?P.+?)-(?P.+?)-(?P.+?)
+ )\.whl$""",
+ re.VERBOSE,
+).match
+
+NAMESPACE_PACKAGE_INIT = "__import__('pkg_resources').declare_namespace(__name__)\n"
+
+
+@functools.cache
+def _get_supported_tags():
+ # We calculate the supported tags only once, otherwise calling
+ # this method on thousands of wheels takes seconds instead of
+ # milliseconds.
+ return {(t.interpreter, t.abi, t.platform) for t in sys_tags()}
+
+
+def unpack(src_dir, dst_dir) -> None:
+ """Move everything under `src_dir` to `dst_dir`, and delete the former."""
+ for dirpath, dirnames, filenames in os.walk(src_dir):
+ subdir = os.path.relpath(dirpath, src_dir)
+ for f in filenames:
+ src = os.path.join(dirpath, f)
+ dst = os.path.join(dst_dir, subdir, f)
+ os.renames(src, dst)
+ for n, d in reversed(list(enumerate(dirnames))):
+ src = os.path.join(dirpath, d)
+ dst = os.path.join(dst_dir, subdir, d)
+ if not os.path.exists(dst):
+ # Directory does not exist in destination,
+ # rename it and prune it from os.walk list.
+ os.renames(src, dst)
+ del dirnames[n]
+ # Cleanup.
+ for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):
+ assert not filenames
+ os.rmdir(dirpath)
+
+
+@contextlib.contextmanager
+def disable_info_traces() -> Iterator[None]:
+ """
+ Temporarily disable info traces.
+ """
+ from distutils import log
+
+ saved = log.set_threshold(log.WARN)
+ try:
+ yield
+ finally:
+ log.set_threshold(saved)
+
+
+class Wheel:
+ def __init__(self, filename) -> None:
+ match = WHEEL_NAME(os.path.basename(filename))
+ if match is None:
+ raise ValueError(f'invalid wheel name: {filename!r}')
+ self.filename = filename
+ for k, v in match.groupdict().items():
+ setattr(self, k, v)
+
+ def tags(self):
+ """List tags (py_version, abi, platform) supported by this wheel."""
+ return itertools.product(
+ self.py_version.split('.'),
+ self.abi.split('.'),
+ self.platform.split('.'),
+ )
+
+ def is_compatible(self):
+ """Is the wheel compatible with the current platform?"""
+ return next((True for t in self.tags() if t in _get_supported_tags()), False)
+
+ def egg_name(self):
+ return (
+ _egg_basename(
+ self.project_name,
+ self.version,
+ platform=(None if self.platform == 'any' else get_platform()),
+ )
+ + ".egg"
+ )
+
+ def get_dist_info(self, zf):
+ # find the correct name of the .dist-info dir in the wheel file
+ for member in zf.namelist():
+ dirname = posixpath.dirname(member)
+ if dirname.endswith('.dist-info') and canonicalize_name(dirname).startswith(
+ canonicalize_name(self.project_name)
+ ):
+ return dirname
+ raise ValueError("unsupported wheel format. .dist-info not found")
+
+ def install_as_egg(self, destination_eggdir) -> None:
+ """Install wheel as an egg directory."""
+ with zipfile.ZipFile(self.filename) as zf:
+ self._install_as_egg(destination_eggdir, zf)
+
+ def _install_as_egg(self, destination_eggdir, zf):
+ dist_basename = f'{self.project_name}-{self.version}'
+ dist_info = self.get_dist_info(zf)
+ dist_data = f'{dist_basename}.data'
+ egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
+
+ self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)
+ self._move_data_entries(destination_eggdir, dist_data)
+ self._fix_namespace_packages(egg_info, destination_eggdir)
+
+ @staticmethod
+ def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):
+ def get_metadata(name):
+ with zf.open(posixpath.join(dist_info, name)) as fp:
+ value = fp.read().decode('utf-8')
+ return email.parser.Parser().parsestr(value)
+
+ wheel_metadata = get_metadata('WHEEL')
+ # Check wheel format version is supported.
+ wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
+ wheel_v1 = parse_version('1.0') <= wheel_version < parse_version('2.0dev0')
+ if not wheel_v1:
+ raise ValueError(f'unsupported wheel format version: {wheel_version}')
+ # Extract to target directory.
+ _unpack_zipfile_obj(zf, destination_eggdir)
+ dist_info = os.path.join(destination_eggdir, dist_info)
+ install_requires, extras_require = Wheel._convert_requires(
+ destination_eggdir, dist_info
+ )
+ os.rename(dist_info, egg_info)
+ os.rename(
+ os.path.join(egg_info, 'METADATA'),
+ os.path.join(egg_info, 'PKG-INFO'),
+ )
+ setup_dist = setuptools.Distribution(
+ attrs=dict(
+ install_requires=install_requires,
+ extras_require=extras_require,
+ ),
+ )
+ with disable_info_traces():
+ write_requirements(
+ setup_dist.get_command_obj('egg_info'),
+ None,
+ os.path.join(egg_info, 'requires.txt'),
+ )
+
+ @staticmethod
+ def _convert_requires(destination_eggdir, dist_info):
+ md = metadata.Distribution.at(dist_info).metadata
+ deps = md.get_all('Requires-Dist') or []
+ reqs = list(map(Requirement, deps))
+
+ extras = extras_from_deps(deps)
+
+ # Note: Evaluate and strip markers now,
+ # as it's difficult to convert back from the syntax:
+ # foobar; "linux" in sys_platform and extra == 'test'
+ def raw_req(req):
+ req = Requirement(str(req))
+ req.marker = None
+ return str(req)
+
+ def eval(req, **env):
+ return not req.marker or req.marker.evaluate(env)
+
+ def for_extra(req):
+ try:
+ markers = req.marker._markers
+ except AttributeError:
+ markers = ()
+ return set(
+ marker[2].value
+ for marker in markers
+ if isinstance(marker, tuple) and marker[0].value == 'extra'
+ )
+
+ install_requires = list(
+ map(raw_req, filter(eval, itertools.filterfalse(for_extra, reqs)))
+ )
+ extras_require = {
+ extra: list(
+ map(
+ raw_req,
+ (req for req in reqs if for_extra(req) and eval(req, extra=extra)),
+ )
+ )
+ for extra in extras
+ }
+ return install_requires, extras_require
+
+ @staticmethod
+ def _move_data_entries(destination_eggdir, dist_data):
+ """Move data entries to their correct location."""
+ dist_data = os.path.join(destination_eggdir, dist_data)
+ dist_data_scripts = os.path.join(dist_data, 'scripts')
+ if os.path.exists(dist_data_scripts):
+ egg_info_scripts = os.path.join(destination_eggdir, 'EGG-INFO', 'scripts')
+ os.mkdir(egg_info_scripts)
+ for entry in os.listdir(dist_data_scripts):
+ # Remove bytecode, as it's not properly handled
+ # during easy_install scripts install phase.
+ if entry.endswith('.pyc'):
+ os.unlink(os.path.join(dist_data_scripts, entry))
+ else:
+ os.rename(
+ os.path.join(dist_data_scripts, entry),
+ os.path.join(egg_info_scripts, entry),
+ )
+ os.rmdir(dist_data_scripts)
+ for subdir in filter(
+ os.path.exists,
+ (
+ os.path.join(dist_data, d)
+ for d in ('data', 'headers', 'purelib', 'platlib')
+ ),
+ ):
+ unpack(subdir, destination_eggdir)
+ if os.path.exists(dist_data):
+ os.rmdir(dist_data)
+
+ @staticmethod
+ def _fix_namespace_packages(egg_info, destination_eggdir):
+ namespace_packages = os.path.join(egg_info, 'namespace_packages.txt')
+ if os.path.exists(namespace_packages):
+ namespace_packages = _read_utf8_with_fallback(namespace_packages).split()
+
+ for mod in namespace_packages:
+ mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
+ mod_init = os.path.join(mod_dir, '__init__.py')
+ if not os.path.exists(mod_dir):
+ os.mkdir(mod_dir)
+ if not os.path.exists(mod_init):
+ with open(mod_init, 'w', encoding="utf-8") as fp:
+ fp.write(NAMESPACE_PACKAGE_INIT)
diff --git a/lib/python3.12/site-packages/setuptools/windows_support.py b/lib/python3.12/site-packages/setuptools/windows_support.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a2b53a291409c66851961a559eb4d69be0f4acc
--- /dev/null
+++ b/lib/python3.12/site-packages/setuptools/windows_support.py
@@ -0,0 +1,30 @@
+import platform
+
+
+def windows_only(func):
+ if platform.system() != 'Windows':
+ return lambda *args, **kwargs: None
+ return func
+
+
+@windows_only
+def hide_file(path: str) -> None:
+ """
+ Set the hidden attribute on a file or directory.
+
+ From https://stackoverflow.com/questions/19622133/
+
+ `path` must be text.
+ """
+ import ctypes
+ import ctypes.wintypes
+
+ SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
+ SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
+ SetFileAttributes.restype = ctypes.wintypes.BOOL
+
+ FILE_ATTRIBUTE_HIDDEN = 0x02
+
+ ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN)
+ if not ret:
+ raise ctypes.WinError()
diff --git a/lib/python3.12/site-packages/six-1.17.0.dist-info/INSTALLER b/lib/python3.12/site-packages/six-1.17.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE b/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..1cc22a5aa7679ebaa10934212f356823931bdc3e
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2010-2024 Benjamin Peterson
+
+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/lib/python3.12/site-packages/six-1.17.0.dist-info/METADATA b/lib/python3.12/site-packages/six-1.17.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..cfde03c2631c5c1d5cdc0949d0ee3379e7110f0e
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/METADATA
@@ -0,0 +1,43 @@
+Metadata-Version: 2.1
+Name: six
+Version: 1.17.0
+Summary: Python 2 and 3 compatibility utilities
+Home-page: https://github.com/benjaminp/six
+Author: Benjamin Peterson
+Author-email: benjamin@python.org
+License: MIT
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*
+License-File: LICENSE
+
+.. image:: https://img.shields.io/pypi/v/six.svg
+ :target: https://pypi.org/project/six/
+ :alt: six on PyPI
+
+.. image:: https://readthedocs.org/projects/six/badge/?version=latest
+ :target: https://six.readthedocs.io/
+ :alt: six's documentation on Read the Docs
+
+.. image:: https://img.shields.io/badge/license-MIT-green.svg
+ :target: https://github.com/benjaminp/six/blob/master/LICENSE
+ :alt: MIT License badge
+
+Six is a Python 2 and 3 compatibility library. It provides utility functions
+for smoothing over the differences between the Python versions with the goal of
+writing Python code that is compatible on both Python versions. See the
+documentation for more information on what is provided.
+
+Six supports Python 2.7 and 3.3+. It is contained in only one Python
+file, so it can be easily copied into your project. (The copyright and license
+notice must be retained.)
+
+Online documentation is at https://six.readthedocs.io/.
+
+Bugs can be reported to https://github.com/benjaminp/six. The code can also
+be found there.
diff --git a/lib/python3.12/site-packages/six-1.17.0.dist-info/RECORD b/lib/python3.12/site-packages/six-1.17.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..bb90c1a52e0f905a3facec665e8600ef20931282
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/RECORD
@@ -0,0 +1,8 @@
+__pycache__/six.cpython-312.pyc,,
+six-1.17.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+six-1.17.0.dist-info/LICENSE,sha256=Q3W6IOK5xsTnytKUCmKP2Q6VzD1Q7pKq51VxXYuh-9A,1066
+six-1.17.0.dist-info/METADATA,sha256=ViBCB4wnUlSfbYp8htvF3XCAiKe-bYBnLsewcQC3JGg,1658
+six-1.17.0.dist-info/RECORD,,
+six-1.17.0.dist-info/WHEEL,sha256=pxeNX5JdtCe58PUSYP9upmc7jdRPgvT0Gm9kb1SHlVw,109
+six-1.17.0.dist-info/top_level.txt,sha256=_iVH_iYEtEXnD8nYGQYpYFUvkUW9sEO1GYbkeKSAais,4
+six.py,sha256=xRyR9wPT1LNpbJI8tf7CE-BeddkhU5O--sfy-mo5BN8,34703
diff --git a/lib/python3.12/site-packages/six-1.17.0.dist-info/WHEEL b/lib/python3.12/site-packages/six-1.17.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..104f3874635f24f0d2918dfeaf6a59652274460c
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: setuptools (75.6.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/six-1.17.0.dist-info/top_level.txt b/lib/python3.12/site-packages/six-1.17.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ffe2fce498955b628014618b28c6bcf152466a4a
--- /dev/null
+++ b/lib/python3.12/site-packages/six-1.17.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+six
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/INSTALLER b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/LICENCE b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/LICENCE
new file mode 100644
index 0000000000000000000000000000000000000000..a8922b182e80d9bcb955e8b8ae2bd9a017d72977
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/LICENCE
@@ -0,0 +1,49 @@
+`tqdm` is a product of collaborative work.
+Unless otherwise stated, all authors (see commit logs) retain copyright
+for their respective work, and release the work under the MIT licence
+(text below).
+
+Exceptions or notable authors are listed below
+in reverse chronological order:
+
+* files: *
+ MPL-2.0 2015-2024 (c) Casper da Costa-Luis
+ [casperdcl](https://github.com/casperdcl).
+* files: tqdm/_tqdm.py
+ MIT 2016 (c) [PR #96] on behalf of Google Inc.
+* files: tqdm/_tqdm.py README.rst .gitignore
+ MIT 2013 (c) Noam Yorav-Raphael, original author.
+
+[PR #96]: https://github.com/tqdm/tqdm/pull/96
+
+
+Mozilla Public Licence (MPL) v. 2.0 - Exhibit A
+-----------------------------------------------
+
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this project,
+You can obtain one at https://mozilla.org/MPL/2.0/.
+
+
+MIT License (MIT)
+-----------------
+
+Copyright (c) 2013 noamraph
+
+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/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/METADATA b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..181b4dc8b2f8697d1c0374a612ffd8b2f2db346a
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/METADATA
@@ -0,0 +1,1594 @@
+Metadata-Version: 2.1
+Name: tqdm
+Version: 4.67.1
+Summary: Fast, Extensible Progress Meter
+Maintainer-email: tqdm developers
+License: MPL-2.0 AND MIT
+Project-URL: homepage, https://tqdm.github.io
+Project-URL: repository, https://github.com/tqdm/tqdm
+Project-URL: changelog, https://tqdm.github.io/releases
+Project-URL: wiki, https://github.com/tqdm/tqdm/wiki
+Keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: MacOS X
+Classifier: Environment :: Other Environment
+Classifier: Environment :: Win32 (MS Windows)
+Classifier: Environment :: X11 Applications
+Classifier: Framework :: IPython
+Classifier: Framework :: Jupyter
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Education
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: Intended Audience :: Other Audience
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: MIT License
+Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft
+Classifier: Operating System :: Microsoft :: MS-DOS
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: POSIX :: BSD
+Classifier: Operating System :: POSIX :: BSD :: FreeBSD
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: POSIX :: SunOS/Solaris
+Classifier: Operating System :: Unix
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation
+Classifier: Programming Language :: Python :: Implementation :: IronPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Unix Shell
+Classifier: Topic :: Desktop Environment
+Classifier: Topic :: Education :: Computer Aided Instruction (CAI)
+Classifier: Topic :: Education :: Testing
+Classifier: Topic :: Office/Business
+Classifier: Topic :: Other/Nonlisted Topic
+Classifier: Topic :: Software Development :: Build Tools
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Pre-processors
+Classifier: Topic :: Software Development :: User Interfaces
+Classifier: Topic :: System :: Installation/Setup
+Classifier: Topic :: System :: Logging
+Classifier: Topic :: System :: Monitoring
+Classifier: Topic :: System :: Shells
+Classifier: Topic :: Terminals
+Classifier: Topic :: Utilities
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENCE
+Requires-Dist: colorama; platform_system == "Windows"
+Provides-Extra: dev
+Requires-Dist: pytest>=6; extra == "dev"
+Requires-Dist: pytest-cov; extra == "dev"
+Requires-Dist: pytest-timeout; extra == "dev"
+Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
+Requires-Dist: nbval; extra == "dev"
+Provides-Extra: discord
+Requires-Dist: requests; extra == "discord"
+Provides-Extra: slack
+Requires-Dist: slack-sdk; extra == "slack"
+Provides-Extra: telegram
+Requires-Dist: requests; extra == "telegram"
+Provides-Extra: notebook
+Requires-Dist: ipywidgets>=6; extra == "notebook"
+
+|Logo|
+
+tqdm
+====
+
+|Py-Versions| |Versions| |Conda-Forge-Status| |Docker| |Snapcraft|
+
+|Build-Status| |Coverage-Status| |Branch-Coverage-Status| |Codacy-Grade| |Libraries-Rank| |PyPI-Downloads|
+
+|LICENCE| |OpenHub-Status| |binder-demo| |awesome-python|
+
+``tqdm`` derives from the Arabic word *taqaddum* (تقدّم) which can mean "progress,"
+and is an abbreviation for "I love you so much" in Spanish (*te quiero demasiado*).
+
+Instantly make your loops show a smart progress meter - just wrap any
+iterable with ``tqdm(iterable)``, and you're done!
+
+.. code:: python
+
+ from tqdm import tqdm
+ for i in tqdm(range(10000)):
+ ...
+
+``76%|████████████████████████ | 7568/10000 [00:33<00:10, 229.00it/s]``
+
+``trange(N)`` can be also used as a convenient shortcut for
+``tqdm(range(N))``.
+
+|Screenshot|
+ |Video| |Slides| |Merch|
+
+It can also be executed as a module with pipes:
+
+.. code:: sh
+
+ $ seq 9999999 | tqdm --bytes | wc -l
+ 75.2MB [00:00, 217MB/s]
+ 9999999
+
+ $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \
+ > backup.tgz
+ 32%|██████████▍ | 8.89G/27.9G [00:42<01:31, 223MB/s]
+
+Overhead is low -- about 60ns per iteration (80ns with ``tqdm.gui``), and is
+unit tested against performance regression.
+By comparison, the well-established
+`ProgressBar `__ has
+an 800ns/iter overhead.
+
+In addition to its low overhead, ``tqdm`` uses smart algorithms to predict
+the remaining time and to skip unnecessary iteration displays, which allows
+for a negligible overhead in most cases.
+
+``tqdm`` works on any platform
+(Linux, Windows, Mac, FreeBSD, NetBSD, Solaris/SunOS),
+in any console or in a GUI, and is also friendly with IPython/Jupyter notebooks.
+
+``tqdm`` does not require any dependencies (not even ``curses``!), just
+Python and an environment supporting ``carriage return \r`` and
+``line feed \n`` control characters.
+
+------------------------------------------
+
+.. contents:: Table of contents
+ :backlinks: top
+ :local:
+
+
+Installation
+------------
+
+Latest PyPI stable release
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+|Versions| |PyPI-Downloads| |Libraries-Dependents|
+
+.. code:: sh
+
+ pip install tqdm
+
+Latest development release on GitHub
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+|GitHub-Status| |GitHub-Stars| |GitHub-Commits| |GitHub-Forks| |GitHub-Updated|
+
+Pull and install pre-release ``devel`` branch:
+
+.. code:: sh
+
+ pip install "git+https://github.com/tqdm/tqdm.git@devel#egg=tqdm"
+
+Latest Conda release
+~~~~~~~~~~~~~~~~~~~~
+
+|Conda-Forge-Status|
+
+.. code:: sh
+
+ conda install -c conda-forge tqdm
+
+Latest Snapcraft release
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+|Snapcraft|
+
+There are 3 channels to choose from:
+
+.. code:: sh
+
+ snap install tqdm # implies --stable, i.e. latest tagged release
+ snap install tqdm --candidate # master branch
+ snap install tqdm --edge # devel branch
+
+Note that ``snap`` binaries are purely for CLI use (not ``import``-able), and
+automatically set up ``bash`` tab-completion.
+
+Latest Docker release
+~~~~~~~~~~~~~~~~~~~~~
+
+|Docker|
+
+.. code:: sh
+
+ docker pull tqdm/tqdm
+ docker run -i --rm tqdm/tqdm --help
+
+Other
+~~~~~
+
+There are other (unofficial) places where ``tqdm`` may be downloaded, particularly for CLI use:
+
+|Repology|
+
+.. |Repology| image:: https://repology.org/badge/tiny-repos/python:tqdm.svg
+ :target: https://repology.org/project/python:tqdm/versions
+
+Changelog
+---------
+
+The list of all changes is available either on GitHub's Releases:
+|GitHub-Status|, on the
+`wiki `__, or on the
+`website `__.
+
+
+Usage
+-----
+
+``tqdm`` is very versatile and can be used in a number of ways.
+The three main ones are given below.
+
+Iterable-based
+~~~~~~~~~~~~~~
+
+Wrap ``tqdm()`` around any iterable:
+
+.. code:: python
+
+ from tqdm import tqdm
+ from time import sleep
+
+ text = ""
+ for char in tqdm(["a", "b", "c", "d"]):
+ sleep(0.25)
+ text = text + char
+
+``trange(i)`` is a special optimised instance of ``tqdm(range(i))``:
+
+.. code:: python
+
+ from tqdm import trange
+
+ for i in trange(100):
+ sleep(0.01)
+
+Instantiation outside of the loop allows for manual control over ``tqdm()``:
+
+.. code:: python
+
+ pbar = tqdm(["a", "b", "c", "d"])
+ for char in pbar:
+ sleep(0.25)
+ pbar.set_description("Processing %s" % char)
+
+Manual
+~~~~~~
+
+Manual control of ``tqdm()`` updates using a ``with`` statement:
+
+.. code:: python
+
+ with tqdm(total=100) as pbar:
+ for i in range(10):
+ sleep(0.1)
+ pbar.update(10)
+
+If the optional variable ``total`` (or an iterable with ``len()``) is
+provided, predictive stats are displayed.
+
+``with`` is also optional (you can just assign ``tqdm()`` to a variable,
+but in this case don't forget to ``del`` or ``close()`` at the end:
+
+.. code:: python
+
+ pbar = tqdm(total=100)
+ for i in range(10):
+ sleep(0.1)
+ pbar.update(10)
+ pbar.close()
+
+Module
+~~~~~~
+
+Perhaps the most wonderful use of ``tqdm`` is in a script or on the command
+line. Simply inserting ``tqdm`` (or ``python -m tqdm``) between pipes will pass
+through all ``stdin`` to ``stdout`` while printing progress to ``stderr``.
+
+The example below demonstrate counting the number of lines in all Python files
+in the current directory, with timing information included.
+
+.. code:: sh
+
+ $ time find . -name '*.py' -type f -exec cat \{} \; | wc -l
+ 857365
+
+ real 0m3.458s
+ user 0m0.274s
+ sys 0m3.325s
+
+ $ time find . -name '*.py' -type f -exec cat \{} \; | tqdm | wc -l
+ 857366it [00:03, 246471.31it/s]
+ 857365
+
+ real 0m3.585s
+ user 0m0.862s
+ sys 0m3.358s
+
+Note that the usual arguments for ``tqdm`` can also be specified.
+
+.. code:: sh
+
+ $ find . -name '*.py' -type f -exec cat \{} \; |
+ tqdm --unit loc --unit_scale --total 857366 >> /dev/null
+ 100%|█████████████████████████████████| 857K/857K [00:04<00:00, 246Kloc/s]
+
+Backing up a large directory?
+
+.. code:: sh
+
+ $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \
+ > backup.tgz
+ 44%|██████████████▊ | 153M/352M [00:14<00:18, 11.0MB/s]
+
+This can be beautified further:
+
+.. code:: sh
+
+ $ BYTES=$(du -sb docs/ | cut -f1)
+ $ tar -cf - docs/ \
+ | tqdm --bytes --total "$BYTES" --desc Processing | gzip \
+ | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \
+ > ~/backup.tgz
+ Processing: 100%|██████████████████████| 352M/352M [00:14<00:00, 30.2MB/s]
+ Compressed: 42%|█████████▎ | 148M/352M [00:14<00:19, 10.9MB/s]
+
+Or done on a file level using 7-zip:
+
+.. code:: sh
+
+ $ 7z a -bd -r backup.7z docs/ | grep Compressing \
+ | tqdm --total $(find docs/ -type f | wc -l) --unit files \
+ | grep -v Compressing
+ 100%|██████████████████████████▉| 15327/15327 [01:00<00:00, 712.96files/s]
+
+Pre-existing CLI programs already outputting basic progress information will
+benefit from ``tqdm``'s ``--update`` and ``--update_to`` flags:
+
+.. code:: sh
+
+ $ seq 3 0.1 5 | tqdm --total 5 --update_to --null
+ 100%|████████████████████████████████████| 5.0/5 [00:00<00:00, 9673.21it/s]
+ $ seq 10 | tqdm --update --null # 1 + 2 + ... + 10 = 55 iterations
+ 55it [00:00, 90006.52it/s]
+
+FAQ and Known Issues
+--------------------
+
+|GitHub-Issues|
+
+The most common issues relate to excessive output on multiple lines, instead
+of a neat one-line progress bar.
+
+- Consoles in general: require support for carriage return (``CR``, ``\r``).
+
+ * Some cloud logging consoles which don't support ``\r`` properly
+ (`cloudwatch `__,
+ `K8s `__) may benefit from
+ ``export TQDM_POSITION=-1``.
+
+- Nested progress bars:
+
+ * Consoles in general: require support for moving cursors up to the
+ previous line. For example,
+ `IDLE `__,
+ `ConEmu `__ and
+ `PyCharm `__ (also
+ `here `__,
+ `here `__, and
+ `here `__)
+ lack full support.
+ * Windows: additionally may require the Python module ``colorama``
+ to ensure nested bars stay within their respective lines.
+
+- Unicode:
+
+ * Environments which report that they support unicode will have solid smooth
+ progressbars. The fallback is an ``ascii``-only bar.
+ * Windows consoles often only partially support unicode and thus
+ `often require explicit ascii=True `__
+ (also `here `__). This is due to
+ either normal-width unicode characters being incorrectly displayed as
+ "wide", or some unicode characters not rendering.
+
+- Wrapping generators:
+
+ * Generator wrapper functions tend to hide the length of iterables.
+ ``tqdm`` does not.
+ * Replace ``tqdm(enumerate(...))`` with ``enumerate(tqdm(...))`` or
+ ``tqdm(enumerate(x), total=len(x), ...)``.
+ The same applies to ``numpy.ndenumerate``.
+ * Replace ``tqdm(zip(a, b))`` with ``zip(tqdm(a), b)`` or even
+ ``zip(tqdm(a), tqdm(b))``.
+ * The same applies to ``itertools``.
+ * Some useful convenience functions can be found under ``tqdm.contrib``.
+
+- `No intermediate output in docker-compose `__:
+ use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``.
+
+- Overriding defaults via environment variables:
+ e.g. in CI/cloud jobs, ``export TQDM_MININTERVAL=5`` to avoid log spam.
+ This override logic is handled by the ``tqdm.utils.envwrap`` decorator
+ (useful independent of ``tqdm``).
+
+If you come across any other difficulties, browse and file |GitHub-Issues|.
+
+Documentation
+-------------
+
+|Py-Versions| |README-Hits| (Since 19 May 2016)
+
+.. code:: python
+
+ class tqdm():
+ """
+ Decorate an iterable object, returning an iterator which acts exactly
+ like the original iterable, but prints a dynamically updating
+ progressbar every time a value is requested.
+ """
+
+ @envwrap("TQDM_") # override defaults via env vars
+ def __init__(self, iterable=None, desc=None, total=None, leave=True,
+ file=None, ncols=None, mininterval=0.1,
+ maxinterval=10.0, miniters=None, ascii=None, disable=False,
+ unit='it', unit_scale=False, dynamic_ncols=False,
+ smoothing=0.3, bar_format=None, initial=0, position=None,
+ postfix=None, unit_divisor=1000, write_bytes=False,
+ lock_args=None, nrows=None, colour=None, delay=0):
+
+Parameters
+~~~~~~~~~~
+
+* iterable : iterable, optional
+ Iterable to decorate with a progressbar.
+ Leave blank to manually manage the updates.
+* desc : str, optional
+ Prefix for the progressbar.
+* total : int or float, optional
+ The number of expected iterations. If unspecified,
+ len(iterable) is used if possible. If float("inf") or as a last
+ resort, only basic progress statistics are displayed
+ (no ETA, no progressbar).
+ If ``gui`` is True and this parameter needs subsequent updating,
+ specify an initial arbitrary large positive number,
+ e.g. 9e9.
+* leave : bool, optional
+ If [default: True], keeps all traces of the progressbar
+ upon termination of iteration.
+ If ``None``, will leave only if ``position`` is ``0``.
+* file : ``io.TextIOWrapper`` or ``io.StringIO``, optional
+ Specifies where to output the progress messages
+ (default: sys.stderr). Uses ``file.write(str)`` and ``file.flush()``
+ methods. For encoding, see ``write_bytes``.
+* ncols : int, optional
+ The width of the entire output message. If specified,
+ dynamically resizes the progressbar to stay within this bound.
+ If unspecified, attempts to use environment width. The
+ fallback is a meter width of 10 and no limit for the counter and
+ statistics. If 0, will not print any meter (only stats).
+* mininterval : float, optional
+ Minimum progress display update interval [default: 0.1] seconds.
+* maxinterval : float, optional
+ Maximum progress display update interval [default: 10] seconds.
+ Automatically adjusts ``miniters`` to correspond to ``mininterval``
+ after long display update lag. Only works if ``dynamic_miniters``
+ or monitor thread is enabled.
+* miniters : int or float, optional
+ Minimum progress display update interval, in iterations.
+ If 0 and ``dynamic_miniters``, will automatically adjust to equal
+ ``mininterval`` (more CPU efficient, good for tight loops).
+ If > 0, will skip display of specified number of iterations.
+ Tweak this and ``mininterval`` to get very efficient loops.
+ If your progress is erratic with both fast and slow iterations
+ (network, skipping items, etc) you should set miniters=1.
+* ascii : bool or str, optional
+ If unspecified or False, use unicode (smooth blocks) to fill
+ the meter. The fallback is to use ASCII characters " 123456789#".
+* disable : bool, optional
+ Whether to disable the entire progressbar wrapper
+ [default: False]. If set to None, disable on non-TTY.
+* unit : str, optional
+ String that will be used to define the unit of each iteration
+ [default: it].
+* unit_scale : bool or int or float, optional
+ If 1 or True, the number of iterations will be reduced/scaled
+ automatically and a metric prefix following the
+ International System of Units standard will be added
+ (kilo, mega, etc.) [default: False]. If any other non-zero
+ number, will scale ``total`` and ``n``.
+* dynamic_ncols : bool, optional
+ If set, constantly alters ``ncols`` and ``nrows`` to the
+ environment (allowing for window resizes) [default: False].
+* smoothing : float, optional
+ Exponential moving average smoothing factor for speed estimates
+ (ignored in GUI mode). Ranges from 0 (average speed) to 1
+ (current/instantaneous speed) [default: 0.3].
+* bar_format : str, optional
+ Specify a custom bar string formatting. May impact performance.
+ [default: '{l_bar}{bar}{r_bar}'], where
+ l_bar='{desc}: {percentage:3.0f}%|' and
+ r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, '
+ '{rate_fmt}{postfix}]'
+ Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt,
+ percentage, elapsed, elapsed_s, ncols, nrows, desc, unit,
+ rate, rate_fmt, rate_noinv, rate_noinv_fmt,
+ rate_inv, rate_inv_fmt, postfix, unit_divisor,
+ remaining, remaining_s, eta.
+ Note that a trailing ": " is automatically removed after {desc}
+ if the latter is empty.
+* initial : int or float, optional
+ The initial counter value. Useful when restarting a progress
+ bar [default: 0]. If using float, consider specifying ``{n:.3f}``
+ or similar in ``bar_format``, or specifying ``unit_scale``.
+* position : int, optional
+ Specify the line offset to print this bar (starting from 0)
+ Automatic if unspecified.
+ Useful to manage multiple bars at once (eg, from threads).
+* postfix : dict or ``*``, optional
+ Specify additional stats to display at the end of the bar.
+ Calls ``set_postfix(**postfix)`` if possible (dict).
+* unit_divisor : float, optional
+ [default: 1000], ignored unless ``unit_scale`` is True.
+* write_bytes : bool, optional
+ Whether to write bytes. If (default: False) will write unicode.
+* lock_args : tuple, optional
+ Passed to ``refresh`` for intermediate output
+ (initialisation, iterating, and updating).
+* nrows : int, optional
+ The screen height. If specified, hides nested bars outside this
+ bound. If unspecified, attempts to use environment height.
+ The fallback is 20.
+* colour : str, optional
+ Bar colour (e.g. 'green', '#00ff00').
+* delay : float, optional
+ Don't display until [default: 0] seconds have elapsed.
+
+Extra CLI Options
+~~~~~~~~~~~~~~~~~
+
+* delim : chr, optional
+ Delimiting character [default: '\n']. Use '\0' for null.
+ N.B.: on Windows systems, Python converts '\n' to '\r\n'.
+* buf_size : int, optional
+ String buffer size in bytes [default: 256]
+ used when ``delim`` is specified.
+* bytes : bool, optional
+ If true, will count bytes, ignore ``delim``, and default
+ ``unit_scale`` to True, ``unit_divisor`` to 1024, and ``unit`` to 'B'.
+* tee : bool, optional
+ If true, passes ``stdin`` to both ``stderr`` and ``stdout``.
+* update : bool, optional
+ If true, will treat input as newly elapsed iterations,
+ i.e. numbers to pass to ``update()``. Note that this is slow
+ (~2e5 it/s) since every input must be decoded as a number.
+* update_to : bool, optional
+ If true, will treat input as total elapsed iterations,
+ i.e. numbers to assign to ``self.n``. Note that this is slow
+ (~2e5 it/s) since every input must be decoded as a number.
+* null : bool, optional
+ If true, will discard input (no stdout).
+* manpath : str, optional
+ Directory in which to install tqdm man pages.
+* comppath : str, optional
+ Directory in which to place tqdm completion.
+* log : str, optional
+ CRITICAL|FATAL|ERROR|WARN(ING)|[default: 'INFO']|DEBUG|NOTSET.
+
+Returns
+~~~~~~~
+
+* out : decorated iterator.
+
+.. code:: python
+
+ class tqdm():
+ def update(self, n=1):
+ """
+ Manually update the progress bar, useful for streams
+ such as reading files.
+ E.g.:
+ >>> t = tqdm(total=filesize) # Initialise
+ >>> for current_buffer in stream:
+ ... ...
+ ... t.update(len(current_buffer))
+ >>> t.close()
+ The last line is highly recommended, but possibly not necessary if
+ ``t.update()`` will be called in such a way that ``filesize`` will be
+ exactly reached and printed.
+
+ Parameters
+ ----------
+ n : int or float, optional
+ Increment to add to the internal counter of iterations
+ [default: 1]. If using float, consider specifying ``{n:.3f}``
+ or similar in ``bar_format``, or specifying ``unit_scale``.
+
+ Returns
+ -------
+ out : bool or None
+ True if a ``display()`` was triggered.
+ """
+
+ def close(self):
+ """Cleanup and (if leave=False) close the progressbar."""
+
+ def clear(self, nomove=False):
+ """Clear current bar display."""
+
+ def refresh(self):
+ """
+ Force refresh the display of this bar.
+
+ Parameters
+ ----------
+ nolock : bool, optional
+ If ``True``, does not lock.
+ If [default: ``False``]: calls ``acquire()`` on internal lock.
+ lock_args : tuple, optional
+ Passed to internal lock's ``acquire()``.
+ If specified, will only ``display()`` if ``acquire()`` returns ``True``.
+ """
+
+ def unpause(self):
+ """Restart tqdm timer from last print time."""
+
+ def reset(self, total=None):
+ """
+ Resets to 0 iterations for repeated use.
+
+ Consider combining with ``leave=True``.
+
+ Parameters
+ ----------
+ total : int or float, optional. Total to use for the new bar.
+ """
+
+ def set_description(self, desc=None, refresh=True):
+ """
+ Set/modify description of the progress bar.
+
+ Parameters
+ ----------
+ desc : str, optional
+ refresh : bool, optional
+ Forces refresh [default: True].
+ """
+
+ def set_postfix(self, ordered_dict=None, refresh=True, **tqdm_kwargs):
+ """
+ Set/modify postfix (additional stats)
+ with automatic formatting based on datatype.
+
+ Parameters
+ ----------
+ ordered_dict : dict or OrderedDict, optional
+ refresh : bool, optional
+ Forces refresh [default: True].
+ kwargs : dict, optional
+ """
+
+ @classmethod
+ def write(cls, s, file=sys.stdout, end="\n"):
+ """Print a message via tqdm (without overlap with bars)."""
+
+ @property
+ def format_dict(self):
+ """Public API for read-only member access."""
+
+ def display(self, msg=None, pos=None):
+ """
+ Use ``self.sp`` to display ``msg`` in the specified ``pos``.
+
+ Consider overloading this function when inheriting to use e.g.:
+ ``self.some_frontend(**self.format_dict)`` instead of ``self.sp``.
+
+ Parameters
+ ----------
+ msg : str, optional. What to display (default: ``repr(self)``).
+ pos : int, optional. Position to ``moveto``
+ (default: ``abs(self.pos)``).
+ """
+
+ @classmethod
+ @contextmanager
+ def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs):
+ """
+ stream : file-like object.
+ method : str, "read" or "write". The result of ``read()`` and
+ the first argument of ``write()`` should have a ``len()``.
+
+ >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj:
+ ... while True:
+ ... chunk = fobj.read(chunk_size)
+ ... if not chunk:
+ ... break
+ """
+
+ @classmethod
+ def pandas(cls, *targs, **tqdm_kwargs):
+ """Registers the current `tqdm` class with `pandas`."""
+
+ def trange(*args, **tqdm_kwargs):
+ """Shortcut for `tqdm(range(*args), **tqdm_kwargs)`."""
+
+Convenience Functions
+~~~~~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+ def tqdm.contrib.tenumerate(iterable, start=0, total=None,
+ tqdm_class=tqdm.auto.tqdm, **tqdm_kwargs):
+ """Equivalent of `numpy.ndenumerate` or builtin `enumerate`."""
+
+ def tqdm.contrib.tzip(iter1, *iter2plus, **tqdm_kwargs):
+ """Equivalent of builtin `zip`."""
+
+ def tqdm.contrib.tmap(function, *sequences, **tqdm_kwargs):
+ """Equivalent of builtin `map`."""
+
+Submodules
+~~~~~~~~~~
+
+.. code:: python
+
+ class tqdm.notebook.tqdm(tqdm.tqdm):
+ """IPython/Jupyter Notebook widget."""
+
+ class tqdm.auto.tqdm(tqdm.tqdm):
+ """Automatically chooses beween `tqdm.notebook` and `tqdm.tqdm`."""
+
+ class tqdm.asyncio.tqdm(tqdm.tqdm):
+ """Asynchronous version."""
+ @classmethod
+ def as_completed(cls, fs, *, loop=None, timeout=None, total=None,
+ **tqdm_kwargs):
+ """Wrapper for `asyncio.as_completed`."""
+
+ class tqdm.gui.tqdm(tqdm.tqdm):
+ """Matplotlib GUI version."""
+
+ class tqdm.tk.tqdm(tqdm.tqdm):
+ """Tkinter GUI version."""
+
+ class tqdm.rich.tqdm(tqdm.tqdm):
+ """`rich.progress` version."""
+
+ class tqdm.keras.TqdmCallback(keras.callbacks.Callback):
+ """Keras callback for epoch and batch progress."""
+
+ class tqdm.dask.TqdmCallback(dask.callbacks.Callback):
+ """Dask callback for task progress."""
+
+
+``contrib``
++++++++++++
+
+The ``tqdm.contrib`` package also contains experimental modules:
+
+- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools``
+- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures``
+- ``tqdm.contrib.slack``: Posts to `Slack `__ bots
+- ``tqdm.contrib.discord``: Posts to `Discord `__ bots
+- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots
+- ``tqdm.contrib.bells``: Automagically enables all optional features
+
+ * ``auto``, ``pandas``, ``slack``, ``discord``, ``telegram``
+
+Examples and Advanced Usage
+---------------------------
+
+- See the `examples `__
+ folder;
+- import the module and run ``help()``;
+- consult the `wiki `__;
+
+ * this has an
+ `excellent article `__
+ on how to make a **great** progressbar;
+
+- check out the `slides from PyData London `__, or
+- run the |binder-demo|.
+
+Description and additional stats
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Custom information can be displayed and updated dynamically on ``tqdm`` bars
+with the ``desc`` and ``postfix`` arguments:
+
+.. code:: python
+
+ from tqdm import tqdm, trange
+ from random import random, randint
+ from time import sleep
+
+ with trange(10) as t:
+ for i in t:
+ # Description will be displayed on the left
+ t.set_description('GEN %i' % i)
+ # Postfix will be displayed on the right,
+ # formatted automatically based on argument's datatype
+ t.set_postfix(loss=random(), gen=randint(1,999), str='h',
+ lst=[1, 2])
+ sleep(0.1)
+
+ with tqdm(total=10, bar_format="{postfix[0]} {postfix[1][value]:>8.2g}",
+ postfix=["Batch", {"value": 0}]) as t:
+ for i in range(10):
+ sleep(0.1)
+ t.postfix[1]["value"] = i / 2
+ t.update()
+
+Points to remember when using ``{postfix[...]}`` in the ``bar_format`` string:
+
+- ``postfix`` also needs to be passed as an initial argument in a compatible
+ format, and
+- ``postfix`` will be auto-converted to a string if it is a ``dict``-like
+ object. To prevent this behaviour, insert an extra item into the dictionary
+ where the key is not a string.
+
+Additional ``bar_format`` parameters may also be defined by overriding
+``format_dict``, and the bar itself may be modified using ``ascii``:
+
+.. code:: python
+
+ from tqdm import tqdm
+ class TqdmExtraFormat(tqdm):
+ """Provides a `total_time` format parameter"""
+ @property
+ def format_dict(self):
+ d = super().format_dict
+ total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1)
+ d.update(total_time=self.format_interval(total_time) + " in total")
+ return d
+
+ for i in TqdmExtraFormat(
+ range(9), ascii=" .oO0",
+ bar_format="{total_time}: {percentage:.0f}%|{bar}{r_bar}"):
+ if i == 4:
+ break
+
+.. code::
+
+ 00:00 in total: 44%|0000. | 4/9 [00:00<00:00, 962.93it/s]
+
+Note that ``{bar}`` also supports a format specifier ``[width][type]``.
+
+- ``width``
+
+ * unspecified (default): automatic to fill ``ncols``
+ * ``int >= 0``: fixed width overriding ``ncols`` logic
+ * ``int < 0``: subtract from the automatic default
+
+- ``type``
+
+ * ``a``: ascii (``ascii=True`` override)
+ * ``u``: unicode (``ascii=False`` override)
+ * ``b``: blank (``ascii=" "`` override)
+
+This means a fixed bar with right-justified text may be created by using:
+``bar_format="{l_bar}{bar:10}|{bar:-10b}right-justified"``
+
+Nested progress bars
+~~~~~~~~~~~~~~~~~~~~
+
+``tqdm`` supports nested progress bars. Here's an example:
+
+.. code:: python
+
+ from tqdm.auto import trange
+ from time import sleep
+
+ for i in trange(4, desc='1st loop'):
+ for j in trange(5, desc='2nd loop'):
+ for k in trange(50, desc='3rd loop', leave=False):
+ sleep(0.01)
+
+For manual control over positioning (e.g. for multi-processing use),
+you may specify ``position=n`` where ``n=0`` for the outermost bar,
+``n=1`` for the next, and so on.
+However, it's best to check if ``tqdm`` can work without manual ``position``
+first.
+
+.. code:: python
+
+ from time import sleep
+ from tqdm import trange, tqdm
+ from multiprocessing import Pool, RLock, freeze_support
+
+ L = list(range(9))
+
+ def progresser(n):
+ interval = 0.001 / (n + 2)
+ total = 5000
+ text = f"#{n}, est. {interval * total:<04.2}s"
+ for _ in trange(total, desc=text, position=n):
+ sleep(interval)
+
+ if __name__ == '__main__':
+ freeze_support() # for Windows support
+ tqdm.set_lock(RLock()) # for managing output contention
+ p = Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),))
+ p.map(progresser, L)
+
+Note that in Python 3, ``tqdm.write`` is thread-safe:
+
+.. code:: python
+
+ from time import sleep
+ from tqdm import tqdm, trange
+ from concurrent.futures import ThreadPoolExecutor
+
+ L = list(range(9))
+
+ def progresser(n):
+ interval = 0.001 / (n + 2)
+ total = 5000
+ text = f"#{n}, est. {interval * total:<04.2}s"
+ for _ in trange(total, desc=text):
+ sleep(interval)
+ if n == 6:
+ tqdm.write("n == 6 completed.")
+ tqdm.write("`tqdm.write()` is thread-safe in py3!")
+
+ if __name__ == '__main__':
+ with ThreadPoolExecutor() as p:
+ p.map(progresser, L)
+
+Hooks and callbacks
+~~~~~~~~~~~~~~~~~~~
+
+``tqdm`` can easily support callbacks/hooks and manual updates.
+Here's an example with ``urllib``:
+
+**``urllib.urlretrieve`` documentation**
+
+ | [...]
+ | If present, the hook function will be called once
+ | on establishment of the network connection and once after each block read
+ | thereafter. The hook will be passed three arguments; a count of blocks
+ | transferred so far, a block size in bytes, and the total size of the file.
+ | [...]
+
+.. code:: python
+
+ import urllib, os
+ from tqdm import tqdm
+ urllib = getattr(urllib, 'request', urllib)
+
+ class TqdmUpTo(tqdm):
+ """Provides `update_to(n)` which uses `tqdm.update(delta_n)`."""
+ def update_to(self, b=1, bsize=1, tsize=None):
+ """
+ b : int, optional
+ Number of blocks transferred so far [default: 1].
+ bsize : int, optional
+ Size of each block (in tqdm units) [default: 1].
+ tsize : int, optional
+ Total size (in tqdm units). If [default: None] remains unchanged.
+ """
+ if tsize is not None:
+ self.total = tsize
+ return self.update(b * bsize - self.n) # also sets self.n = b * bsize
+
+ eg_link = "https://caspersci.uk.to/matryoshka.zip"
+ with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1,
+ desc=eg_link.split('/')[-1]) as t: # all optional kwargs
+ urllib.urlretrieve(eg_link, filename=os.devnull,
+ reporthook=t.update_to, data=None)
+ t.total = t.n
+
+Inspired by `twine#242 `__.
+Functional alternative in
+`examples/tqdm_wget.py `__.
+
+It is recommend to use ``miniters=1`` whenever there is potentially
+large differences in iteration speed (e.g. downloading a file over
+a patchy connection).
+
+**Wrapping read/write methods**
+
+To measure throughput through a file-like object's ``read`` or ``write``
+methods, use ``CallbackIOWrapper``:
+
+.. code:: python
+
+ from tqdm.auto import tqdm
+ from tqdm.utils import CallbackIOWrapper
+
+ with tqdm(total=file_obj.size,
+ unit='B', unit_scale=True, unit_divisor=1024) as t:
+ fobj = CallbackIOWrapper(t.update, file_obj, "read")
+ while True:
+ chunk = fobj.read(chunk_size)
+ if not chunk:
+ break
+ t.reset()
+ # ... continue to use `t` for something else
+
+Alternatively, use the even simpler ``wrapattr`` convenience function,
+which would condense both the ``urllib`` and ``CallbackIOWrapper`` examples
+down to:
+
+.. code:: python
+
+ import urllib, os
+ from tqdm import tqdm
+
+ eg_link = "https://caspersci.uk.to/matryoshka.zip"
+ response = getattr(urllib, 'request', urllib).urlopen(eg_link)
+ with tqdm.wrapattr(open(os.devnull, "wb"), "write",
+ miniters=1, desc=eg_link.split('/')[-1],
+ total=getattr(response, 'length', None)) as fout:
+ for chunk in response:
+ fout.write(chunk)
+
+The ``requests`` equivalent is nearly identical:
+
+.. code:: python
+
+ import requests, os
+ from tqdm import tqdm
+
+ eg_link = "https://caspersci.uk.to/matryoshka.zip"
+ response = requests.get(eg_link, stream=True)
+ with tqdm.wrapattr(open(os.devnull, "wb"), "write",
+ miniters=1, desc=eg_link.split('/')[-1],
+ total=int(response.headers.get('content-length', 0))) as fout:
+ for chunk in response.iter_content(chunk_size=4096):
+ fout.write(chunk)
+
+**Custom callback**
+
+``tqdm`` is known for intelligently skipping unnecessary displays. To make a
+custom callback take advantage of this, simply use the return value of
+``update()``. This is set to ``True`` if a ``display()`` was triggered.
+
+.. code:: python
+
+ from tqdm.auto import tqdm as std_tqdm
+
+ def external_callback(*args, **kwargs):
+ ...
+
+ class TqdmExt(std_tqdm):
+ def update(self, n=1):
+ displayed = super().update(n)
+ if displayed:
+ external_callback(**self.format_dict)
+ return displayed
+
+``asyncio``
+~~~~~~~~~~~
+
+Note that ``break`` isn't currently caught by asynchronous iterators.
+This means that ``tqdm`` cannot clean up after itself in this case:
+
+.. code:: python
+
+ from tqdm.asyncio import tqdm
+
+ async for i in tqdm(range(9)):
+ if i == 2:
+ break
+
+Instead, either call ``pbar.close()`` manually or use the context manager syntax:
+
+.. code:: python
+
+ from tqdm.asyncio import tqdm
+
+ with tqdm(range(9)) as pbar:
+ async for i in pbar:
+ if i == 2:
+ break
+
+Pandas Integration
+~~~~~~~~~~~~~~~~~~
+
+Due to popular demand we've added support for ``pandas`` -- here's an example
+for ``DataFrame.progress_apply`` and ``DataFrameGroupBy.progress_apply``:
+
+.. code:: python
+
+ import pandas as pd
+ import numpy as np
+ from tqdm import tqdm
+
+ df = pd.DataFrame(np.random.randint(0, 100, (100000, 6)))
+
+ # Register `pandas.progress_apply` and `pandas.Series.map_apply` with `tqdm`
+ # (can use `tqdm.gui.tqdm`, `tqdm.notebook.tqdm`, optional kwargs, etc.)
+ tqdm.pandas(desc="my bar!")
+
+ # Now you can use `progress_apply` instead of `apply`
+ # and `progress_map` instead of `map`
+ df.progress_apply(lambda x: x**2)
+ # can also groupby:
+ # df.groupby(0).progress_apply(lambda x: x**2)
+
+In case you're interested in how this works (and how to modify it for your
+own callbacks), see the
+`examples `__
+folder or import the module and run ``help()``.
+
+Keras Integration
+~~~~~~~~~~~~~~~~~
+
+A ``keras`` callback is also available:
+
+.. code:: python
+
+ from tqdm.keras import TqdmCallback
+
+ ...
+
+ model.fit(..., verbose=0, callbacks=[TqdmCallback()])
+
+Dask Integration
+~~~~~~~~~~~~~~~~
+
+A ``dask`` callback is also available:
+
+.. code:: python
+
+ from tqdm.dask import TqdmCallback
+
+ with TqdmCallback(desc="compute"):
+ ...
+ arr.compute()
+
+ # or use callback globally
+ cb = TqdmCallback(desc="global")
+ cb.register()
+ arr.compute()
+
+IPython/Jupyter Integration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+IPython/Jupyter is supported via the ``tqdm.notebook`` submodule:
+
+.. code:: python
+
+ from tqdm.notebook import trange, tqdm
+ from time import sleep
+
+ for i in trange(3, desc='1st loop'):
+ for j in tqdm(range(100), desc='2nd loop'):
+ sleep(0.01)
+
+In addition to ``tqdm`` features, the submodule provides a native Jupyter
+widget (compatible with IPython v1-v4 and Jupyter), fully working nested bars
+and colour hints (blue: normal, green: completed, red: error/interrupt,
+light blue: no ETA); as demonstrated below.
+
+|Screenshot-Jupyter1|
+|Screenshot-Jupyter2|
+|Screenshot-Jupyter3|
+
+The ``notebook`` version supports percentage or pixels for overall width
+(e.g.: ``ncols='100%'`` or ``ncols='480px'``).
+
+It is also possible to let ``tqdm`` automatically choose between
+console or notebook versions by using the ``autonotebook`` submodule:
+
+.. code:: python
+
+ from tqdm.autonotebook import tqdm
+ tqdm.pandas()
+
+Note that this will issue a ``TqdmExperimentalWarning`` if run in a notebook
+since it is not meant to be possible to distinguish between ``jupyter notebook``
+and ``jupyter console``. Use ``auto`` instead of ``autonotebook`` to suppress
+this warning.
+
+Note that notebooks will display the bar in the cell where it was created.
+This may be a different cell from the one where it is used.
+If this is not desired, either
+
+- delay the creation of the bar to the cell where it must be displayed, or
+- create the bar with ``display=False``, and in a later cell call
+ ``display(bar.container)``:
+
+.. code:: python
+
+ from tqdm.notebook import tqdm
+ pbar = tqdm(..., display=False)
+
+.. code:: python
+
+ # different cell
+ display(pbar.container)
+
+The ``keras`` callback has a ``display()`` method which can be used likewise:
+
+.. code:: python
+
+ from tqdm.keras import TqdmCallback
+ cbk = TqdmCallback(display=False)
+
+.. code:: python
+
+ # different cell
+ cbk.display()
+ model.fit(..., verbose=0, callbacks=[cbk])
+
+Another possibility is to have a single bar (near the top of the notebook)
+which is constantly re-used (using ``reset()`` rather than ``close()``).
+For this reason, the notebook version (unlike the CLI version) does not
+automatically call ``close()`` upon ``Exception``.
+
+.. code:: python
+
+ from tqdm.notebook import tqdm
+ pbar = tqdm()
+
+.. code:: python
+
+ # different cell
+ iterable = range(100)
+ pbar.reset(total=len(iterable)) # initialise with new `total`
+ for i in iterable:
+ pbar.update()
+ pbar.refresh() # force print final status but don't `close()`
+
+Custom Integration
+~~~~~~~~~~~~~~~~~~
+
+To change the default arguments (such as making ``dynamic_ncols=True``),
+simply use built-in Python magic:
+
+.. code:: python
+
+ from functools import partial
+ from tqdm import tqdm as std_tqdm
+ tqdm = partial(std_tqdm, dynamic_ncols=True)
+
+For further customisation,
+``tqdm`` may be inherited from to create custom callbacks (as with the
+``TqdmUpTo`` example `above <#hooks-and-callbacks>`__) or for custom frontends
+(e.g. GUIs such as notebook or plotting packages). In the latter case:
+
+1. ``def __init__()`` to call ``super().__init__(..., gui=True)`` to disable
+ terminal ``status_printer`` creation.
+2. Redefine: ``close()``, ``clear()``, ``display()``.
+
+Consider overloading ``display()`` to use e.g.
+``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``.
+
+Some submodule examples of inheritance:
+
+- `tqdm/notebook.py `__
+- `tqdm/gui.py `__
+- `tqdm/tk.py `__
+- `tqdm/contrib/slack.py `__
+- `tqdm/contrib/discord.py `__
+- `tqdm/contrib/telegram.py `__
+
+Dynamic Monitor/Meter
+~~~~~~~~~~~~~~~~~~~~~
+
+You can use a ``tqdm`` as a meter which is not monotonically increasing.
+This could be because ``n`` decreases (e.g. a CPU usage monitor) or ``total``
+changes.
+
+One example would be recursively searching for files. The ``total`` is the
+number of objects found so far, while ``n`` is the number of those objects which
+are files (rather than folders):
+
+.. code:: python
+
+ from tqdm import tqdm
+ import os.path
+
+ def find_files_recursively(path, show_progress=True):
+ files = []
+ # total=1 assumes `path` is a file
+ t = tqdm(total=1, unit="file", disable=not show_progress)
+ if not os.path.exists(path):
+ raise IOError("Cannot find:" + path)
+
+ def append_found_file(f):
+ files.append(f)
+ t.update()
+
+ def list_found_dir(path):
+ """returns os.listdir(path) assuming os.path.isdir(path)"""
+ listing = os.listdir(path)
+ # subtract 1 since a "file" we found was actually this directory
+ t.total += len(listing) - 1
+ # fancy way to give info without forcing a refresh
+ t.set_postfix(dir=path[-10:], refresh=False)
+ t.update(0) # may trigger a refresh
+ return listing
+
+ def recursively_search(path):
+ if os.path.isdir(path):
+ for f in list_found_dir(path):
+ recursively_search(os.path.join(path, f))
+ else:
+ append_found_file(path)
+
+ recursively_search(path)
+ t.set_postfix(dir=path)
+ t.close()
+ return files
+
+Using ``update(0)`` is a handy way to let ``tqdm`` decide when to trigger a
+display refresh to avoid console spamming.
+
+Writing messages
+~~~~~~~~~~~~~~~~
+
+This is a work in progress (see
+`#737 `__).
+
+Since ``tqdm`` uses a simple printing mechanism to display progress bars,
+you should not write any message in the terminal using ``print()`` while
+a progressbar is open.
+
+To write messages in the terminal without any collision with ``tqdm`` bar
+display, a ``.write()`` method is provided:
+
+.. code:: python
+
+ from tqdm.auto import tqdm, trange
+ from time import sleep
+
+ bar = trange(10)
+ for i in bar:
+ # Print using tqdm class method .write()
+ sleep(0.1)
+ if not (i % 3):
+ tqdm.write("Done task %i" % i)
+ # Can also use bar.write()
+
+By default, this will print to standard output ``sys.stdout``. but you can
+specify any file-like object using the ``file`` argument. For example, this
+can be used to redirect the messages writing to a log file or class.
+
+Redirecting writing
+~~~~~~~~~~~~~~~~~~~
+
+If using a library that can print messages to the console, editing the library
+by replacing ``print()`` with ``tqdm.write()`` may not be desirable.
+In that case, redirecting ``sys.stdout`` to ``tqdm.write()`` is an option.
+
+To redirect ``sys.stdout``, create a file-like class that will write
+any input string to ``tqdm.write()``, and supply the arguments
+``file=sys.stdout, dynamic_ncols=True``.
+
+A reusable canonical example is given below:
+
+.. code:: python
+
+ from time import sleep
+ import contextlib
+ import sys
+ from tqdm import tqdm
+ from tqdm.contrib import DummyTqdmFile
+
+
+ @contextlib.contextmanager
+ def std_out_err_redirect_tqdm():
+ orig_out_err = sys.stdout, sys.stderr
+ try:
+ sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err)
+ yield orig_out_err[0]
+ # Relay exceptions
+ except Exception as exc:
+ raise exc
+ # Always restore sys.stdout/err if necessary
+ finally:
+ sys.stdout, sys.stderr = orig_out_err
+
+ def some_fun(i):
+ print("Fee, fi, fo,".split()[i])
+
+ # Redirect stdout to tqdm.write() (don't forget the `as save_stdout`)
+ with std_out_err_redirect_tqdm() as orig_stdout:
+ # tqdm needs the original stdout
+ # and dynamic_ncols=True to autodetect console width
+ for i in tqdm(range(3), file=orig_stdout, dynamic_ncols=True):
+ sleep(.5)
+ some_fun(i)
+
+ # After the `with`, printing is restored
+ print("Done!")
+
+Redirecting ``logging``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging``
+may also be redirected to ``tqdm.write()``.
+
+Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to
+redirect ``logging`` first if needed.
+
+Helper methods are available in ``tqdm.contrib.logging``. For example:
+
+.. code:: python
+
+ import logging
+ from tqdm import trange
+ from tqdm.contrib.logging import logging_redirect_tqdm
+
+ LOG = logging.getLogger(__name__)
+
+ if __name__ == '__main__':
+ logging.basicConfig(level=logging.INFO)
+ with logging_redirect_tqdm():
+ for i in trange(9):
+ if i == 4:
+ LOG.info("console logging redirected to `tqdm.write()`")
+ # logging restored
+
+Monitoring thread, intervals and miniters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``tqdm`` implements a few tricks to increase efficiency and reduce overhead.
+
+- Avoid unnecessary frequent bar refreshing: ``mininterval`` defines how long
+ to wait between each refresh. ``tqdm`` always gets updated in the background,
+ but it will display only every ``mininterval``.
+- Reduce number of calls to check system clock/time.
+- ``mininterval`` is more intuitive to configure than ``miniters``.
+ A clever adjustment system ``dynamic_miniters`` will automatically adjust
+ ``miniters`` to the amount of iterations that fit into time ``mininterval``.
+ Essentially, ``tqdm`` will check if it's time to print without actually
+ checking time. This behaviour can be still be bypassed by manually setting
+ ``miniters``.
+
+However, consider a case with a combination of fast and slow iterations.
+After a few fast iterations, ``dynamic_miniters`` will set ``miniters`` to a
+large number. When iteration rate subsequently slows, ``miniters`` will
+remain large and thus reduce display update frequency. To address this:
+
+- ``maxinterval`` defines the maximum time between display refreshes.
+ A concurrent monitoring thread checks for overdue updates and forces one
+ where necessary.
+
+The monitoring thread should not have a noticeable overhead, and guarantees
+updates at least every 10 seconds by default.
+This value can be directly changed by setting the ``monitor_interval`` of
+any ``tqdm`` instance (i.e. ``t = tqdm.tqdm(...); t.monitor_interval = 2``).
+The monitor thread may be disabled application-wide by setting
+``tqdm.tqdm.monitor_interval = 0`` before instantiation of any ``tqdm`` bar.
+
+
+Merch
+-----
+
+You can buy `tqdm branded merch `__ now!
+
+Contributions
+-------------
+
+|GitHub-Commits| |GitHub-Issues| |GitHub-PRs| |OpenHub-Status| |GitHub-Contributions| |CII Best Practices|
+
+All source code is hosted on `GitHub `__.
+Contributions are welcome.
+
+See the
+`CONTRIBUTING `__
+file for more information.
+
+Developers who have made significant contributions, ranked by *SLoC*
+(surviving lines of code,
+`git fame `__ ``-wMC --excl '\.(png|gif|jpg)$'``),
+are:
+
+==================== ======================================================== ==== ================================
+Name ID SLoC Notes
+==================== ======================================================== ==== ================================
+Casper da Costa-Luis `casperdcl `__ ~80% primary maintainer |Gift-Casper|
+Stephen Larroque `lrq3000 `__ ~9% team member
+Martin Zugnoni `martinzugnoni `__ ~3%
+Daniel Ecer `de-code `__ ~2%
+Richard Sheridan `richardsheridan `__ ~1%
+Guangshuo Chen `chengs `__ ~1%
+Helio Machado `0x2b3bfa0 `__ ~1%
+Kyle Altendorf `altendky `__ <1%
+Noam Yorav-Raphael `noamraph `__ <1% original author
+Matthew Stevens `mjstevens777 `__ <1%
+Hadrien Mary `hadim `__ <1% team member
+Mikhail Korobov `kmike `__ <1% team member
+==================== ======================================================== ==== ================================
+
+Ports to Other Languages
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+A list is available on
+`this wiki page `__.
+
+
+LICENCE
+-------
+
+Open Source (OSI approved): |LICENCE|
+
+Citation information: |DOI|
+
+|README-Hits| (Since 19 May 2016)
+
+.. |Logo| image:: https://tqdm.github.io/img/logo.gif
+.. |Screenshot| image:: https://tqdm.github.io/img/tqdm.gif
+.. |Video| image:: https://tqdm.github.io/img/video.jpg
+ :target: https://tqdm.github.io/video
+.. |Slides| image:: https://tqdm.github.io/img/slides.jpg
+ :target: https://tqdm.github.io/PyData2019/slides.html
+.. |Merch| image:: https://tqdm.github.io/img/merch.jpg
+ :target: https://tqdm.github.io/merch
+.. |Build-Status| image:: https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub
+ :target: https://github.com/tqdm/tqdm/actions/workflows/test.yml
+.. |Coverage-Status| image:: https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls
+ :target: https://coveralls.io/github/tqdm/tqdm
+.. |Branch-Coverage-Status| image:: https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/tqdm/tqdm
+.. |Codacy-Grade| image:: https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177
+ :target: https://www.codacy.com/gh/tqdm/tqdm/dashboard
+.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/3264/badge
+ :target: https://bestpractices.coreinfrastructure.org/projects/3264
+.. |GitHub-Status| image:: https://img.shields.io/github/tag/tqdm/tqdm.svg?maxAge=86400&logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/releases
+.. |GitHub-Forks| image:: https://img.shields.io/github/forks/tqdm/tqdm.svg?logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/network
+.. |GitHub-Stars| image:: https://img.shields.io/github/stars/tqdm/tqdm.svg?logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/stargazers
+.. |GitHub-Commits| image:: https://img.shields.io/github/commit-activity/y/tqdm/tqdm.svg?logo=git&logoColor=white
+ :target: https://github.com/tqdm/tqdm/graphs/commit-activity
+.. |GitHub-Issues| image:: https://img.shields.io/github/issues-closed/tqdm/tqdm.svg?logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/issues?q=
+.. |GitHub-PRs| image:: https://img.shields.io/github/issues-pr-closed/tqdm/tqdm.svg?logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/pulls
+.. |GitHub-Contributions| image:: https://img.shields.io/github/contributors/tqdm/tqdm.svg?logo=github&logoColor=white
+ :target: https://github.com/tqdm/tqdm/graphs/contributors
+.. |GitHub-Updated| image:: https://img.shields.io/github/last-commit/tqdm/tqdm/master.svg?logo=github&logoColor=white&label=pushed
+ :target: https://github.com/tqdm/tqdm/pulse
+.. |Gift-Casper| image:: https://img.shields.io/badge/dynamic/json.svg?color=ff69b4&label=gifts%20received&prefix=%C2%A3&query=%24..sum&url=https%3A%2F%2Fcaspersci.uk.to%2Fgifts.json
+ :target: https://cdcl.ml/sponsor
+.. |Versions| image:: https://img.shields.io/pypi/v/tqdm.svg
+ :target: https://tqdm.github.io/releases
+.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white
+ :target: https://pepy.tech/project/tqdm
+.. |Py-Versions| image:: https://img.shields.io/pypi/pyversions/tqdm.svg?logo=python&logoColor=white
+ :target: https://pypi.org/project/tqdm
+.. |Conda-Forge-Status| image:: https://img.shields.io/conda/v/conda-forge/tqdm.svg?label=conda-forge&logo=conda-forge
+ :target: https://anaconda.org/conda-forge/tqdm
+.. |Snapcraft| image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft
+ :target: https://snapcraft.io/tqdm
+.. |Docker| image:: https://img.shields.io/badge/docker-pull-blue.svg?logo=docker&logoColor=white
+ :target: https://hub.docker.com/r/tqdm/tqdm
+.. |Libraries-Rank| image:: https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white
+ :target: https://libraries.io/pypi/tqdm
+.. |Libraries-Dependents| image:: https://img.shields.io/librariesio/dependent-repos/pypi/tqdm.svg?logo=koding&logoColor=white
+ :target: https://github.com/tqdm/tqdm/network/dependents
+.. |OpenHub-Status| image:: https://www.openhub.net/p/tqdm/widgets/project_thin_badge?format=gif
+ :target: https://www.openhub.net/p/tqdm?ref=Thin+badge
+.. |awesome-python| image:: https://awesome.re/mentioned-badge.svg
+ :target: https://github.com/vinta/awesome-python
+.. |LICENCE| image:: https://img.shields.io/pypi/l/tqdm.svg
+ :target: https://raw.githubusercontent.com/tqdm/tqdm/master/LICENCE
+.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.595120-blue.svg
+ :target: https://doi.org/10.5281/zenodo.595120
+.. |binder-demo| image:: https://mybinder.org/badge_logo.svg
+ :target: https://mybinder.org/v2/gh/tqdm/tqdm/master?filepath=DEMO.ipynb
+.. |Screenshot-Jupyter1| image:: https://tqdm.github.io/img/jupyter-1.gif
+.. |Screenshot-Jupyter2| image:: https://tqdm.github.io/img/jupyter-2.gif
+.. |Screenshot-Jupyter3| image:: https://tqdm.github.io/img/jupyter-3.gif
+.. |README-Hits| image:: https://cgi.cdcl.ml/hits?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif
+ :target: https://cgi.cdcl.ml/hits?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif&style=social
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/RECORD b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..d110040e58686096bbb699a8f01e221b88b11a67
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/RECORD
@@ -0,0 +1,75 @@
+../../../bin/tqdm,sha256=1YTmEpk7ppczVfmPRZrFH-PBaYf1hvSAwdjKzGFbuoc,243
+tqdm-4.67.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+tqdm-4.67.1.dist-info/LICENCE,sha256=3DMlLoKQFeOxUAhvubOkD2rW-zLC9GEM6BL6Z301mGo,1985
+tqdm-4.67.1.dist-info/METADATA,sha256=aIoWMt9SWhmP7FLc_vsSRtMerO6cA1qsrC1-r42P9mk,57675
+tqdm-4.67.1.dist-info/RECORD,,
+tqdm-4.67.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+tqdm-4.67.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
+tqdm-4.67.1.dist-info/entry_points.txt,sha256=ReJCH7Ui3Zyh6M16E4OhsZ1oU7WtMXCfbtoyBhGO29Y,39
+tqdm-4.67.1.dist-info/top_level.txt,sha256=NLiUJNfmc9At15s7JURiwvqMEjUi9G5PMGRrmMYzNSM,5
+tqdm/__init__.py,sha256=9mQNYSSqP99JasubEC1POJLMmhkkBH6cJZxPIR5G2pQ,1572
+tqdm/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
+tqdm/__pycache__/__init__.cpython-312.pyc,,
+tqdm/__pycache__/__main__.cpython-312.pyc,,
+tqdm/__pycache__/_dist_ver.cpython-312.pyc,,
+tqdm/__pycache__/_main.cpython-312.pyc,,
+tqdm/__pycache__/_monitor.cpython-312.pyc,,
+tqdm/__pycache__/_tqdm.cpython-312.pyc,,
+tqdm/__pycache__/_tqdm_gui.cpython-312.pyc,,
+tqdm/__pycache__/_tqdm_notebook.cpython-312.pyc,,
+tqdm/__pycache__/_tqdm_pandas.cpython-312.pyc,,
+tqdm/__pycache__/_utils.cpython-312.pyc,,
+tqdm/__pycache__/asyncio.cpython-312.pyc,,
+tqdm/__pycache__/auto.cpython-312.pyc,,
+tqdm/__pycache__/autonotebook.cpython-312.pyc,,
+tqdm/__pycache__/cli.cpython-312.pyc,,
+tqdm/__pycache__/dask.cpython-312.pyc,,
+tqdm/__pycache__/gui.cpython-312.pyc,,
+tqdm/__pycache__/keras.cpython-312.pyc,,
+tqdm/__pycache__/notebook.cpython-312.pyc,,
+tqdm/__pycache__/rich.cpython-312.pyc,,
+tqdm/__pycache__/std.cpython-312.pyc,,
+tqdm/__pycache__/tk.cpython-312.pyc,,
+tqdm/__pycache__/utils.cpython-312.pyc,,
+tqdm/__pycache__/version.cpython-312.pyc,,
+tqdm/_dist_ver.py,sha256=m5AdYI-jB-v6P0VJ_70isH_p24EzSOGSwVvuAZmkmKY,23
+tqdm/_main.py,sha256=9ySvgmi_2Sw4CAo5UDW0Q2dxfTryboEWGHohfCJz0sA,283
+tqdm/_monitor.py,sha256=Uku-DPWgzJ7dO5CK08xKJK-E_F6qQ-JB3ksuXczSYR0,3699
+tqdm/_tqdm.py,sha256=LfLCuJ6bpsVo9xilmtBXyEm1vGnUCFrliW85j3J-nD4,283
+tqdm/_tqdm_gui.py,sha256=03Hc8KayxJveieI5-0-2NGiDpLvw9jZekofJUV7CCwk,287
+tqdm/_tqdm_notebook.py,sha256=BuHiLuxu6uEfZFaPJW3RPpPaxaVctEQA3kdSJSDL1hw,307
+tqdm/_tqdm_pandas.py,sha256=c9jptUgigN6axRDhRd4Rif98Tmxeopc1nFNFhIpbFUE,888
+tqdm/_utils.py,sha256=_4E73bfDj4f1s3sM42NLHNrZDOkijZoWq-n6xWLkdZ8,553
+tqdm/asyncio.py,sha256=Kp2rSkNRf9KRqa3d9YpgeZQ7L7EZf2Ki4bSc7UPIyoo,2757
+tqdm/auto.py,sha256=nDZflj6p2zKkjBCNBourrhS81zYfZy1_dQvbckrdW8o,871
+tqdm/autonotebook.py,sha256=Yb9F5uaiBPhfbDDFpbtoG8I2YUw3uQJ89rUDLbfR6ws,956
+tqdm/cli.py,sha256=SbKlN8QyZ2ogenqt-wT_p6_sx2OOdCjCyhoZBFnlmyI,11010
+tqdm/completion.sh,sha256=j79KbSmpIj_E11jfTfBXrGnUTzKXVpQ1vGVQvsyDRl4,946
+tqdm/contrib/__init__.py,sha256=OgSwVXm-vlDJ-2imtoQ9z8qdom4snMSRztH72KMA82A,2494
+tqdm/contrib/__pycache__/__init__.cpython-312.pyc,,
+tqdm/contrib/__pycache__/bells.cpython-312.pyc,,
+tqdm/contrib/__pycache__/concurrent.cpython-312.pyc,,
+tqdm/contrib/__pycache__/discord.cpython-312.pyc,,
+tqdm/contrib/__pycache__/itertools.cpython-312.pyc,,
+tqdm/contrib/__pycache__/logging.cpython-312.pyc,,
+tqdm/contrib/__pycache__/slack.cpython-312.pyc,,
+tqdm/contrib/__pycache__/telegram.cpython-312.pyc,,
+tqdm/contrib/__pycache__/utils_worker.cpython-312.pyc,,
+tqdm/contrib/bells.py,sha256=Yx1HqGCmHrESCAO700j5wE__JCleNODJxedh1ijPLD0,837
+tqdm/contrib/concurrent.py,sha256=K1yjloKS5WRNFyjLRth0DmU5PAnDbF0A-GD27N-J4a8,3986
+tqdm/contrib/discord.py,sha256=MtVIL1s_dxH21G4sL8FBgQ4Wei23ho9Ek5T-AommvNc,5243
+tqdm/contrib/itertools.py,sha256=WdKKQU5eSzsqHu29SN_oH12huYZo0Jihqoi9-nVhwz4,774
+tqdm/contrib/logging.py,sha256=NsYtnKttj2mMrGm58mEdo5a9DP_2vv8pZyrimSuWulA,3760
+tqdm/contrib/slack.py,sha256=eP_Mr5sQonYniHxxQNGue3jk2JkIPmPWFZqIYxnOui0,4007
+tqdm/contrib/telegram.py,sha256=vn_9SATMbbwn2PAbzSDyOX6av3eBB01QBug11P4H-Og,5008
+tqdm/contrib/utils_worker.py,sha256=HJP5Mz1S1xyzEke2JaqJ2sYLHXADYoo2epT5AzQ38eA,1207
+tqdm/dask.py,sha256=9Ei58eVqTossRLhAfWyUFCduXYKjmLmwkaXIy-CHYfs,1319
+tqdm/gui.py,sha256=STIB3K8iDzDgkNUqWIpvcI_u0OGtbGNy5NwpALXhfWs,5479
+tqdm/keras.py,sha256=op9sBkb6q6c6dw2wJ0SD2ZwpPK7yM1Vbg4l1Qiy3MIo,4373
+tqdm/notebook.py,sha256=GtZ3IapLL1v8WNDaTSvPw0bJGTyfp71Vfz5HDnAzx1M,10895
+tqdm/rich.py,sha256=YyMPkEHVyYUVUR3adJKbVX26iTmNKpNMf3DEqmm-m60,5021
+tqdm/std.py,sha256=tWjz6-QCa92aqYjz7PIdkLUCAfiy-lJZheBtZyIIyO0,57461
+tqdm/tk.py,sha256=Gu0uwXwLCGPRGHORdi3WvBLGiseUp_xxX_h_gp9VpK0,6701
+tqdm/tqdm.1,sha256=aILyUPk2S4OPe_uWy2P4AMjUf0oQ6PUW0nLYXB-BWwI,7889
+tqdm/utils.py,sha256=6E0BQw3Sg7uGWKBM_cDn3P42tXswRhzkggbhBgLDjl8,11821
+tqdm/version.py,sha256=-1yWjfu3P0eghVsysHH07fbzdiADNRdzRtYPqOaqR2A,333
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/REQUESTED b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/WHEEL b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..ae527e7d64811439e61b93aa375defb30e06edfe
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (75.6.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/entry_points.txt b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..540e60f4e073bc53a5f0a521a3639e0d80780af4
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/entry_points.txt
@@ -0,0 +1,2 @@
+[console_scripts]
+tqdm = tqdm.cli:main
diff --git a/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/top_level.txt b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..78620c472c9d799a14ccb02a0233f4669b3bcdcb
--- /dev/null
+++ b/lib/python3.12/site-packages/tqdm-4.67.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+tqdm
diff --git a/lib/python3.12/site-packages/zipp-3.23.0.dist-info/INSTALLER b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/zipp-3.23.0.dist-info/METADATA b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..6420117987041c052142deea6b16884cdb435c2f
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/METADATA
@@ -0,0 +1,106 @@
+Metadata-Version: 2.4
+Name: zipp
+Version: 3.23.0
+Summary: Backport of pathlib-compatible object wrapper for zip files
+Author-email: "Jason R. Coombs"
+License-Expression: MIT
+Project-URL: Source, https://github.com/jaraco/zipp
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Requires-Python: >=3.9
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Provides-Extra: test
+Requires-Dist: pytest!=8.1.*,>=6; extra == "test"
+Requires-Dist: jaraco.itertools; extra == "test"
+Requires-Dist: jaraco.functools; extra == "test"
+Requires-Dist: more_itertools; extra == "test"
+Requires-Dist: big-O; extra == "test"
+Requires-Dist: pytest-ignore-flaky; extra == "test"
+Requires-Dist: jaraco.test; extra == "test"
+Provides-Extra: doc
+Requires-Dist: sphinx>=3.5; extra == "doc"
+Requires-Dist: jaraco.packaging>=9.3; extra == "doc"
+Requires-Dist: rst.linker>=1.9; extra == "doc"
+Requires-Dist: furo; extra == "doc"
+Requires-Dist: sphinx-lint; extra == "doc"
+Requires-Dist: jaraco.tidelift>=1.4; extra == "doc"
+Provides-Extra: check
+Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
+Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra == "check"
+Provides-Extra: cover
+Requires-Dist: pytest-cov; extra == "cover"
+Provides-Extra: enabler
+Requires-Dist: pytest-enabler>=2.2; extra == "enabler"
+Provides-Extra: type
+Requires-Dist: pytest-mypy; extra == "type"
+Dynamic: license-file
+
+.. image:: https://img.shields.io/pypi/v/zipp.svg
+ :target: https://pypi.org/project/zipp
+
+.. image:: https://img.shields.io/pypi/pyversions/zipp.svg
+
+.. image:: https://github.com/jaraco/zipp/actions/workflows/main.yml/badge.svg
+ :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22
+ :alt: tests
+
+.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
+ :target: https://github.com/astral-sh/ruff
+ :alt: Ruff
+
+.. image:: https://readthedocs.org/projects/zipp/badge/?version=latest
+.. :target: https://zipp.readthedocs.io/en/latest/?badge=latest
+
+.. image:: https://img.shields.io/badge/skeleton-2025-informational
+ :target: https://blog.jaraco.com/skeleton
+
+.. image:: https://tidelift.com/badges/package/pypi/zipp
+ :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme
+
+
+A pathlib-compatible Zipfile object wrapper. Official backport of the standard library
+`Path object `_.
+
+
+Compatibility
+=============
+
+New features are introduced in this third-party library and later merged
+into CPython. The following table indicates which versions of this library
+were contributed to different versions in the standard library:
+
+.. list-table::
+ :header-rows: 1
+
+ * - zipp
+ - stdlib
+ * - 3.18
+ - 3.13
+ * - 3.16
+ - 3.12
+ * - 3.5
+ - 3.11
+ * - 3.2
+ - 3.10
+ * - 3.3 ??
+ - 3.9
+ * - 1.0
+ - 3.8
+
+
+Usage
+=====
+
+Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python.
+
+For Enterprise
+==============
+
+Available as part of the Tidelift Subscription.
+
+This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
+
+`Learn more `_.
diff --git a/lib/python3.12/site-packages/zipp-3.23.0.dist-info/RECORD b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..93f6395fdf1cd20240b0881c5c0adba547a1ba5b
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/RECORD
@@ -0,0 +1,20 @@
+zipp-3.23.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+zipp-3.23.0.dist-info/METADATA,sha256=vdZ9TRbPC_O4k-fRjNPS13StuC837Zhbx3cMYHIms1s,3563
+zipp-3.23.0.dist-info/RECORD,,
+zipp-3.23.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
+zipp-3.23.0.dist-info/licenses/LICENSE,sha256=WlfLTbheKi3YjCkGKJCK3VfjRRRJ4KmnH9-zh3b9dZ0,1076
+zipp-3.23.0.dist-info/top_level.txt,sha256=iAbdoSHfaGqBfVb2XuR9JqSQHCoOsOtG6y9C_LSpqFw,5
+zipp/__init__.py,sha256=ieXh9GIMdABjKRX_JUJtP9k5wdBLK4Mt5X4nszSkmYE,11976
+zipp/__pycache__/__init__.cpython-312.pyc,,
+zipp/__pycache__/_functools.cpython-312.pyc,,
+zipp/__pycache__/glob.cpython-312.pyc,,
+zipp/_functools.py,sha256=f6Kt9LxZ4TE-cY1lJVdXSId3memSXmH9IdgMbU-_x2k,575
+zipp/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+zipp/compat/__pycache__/__init__.cpython-312.pyc,,
+zipp/compat/__pycache__/overlay.cpython-312.pyc,,
+zipp/compat/__pycache__/py310.cpython-312.pyc,,
+zipp/compat/__pycache__/py313.cpython-312.pyc,,
+zipp/compat/overlay.py,sha256=oEIGAnbr8yGjuKTrVSO2ByewPui71uppbX18BLnYTKE,783
+zipp/compat/py310.py,sha256=S7i6N9mToEn3asNb2ILyjnzvITOXrATD_J4emjyBbDU,256
+zipp/compat/py313.py,sha256=RndvDNtuY7H2D9ecnnzcPBMZ8mZc42gmXD_IwQAXXAE,654
+zipp/glob.py,sha256=DLV9LBsDxA6YVW82e3-tkoNrus1h4R-j3BR6VqS0AzE,3382
diff --git a/lib/python3.12/site-packages/zipp-3.23.0.dist-info/WHEEL b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..e7fa31b6f3f78deb1022c1f7927f07d4d16da822
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (80.9.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/lib/python3.12/site-packages/zipp-3.23.0.dist-info/licenses/LICENSE b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/licenses/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f60bd572013c6abcb3a82ba9b50d84935de6394f
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/licenses/LICENSE
@@ -0,0 +1,18 @@
+MIT License
+
+Copyright (c) 2025
+
+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/lib/python3.12/site-packages/zipp-3.23.0.dist-info/top_level.txt b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e82f676f82a3381fa909d1e6578c7a22044fafca
--- /dev/null
+++ b/lib/python3.12/site-packages/zipp-3.23.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+zipp