"
+ else:
+ s = "<" + str(t.type) + ">"
+ s = s.replace("\n","\\n")
+ s = s.replace("\r","\\r")
+ s = s.replace("\t","\\t")
+ return "'" + s + "'"
+
+ def getErrorListenerDispatch(self):
+ return ProxyErrorListener(self._listeners)
+
+ # subclass needs to override these if there are sempreds or actions
+ # that the ATN interp needs to execute
+ def sempred(self, localctx:RuleContext, ruleIndex:int, actionIndex:int):
+ return True
+
+ def precpred(self, localctx:RuleContext , precedence:int):
+ return True
+
+ @property
+ def state(self):
+ return self._stateNumber
+
+ # Indicate that the recognizer has changed internal state that is
+ # consistent with the ATN state passed in. This way we always know
+ # where we are in the ATN as the parser goes along. The rule
+ # context objects form a stack that lets us see the stack of
+ # invoking rules. Combine this and we have complete ATN
+ # configuration information.
+
+ @state.setter
+ def state(self, atnState:int):
+ self._stateNumber = atnState
+
+del RecognitionException
diff --git a/lib/python3.12/site-packages/antlr4/RuleContext.py b/lib/python3.12/site-packages/antlr4/RuleContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..7812ba3b1c87cc53dd5a7e64cdd0c556f6a43822
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/RuleContext.py
@@ -0,0 +1,227 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+
+# A rule context is a record of a single rule invocation. It knows
+# which context invoked it, if any. If there is no parent context, then
+# naturally the invoking state is not valid. The parent link
+# provides a chain upwards from the current rule invocation to the root
+# of the invocation tree, forming a stack. We actually carry no
+# information about the rule associated with this context (except
+# when parsing). We keep only the state number of the invoking state from
+# the ATN submachine that invoked this. Contrast this with the s
+# pointer inside ParserRuleContext that tracks the current state
+# being "executed" for the current rule.
+#
+# The parent contexts are useful for computing lookahead sets and
+# getting error information.
+#
+# These objects are used during parsing and prediction.
+# For the special case of parsers, we use the subclass
+# ParserRuleContext.
+#
+# @see ParserRuleContext
+#/
+from io import StringIO
+from antlr4.tree.Tree import RuleNode, INVALID_INTERVAL, ParseTreeVisitor
+from antlr4.tree.Trees import Trees
+
+# need forward declarations
+RuleContext = None
+Parser = None
+
+class RuleContext(RuleNode):
+ __slots__ = ('parentCtx', 'invokingState')
+ EMPTY = None
+
+ def __init__(self, parent:RuleContext=None, invokingState:int=-1):
+ super().__init__()
+ # What context invoked this rule?
+ self.parentCtx = parent
+ # What state invoked the rule associated with this context?
+ # The "return address" is the followState of invokingState
+ # If parent is null, this should be -1.
+ self.invokingState = invokingState
+
+
+ def depth(self):
+ n = 0
+ p = self
+ while p is not None:
+ p = p.parentCtx
+ n += 1
+ return n
+
+ # A context is empty if there is no invoking state; meaning nobody call
+ # current context.
+ def isEmpty(self):
+ return self.invokingState == -1
+
+ # satisfy the ParseTree / SyntaxTree interface
+
+ def getSourceInterval(self):
+ return INVALID_INTERVAL
+
+ def getRuleContext(self):
+ return self
+
+ def getPayload(self):
+ return self
+
+ # Return the combined text of all child nodes. This method only considers
+ # tokens which have been added to the parse tree.
+ #
+ # Since tokens on hidden channels (e.g. whitespace or comments) are not
+ # added to the parse trees, they will not appear in the output of this
+ # method.
+ #/
+ def getText(self):
+ if self.getChildCount() == 0:
+ return ""
+ with StringIO() as builder:
+ for child in self.getChildren():
+ builder.write(child.getText())
+ return builder.getvalue()
+
+ def getRuleIndex(self):
+ return -1
+
+ # For rule associated with this parse tree internal node, return
+ # the outer alternative number used to match the input. Default
+ # implementation does not compute nor store this alt num. Create
+ # a subclass of ParserRuleContext with backing field and set
+ # option contextSuperClass.
+ # to set it.
+ def getAltNumber(self):
+ return 0 # should use ATN.INVALID_ALT_NUMBER but won't compile
+
+ # Set the outer alternative number for this context node. Default
+ # implementation does nothing to avoid backing field overhead for
+ # trees that don't need it. Create
+ # a subclass of ParserRuleContext with backing field and set
+ # option contextSuperClass.
+ def setAltNumber(self, altNumber:int):
+ pass
+
+ def getChild(self, i:int):
+ return None
+
+ def getChildCount(self):
+ return 0
+
+ def getChildren(self):
+ for c in []:
+ yield c
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitChildren(self)
+
+ # # Call this method to view a parse tree in a dialog box visually.#/
+ # public Future inspect(@Nullable Parser parser) {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # return inspect(ruleNames);
+ # }
+ #
+ # public Future inspect(@Nullable List ruleNames) {
+ # TreeViewer viewer = new TreeViewer(ruleNames, this);
+ # return viewer.open();
+ # }
+ #
+ # # Save this tree in a postscript file#/
+ # public void save(@Nullable Parser parser, String fileName)
+ # throws IOException, PrintException
+ # {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # save(ruleNames, fileName);
+ # }
+ #
+ # # Save this tree in a postscript file using a particular font name and size#/
+ # public void save(@Nullable Parser parser, String fileName,
+ # String fontName, int fontSize)
+ # throws IOException
+ # {
+ # List ruleNames = parser != null ? Arrays.asList(parser.getRuleNames()) : null;
+ # save(ruleNames, fileName, fontName, fontSize);
+ # }
+ #
+ # # Save this tree in a postscript file#/
+ # public void save(@Nullable List ruleNames, String fileName)
+ # throws IOException, PrintException
+ # {
+ # Trees.writePS(this, ruleNames, fileName);
+ # }
+ #
+ # # Save this tree in a postscript file using a particular font name and size#/
+ # public void save(@Nullable List ruleNames, String fileName,
+ # String fontName, int fontSize)
+ # throws IOException
+ # {
+ # Trees.writePS(this, ruleNames, fileName, fontName, fontSize);
+ # }
+ #
+ # # Print out a whole tree, not just a node, in LISP format
+ # # (root child1 .. childN). Print just a node if this is a leaf.
+ # # We have to know the recognizer so we can get rule names.
+ # #/
+ # @Override
+ # public String toStringTree(@Nullable Parser recog) {
+ # return Trees.toStringTree(this, recog);
+ # }
+ #
+ # Print out a whole tree, not just a node, in LISP format
+ # (root child1 .. childN). Print just a node if this is a leaf.
+ #
+ def toStringTree(self, ruleNames:list=None, recog:Parser=None):
+ return Trees.toStringTree(self, ruleNames=ruleNames, recog=recog)
+ # }
+ #
+ # @Override
+ # public String toStringTree() {
+ # return toStringTree((List)null);
+ # }
+ #
+ def __str__(self):
+ return self.toString(None, None)
+
+ # @Override
+ # public String toString() {
+ # return toString((List)null, (RuleContext)null);
+ # }
+ #
+ # public final String toString(@Nullable Recognizer,?> recog) {
+ # return toString(recog, ParserRuleContext.EMPTY);
+ # }
+ #
+ # public final String toString(@Nullable List ruleNames) {
+ # return toString(ruleNames, null);
+ # }
+ #
+ # // recog null unless ParserRuleContext, in which case we use subclass toString(...)
+ # public String toString(@Nullable Recognizer,?> recog, @Nullable RuleContext stop) {
+ # String[] ruleNames = recog != null ? recog.getRuleNames() : null;
+ # List ruleNamesList = ruleNames != null ? Arrays.asList(ruleNames) : null;
+ # return toString(ruleNamesList, stop);
+ # }
+
+ def toString(self, ruleNames:list, stop:RuleContext)->str:
+ with StringIO() as buf:
+ p = self
+ buf.write("[")
+ while p is not None and p is not stop:
+ if ruleNames is None:
+ if not p.isEmpty():
+ buf.write(str(p.invokingState))
+ else:
+ ri = p.getRuleIndex()
+ ruleName = ruleNames[ri] if ri >= 0 and ri < len(ruleNames) else str(ri)
+ buf.write(ruleName)
+
+ if p.parentCtx is not None and (ruleNames is not None or not p.parentCtx.isEmpty()):
+ buf.write(" ")
+
+ p = p.parentCtx
+
+ buf.write("]")
+ return buf.getvalue()
diff --git a/lib/python3.12/site-packages/antlr4/StdinStream.py b/lib/python3.12/site-packages/antlr4/StdinStream.py
new file mode 100644
index 0000000000000000000000000000000000000000..f044fc4d770b4bc86cce2a5578f2c2fa00fc7602
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/StdinStream.py
@@ -0,0 +1,11 @@
+import codecs
+import sys
+
+from antlr4.InputStream import InputStream
+
+
+class StdinStream(InputStream):
+ def __init__(self, encoding:str='ascii', errors:str='strict') -> None:
+ bytes = sys.stdin.buffer.read()
+ data = codecs.decode(bytes, encoding, errors)
+ super().__init__(data)
diff --git a/lib/python3.12/site-packages/antlr4/Token.py b/lib/python3.12/site-packages/antlr4/Token.py
new file mode 100644
index 0000000000000000000000000000000000000000..10a68a8c2260e47838a1c9403def21725aecc244
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/Token.py
@@ -0,0 +1,155 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# A token has properties: text, type, line, character position in the line
+# (so we can ignore tabs), token channel, index, and source from which
+# we obtained this token.
+from io import StringIO
+
+
+class Token (object):
+ __slots__ = ('source', 'type', 'channel', 'start', 'stop', 'tokenIndex', 'line', 'column', '_text')
+
+ INVALID_TYPE = 0
+
+ # During lookahead operations, this "token" signifies we hit rule end ATN state
+ # and did not follow it despite needing to.
+ EPSILON = -2
+
+ MIN_USER_TOKEN_TYPE = 1
+
+ EOF = -1
+
+ # All tokens go to the parser (unless skip() is called in that rule)
+ # on a particular "channel". The parser tunes to a particular channel
+ # so that whitespace etc... can go to the parser on a "hidden" channel.
+
+ DEFAULT_CHANNEL = 0
+
+ # Anything on different channel than DEFAULT_CHANNEL is not parsed
+ # by parser.
+
+ HIDDEN_CHANNEL = 1
+
+ def __init__(self):
+ self.source = None
+ self.type = None # token type of the token
+ self.channel = None # The parser ignores everything not on DEFAULT_CHANNEL
+ self.start = None # optional; return -1 if not implemented.
+ self.stop = None # optional; return -1 if not implemented.
+ self.tokenIndex = None # from 0..n-1 of the token object in the input stream
+ self.line = None # line=1..n of the 1st character
+ self.column = None # beginning of the line at which it occurs, 0..n-1
+ self._text = None # text of the token.
+
+ @property
+ def text(self):
+ return self._text
+
+ # Explicitly set the text for this token. If {code text} is not
+ # {@code null}, then {@link #getText} will return this value rather than
+ # extracting the text from the input.
+ #
+ # @param text The explicit text of the token, or {@code null} if the text
+ # should be obtained from the input along with the start and stop indexes
+ # of the token.
+
+ @text.setter
+ def text(self, text:str):
+ self._text = text
+
+
+ def getTokenSource(self):
+ return self.source[0]
+
+ def getInputStream(self):
+ return self.source[1]
+
+class CommonToken(Token):
+
+ # An empty {@link Pair} which is used as the default value of
+ # {@link #source} for tokens that do not have a source.
+ EMPTY_SOURCE = (None, None)
+
+ def __init__(self, source:tuple = EMPTY_SOURCE, type:int = None, channel:int=Token.DEFAULT_CHANNEL, start:int=-1, stop:int=-1):
+ super().__init__()
+ self.source = source
+ self.type = type
+ self.channel = channel
+ self.start = start
+ self.stop = stop
+ self.tokenIndex = -1
+ if source[0] is not None:
+ self.line = source[0].line
+ self.column = source[0].column
+ else:
+ self.column = -1
+
+ # Constructs a new {@link CommonToken} as a copy of another {@link Token}.
+ #
+ #
+ # If {@code oldToken} is also a {@link CommonToken} instance, the newly
+ # constructed token will share a reference to the {@link #text} field and
+ # the {@link Pair} stored in {@link #source}. Otherwise, {@link #text} will
+ # be assigned the result of calling {@link #getText}, and {@link #source}
+ # will be constructed from the result of {@link Token#getTokenSource} and
+ # {@link Token#getInputStream}.
+ #
+ # @param oldToken The token to copy.
+ #
+ def clone(self):
+ t = CommonToken(self.source, self.type, self.channel, self.start, self.stop)
+ t.tokenIndex = self.tokenIndex
+ t.line = self.line
+ t.column = self.column
+ t.text = self.text
+ return t
+
+ @property
+ def text(self):
+ if self._text is not None:
+ return self._text
+ input = self.getInputStream()
+ if input is None:
+ return None
+ n = input.size
+ if self.start < n and self.stop < n:
+ return input.getText(self.start, self.stop)
+ else:
+ return ""
+
+ @text.setter
+ def text(self, text:str):
+ self._text = text
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write("[@")
+ buf.write(str(self.tokenIndex))
+ buf.write(",")
+ buf.write(str(self.start))
+ buf.write(":")
+ buf.write(str(self.stop))
+ buf.write("='")
+ txt = self.text
+ if txt is not None:
+ txt = txt.replace("\n","\\n")
+ txt = txt.replace("\r","\\r")
+ txt = txt.replace("\t","\\t")
+ else:
+ txt = ""
+ buf.write(txt)
+ buf.write("',<")
+ buf.write(str(self.type))
+ buf.write(">")
+ if self.channel > 0:
+ buf.write(",channel=")
+ buf.write(str(self.channel))
+ buf.write(",")
+ buf.write(str(self.line))
+ buf.write(":")
+ buf.write(str(self.column))
+ buf.write("]")
+ return buf.getvalue()
diff --git a/lib/python3.12/site-packages/antlr4/TokenStreamRewriter.py b/lib/python3.12/site-packages/antlr4/TokenStreamRewriter.py
new file mode 100644
index 0000000000000000000000000000000000000000..59baf8f47a1ba447a143842c8df825ef58035918
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/TokenStreamRewriter.py
@@ -0,0 +1,255 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+from io import StringIO
+from antlr4.Token import Token
+
+from antlr4.CommonTokenStream import CommonTokenStream
+
+
+class TokenStreamRewriter(object):
+ __slots__ = ('tokens', 'programs', 'lastRewriteTokenIndexes')
+
+ DEFAULT_PROGRAM_NAME = "default"
+ PROGRAM_INIT_SIZE = 100
+ MIN_TOKEN_INDEX = 0
+
+ def __init__(self, tokens):
+ """
+ :type tokens: antlr4.BufferedTokenStream.BufferedTokenStream
+ :param tokens:
+ :return:
+ """
+ super(TokenStreamRewriter, self).__init__()
+ self.tokens = tokens
+ self.programs = {self.DEFAULT_PROGRAM_NAME: []}
+ self.lastRewriteTokenIndexes = {}
+
+ def getTokenStream(self):
+ return self.tokens
+
+ def rollback(self, instruction_index, program_name):
+ ins = self.programs.get(program_name, None)
+ if ins:
+ self.programs[program_name] = ins[self.MIN_TOKEN_INDEX: instruction_index]
+
+ def deleteProgram(self, program_name=DEFAULT_PROGRAM_NAME):
+ self.rollback(self.MIN_TOKEN_INDEX, program_name)
+
+ def insertAfterToken(self, token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.insertAfter(token.tokenIndex, text, program_name)
+
+ def insertAfter(self, index, text, program_name=DEFAULT_PROGRAM_NAME):
+ op = self.InsertAfterOp(self.tokens, index + 1, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def insertBeforeIndex(self, index, text):
+ self.insertBefore(self.DEFAULT_PROGRAM_NAME, index, text)
+
+ def insertBeforeToken(self, token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.insertBefore(program_name, token.tokenIndex, text)
+
+ def insertBefore(self, program_name, index, text):
+ op = self.InsertBeforeOp(self.tokens, index, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def replaceIndex(self, index, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, index, index, text)
+
+ def replaceRange(self, from_idx, to_idx, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, from_idx, to_idx, text)
+
+ def replaceSingleToken(self, token, text):
+ self.replace(self.DEFAULT_PROGRAM_NAME, token.tokenIndex, token.tokenIndex, text)
+
+ def replaceRangeTokens(self, from_token, to_token, text, program_name=DEFAULT_PROGRAM_NAME):
+ self.replace(program_name, from_token.tokenIndex, to_token.tokenIndex, text)
+
+ def replace(self, program_name, from_idx, to_idx, text):
+ if any((from_idx > to_idx, from_idx < 0, to_idx < 0, to_idx >= len(self.tokens.tokens))):
+ raise ValueError(
+ 'replace: range invalid: {}..{}(size={})'.format(from_idx, to_idx, len(self.tokens.tokens)))
+ op = self.ReplaceOp(from_idx, to_idx, self.tokens, text)
+ rewrites = self.getProgram(program_name)
+ op.instructionIndex = len(rewrites)
+ rewrites.append(op)
+
+ def deleteToken(self, token):
+ self.delete(self.DEFAULT_PROGRAM_NAME, token, token)
+
+ def deleteIndex(self, index):
+ self.delete(self.DEFAULT_PROGRAM_NAME, index, index)
+
+ def delete(self, program_name, from_idx, to_idx):
+ if isinstance(from_idx, Token):
+ self.replace(program_name, from_idx.tokenIndex, to_idx.tokenIndex, "")
+ else:
+ self.replace(program_name, from_idx, to_idx, "")
+
+ def lastRewriteTokenIndex(self, program_name=DEFAULT_PROGRAM_NAME):
+ return self.lastRewriteTokenIndexes.get(program_name, -1)
+
+ def setLastRewriteTokenIndex(self, program_name, i):
+ self.lastRewriteTokenIndexes[program_name] = i
+
+ def getProgram(self, program_name):
+ return self.programs.setdefault(program_name, [])
+
+ def getDefaultText(self):
+ return self.getText(self.DEFAULT_PROGRAM_NAME, 0, len(self.tokens.tokens) - 1)
+
+ def getText(self, program_name, start:int, stop:int):
+ """
+ :return: the text in tokens[start, stop](closed interval)
+ """
+ rewrites = self.programs.get(program_name)
+
+ # ensure start/end are in range
+ if stop > len(self.tokens.tokens) - 1:
+ stop = len(self.tokens.tokens) - 1
+ if start < 0:
+ start = 0
+
+ # if no instructions to execute
+ if not rewrites: return self.tokens.getText(start, stop)
+ buf = StringIO()
+ indexToOp = self._reduceToSingleOperationPerIndex(rewrites)
+ i = start
+ while all((i <= stop, i < len(self.tokens.tokens))):
+ op = indexToOp.pop(i, None)
+ token = self.tokens.get(i)
+ if op is None:
+ if token.type != Token.EOF: buf.write(token.text)
+ i += 1
+ else:
+ i = op.execute(buf)
+
+ if stop == len(self.tokens.tokens)-1:
+ for op in indexToOp.values():
+ if op.index >= len(self.tokens.tokens)-1: buf.write(op.text)
+
+ return buf.getvalue()
+
+ def _reduceToSingleOperationPerIndex(self, rewrites):
+ # Walk replaces
+ for i, rop in enumerate(rewrites):
+ if any((rop is None, not isinstance(rop, TokenStreamRewriter.ReplaceOp))):
+ continue
+ # Wipe prior inserts within range
+ inserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
+ for iop in inserts:
+ if iop.index == rop.index:
+ rewrites[iop.instructionIndex] = None
+ rop.text = '{}{}'.format(iop.text, rop.text)
+ elif all((iop.index > rop.index, iop.index <= rop.last_index)):
+ rewrites[iop.instructionIndex] = None
+
+ # Drop any prior replaces contained within
+ prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
+ for prevRop in prevReplaces:
+ if all((prevRop.index >= rop.index, prevRop.last_index <= rop.last_index)):
+ rewrites[prevRop.instructionIndex] = None
+ continue
+ isDisjoint = any((prevRop.last_indexrop.last_index))
+ if all((prevRop.text is None, rop.text is None, not isDisjoint)):
+ rewrites[prevRop.instructionIndex] = None
+ rop.index = min(prevRop.index, rop.index)
+ rop.last_index = min(prevRop.last_index, rop.last_index)
+ print('New rop {}'.format(rop))
+ elif (not(isDisjoint)):
+ raise ValueError("replace op boundaries of {} overlap with previous {}".format(rop, prevRop))
+
+ # Walk inserts
+ for i, iop in enumerate(rewrites):
+ if any((iop is None, not isinstance(iop, TokenStreamRewriter.InsertBeforeOp))):
+ continue
+ prevInserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
+ for prev_index, prevIop in enumerate(prevInserts):
+ if prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertBeforeOp:
+ iop.text += prevIop.text
+ rewrites[prev_index] = None
+ elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:
+ iop.text = prevIop.text + iop.text
+ rewrites[prev_index] = None
+ # look for replaces where iop.index is in range; error
+ prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
+ for rop in prevReplaces:
+ if iop.index == rop.index:
+ rop.text = iop.text + rop.text
+ rewrites[i] = None
+ continue
+ if all((iop.index >= rop.index, iop.index <= rop.last_index)):
+ raise ValueError("insert op {} within boundaries of previous {}".format(iop, rop))
+
+ reduced = {}
+ for i, op in enumerate(rewrites):
+ if op is None: continue
+ if reduced.get(op.index): raise ValueError('should be only one op per index')
+ reduced[op.index] = op
+
+ return reduced
+
+ class RewriteOperation(object):
+ __slots__ = ('tokens', 'index', 'text', 'instructionIndex')
+
+ def __init__(self, tokens, index, text=""):
+ """
+ :type tokens: CommonTokenStream
+ :param tokens:
+ :param index:
+ :param text:
+ :return:
+ """
+ self.tokens = tokens
+ self.index = index
+ self.text = text
+ self.instructionIndex = 0
+
+ def execute(self, buf):
+ """
+ :type buf: StringIO.StringIO
+ :param buf:
+ :return:
+ """
+ return self.index
+
+ def __str__(self):
+ return '<{}@{}:"{}">'.format(self.__class__.__name__, self.tokens.get(self.index), self.text)
+
+ class InsertBeforeOp(RewriteOperation):
+
+ def __init__(self, tokens, index, text=""):
+ super(TokenStreamRewriter.InsertBeforeOp, self).__init__(tokens, index, text)
+
+ def execute(self, buf):
+ buf.write(self.text)
+ if self.tokens.get(self.index).type != Token.EOF:
+ buf.write(self.tokens.get(self.index).text)
+ return self.index + 1
+
+ class InsertAfterOp(InsertBeforeOp):
+ pass
+
+ class ReplaceOp(RewriteOperation):
+ __slots__ = 'last_index'
+
+ def __init__(self, from_idx, to_idx, tokens, text):
+ super(TokenStreamRewriter.ReplaceOp, self).__init__(tokens, from_idx, text)
+ self.last_index = to_idx
+
+ def execute(self, buf):
+ if self.text:
+ buf.write(self.text)
+ return self.last_index + 1
+
+ def __str__(self):
+ if self.text:
+ return ''.format(self.tokens.get(self.index), self.tokens.get(self.last_index),
+ self.text)
diff --git a/lib/python3.12/site-packages/antlr4/Utils.py b/lib/python3.12/site-packages/antlr4/Utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..88c870dae63ef4bda74e25aea47befb75be34a5d
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/Utils.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+from io import StringIO
+
+def str_list(val):
+ with StringIO() as buf:
+ buf.write('[')
+ first = True
+ for item in val:
+ if not first:
+ buf.write(', ')
+ buf.write(str(item))
+ first = False
+ buf.write(']')
+ return buf.getvalue()
+
+def escapeWhitespace(s:str, escapeSpaces:bool):
+ with StringIO() as buf:
+ for c in s:
+ if c==' ' and escapeSpaces:
+ buf.write('\u00B7')
+ elif c=='\t':
+ buf.write("\\t")
+ elif c=='\n':
+ buf.write("\\n")
+ elif c=='\r':
+ buf.write("\\r")
+ else:
+ buf.write(c)
+ return buf.getvalue()
diff --git a/lib/python3.12/site-packages/antlr4/__init__.py b/lib/python3.12/site-packages/antlr4/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..42027289e7af293a297b538f07f43ca4a566ef62
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/__init__.py
@@ -0,0 +1,21 @@
+from antlr4.Token import Token
+from antlr4.InputStream import InputStream
+from antlr4.FileStream import FileStream
+from antlr4.StdinStream import StdinStream
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.CommonTokenStream import CommonTokenStream
+from antlr4.Lexer import Lexer
+from antlr4.Parser import Parser
+from antlr4.dfa.DFA import DFA
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNDeserializer import ATNDeserializer
+from antlr4.atn.LexerATNSimulator import LexerATNSimulator
+from antlr4.atn.ParserATNSimulator import ParserATNSimulator
+from antlr4.atn.PredictionMode import PredictionMode
+from antlr4.PredictionContext import PredictionContextCache
+from antlr4.ParserRuleContext import RuleContext, ParserRuleContext
+from antlr4.tree.Tree import ParseTreeListener, ParseTreeVisitor, ParseTreeWalker, TerminalNode, ErrorNode, RuleNode
+from antlr4.error.Errors import RecognitionException, IllegalStateException, NoViableAltException
+from antlr4.error.ErrorStrategy import BailErrorStrategy
+from antlr4.error.DiagnosticErrorListener import DiagnosticErrorListener
+from antlr4.Utils import str_list
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/BufferedTokenStream.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/BufferedTokenStream.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a9b61c7fbdda1da20f9799ffad59935551e079d0
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/BufferedTokenStream.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenFactory.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenFactory.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..15f0df0a0614f58b62d9d121cfc9a42cdb92f5c6
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenFactory.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenStream.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenStream.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..094d79ef856fbfb4bedd75bdd1490318871adeb6
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/CommonTokenStream.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/FileStream.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/FileStream.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..60c34f39505cd133b94203cbf3d384d94c8b04bd
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/FileStream.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/InputStream.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/InputStream.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ee65b5fbf0691c6edbbf6f6af010dec87d75affe
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/InputStream.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/IntervalSet.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/IntervalSet.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..44747fbd5481f79d1ed6eb2afc0c81a95e93c5cc
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/IntervalSet.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/LL1Analyzer.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/LL1Analyzer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c5d7f298c4649e3ec14ee43206235638ece7d7ce
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/LL1Analyzer.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/Lexer.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/Lexer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..942cd0a129dbdae32e9f6c7fe2cf036f4b599300
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/Lexer.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/ListTokenSource.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/ListTokenSource.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df0367988ac96a7e44cf9cb1253386fd0f41f76e
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/ListTokenSource.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/Parser.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/Parser.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b859b08d4be73788fedd4e2365d17beecc5c641e
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/Parser.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/ParserInterpreter.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/ParserInterpreter.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ae2efe0110e6b0c08f75602003293520c311cab0
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/ParserInterpreter.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/ParserRuleContext.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/ParserRuleContext.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0e1698a4c87c2de72338fc55f1d5765506673a57
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/ParserRuleContext.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/PredictionContext.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/PredictionContext.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..68f18099cd2f20cedc472ce81394f20cdbfb7f1c
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/PredictionContext.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/Recognizer.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/Recognizer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..45d183fe4809d86ca0eb753e7996c1542a2c5b2a
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/Recognizer.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/RuleContext.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/RuleContext.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3d6e16bf2b3824bccccff4086b2150ae6e078bf8
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/RuleContext.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/StdinStream.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/StdinStream.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6072ea8fbb6c5a483344a07e8d6936fa9f28dee9
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/StdinStream.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/Token.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/Token.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4a2244f6df60f2e2d9198561af9f06035adadf74
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/Token.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/TokenStreamRewriter.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/TokenStreamRewriter.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cd8f077f5926536e1e18784dbab98943ef471a8d
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/TokenStreamRewriter.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/Utils.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/Utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..76d5d1ff6614b3038988e5c3d6cd1820499f7fd5
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/Utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f4e315d06b792f112c4503ad7aa57a328efffca6
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATN.py b/lib/python3.12/site-packages/antlr4/atn/ATN.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f1abe0a4a7faacde5140d5631dfe48a79325cd4
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATN.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from antlr4.IntervalSet import IntervalSet
+
+from antlr4.RuleContext import RuleContext
+
+from antlr4.Token import Token
+from antlr4.atn.ATNType import ATNType
+from antlr4.atn.ATNState import ATNState, DecisionState
+
+
+class ATN(object):
+ __slots__ = (
+ 'grammarType', 'maxTokenType', 'states', 'decisionToState',
+ 'ruleToStartState', 'ruleToStopState', 'modeNameToStartState',
+ 'ruleToTokenType', 'lexerActions', 'modeToStartState'
+ )
+
+ INVALID_ALT_NUMBER = 0
+
+ # Used for runtime deserialization of ATNs from strings#/
+ def __init__(self, grammarType:ATNType , maxTokenType:int ):
+ # The type of the ATN.
+ self.grammarType = grammarType
+ # The maximum value for any symbol recognized by a transition in the ATN.
+ self.maxTokenType = maxTokenType
+ self.states = []
+ # Each subrule/rule is a decision point and we must track them so we
+ # can go back later and build DFA predictors for them. This includes
+ # all the rules, subrules, optional blocks, ()+, ()* etc...
+ self.decisionToState = []
+ # Maps from rule index to starting state number.
+ self.ruleToStartState = []
+ # Maps from rule index to stop state number.
+ self.ruleToStopState = None
+ self.modeNameToStartState = dict()
+ # For lexer ATNs, this maps the rule index to the resulting token type.
+ # For parser ATNs, this maps the rule index to the generated bypass token
+ # type if the
+ # {@link ATNDeserializationOptions#isGenerateRuleBypassTransitions}
+ # deserialization option was specified; otherwise, this is {@code null}.
+ self.ruleToTokenType = None
+ # For lexer ATNs, this is an array of {@link LexerAction} objects which may
+ # be referenced by action transitions in the ATN.
+ self.lexerActions = None
+ self.modeToStartState = []
+
+ # Compute the set of valid tokens that can occur starting in state {@code s}.
+ # If {@code ctx} is null, the set of tokens will not include what can follow
+ # the rule surrounding {@code s}. In other words, the set will be
+ # restricted to tokens reachable staying within {@code s}'s rule.
+ def nextTokensInContext(self, s:ATNState, ctx:RuleContext):
+ from antlr4.LL1Analyzer import LL1Analyzer
+ anal = LL1Analyzer(self)
+ return anal.LOOK(s, ctx=ctx)
+
+ # Compute the set of valid tokens that can occur starting in {@code s} and
+ # staying in same rule. {@link Token#EPSILON} is in set if we reach end of
+ # rule.
+ def nextTokensNoContext(self, s:ATNState):
+ if s.nextTokenWithinRule is not None:
+ return s.nextTokenWithinRule
+ s.nextTokenWithinRule = self.nextTokensInContext(s, None)
+ s.nextTokenWithinRule.readonly = True
+ return s.nextTokenWithinRule
+
+ def nextTokens(self, s:ATNState, ctx:RuleContext = None):
+ if ctx==None:
+ return self.nextTokensNoContext(s)
+ else:
+ return self.nextTokensInContext(s, ctx)
+
+ def addState(self, state:ATNState):
+ if state is not None:
+ state.atn = self
+ state.stateNumber = len(self.states)
+ self.states.append(state)
+
+ def removeState(self, state:ATNState):
+ self.states[state.stateNumber] = None # just free mem, don't shift states in list
+
+ def defineDecisionState(self, s:DecisionState):
+ self.decisionToState.append(s)
+ s.decision = len(self.decisionToState)-1
+ return s.decision
+
+ def getDecisionState(self, decision:int):
+ if len(self.decisionToState)==0:
+ return None
+ else:
+ return self.decisionToState[decision]
+
+ # Computes the set of input symbols which could follow ATN state number
+ # {@code stateNumber} in the specified full {@code context}. This method
+ # considers the complete parser context, but does not evaluate semantic
+ # predicates (i.e. all predicates encountered during the calculation are
+ # assumed true). If a path in the ATN exists from the starting state to the
+ # {@link RuleStopState} of the outermost context without matching any
+ # symbols, {@link Token#EOF} is added to the returned set.
+ #
+ # If {@code context} is {@code null}, it is treated as
+ # {@link ParserRuleContext#EMPTY}.
+ #
+ # @param stateNumber the ATN state number
+ # @param context the full parse context
+ # @return The set of potentially valid input symbols which could follow the
+ # specified state in the specified context.
+ # @throws IllegalArgumentException if the ATN does not contain a state with
+ # number {@code stateNumber}
+ #/
+ def getExpectedTokens(self, stateNumber:int, ctx:RuleContext ):
+ if stateNumber < 0 or stateNumber >= len(self.states):
+ raise Exception("Invalid state number.")
+ s = self.states[stateNumber]
+ following = self.nextTokens(s)
+ if Token.EPSILON not in following:
+ return following
+ expected = IntervalSet()
+ expected.addSet(following)
+ expected.removeOne(Token.EPSILON)
+ while (ctx != None and ctx.invokingState >= 0 and Token.EPSILON in following):
+ invokingState = self.states[ctx.invokingState]
+ rt = invokingState.transitions[0]
+ following = self.nextTokens(rt.followState)
+ expected.addSet(following)
+ expected.removeOne(Token.EPSILON)
+ ctx = ctx.parentCtx
+ if Token.EPSILON in following:
+ expected.addOne(Token.EOF)
+ return expected
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNConfig.py b/lib/python3.12/site-packages/antlr4/atn/ATNConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..e008fb2efac45a24b6d9566170bbd227cee252b6
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNConfig.py
@@ -0,0 +1,159 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# A tuple: (ATN state, predicted alt, syntactic, semantic context).
+# The syntactic context is a graph-structured stack node whose
+# path(s) to the root is the rule invocation(s)
+# chain used to arrive at the state. The semantic context is
+# the tree of semantic predicates encountered before reaching
+# an ATN state.
+#/
+from io import StringIO
+from antlr4.PredictionContext import PredictionContext
+from antlr4.atn.ATNState import ATNState, DecisionState
+from antlr4.atn.LexerActionExecutor import LexerActionExecutor
+from antlr4.atn.SemanticContext import SemanticContext
+
+# need a forward declaration
+ATNConfig = None
+
+class ATNConfig(object):
+ __slots__ = (
+ 'state', 'alt', 'context', 'semanticContext', 'reachesIntoOuterContext',
+ 'precedenceFilterSuppressed'
+ )
+
+ def __init__(self, state:ATNState=None, alt:int=None, context:PredictionContext=None, semantic:SemanticContext=None, config:ATNConfig=None):
+ if config is not None:
+ if state is None:
+ state = config.state
+ if alt is None:
+ alt = config.alt
+ if context is None:
+ context = config.context
+ if semantic is None:
+ semantic = config.semanticContext
+ if semantic is None:
+ semantic = SemanticContext.NONE
+ # The ATN state associated with this configuration#/
+ self.state = state
+ # What alt (or lexer rule) is predicted by this configuration#/
+ self.alt = alt
+ # The stack of invoking states leading to the rule/states associated
+ # with this config. We track only those contexts pushed during
+ # execution of the ATN simulator.
+ self.context = context
+ self.semanticContext = semantic
+ # We cannot execute predicates dependent upon local context unless
+ # we know for sure we are in the correct context. Because there is
+ # no way to do this efficiently, we simply cannot evaluate
+ # dependent predicates unless we are in the rule that initially
+ # invokes the ATN simulator.
+ #
+ # closure() tracks the depth of how far we dip into the
+ # outer context: depth > 0. Note that it may not be totally
+ # accurate depth since I don't ever decrement. TODO: make it a boolean then
+ self.reachesIntoOuterContext = 0 if config is None else config.reachesIntoOuterContext
+ self.precedenceFilterSuppressed = False if config is None else config.precedenceFilterSuppressed
+
+ # An ATN configuration is equal to another if both have
+ # the same state, they predict the same alternative, and
+ # syntactic/semantic contexts are the same.
+ #/
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfig):
+ return False
+ else:
+ return self.state.stateNumber==other.state.stateNumber \
+ and self.alt==other.alt \
+ and ((self.context is other.context) or (self.context==other.context)) \
+ and self.semanticContext==other.semanticContext \
+ and self.precedenceFilterSuppressed==other.precedenceFilterSuppressed
+
+ def __hash__(self):
+ return hash((self.state.stateNumber, self.alt, self.context, self.semanticContext))
+
+ def hashCodeForConfigSet(self):
+ return hash((self.state.stateNumber, self.alt, hash(self.semanticContext)))
+
+ def equalsForConfigSet(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfig):
+ return False
+ else:
+ return self.state.stateNumber==other.state.stateNumber \
+ and self.alt==other.alt \
+ and self.semanticContext==other.semanticContext
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write('(')
+ buf.write(str(self.state))
+ buf.write(",")
+ buf.write(str(self.alt))
+ if self.context is not None:
+ buf.write(",[")
+ buf.write(str(self.context))
+ buf.write("]")
+ if self.semanticContext is not None and self.semanticContext is not SemanticContext.NONE:
+ buf.write(",")
+ buf.write(str(self.semanticContext))
+ if self.reachesIntoOuterContext>0:
+ buf.write(",up=")
+ buf.write(str(self.reachesIntoOuterContext))
+ buf.write(')')
+ return buf.getvalue()
+
+# need a forward declaration
+LexerATNConfig = None
+
+class LexerATNConfig(ATNConfig):
+ __slots__ = ('lexerActionExecutor', 'passedThroughNonGreedyDecision')
+
+ def __init__(self, state:ATNState, alt:int=None, context:PredictionContext=None, semantic:SemanticContext=SemanticContext.NONE,
+ lexerActionExecutor:LexerActionExecutor=None, config:LexerATNConfig=None):
+ super().__init__(state=state, alt=alt, context=context, semantic=semantic, config=config)
+ if config is not None:
+ if lexerActionExecutor is None:
+ lexerActionExecutor = config.lexerActionExecutor
+ # This is the backing field for {@link #getLexerActionExecutor}.
+ self.lexerActionExecutor = lexerActionExecutor
+ self.passedThroughNonGreedyDecision = False if config is None else self.checkNonGreedyDecision(config, state)
+
+ def __hash__(self):
+ return hash((self.state.stateNumber, self.alt, self.context,
+ self.semanticContext, self.passedThroughNonGreedyDecision,
+ self.lexerActionExecutor))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerATNConfig):
+ return False
+ if self.passedThroughNonGreedyDecision != other.passedThroughNonGreedyDecision:
+ return False
+ if not(self.lexerActionExecutor == other.lexerActionExecutor):
+ return False
+ return super().__eq__(other)
+
+
+
+ def hashCodeForConfigSet(self):
+ return hash(self)
+
+
+
+ def equalsForConfigSet(self, other):
+ return self==other
+
+
+
+ def checkNonGreedyDecision(self, source:LexerATNConfig, target:ATNState):
+ return source.passedThroughNonGreedyDecision \
+ or isinstance(target, DecisionState) and target.nonGreedy
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNConfigSet.py b/lib/python3.12/site-packages/antlr4/atn/ATNConfigSet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e9a512a682431e46a3f5b848420a762af3c7914
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNConfigSet.py
@@ -0,0 +1,212 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+#
+# Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track
+# info about the set, with support for combining similar configurations using a
+# graph-structured stack.
+#/
+from io import StringIO
+from functools import reduce
+from antlr4.PredictionContext import PredictionContext, merge
+from antlr4.Utils import str_list
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.SemanticContext import SemanticContext
+from antlr4.error.Errors import UnsupportedOperationException, IllegalStateException
+
+ATNSimulator = None
+
+class ATNConfigSet(object):
+ __slots__ = (
+ 'configLookup', 'fullCtx', 'readonly', 'configs', 'uniqueAlt',
+ 'conflictingAlts', 'hasSemanticContext', 'dipsIntoOuterContext',
+ 'cachedHashCode'
+ )
+
+ #
+ # The reason that we need this is because we don't want the hash map to use
+ # the standard hash code and equals. We need all configurations with the same
+ # {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
+ # the number of objects associated with ATNConfigs. The other solution is to
+ # use a hash table that lets us specify the equals/hashcode operation.
+
+ def __init__(self, fullCtx:bool=True):
+ # All configs but hashed by (s, i, _, pi) not including context. Wiped out
+ # when we go readonly as this set becomes a DFA state.
+ self.configLookup = dict()
+ # Indicates that this configuration set is part of a full context
+ # LL prediction. It will be used to determine how to merge $. With SLL
+ # it's a wildcard whereas it is not for LL context merge.
+ self.fullCtx = fullCtx
+ # Indicates that the set of configurations is read-only. Do not
+ # allow any code to manipulate the set; DFA states will point at
+ # the sets and they must not change. This does not protect the other
+ # fields; in particular, conflictingAlts is set after
+ # we've made this readonly.
+ self.readonly = False
+ # Track the elements as they are added to the set; supports get(i)#/
+ self.configs = []
+
+ # TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves recomputation
+ # TODO: can we track conflicts as they are added to save scanning configs later?
+ self.uniqueAlt = 0
+ self.conflictingAlts = None
+
+ # Used in parser and lexer. In lexer, it indicates we hit a pred
+ # while computing a closure operation. Don't make a DFA state from this.
+ self.hasSemanticContext = False
+ self.dipsIntoOuterContext = False
+
+ self.cachedHashCode = -1
+
+ def __iter__(self):
+ return self.configs.__iter__()
+
+ # Adding a new config means merging contexts with existing configs for
+ # {@code (s, i, pi, _)}, where {@code s} is the
+ # {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
+ # {@code pi} is the {@link ATNConfig#semanticContext}. We use
+ # {@code (s,i,pi)} as key.
+ #
+ # This method updates {@link #dipsIntoOuterContext} and
+ # {@link #hasSemanticContext} when necessary.
+ #/
+ def add(self, config:ATNConfig, mergeCache=None):
+ if self.readonly:
+ raise Exception("This set is readonly")
+ if config.semanticContext is not SemanticContext.NONE:
+ self.hasSemanticContext = True
+ if config.reachesIntoOuterContext > 0:
+ self.dipsIntoOuterContext = True
+ existing = self.getOrAdd(config)
+ if existing is config:
+ self.cachedHashCode = -1
+ self.configs.append(config) # track order here
+ return True
+ # a previous (s,i,pi,_), merge with it and save result
+ rootIsWildcard = not self.fullCtx
+ merged = merge(existing.context, config.context, rootIsWildcard, mergeCache)
+ # no need to check for existing.context, config.context in cache
+ # since only way to create new graphs is "call rule" and here.
+ # We cache at both places.
+ existing.reachesIntoOuterContext = max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext)
+ # make sure to preserve the precedence filter suppression during the merge
+ if config.precedenceFilterSuppressed:
+ existing.precedenceFilterSuppressed = True
+ existing.context = merged # replace context; no need to alt mapping
+ return True
+
+ def getOrAdd(self, config:ATNConfig):
+ h = config.hashCodeForConfigSet()
+ l = self.configLookup.get(h, None)
+ if l is not None:
+ r = next((cfg for cfg in l if config.equalsForConfigSet(cfg)), None)
+ if r is not None:
+ return r
+ if l is None:
+ l = [config]
+ self.configLookup[h] = l
+ else:
+ l.append(config)
+ return config
+
+ def getStates(self):
+ return set(c.state for c in self.configs)
+
+ def getPredicates(self):
+ return list(cfg.semanticContext for cfg in self.configs if cfg.semanticContext!=SemanticContext.NONE)
+
+ def get(self, i:int):
+ return self.configs[i]
+
+ def optimizeConfigs(self, interpreter:ATNSimulator):
+ if self.readonly:
+ raise IllegalStateException("This set is readonly")
+ if len(self.configs)==0:
+ return
+ for config in self.configs:
+ config.context = interpreter.getCachedContext(config.context)
+
+ def addAll(self, coll:list):
+ for c in coll:
+ self.add(c)
+ return False
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, ATNConfigSet):
+ return False
+
+ same = self.configs is not None and \
+ self.configs==other.configs and \
+ self.fullCtx == other.fullCtx and \
+ self.uniqueAlt == other.uniqueAlt and \
+ self.conflictingAlts == other.conflictingAlts and \
+ self.hasSemanticContext == other.hasSemanticContext and \
+ self.dipsIntoOuterContext == other.dipsIntoOuterContext
+
+ return same
+
+ def __hash__(self):
+ if self.readonly:
+ if self.cachedHashCode == -1:
+ self.cachedHashCode = self.hashConfigs()
+ return self.cachedHashCode
+ return self.hashConfigs()
+
+ def hashConfigs(self):
+ return reduce(lambda h, cfg: hash((h, cfg)), self.configs, 0)
+
+ def __len__(self):
+ return len(self.configs)
+
+ def isEmpty(self):
+ return len(self.configs)==0
+
+ def __contains__(self, config):
+ if self.configLookup is None:
+ raise UnsupportedOperationException("This method is not implemented for readonly sets.")
+ h = config.hashCodeForConfigSet()
+ l = self.configLookup.get(h, None)
+ if l is not None:
+ for c in l:
+ if config.equalsForConfigSet(c):
+ return True
+ return False
+
+ def clear(self):
+ if self.readonly:
+ raise IllegalStateException("This set is readonly")
+ self.configs.clear()
+ self.cachedHashCode = -1
+ self.configLookup.clear()
+
+ def setReadonly(self, readonly:bool):
+ self.readonly = readonly
+ self.configLookup = None # can't mod, no need for lookup cache
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write(str_list(self.configs))
+ if self.hasSemanticContext:
+ buf.write(",hasSemanticContext=")
+ buf.write(str(self.hasSemanticContext))
+ if self.uniqueAlt!=ATN.INVALID_ALT_NUMBER:
+ buf.write(",uniqueAlt=")
+ buf.write(str(self.uniqueAlt))
+ if self.conflictingAlts is not None:
+ buf.write(",conflictingAlts=")
+ buf.write(str(self.conflictingAlts))
+ if self.dipsIntoOuterContext:
+ buf.write(",dipsIntoOuterContext")
+ return buf.getvalue()
+
+
+class OrderedATNConfigSet(ATNConfigSet):
+
+ def __init__(self):
+ super().__init__()
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNDeserializationOptions.py b/lib/python3.12/site-packages/antlr4/atn/ATNDeserializationOptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..69d5437f35437874a60bcc76118f018edbf65fed
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNDeserializationOptions.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+# need a forward declaration
+ATNDeserializationOptions = None
+
+class ATNDeserializationOptions(object):
+ __slots__ = ('readonly', 'verifyATN', 'generateRuleBypassTransitions')
+
+ defaultOptions = None
+
+ def __init__(self, copyFrom:ATNDeserializationOptions = None):
+ self.readonly = False
+ self.verifyATN = True if copyFrom is None else copyFrom.verifyATN
+ self.generateRuleBypassTransitions = False if copyFrom is None else copyFrom.generateRuleBypassTransitions
+
+ def __setattr__(self, key, value):
+ if key!="readonly" and self.readonly:
+ raise Exception("The object is read only.")
+ super(type(self), self).__setattr__(key,value)
+
+ATNDeserializationOptions.defaultOptions = ATNDeserializationOptions()
+ATNDeserializationOptions.defaultOptions.readonly = True
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNDeserializer.py b/lib/python3.12/site-packages/antlr4/atn/ATNDeserializer.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc100d05a43e10b91390dd47a7b05f2eff6fe23f
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNDeserializer.py
@@ -0,0 +1,529 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from uuid import UUID
+from io import StringIO
+from typing import Callable
+from antlr4.Token import Token
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNType import ATNType
+from antlr4.atn.ATNState import *
+from antlr4.atn.Transition import *
+from antlr4.atn.LexerAction import *
+from antlr4.atn.ATNDeserializationOptions import ATNDeserializationOptions
+
+# This is the earliest supported serialized UUID.
+BASE_SERIALIZED_UUID = UUID("AADB8D7E-AEEF-4415-AD2B-8204D6CF042E")
+
+# This UUID indicates the serialized ATN contains two sets of
+# IntervalSets, where the second set's values are encoded as
+# 32-bit integers to support the full Unicode SMP range up to U+10FFFF.
+ADDED_UNICODE_SMP = UUID("59627784-3BE5-417A-B9EB-8131A7286089")
+
+# This list contains all of the currently supported UUIDs, ordered by when
+# the feature first appeared in this branch.
+SUPPORTED_UUIDS = [ BASE_SERIALIZED_UUID, ADDED_UNICODE_SMP ]
+
+SERIALIZED_VERSION = 3
+
+# This is the current serialized UUID.
+SERIALIZED_UUID = ADDED_UNICODE_SMP
+
+class ATNDeserializer (object):
+ __slots__ = ('deserializationOptions', 'data', 'pos', 'uuid')
+
+ def __init__(self, options : ATNDeserializationOptions = None):
+ if options is None:
+ options = ATNDeserializationOptions.defaultOptions
+ self.deserializationOptions = options
+
+ # Determines if a particular serialized representation of an ATN supports
+ # a particular feature, identified by the {@link UUID} used for serializing
+ # the ATN at the time the feature was first introduced.
+ #
+ # @param feature The {@link UUID} marking the first time the feature was
+ # supported in the serialized ATN.
+ # @param actualUuid The {@link UUID} of the actual serialized ATN which is
+ # currently being deserialized.
+ # @return {@code true} if the {@code actualUuid} value represents a
+ # serialized ATN at or after the feature identified by {@code feature} was
+ # introduced; otherwise, {@code false}.
+
+ def isFeatureSupported(self, feature : UUID , actualUuid : UUID ):
+ idx1 = SUPPORTED_UUIDS.index(feature)
+ if idx1<0:
+ return False
+ idx2 = SUPPORTED_UUIDS.index(actualUuid)
+ return idx2 >= idx1
+
+ def deserialize(self, data : str):
+ self.reset(data)
+ self.checkVersion()
+ self.checkUUID()
+ atn = self.readATN()
+ self.readStates(atn)
+ self.readRules(atn)
+ self.readModes(atn)
+ sets = []
+ # First, read all sets with 16-bit Unicode code points <= U+FFFF.
+ self.readSets(atn, sets, self.readInt)
+ # Next, if the ATN was serialized with the Unicode SMP feature,
+ # deserialize sets with 32-bit arguments <= U+10FFFF.
+ if self.isFeatureSupported(ADDED_UNICODE_SMP, self.uuid):
+ self.readSets(atn, sets, self.readInt32)
+ self.readEdges(atn, sets)
+ self.readDecisions(atn)
+ self.readLexerActions(atn)
+ self.markPrecedenceDecisions(atn)
+ self.verifyATN(atn)
+ if self.deserializationOptions.generateRuleBypassTransitions \
+ and atn.grammarType == ATNType.PARSER:
+ self.generateRuleBypassTransitions(atn)
+ # re-verify after modification
+ self.verifyATN(atn)
+ return atn
+
+ def reset(self, data:str):
+ def adjust(c):
+ v = ord(c)
+ return v-2 if v>1 else v + 65533
+ temp = [ adjust(c) for c in data ]
+ # don't adjust the first value since that's the version number
+ temp[0] = ord(data[0])
+ self.data = temp
+ self.pos = 0
+
+ def checkVersion(self):
+ version = self.readInt()
+ if version != SERIALIZED_VERSION:
+ raise Exception("Could not deserialize ATN with version " + str(version) + " (expected " + str(SERIALIZED_VERSION) + ").")
+
+ def checkUUID(self):
+ uuid = self.readUUID()
+ if not uuid in SUPPORTED_UUIDS:
+ raise Exception("Could not deserialize ATN with UUID: " + str(uuid) + \
+ " (expected " + str(SERIALIZED_UUID) + " or a legacy UUID).", uuid, SERIALIZED_UUID)
+ self.uuid = uuid
+
+ def readATN(self):
+ idx = self.readInt()
+ grammarType = ATNType.fromOrdinal(idx)
+ maxTokenType = self.readInt()
+ return ATN(grammarType, maxTokenType)
+
+ def readStates(self, atn:ATN):
+ loopBackStateNumbers = []
+ endStateNumbers = []
+ nstates = self.readInt()
+ for i in range(0, nstates):
+ stype = self.readInt()
+ # ignore bad type of states
+ if stype==ATNState.INVALID_TYPE:
+ atn.addState(None)
+ continue
+ ruleIndex = self.readInt()
+ if ruleIndex == 0xFFFF:
+ ruleIndex = -1
+
+ s = self.stateFactory(stype, ruleIndex)
+ if stype == ATNState.LOOP_END: # special case
+ loopBackStateNumber = self.readInt()
+ loopBackStateNumbers.append((s, loopBackStateNumber))
+ elif isinstance(s, BlockStartState):
+ endStateNumber = self.readInt()
+ endStateNumbers.append((s, endStateNumber))
+
+ atn.addState(s)
+
+ # delay the assignment of loop back and end states until we know all the state instances have been initialized
+ for pair in loopBackStateNumbers:
+ pair[0].loopBackState = atn.states[pair[1]]
+
+ for pair in endStateNumbers:
+ pair[0].endState = atn.states[pair[1]]
+
+ numNonGreedyStates = self.readInt()
+ for i in range(0, numNonGreedyStates):
+ stateNumber = self.readInt()
+ atn.states[stateNumber].nonGreedy = True
+
+ numPrecedenceStates = self.readInt()
+ for i in range(0, numPrecedenceStates):
+ stateNumber = self.readInt()
+ atn.states[stateNumber].isPrecedenceRule = True
+
+ def readRules(self, atn:ATN):
+ nrules = self.readInt()
+ if atn.grammarType == ATNType.LEXER:
+ atn.ruleToTokenType = [0] * nrules
+
+ atn.ruleToStartState = [0] * nrules
+ for i in range(0, nrules):
+ s = self.readInt()
+ startState = atn.states[s]
+ atn.ruleToStartState[i] = startState
+ if atn.grammarType == ATNType.LEXER:
+ tokenType = self.readInt()
+ if tokenType == 0xFFFF:
+ tokenType = Token.EOF
+
+ atn.ruleToTokenType[i] = tokenType
+
+ atn.ruleToStopState = [0] * nrules
+ for state in atn.states:
+ if not isinstance(state, RuleStopState):
+ continue
+ atn.ruleToStopState[state.ruleIndex] = state
+ atn.ruleToStartState[state.ruleIndex].stopState = state
+
+ def readModes(self, atn:ATN):
+ nmodes = self.readInt()
+ for i in range(0, nmodes):
+ s = self.readInt()
+ atn.modeToStartState.append(atn.states[s])
+
+ def readSets(self, atn:ATN, sets:list, readUnicode:Callable[[], int]):
+ m = self.readInt()
+ for i in range(0, m):
+ iset = IntervalSet()
+ sets.append(iset)
+ n = self.readInt()
+ containsEof = self.readInt()
+ if containsEof!=0:
+ iset.addOne(-1)
+ for j in range(0, n):
+ i1 = readUnicode()
+ i2 = readUnicode()
+ iset.addRange(range(i1, i2 + 1)) # range upper limit is exclusive
+
+ def readEdges(self, atn:ATN, sets:list):
+ nedges = self.readInt()
+ for i in range(0, nedges):
+ src = self.readInt()
+ trg = self.readInt()
+ ttype = self.readInt()
+ arg1 = self.readInt()
+ arg2 = self.readInt()
+ arg3 = self.readInt()
+ trans = self.edgeFactory(atn, ttype, src, trg, arg1, arg2, arg3, sets)
+ srcState = atn.states[src]
+ srcState.addTransition(trans)
+
+ # edges for rule stop states can be derived, so they aren't serialized
+ for state in atn.states:
+ for i in range(0, len(state.transitions)):
+ t = state.transitions[i]
+ if not isinstance(t, RuleTransition):
+ continue
+ outermostPrecedenceReturn = -1
+ if atn.ruleToStartState[t.target.ruleIndex].isPrecedenceRule:
+ if t.precedence == 0:
+ outermostPrecedenceReturn = t.target.ruleIndex
+ trans = EpsilonTransition(t.followState, outermostPrecedenceReturn)
+ atn.ruleToStopState[t.target.ruleIndex].addTransition(trans)
+
+ for state in atn.states:
+ if isinstance(state, BlockStartState):
+ # we need to know the end state to set its start state
+ if state.endState is None:
+ raise Exception("IllegalState")
+ # block end states can only be associated to a single block start state
+ if state.endState.startState is not None:
+ raise Exception("IllegalState")
+ state.endState.startState = state
+
+ if isinstance(state, PlusLoopbackState):
+ for i in range(0, len(state.transitions)):
+ target = state.transitions[i].target
+ if isinstance(target, PlusBlockStartState):
+ target.loopBackState = state
+ elif isinstance(state, StarLoopbackState):
+ for i in range(0, len(state.transitions)):
+ target = state.transitions[i].target
+ if isinstance(target, StarLoopEntryState):
+ target.loopBackState = state
+
+ def readDecisions(self, atn:ATN):
+ ndecisions = self.readInt()
+ for i in range(0, ndecisions):
+ s = self.readInt()
+ decState = atn.states[s]
+ atn.decisionToState.append(decState)
+ decState.decision = i
+
+ def readLexerActions(self, atn:ATN):
+ if atn.grammarType == ATNType.LEXER:
+ count = self.readInt()
+ atn.lexerActions = [ None ] * count
+ for i in range(0, count):
+ actionType = self.readInt()
+ data1 = self.readInt()
+ if data1 == 0xFFFF:
+ data1 = -1
+ data2 = self.readInt()
+ if data2 == 0xFFFF:
+ data2 = -1
+ lexerAction = self.lexerActionFactory(actionType, data1, data2)
+ atn.lexerActions[i] = lexerAction
+
+ def generateRuleBypassTransitions(self, atn:ATN):
+
+ count = len(atn.ruleToStartState)
+ atn.ruleToTokenType = [ 0 ] * count
+ for i in range(0, count):
+ atn.ruleToTokenType[i] = atn.maxTokenType + i + 1
+
+ for i in range(0, count):
+ self.generateRuleBypassTransition(atn, i)
+
+ def generateRuleBypassTransition(self, atn:ATN, idx:int):
+
+ bypassStart = BasicBlockStartState()
+ bypassStart.ruleIndex = idx
+ atn.addState(bypassStart)
+
+ bypassStop = BlockEndState()
+ bypassStop.ruleIndex = idx
+ atn.addState(bypassStop)
+
+ bypassStart.endState = bypassStop
+ atn.defineDecisionState(bypassStart)
+
+ bypassStop.startState = bypassStart
+
+ excludeTransition = None
+
+ if atn.ruleToStartState[idx].isPrecedenceRule:
+ # wrap from the beginning of the rule to the StarLoopEntryState
+ endState = None
+ for state in atn.states:
+ if self.stateIsEndStateFor(state, idx):
+ endState = state
+ excludeTransition = state.loopBackState.transitions[0]
+ break
+
+ if excludeTransition is None:
+ raise Exception("Couldn't identify final state of the precedence rule prefix section.")
+
+ else:
+
+ endState = atn.ruleToStopState[idx]
+
+ # all non-excluded transitions that currently target end state need to target blockEnd instead
+ for state in atn.states:
+ for transition in state.transitions:
+ if transition == excludeTransition:
+ continue
+ if transition.target == endState:
+ transition.target = bypassStop
+
+ # all transitions leaving the rule start state need to leave blockStart instead
+ ruleToStartState = atn.ruleToStartState[idx]
+ count = len(ruleToStartState.transitions)
+ while count > 0:
+ bypassStart.addTransition(ruleToStartState.transitions[count-1])
+ del ruleToStartState.transitions[-1]
+
+ # link the new states
+ atn.ruleToStartState[idx].addTransition(EpsilonTransition(bypassStart))
+ bypassStop.addTransition(EpsilonTransition(endState))
+
+ matchState = BasicState()
+ atn.addState(matchState)
+ matchState.addTransition(AtomTransition(bypassStop, atn.ruleToTokenType[idx]))
+ bypassStart.addTransition(EpsilonTransition(matchState))
+
+
+ def stateIsEndStateFor(self, state:ATNState, idx:int):
+ if state.ruleIndex != idx:
+ return None
+ if not isinstance(state, StarLoopEntryState):
+ return None
+
+ maybeLoopEndState = state.transitions[len(state.transitions) - 1].target
+ if not isinstance(maybeLoopEndState, LoopEndState):
+ return None
+
+ if maybeLoopEndState.epsilonOnlyTransitions and \
+ isinstance(maybeLoopEndState.transitions[0].target, RuleStopState):
+ return state
+ else:
+ return None
+
+
+ #
+ # Analyze the {@link StarLoopEntryState} states in the specified ATN to set
+ # the {@link StarLoopEntryState#isPrecedenceDecision} field to the
+ # correct value.
+ #
+ # @param atn The ATN.
+ #
+ def markPrecedenceDecisions(self, atn:ATN):
+ for state in atn.states:
+ if not isinstance(state, StarLoopEntryState):
+ continue
+
+ # We analyze the ATN to determine if this ATN decision state is the
+ # decision for the closure block that determines whether a
+ # precedence rule should continue or complete.
+ #
+ if atn.ruleToStartState[state.ruleIndex].isPrecedenceRule:
+ maybeLoopEndState = state.transitions[len(state.transitions) - 1].target
+ if isinstance(maybeLoopEndState, LoopEndState):
+ if maybeLoopEndState.epsilonOnlyTransitions and \
+ isinstance(maybeLoopEndState.transitions[0].target, RuleStopState):
+ state.isPrecedenceDecision = True
+
+ def verifyATN(self, atn:ATN):
+ if not self.deserializationOptions.verifyATN:
+ return
+ # verify assumptions
+ for state in atn.states:
+ if state is None:
+ continue
+
+ self.checkCondition(state.epsilonOnlyTransitions or len(state.transitions) <= 1)
+
+ if isinstance(state, PlusBlockStartState):
+ self.checkCondition(state.loopBackState is not None)
+
+ if isinstance(state, StarLoopEntryState):
+ self.checkCondition(state.loopBackState is not None)
+ self.checkCondition(len(state.transitions) == 2)
+
+ if isinstance(state.transitions[0].target, StarBlockStartState):
+ self.checkCondition(isinstance(state.transitions[1].target, LoopEndState))
+ self.checkCondition(not state.nonGreedy)
+ elif isinstance(state.transitions[0].target, LoopEndState):
+ self.checkCondition(isinstance(state.transitions[1].target, StarBlockStartState))
+ self.checkCondition(state.nonGreedy)
+ else:
+ raise Exception("IllegalState")
+
+ if isinstance(state, StarLoopbackState):
+ self.checkCondition(len(state.transitions) == 1)
+ self.checkCondition(isinstance(state.transitions[0].target, StarLoopEntryState))
+
+ if isinstance(state, LoopEndState):
+ self.checkCondition(state.loopBackState is not None)
+
+ if isinstance(state, RuleStartState):
+ self.checkCondition(state.stopState is not None)
+
+ if isinstance(state, BlockStartState):
+ self.checkCondition(state.endState is not None)
+
+ if isinstance(state, BlockEndState):
+ self.checkCondition(state.startState is not None)
+
+ if isinstance(state, DecisionState):
+ self.checkCondition(len(state.transitions) <= 1 or state.decision >= 0)
+ else:
+ self.checkCondition(len(state.transitions) <= 1 or isinstance(state, RuleStopState))
+
+ def checkCondition(self, condition:bool, message=None):
+ if not condition:
+ if message is None:
+ message = "IllegalState"
+ raise Exception(message)
+
+ def readInt(self):
+ i = self.data[self.pos]
+ self.pos += 1
+ return i
+
+ def readInt32(self):
+ low = self.readInt()
+ high = self.readInt()
+ return low | (high << 16)
+
+ def readLong(self):
+ low = self.readInt32()
+ high = self.readInt32()
+ return (low & 0x00000000FFFFFFFF) | (high << 32)
+
+ def readUUID(self):
+ low = self.readLong()
+ high = self.readLong()
+ allBits = (low & 0xFFFFFFFFFFFFFFFF) | (high << 64)
+ return UUID(int=allBits)
+
+ edgeFactories = [ lambda args : None,
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : EpsilonTransition(target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ RangeTransition(target, Token.EOF, arg2) if arg3 != 0 else RangeTransition(target, arg1, arg2),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ RuleTransition(atn.states[arg1], arg2, arg3, target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ PredicateTransition(target, arg1, arg2, arg3 != 0),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ AtomTransition(target, Token.EOF) if arg3 != 0 else AtomTransition(target, arg1),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ ActionTransition(target, arg1, arg2, arg3 != 0),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ SetTransition(target, sets[arg1]),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ NotSetTransition(target, sets[arg1]),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ WildcardTransition(target),
+ lambda atn, src, trg, arg1, arg2, arg3, sets, target : \
+ PrecedencePredicateTransition(target, arg1)
+ ]
+
+ def edgeFactory(self, atn:ATN, type:int, src:int, trg:int, arg1:int, arg2:int, arg3:int, sets:list):
+ target = atn.states[trg]
+ if type > len(self.edgeFactories) or self.edgeFactories[type] is None:
+ raise Exception("The specified transition type: " + str(type) + " is not valid.")
+ else:
+ return self.edgeFactories[type](atn, src, trg, arg1, arg2, arg3, sets, target)
+
+ stateFactories = [ lambda : None,
+ lambda : BasicState(),
+ lambda : RuleStartState(),
+ lambda : BasicBlockStartState(),
+ lambda : PlusBlockStartState(),
+ lambda : StarBlockStartState(),
+ lambda : TokensStartState(),
+ lambda : RuleStopState(),
+ lambda : BlockEndState(),
+ lambda : StarLoopbackState(),
+ lambda : StarLoopEntryState(),
+ lambda : PlusLoopbackState(),
+ lambda : LoopEndState()
+ ]
+
+ def stateFactory(self, type:int, ruleIndex:int):
+ if type> len(self.stateFactories) or self.stateFactories[type] is None:
+ raise Exception("The specified state type " + str(type) + " is not valid.")
+ else:
+ s = self.stateFactories[type]()
+ if s is not None:
+ s.ruleIndex = ruleIndex
+ return s
+
+ CHANNEL = 0 #The type of a {@link LexerChannelAction} action.
+ CUSTOM = 1 #The type of a {@link LexerCustomAction} action.
+ MODE = 2 #The type of a {@link LexerModeAction} action.
+ MORE = 3 #The type of a {@link LexerMoreAction} action.
+ POP_MODE = 4 #The type of a {@link LexerPopModeAction} action.
+ PUSH_MODE = 5 #The type of a {@link LexerPushModeAction} action.
+ SKIP = 6 #The type of a {@link LexerSkipAction} action.
+ TYPE = 7 #The type of a {@link LexerTypeAction} action.
+
+ actionFactories = [ lambda data1, data2: LexerChannelAction(data1),
+ lambda data1, data2: LexerCustomAction(data1, data2),
+ lambda data1, data2: LexerModeAction(data1),
+ lambda data1, data2: LexerMoreAction.INSTANCE,
+ lambda data1, data2: LexerPopModeAction.INSTANCE,
+ lambda data1, data2: LexerPushModeAction(data1),
+ lambda data1, data2: LexerSkipAction.INSTANCE,
+ lambda data1, data2: LexerTypeAction(data1)
+ ]
+
+ def lexerActionFactory(self, type:int, data1:int, data2:int):
+
+ if type > len(self.actionFactories) or self.actionFactories[type] is None:
+ raise Exception("The specified lexer action type " + str(type) + " is not valid.")
+ else:
+ return self.actionFactories[type](data1, data2)
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNSimulator.py b/lib/python3.12/site-packages/antlr4/atn/ATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f6f53f488fa2b731fb39ceecf350b0cb43d1c39
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNSimulator.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+from antlr4.PredictionContext import PredictionContextCache, PredictionContext, getCachedPredictionContext
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.dfa.DFAState import DFAState
+
+
+class ATNSimulator(object):
+ __slots__ = ('atn', 'sharedContextCache', '__dict__')
+
+ # Must distinguish between missing edge and edge we know leads nowhere#/
+ ERROR = DFAState(configs=ATNConfigSet())
+ ERROR.stateNumber = 0x7FFFFFFF
+
+ # The context cache maps all PredictionContext objects that are ==
+ # to a single cached copy. This cache is shared across all contexts
+ # in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet
+ # to use only cached nodes/graphs in addDFAState(). We don't want to
+ # fill this during closure() since there are lots of contexts that
+ # pop up but are not used ever again. It also greatly slows down closure().
+ #
+ # This cache makes a huge difference in memory and a little bit in speed.
+ # For the Java grammar on java.*, it dropped the memory requirements
+ # at the end from 25M to 16M. We don't store any of the full context
+ # graphs in the DFA because they are limited to local context only,
+ # but apparently there's a lot of repetition there as well. We optimize
+ # the config contexts before storing the config set in the DFA states
+ # by literally rebuilding them with cached subgraphs only.
+ #
+ # I tried a cache for use during closure operations, that was
+ # whacked after each adaptivePredict(). It cost a little bit
+ # more time I think and doesn't save on the overall footprint
+ # so it's not worth the complexity.
+ #/
+ def __init__(self, atn:ATN, sharedContextCache:PredictionContextCache):
+ self.atn = atn
+ self.sharedContextCache = sharedContextCache
+
+ def getCachedContext(self, context:PredictionContext):
+ if self.sharedContextCache is None:
+ return context
+ visited = dict()
+ return getCachedPredictionContext(context, self.sharedContextCache, visited)
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNState.py b/lib/python3.12/site-packages/antlr4/atn/ATNState.py
new file mode 100644
index 0000000000000000000000000000000000000000..fbf6a7b9442905c992bb1a88ce343a8a9d63f5b7
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNState.py
@@ -0,0 +1,264 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# The following images show the relation of states and
+# {@link ATNState#transitions} for various grammar constructs.
+#
+#
+#
+# - Solid edges marked with an ε indicate a required
+# {@link EpsilonTransition}.
+#
+# - Dashed edges indicate locations where any transition derived from
+# {@link Transition} might appear.
+#
+# - Dashed nodes are place holders for either a sequence of linked
+# {@link BasicState} states or the inclusion of a block representing a nested
+# construct in one of the forms below.
+#
+# - Nodes showing multiple outgoing alternatives with a {@code ...} support
+# any number of alternatives (one or more). Nodes without the {@code ...} only
+# support the exact number of alternatives shown in the diagram.
+#
+#
+#
+# Basic Blocks
+#
+# Rule
+#
+#
+#
+# Block of 1 or more alternatives
+#
+#
+#
+# Greedy Loops
+#
+# Greedy Closure: {@code (...)*}
+#
+#
+#
+# Greedy Positive Closure: {@code (...)+}
+#
+#
+#
+# Greedy Optional: {@code (...)?}
+#
+#
+#
+# Non-Greedy Loops
+#
+# Non-Greedy Closure: {@code (...)*?}
+#
+#
+#
+# Non-Greedy Positive Closure: {@code (...)+?}
+#
+#
+#
+# Non-Greedy Optional: {@code (...)??}
+#
+#
+#
+
+from antlr4.atn.Transition import Transition
+
+INITIAL_NUM_TRANSITIONS = 4
+
+class ATNState(object):
+ __slots__ = (
+ 'atn', 'stateNumber', 'stateType', 'ruleIndex', 'epsilonOnlyTransitions',
+ 'transitions', 'nextTokenWithinRule',
+ )
+
+ # constants for serialization
+ INVALID_TYPE = 0
+ BASIC = 1
+ RULE_START = 2
+ BLOCK_START = 3
+ PLUS_BLOCK_START = 4
+ STAR_BLOCK_START = 5
+ TOKEN_START = 6
+ RULE_STOP = 7
+ BLOCK_END = 8
+ STAR_LOOP_BACK = 9
+ STAR_LOOP_ENTRY = 10
+ PLUS_LOOP_BACK = 11
+ LOOP_END = 12
+
+ serializationNames = [
+ "INVALID",
+ "BASIC",
+ "RULE_START",
+ "BLOCK_START",
+ "PLUS_BLOCK_START",
+ "STAR_BLOCK_START",
+ "TOKEN_START",
+ "RULE_STOP",
+ "BLOCK_END",
+ "STAR_LOOP_BACK",
+ "STAR_LOOP_ENTRY",
+ "PLUS_LOOP_BACK",
+ "LOOP_END" ]
+
+ INVALID_STATE_NUMBER = -1
+
+ def __init__(self):
+ # Which ATN are we in?
+ self.atn = None
+ self.stateNumber = ATNState.INVALID_STATE_NUMBER
+ self.stateType = None
+ self.ruleIndex = 0 # at runtime, we don't have Rule objects
+ self.epsilonOnlyTransitions = False
+ # Track the transitions emanating from this ATN state.
+ self.transitions = []
+ # Used to cache lookahead during parsing, not used during construction
+ self.nextTokenWithinRule = None
+
+ def __hash__(self):
+ return self.stateNumber
+
+ def __eq__(self, other):
+ return isinstance(other, ATNState) and self.stateNumber==other.stateNumber
+
+ def onlyHasEpsilonTransitions(self):
+ return self.epsilonOnlyTransitions
+
+ def isNonGreedyExitState(self):
+ return False
+
+ def __str__(self):
+ return str(self.stateNumber)
+
+ def addTransition(self, trans:Transition, index:int=-1):
+ if len(self.transitions)==0:
+ self.epsilonOnlyTransitions = trans.isEpsilon
+ elif self.epsilonOnlyTransitions != trans.isEpsilon:
+ self.epsilonOnlyTransitions = False
+ # TODO System.err.format(Locale.getDefault(), "ATN state %d has both epsilon and non-epsilon transitions.\n", stateNumber);
+ if index==-1:
+ self.transitions.append(trans)
+ else:
+ self.transitions.insert(index, trans)
+
+class BasicState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BASIC
+
+
+class DecisionState(ATNState):
+ __slots__ = ('decision', 'nonGreedy')
+ def __init__(self):
+ super().__init__()
+ self.decision = -1
+ self.nonGreedy = False
+
+# The start of a regular {@code (...)} block.
+class BlockStartState(DecisionState):
+ __slots__ = 'endState'
+
+ def __init__(self):
+ super().__init__()
+ self.endState = None
+
+class BasicBlockStartState(BlockStartState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BLOCK_START
+
+# Terminal node of a simple {@code (a|b|c)} block.
+class BlockEndState(ATNState):
+ __slots__ = 'startState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.BLOCK_END
+ self.startState = None
+
+# The last node in the ATN for a rule, unless that rule is the start symbol.
+# In that case, there is one transition to EOF. Later, we might encode
+# references to all calls to this rule to compute FOLLOW sets for
+# error handling.
+#
+class RuleStopState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.RULE_STOP
+
+class RuleStartState(ATNState):
+ __slots__ = ('stopState', 'isPrecedenceRule')
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.RULE_START
+ self.stopState = None
+ self.isPrecedenceRule = False
+
+# Decision state for {@code A+} and {@code (A|B)+}. It has two transitions:
+# one to the loop back to start of the block and one to exit.
+#
+class PlusLoopbackState(DecisionState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.PLUS_LOOP_BACK
+
+# Start of {@code (A|B|...)+} loop. Technically a decision state, but
+# we don't use for code generation; somebody might need it, so I'm defining
+# it for completeness. In reality, the {@link PlusLoopbackState} node is the
+# real decision-making note for {@code A+}.
+#
+class PlusBlockStartState(BlockStartState):
+ __slots__ = 'loopBackState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.PLUS_BLOCK_START
+ self.loopBackState = None
+
+# The block that begins a closure loop.
+class StarBlockStartState(BlockStartState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_BLOCK_START
+
+class StarLoopbackState(ATNState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_LOOP_BACK
+
+
+class StarLoopEntryState(DecisionState):
+ __slots__ = ('loopBackState', 'isPrecedenceDecision')
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.STAR_LOOP_ENTRY
+ self.loopBackState = None
+ # Indicates whether this state can benefit from a precedence DFA during SLL decision making.
+ self.isPrecedenceDecision = None
+
+# Mark the end of a * or + loop.
+class LoopEndState(ATNState):
+ __slots__ = 'loopBackState'
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.LOOP_END
+ self.loopBackState = None
+
+# The Tokens rule start state linking to each lexer rule start state */
+class TokensStartState(DecisionState):
+
+ def __init__(self):
+ super().__init__()
+ self.stateType = self.TOKEN_START
diff --git a/lib/python3.12/site-packages/antlr4/atn/ATNType.py b/lib/python3.12/site-packages/antlr4/atn/ATNType.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc0d4d925c6babee1caeee08f5e32fe4a1d6d0d4
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ATNType.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+from enum import IntEnum
+
+# Represents the type of recognizer an ATN applies to.
+
+class ATNType(IntEnum):
+
+ LEXER = 0
+ PARSER = 1
+
+ @classmethod
+ def fromOrdinal(cls, i:int):
+ return cls._value2member_map_[i]
diff --git a/lib/python3.12/site-packages/antlr4/atn/LexerATNSimulator.py b/lib/python3.12/site-packages/antlr4/atn/LexerATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..71201ff5f989820d340aac8672ab31db8579b55c
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/LexerATNSimulator.py
@@ -0,0 +1,570 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# When we hit an accept state in either the DFA or the ATN, we
+# have to notify the character stream to start buffering characters
+# via {@link IntStream#mark} and record the current state. The current sim state
+# includes the current index into the input, the current line,
+# and current character position in that line. Note that the Lexer is
+# tracking the starting line and characterization of the token. These
+# variables track the "state" of the simulator when it hits an accept state.
+#
+# We track these variables separately for the DFA and ATN simulation
+# because the DFA simulation often has to fail over to the ATN
+# simulation. If the ATN simulation fails, we need the DFA to fall
+# back to its previously accepted state, if any. If the ATN succeeds,
+# then the ATN does the accept and the DFA simulator that invoked it
+# can simply return the predicted token type.
+#/
+
+from antlr4.PredictionContext import PredictionContextCache, SingletonPredictionContext, PredictionContext
+from antlr4.InputStream import InputStream
+from antlr4.Token import Token
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import LexerATNConfig
+from antlr4.atn.ATNSimulator import ATNSimulator
+from antlr4.atn.ATNConfigSet import ATNConfigSet, OrderedATNConfigSet
+from antlr4.atn.ATNState import RuleStopState, ATNState
+from antlr4.atn.LexerActionExecutor import LexerActionExecutor
+from antlr4.atn.Transition import Transition
+from antlr4.dfa.DFAState import DFAState
+from antlr4.error.Errors import LexerNoViableAltException, UnsupportedOperationException
+
+class SimState(object):
+ __slots__ = ('index', 'line', 'column', 'dfaState')
+
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.index = -1
+ self.line = 0
+ self.column = -1
+ self.dfaState = None
+
+# need forward declaration
+Lexer = None
+LexerATNSimulator = None
+
+class LexerATNSimulator(ATNSimulator):
+ __slots__ = (
+ 'decisionToDFA', 'recog', 'startIndex', 'line', 'column', 'mode',
+ 'DEFAULT_MODE', 'MAX_CHAR_VALUE', 'prevAccept'
+ )
+
+ debug = False
+ dfa_debug = False
+
+ MIN_DFA_EDGE = 0
+ MAX_DFA_EDGE = 127 # forces unicode to stay in ATN
+
+ ERROR = None
+
+ def __init__(self, recog:Lexer, atn:ATN, decisionToDFA:list, sharedContextCache:PredictionContextCache):
+ super().__init__(atn, sharedContextCache)
+ self.decisionToDFA = decisionToDFA
+ self.recog = recog
+ # The current token's starting index into the character stream.
+ # Shared across DFA to ATN simulation in case the ATN fails and the
+ # DFA did not have a previous accept state. In this case, we use the
+ # ATN-generated exception object.
+ self.startIndex = -1
+ # line number 1..n within the input#/
+ self.line = 1
+ # The index of the character relative to the beginning of the line 0..n-1#/
+ self.column = 0
+ from antlr4.Lexer import Lexer
+ self.mode = Lexer.DEFAULT_MODE
+ # Cache Lexer properties to avoid further imports
+ self.DEFAULT_MODE = Lexer.DEFAULT_MODE
+ self.MAX_CHAR_VALUE = Lexer.MAX_CHAR_VALUE
+ # Used during DFA/ATN exec to record the most recent accept configuration info
+ self.prevAccept = SimState()
+
+
+ def copyState(self, simulator:LexerATNSimulator ):
+ self.column = simulator.column
+ self.line = simulator.line
+ self.mode = simulator.mode
+ self.startIndex = simulator.startIndex
+
+ def match(self, input:InputStream , mode:int):
+ self.mode = mode
+ mark = input.mark()
+ try:
+ self.startIndex = input.index
+ self.prevAccept.reset()
+ dfa = self.decisionToDFA[mode]
+ if dfa.s0 is None:
+ return self.matchATN(input)
+ else:
+ return self.execATN(input, dfa.s0)
+ finally:
+ input.release(mark)
+
+ def reset(self):
+ self.prevAccept.reset()
+ self.startIndex = -1
+ self.line = 1
+ self.column = 0
+ self.mode = self.DEFAULT_MODE
+
+ def matchATN(self, input:InputStream):
+ startState = self.atn.modeToStartState[self.mode]
+
+ if LexerATNSimulator.debug:
+ print("matchATN mode " + str(self.mode) + " start: " + str(startState))
+
+ old_mode = self.mode
+ s0_closure = self.computeStartState(input, startState)
+ suppressEdge = s0_closure.hasSemanticContext
+ s0_closure.hasSemanticContext = False
+
+ next = self.addDFAState(s0_closure)
+ if not suppressEdge:
+ self.decisionToDFA[self.mode].s0 = next
+
+ predict = self.execATN(input, next)
+
+ if LexerATNSimulator.debug:
+ print("DFA after matchATN: " + str(self.decisionToDFA[old_mode].toLexerString()))
+
+ return predict
+
+ def execATN(self, input:InputStream, ds0:DFAState):
+ if LexerATNSimulator.debug:
+ print("start state closure=" + str(ds0.configs))
+
+ if ds0.isAcceptState:
+ # allow zero-length tokens
+ self.captureSimState(self.prevAccept, input, ds0)
+
+ t = input.LA(1)
+ s = ds0 # s is current/from DFA state
+
+ while True: # while more work
+ if LexerATNSimulator.debug:
+ print("execATN loop starting closure:", str(s.configs))
+
+ # As we move src->trg, src->trg, we keep track of the previous trg to
+ # avoid looking up the DFA state again, which is expensive.
+ # If the previous target was already part of the DFA, we might
+ # be able to avoid doing a reach operation upon t. If s!=null,
+ # it means that semantic predicates didn't prevent us from
+ # creating a DFA state. Once we know s!=null, we check to see if
+ # the DFA state has an edge already for t. If so, we can just reuse
+ # it's configuration set; there's no point in re-computing it.
+ # This is kind of like doing DFA simulation within the ATN
+ # simulation because DFA simulation is really just a way to avoid
+ # computing reach/closure sets. Technically, once we know that
+ # we have a previously added DFA state, we could jump over to
+ # the DFA simulator. But, that would mean popping back and forth
+ # a lot and making things more complicated algorithmically.
+ # This optimization makes a lot of sense for loops within DFA.
+ # A character will take us back to an existing DFA state
+ # that already has lots of edges out of it. e.g., .* in comments.
+ # print("Target for:" + str(s) + " and:" + str(t))
+ target = self.getExistingTargetState(s, t)
+ # print("Existing:" + str(target))
+ if target is None:
+ target = self.computeTargetState(input, s, t)
+ # print("Computed:" + str(target))
+
+ if target == self.ERROR:
+ break
+
+ # If this is a consumable input element, make sure to consume before
+ # capturing the accept state so the input index, line, and char
+ # position accurately reflect the state of the interpreter at the
+ # end of the token.
+ if t != Token.EOF:
+ self.consume(input)
+
+ if target.isAcceptState:
+ self.captureSimState(self.prevAccept, input, target)
+ if t == Token.EOF:
+ break
+
+ t = input.LA(1)
+
+ s = target # flip; current DFA target becomes new src/from state
+
+ return self.failOrAccept(self.prevAccept, input, s.configs, t)
+
+ # Get an existing target state for an edge in the DFA. If the target state
+ # for the edge has not yet been computed or is otherwise not available,
+ # this method returns {@code null}.
+ #
+ # @param s The current DFA state
+ # @param t The next input symbol
+ # @return The existing target DFA state for the given input symbol
+ # {@code t}, or {@code null} if the target state for this edge is not
+ # already cached
+ def getExistingTargetState(self, s:DFAState, t:int):
+ if s.edges is None or t < self.MIN_DFA_EDGE or t > self.MAX_DFA_EDGE:
+ return None
+
+ target = s.edges[t - self.MIN_DFA_EDGE]
+ if LexerATNSimulator.debug and target is not None:
+ print("reuse state", str(s.stateNumber), "edge to", str(target.stateNumber))
+
+ return target
+
+ # Compute a target state for an edge in the DFA, and attempt to add the
+ # computed state and corresponding edge to the DFA.
+ #
+ # @param input The input stream
+ # @param s The current DFA state
+ # @param t The next input symbol
+ #
+ # @return The computed target DFA state for the given input symbol
+ # {@code t}. If {@code t} does not lead to a valid DFA state, this method
+ # returns {@link #ERROR}.
+ def computeTargetState(self, input:InputStream, s:DFAState, t:int):
+ reach = OrderedATNConfigSet()
+
+ # if we don't find an existing DFA state
+ # Fill reach starting from closure, following t transitions
+ self.getReachableConfigSet(input, s.configs, reach, t)
+
+ if len(reach)==0: # we got nowhere on t from s
+ if not reach.hasSemanticContext:
+ # we got nowhere on t, don't throw out this knowledge; it'd
+ # cause a failover from DFA later.
+ self. addDFAEdge(s, t, self.ERROR)
+
+ # stop when we can't match any more char
+ return self.ERROR
+
+ # Add an edge from s to target DFA found/created for reach
+ return self.addDFAEdge(s, t, cfgs=reach)
+
+ def failOrAccept(self, prevAccept:SimState , input:InputStream, reach:ATNConfigSet, t:int):
+ if self.prevAccept.dfaState is not None:
+ lexerActionExecutor = prevAccept.dfaState.lexerActionExecutor
+ self.accept(input, lexerActionExecutor, self.startIndex, prevAccept.index, prevAccept.line, prevAccept.column)
+ return prevAccept.dfaState.prediction
+ else:
+ # if no accept and EOF is first char, return EOF
+ if t==Token.EOF and input.index==self.startIndex:
+ return Token.EOF
+ raise LexerNoViableAltException(self.recog, input, self.startIndex, reach)
+
+ # Given a starting configuration set, figure out all ATN configurations
+ # we can reach upon input {@code t}. Parameter {@code reach} is a return
+ # parameter.
+ def getReachableConfigSet(self, input:InputStream, closure:ATNConfigSet, reach:ATNConfigSet, t:int):
+ # this is used to skip processing for configs which have a lower priority
+ # than a config that already reached an accept state for the same rule
+ skipAlt = ATN.INVALID_ALT_NUMBER
+ for cfg in closure:
+ currentAltReachedAcceptState = ( cfg.alt == skipAlt )
+ if currentAltReachedAcceptState and cfg.passedThroughNonGreedyDecision:
+ continue
+
+ if LexerATNSimulator.debug:
+ print("testing", self.getTokenName(t), "at", str(cfg))
+
+ for trans in cfg.state.transitions: # for each transition
+ target = self.getReachableTarget(trans, t)
+ if target is not None:
+ lexerActionExecutor = cfg.lexerActionExecutor
+ if lexerActionExecutor is not None:
+ lexerActionExecutor = lexerActionExecutor.fixOffsetBeforeMatch(input.index - self.startIndex)
+
+ treatEofAsEpsilon = (t == Token.EOF)
+ config = LexerATNConfig(state=target, lexerActionExecutor=lexerActionExecutor, config=cfg)
+ if self.closure(input, config, reach, currentAltReachedAcceptState, True, treatEofAsEpsilon):
+ # any remaining configs for this alt have a lower priority than
+ # the one that just reached an accept state.
+ skipAlt = cfg.alt
+
+ def accept(self, input:InputStream, lexerActionExecutor:LexerActionExecutor, startIndex:int, index:int, line:int, charPos:int):
+ if LexerATNSimulator.debug:
+ print("ACTION", lexerActionExecutor)
+
+ # seek to after last char in token
+ input.seek(index)
+ self.line = line
+ self.column = charPos
+
+ if lexerActionExecutor is not None and self.recog is not None:
+ lexerActionExecutor.execute(self.recog, input, startIndex)
+
+ def getReachableTarget(self, trans:Transition, t:int):
+ if trans.matches(t, 0, self.MAX_CHAR_VALUE):
+ return trans.target
+ else:
+ return None
+
+ def computeStartState(self, input:InputStream, p:ATNState):
+ initialContext = PredictionContext.EMPTY
+ configs = OrderedATNConfigSet()
+ for i in range(0,len(p.transitions)):
+ target = p.transitions[i].target
+ c = LexerATNConfig(state=target, alt=i+1, context=initialContext)
+ self.closure(input, c, configs, False, False, False)
+ return configs
+
+ # Since the alternatives within any lexer decision are ordered by
+ # preference, this method stops pursuing the closure as soon as an accept
+ # state is reached. After the first accept state is reached by depth-first
+ # search from {@code config}, all other (potentially reachable) states for
+ # this rule would have a lower priority.
+ #
+ # @return {@code true} if an accept state is reached, otherwise
+ # {@code false}.
+ def closure(self, input:InputStream, config:LexerATNConfig, configs:ATNConfigSet, currentAltReachedAcceptState:bool,
+ speculative:bool, treatEofAsEpsilon:bool):
+ if LexerATNSimulator.debug:
+ print("closure(" + str(config) + ")")
+
+ if isinstance( config.state, RuleStopState ):
+ if LexerATNSimulator.debug:
+ if self.recog is not None:
+ print("closure at", self.recog.symbolicNames[config.state.ruleIndex], "rule stop", str(config))
+ else:
+ print("closure at rule stop", str(config))
+
+ if config.context is None or config.context.hasEmptyPath():
+ if config.context is None or config.context.isEmpty():
+ configs.add(config)
+ return True
+ else:
+ configs.add(LexerATNConfig(state=config.state, config=config, context=PredictionContext.EMPTY))
+ currentAltReachedAcceptState = True
+
+ if config.context is not None and not config.context.isEmpty():
+ for i in range(0,len(config.context)):
+ if config.context.getReturnState(i) != PredictionContext.EMPTY_RETURN_STATE:
+ newContext = config.context.getParent(i) # "pop" return state
+ returnState = self.atn.states[config.context.getReturnState(i)]
+ c = LexerATNConfig(state=returnState, config=config, context=newContext)
+ currentAltReachedAcceptState = self.closure(input, c, configs,
+ currentAltReachedAcceptState, speculative, treatEofAsEpsilon)
+
+ return currentAltReachedAcceptState
+
+ # optimization
+ if not config.state.epsilonOnlyTransitions:
+ if not currentAltReachedAcceptState or not config.passedThroughNonGreedyDecision:
+ configs.add(config)
+
+ for t in config.state.transitions:
+ c = self.getEpsilonTarget(input, config, t, configs, speculative, treatEofAsEpsilon)
+ if c is not None:
+ currentAltReachedAcceptState = self.closure(input, c, configs, currentAltReachedAcceptState, speculative, treatEofAsEpsilon)
+
+ return currentAltReachedAcceptState
+
+ # side-effect: can alter configs.hasSemanticContext
+ def getEpsilonTarget(self, input:InputStream, config:LexerATNConfig, t:Transition, configs:ATNConfigSet,
+ speculative:bool, treatEofAsEpsilon:bool):
+ c = None
+ if t.serializationType==Transition.RULE:
+ newContext = SingletonPredictionContext.create(config.context, t.followState.stateNumber)
+ c = LexerATNConfig(state=t.target, config=config, context=newContext)
+
+ elif t.serializationType==Transition.PRECEDENCE:
+ raise UnsupportedOperationException("Precedence predicates are not supported in lexers.")
+
+ elif t.serializationType==Transition.PREDICATE:
+ # Track traversing semantic predicates. If we traverse,
+ # we cannot add a DFA state for this "reach" computation
+ # because the DFA would not test the predicate again in the
+ # future. Rather than creating collections of semantic predicates
+ # like v3 and testing them on prediction, v4 will test them on the
+ # fly all the time using the ATN not the DFA. This is slower but
+ # semantically it's not used that often. One of the key elements to
+ # this predicate mechanism is not adding DFA states that see
+ # predicates immediately afterwards in the ATN. For example,
+
+ # a : ID {p1}? | ID {p2}? ;
+
+ # should create the start state for rule 'a' (to save start state
+ # competition), but should not create target of ID state. The
+ # collection of ATN states the following ID references includes
+ # states reached by traversing predicates. Since this is when we
+ # test them, we cannot cash the DFA state target of ID.
+
+ if LexerATNSimulator.debug:
+ print("EVAL rule "+ str(t.ruleIndex) + ":" + str(t.predIndex))
+ configs.hasSemanticContext = True
+ if self.evaluatePredicate(input, t.ruleIndex, t.predIndex, speculative):
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType==Transition.ACTION:
+ if config.context is None or config.context.hasEmptyPath():
+ # execute actions anywhere in the start rule for a token.
+ #
+ # TODO: if the entry rule is invoked recursively, some
+ # actions may be executed during the recursive call. The
+ # problem can appear when hasEmptyPath() is true but
+ # isEmpty() is false. In this case, the config needs to be
+ # split into two contexts - one with just the empty path
+ # and another with everything but the empty path.
+ # Unfortunately, the current algorithm does not allow
+ # getEpsilonTarget to return two configurations, so
+ # additional modifications are needed before we can support
+ # the split operation.
+ lexerActionExecutor = LexerActionExecutor.append(config.lexerActionExecutor,
+ self.atn.lexerActions[t.actionIndex])
+ c = LexerATNConfig(state=t.target, config=config, lexerActionExecutor=lexerActionExecutor)
+
+ else:
+ # ignore actions in referenced rules
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType==Transition.EPSILON:
+ c = LexerATNConfig(state=t.target, config=config)
+
+ elif t.serializationType in [ Transition.ATOM, Transition.RANGE, Transition.SET ]:
+ if treatEofAsEpsilon:
+ if t.matches(Token.EOF, 0, self.MAX_CHAR_VALUE):
+ c = LexerATNConfig(state=t.target, config=config)
+
+ return c
+
+ # Evaluate a predicate specified in the lexer.
+ #
+ # If {@code speculative} is {@code true}, this method was called before
+ # {@link #consume} for the matched character. This method should call
+ # {@link #consume} before evaluating the predicate to ensure position
+ # sensitive values, including {@link Lexer#getText}, {@link Lexer#getLine},
+ # and {@link Lexer#getcolumn}, properly reflect the current
+ # lexer state. This method should restore {@code input} and the simulator
+ # to the original state before returning (i.e. undo the actions made by the
+ # call to {@link #consume}.
+ #
+ # @param input The input stream.
+ # @param ruleIndex The rule containing the predicate.
+ # @param predIndex The index of the predicate within the rule.
+ # @param speculative {@code true} if the current index in {@code input} is
+ # one character before the predicate's location.
+ #
+ # @return {@code true} if the specified predicate evaluates to
+ # {@code true}.
+ #/
+ def evaluatePredicate(self, input:InputStream, ruleIndex:int, predIndex:int, speculative:bool):
+ # assume true if no recognizer was provided
+ if self.recog is None:
+ return True
+
+ if not speculative:
+ return self.recog.sempred(None, ruleIndex, predIndex)
+
+ savedcolumn = self.column
+ savedLine = self.line
+ index = input.index
+ marker = input.mark()
+ try:
+ self.consume(input)
+ return self.recog.sempred(None, ruleIndex, predIndex)
+ finally:
+ self.column = savedcolumn
+ self.line = savedLine
+ input.seek(index)
+ input.release(marker)
+
+ def captureSimState(self, settings:SimState, input:InputStream, dfaState:DFAState):
+ settings.index = input.index
+ settings.line = self.line
+ settings.column = self.column
+ settings.dfaState = dfaState
+
+ def addDFAEdge(self, from_:DFAState, tk:int, to:DFAState=None, cfgs:ATNConfigSet=None) -> DFAState:
+
+ if to is None and cfgs is not None:
+ # leading to this call, ATNConfigSet.hasSemanticContext is used as a
+ # marker indicating dynamic predicate evaluation makes this edge
+ # dependent on the specific input sequence, so the static edge in the
+ # DFA should be omitted. The target DFAState is still created since
+ # execATN has the ability to resynchronize with the DFA state cache
+ # following the predicate evaluation step.
+ #
+ # TJP notes: next time through the DFA, we see a pred again and eval.
+ # If that gets us to a previously created (but dangling) DFA
+ # state, we can continue in pure DFA mode from there.
+ #/
+ suppressEdge = cfgs.hasSemanticContext
+ cfgs.hasSemanticContext = False
+
+ to = self.addDFAState(cfgs)
+
+ if suppressEdge:
+ return to
+
+ # add the edge
+ if tk < self.MIN_DFA_EDGE or tk > self.MAX_DFA_EDGE:
+ # Only track edges within the DFA bounds
+ return to
+
+ if LexerATNSimulator.debug:
+ print("EDGE " + str(from_) + " -> " + str(to) + " upon "+ chr(tk))
+
+ if from_.edges is None:
+ # make room for tokens 1..n and -1 masquerading as index 0
+ from_.edges = [ None ] * (self.MAX_DFA_EDGE - self.MIN_DFA_EDGE + 1)
+
+ from_.edges[tk - self.MIN_DFA_EDGE] = to # connect
+
+ return to
+
+
+ # Add a new DFA state if there isn't one with this set of
+ # configurations already. This method also detects the first
+ # configuration containing an ATN rule stop state. Later, when
+ # traversing the DFA, we will know which rule to accept.
+ def addDFAState(self, configs:ATNConfigSet) -> DFAState:
+
+ proposed = DFAState(configs=configs)
+ firstConfigWithRuleStopState = next((cfg for cfg in configs if isinstance(cfg.state, RuleStopState)), None)
+
+ if firstConfigWithRuleStopState is not None:
+ proposed.isAcceptState = True
+ proposed.lexerActionExecutor = firstConfigWithRuleStopState.lexerActionExecutor
+ proposed.prediction = self.atn.ruleToTokenType[firstConfigWithRuleStopState.state.ruleIndex]
+
+ dfa = self.decisionToDFA[self.mode]
+ existing = dfa.states.get(proposed, None)
+ if existing is not None:
+ return existing
+
+ newState = proposed
+
+ newState.stateNumber = len(dfa.states)
+ configs.setReadonly(True)
+ newState.configs = configs
+ dfa.states[newState] = newState
+ return newState
+
+ def getDFA(self, mode:int):
+ return self.decisionToDFA[mode]
+
+ # Get the text matched so far for the current token.
+ def getText(self, input:InputStream):
+ # index is first lookahead char, don't include.
+ return input.getText(self.startIndex, input.index-1)
+
+ def consume(self, input:InputStream):
+ curChar = input.LA(1)
+ if curChar==ord('\n'):
+ self.line += 1
+ self.column = 0
+ else:
+ self.column += 1
+ input.consume()
+
+ def getTokenName(self, t:int):
+ if t==-1:
+ return "EOF"
+ else:
+ return "'" + chr(t) + "'"
+
+
+LexerATNSimulator.ERROR = DFAState(0x7FFFFFFF, ATNConfigSet())
+
+del Lexer
diff --git a/lib/python3.12/site-packages/antlr4/atn/LexerAction.py b/lib/python3.12/site-packages/antlr4/atn/LexerAction.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fa7a895f31cea4415b71def30ca1ec90167f1b3
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/LexerAction.py
@@ -0,0 +1,298 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+ #
+
+from enum import IntEnum
+
+# need forward declaration
+Lexer = None
+
+
+class LexerActionType(IntEnum):
+
+ CHANNEL = 0 #The type of a {@link LexerChannelAction} action.
+ CUSTOM = 1 #The type of a {@link LexerCustomAction} action.
+ MODE = 2 #The type of a {@link LexerModeAction} action.
+ MORE = 3 #The type of a {@link LexerMoreAction} action.
+ POP_MODE = 4 #The type of a {@link LexerPopModeAction} action.
+ PUSH_MODE = 5 #The type of a {@link LexerPushModeAction} action.
+ SKIP = 6 #The type of a {@link LexerSkipAction} action.
+ TYPE = 7 #The type of a {@link LexerTypeAction} action.
+
+class LexerAction(object):
+ __slots__ = ('actionType', 'isPositionDependent')
+
+ def __init__(self, action:LexerActionType):
+ self.actionType = action
+ self.isPositionDependent = False
+
+ def __hash__(self):
+ return hash(self.actionType)
+
+ def __eq__(self, other):
+ return self is other
+
+
+#
+# Implements the {@code skip} lexer action by calling {@link Lexer#skip}.
+#
+# The {@code skip} command does not have any parameters, so this action is
+# implemented as a singleton instance exposed by {@link #INSTANCE}.
+class LexerSkipAction(LexerAction):
+
+ # Provides a singleton instance of this parameterless lexer action.
+ INSTANCE = None
+
+ def __init__(self):
+ super().__init__(LexerActionType.SKIP)
+
+ def execute(self, lexer:Lexer):
+ lexer.skip()
+
+ def __str__(self):
+ return "skip"
+
+LexerSkipAction.INSTANCE = LexerSkipAction()
+
+# Implements the {@code type} lexer action by calling {@link Lexer#setType}
+# with the assigned type.
+class LexerTypeAction(LexerAction):
+ __slots__ = 'type'
+
+ def __init__(self, type:int):
+ super().__init__(LexerActionType.TYPE)
+ self.type = type
+
+ def execute(self, lexer:Lexer):
+ lexer.type = self.type
+
+ def __hash__(self):
+ return hash((self.actionType, self.type))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerTypeAction):
+ return False
+ else:
+ return self.type == other.type
+
+ def __str__(self):
+ return "type(" + str(self.type) + ")"
+
+
+# Implements the {@code pushMode} lexer action by calling
+# {@link Lexer#pushMode} with the assigned mode.
+class LexerPushModeAction(LexerAction):
+ __slots__ = 'mode'
+
+ def __init__(self, mode:int):
+ super().__init__(LexerActionType.PUSH_MODE)
+ self.mode = mode
+
+ # This action is implemented by calling {@link Lexer#pushMode} with the
+ # value provided by {@link #getMode}.
+ def execute(self, lexer:Lexer):
+ lexer.pushMode(self.mode)
+
+ def __hash__(self):
+ return hash((self.actionType, self.mode))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerPushModeAction):
+ return False
+ else:
+ return self.mode == other.mode
+
+ def __str__(self):
+ return "pushMode(" + str(self.mode) + ")"
+
+
+# Implements the {@code popMode} lexer action by calling {@link Lexer#popMode}.
+#
+# The {@code popMode} command does not have any parameters, so this action is
+# implemented as a singleton instance exposed by {@link #INSTANCE}.
+class LexerPopModeAction(LexerAction):
+
+ INSTANCE = None
+
+ def __init__(self):
+ super().__init__(LexerActionType.POP_MODE)
+
+ # This action is implemented by calling {@link Lexer#popMode}.
+ def execute(self, lexer:Lexer):
+ lexer.popMode()
+
+ def __str__(self):
+ return "popMode"
+
+LexerPopModeAction.INSTANCE = LexerPopModeAction()
+
+# Implements the {@code more} lexer action by calling {@link Lexer#more}.
+#
+# The {@code more} command does not have any parameters, so this action is
+# implemented as a singleton instance exposed by {@link #INSTANCE}.
+class LexerMoreAction(LexerAction):
+
+ INSTANCE = None
+
+ def __init__(self):
+ super().__init__(LexerActionType.MORE)
+
+ # This action is implemented by calling {@link Lexer#popMode}.
+ def execute(self, lexer:Lexer):
+ lexer.more()
+
+ def __str__(self):
+ return "more"
+
+LexerMoreAction.INSTANCE = LexerMoreAction()
+
+# Implements the {@code mode} lexer action by calling {@link Lexer#mode} with
+# the assigned mode.
+class LexerModeAction(LexerAction):
+ __slots__ = 'mode'
+
+ def __init__(self, mode:int):
+ super().__init__(LexerActionType.MODE)
+ self.mode = mode
+
+ # This action is implemented by calling {@link Lexer#mode} with the
+ # value provided by {@link #getMode}.
+ def execute(self, lexer:Lexer):
+ lexer.mode(self.mode)
+
+ def __hash__(self):
+ return hash((self.actionType, self.mode))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerModeAction):
+ return False
+ else:
+ return self.mode == other.mode
+
+ def __str__(self):
+ return "mode(" + str(self.mode) + ")"
+
+# Executes a custom lexer action by calling {@link Recognizer#action} with the
+# rule and action indexes assigned to the custom action. The implementation of
+# a custom action is added to the generated code for the lexer in an override
+# of {@link Recognizer#action} when the grammar is compiled.
+#
+# This class may represent embedded actions created with the {...}
+# syntax in ANTLR 4, as well as actions created for lexer commands where the
+# command argument could not be evaluated when the grammar was compiled.
+
+class LexerCustomAction(LexerAction):
+ __slots__ = ('ruleIndex', 'actionIndex')
+
+ # Constructs a custom lexer action with the specified rule and action
+ # indexes.
+ #
+ # @param ruleIndex The rule index to use for calls to
+ # {@link Recognizer#action}.
+ # @param actionIndex The action index to use for calls to
+ # {@link Recognizer#action}.
+ #/
+ def __init__(self, ruleIndex:int, actionIndex:int):
+ super().__init__(LexerActionType.CUSTOM)
+ self.ruleIndex = ruleIndex
+ self.actionIndex = actionIndex
+ self.isPositionDependent = True
+
+ # Custom actions are implemented by calling {@link Lexer#action} with the
+ # appropriate rule and action indexes.
+ def execute(self, lexer:Lexer):
+ lexer.action(None, self.ruleIndex, self.actionIndex)
+
+ def __hash__(self):
+ return hash((self.actionType, self.ruleIndex, self.actionIndex))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerCustomAction):
+ return False
+ else:
+ return self.ruleIndex == other.ruleIndex and self.actionIndex == other.actionIndex
+
+# Implements the {@code channel} lexer action by calling
+# {@link Lexer#setChannel} with the assigned channel.
+class LexerChannelAction(LexerAction):
+ __slots__ = 'channel'
+
+ # Constructs a new {@code channel} action with the specified channel value.
+ # @param channel The channel value to pass to {@link Lexer#setChannel}.
+ def __init__(self, channel:int):
+ super().__init__(LexerActionType.CHANNEL)
+ self.channel = channel
+
+ # This action is implemented by calling {@link Lexer#setChannel} with the
+ # value provided by {@link #getChannel}.
+ def execute(self, lexer:Lexer):
+ lexer._channel = self.channel
+
+ def __hash__(self):
+ return hash((self.actionType, self.channel))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerChannelAction):
+ return False
+ else:
+ return self.channel == other.channel
+
+ def __str__(self):
+ return "channel(" + str(self.channel) + ")"
+
+# This implementation of {@link LexerAction} is used for tracking input offsets
+# for position-dependent actions within a {@link LexerActionExecutor}.
+#
+# This action is not serialized as part of the ATN, and is only required for
+# position-dependent lexer actions which appear at a location other than the
+# end of a rule. For more information about DFA optimizations employed for
+# lexer actions, see {@link LexerActionExecutor#append} and
+# {@link LexerActionExecutor#fixOffsetBeforeMatch}.
+class LexerIndexedCustomAction(LexerAction):
+ __slots__ = ('offset', 'action')
+
+ # Constructs a new indexed custom action by associating a character offset
+ # with a {@link LexerAction}.
+ #
+ # Note: This class is only required for lexer actions for which
+ # {@link LexerAction#isPositionDependent} returns {@code true}.
+ #
+ # @param offset The offset into the input {@link CharStream}, relative to
+ # the token start index, at which the specified lexer action should be
+ # executed.
+ # @param action The lexer action to execute at a particular offset in the
+ # input {@link CharStream}.
+ def __init__(self, offset:int, action:LexerAction):
+ super().__init__(action.actionType)
+ self.offset = offset
+ self.action = action
+ self.isPositionDependent = True
+
+ # This method calls {@link #execute} on the result of {@link #getAction}
+ # using the provided {@code lexer}.
+ def execute(self, lexer:Lexer):
+ # assume the input stream position was properly set by the calling code
+ self.action.execute(lexer)
+
+ def __hash__(self):
+ return hash((self.actionType, self.offset, self.action))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerIndexedCustomAction):
+ return False
+ else:
+ return self.offset == other.offset and self.action == other.action
diff --git a/lib/python3.12/site-packages/antlr4/atn/LexerActionExecutor.py b/lib/python3.12/site-packages/antlr4/atn/LexerActionExecutor.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c6462c3a28f4ccbcb0a65dcde96f497dd392416
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/LexerActionExecutor.py
@@ -0,0 +1,143 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# Represents an executor for a sequence of lexer actions which traversed during
+# the matching operation of a lexer rule (token).
+#
+# The executor tracks position information for position-dependent lexer actions
+# efficiently, ensuring that actions appearing only at the end of the rule do
+# not cause bloating of the {@link DFA} created for the lexer.
+
+
+from antlr4.InputStream import InputStream
+from antlr4.atn.LexerAction import LexerAction, LexerIndexedCustomAction
+
+# need a forward declaration
+Lexer = None
+LexerActionExecutor = None
+
+class LexerActionExecutor(object):
+ __slots__ = ('lexerActions', 'hashCode')
+
+ def __init__(self, lexerActions:list=list()):
+ self.lexerActions = lexerActions
+ # Caches the result of {@link #hashCode} since the hash code is an element
+ # of the performance-critical {@link LexerATNConfig#hashCode} operation.
+ self.hashCode = hash("".join([str(la) for la in lexerActions]))
+
+
+ # Creates a {@link LexerActionExecutor} which executes the actions for
+ # the input {@code lexerActionExecutor} followed by a specified
+ # {@code lexerAction}.
+ #
+ # @param lexerActionExecutor The executor for actions already traversed by
+ # the lexer while matching a token within a particular
+ # {@link LexerATNConfig}. If this is {@code null}, the method behaves as
+ # though it were an empty executor.
+ # @param lexerAction The lexer action to execute after the actions
+ # specified in {@code lexerActionExecutor}.
+ #
+ # @return A {@link LexerActionExecutor} for executing the combine actions
+ # of {@code lexerActionExecutor} and {@code lexerAction}.
+ @staticmethod
+ def append(lexerActionExecutor:LexerActionExecutor , lexerAction:LexerAction ):
+ if lexerActionExecutor is None:
+ return LexerActionExecutor([ lexerAction ])
+
+ lexerActions = lexerActionExecutor.lexerActions + [ lexerAction ]
+ return LexerActionExecutor(lexerActions)
+
+ # Creates a {@link LexerActionExecutor} which encodes the current offset
+ # for position-dependent lexer actions.
+ #
+ # Normally, when the executor encounters lexer actions where
+ # {@link LexerAction#isPositionDependent} returns {@code true}, it calls
+ # {@link IntStream#seek} on the input {@link CharStream} to set the input
+ # position to the end of the current token. This behavior provides
+ # for efficient DFA representation of lexer actions which appear at the end
+ # of a lexer rule, even when the lexer rule matches a variable number of
+ # characters.
+ #
+ # Prior to traversing a match transition in the ATN, the current offset
+ # from the token start index is assigned to all position-dependent lexer
+ # actions which have not already been assigned a fixed offset. By storing
+ # the offsets relative to the token start index, the DFA representation of
+ # lexer actions which appear in the middle of tokens remains efficient due
+ # to sharing among tokens of the same length, regardless of their absolute
+ # position in the input stream.
+ #
+ # If the current executor already has offsets assigned to all
+ # position-dependent lexer actions, the method returns {@code this}.
+ #
+ # @param offset The current offset to assign to all position-dependent
+ # lexer actions which do not already have offsets assigned.
+ #
+ # @return A {@link LexerActionExecutor} which stores input stream offsets
+ # for all position-dependent lexer actions.
+ #/
+ def fixOffsetBeforeMatch(self, offset:int):
+ updatedLexerActions = None
+ for i in range(0, len(self.lexerActions)):
+ if self.lexerActions[i].isPositionDependent and not isinstance(self.lexerActions[i], LexerIndexedCustomAction):
+ if updatedLexerActions is None:
+ updatedLexerActions = [ la for la in self.lexerActions ]
+ updatedLexerActions[i] = LexerIndexedCustomAction(offset, self.lexerActions[i])
+
+ if updatedLexerActions is None:
+ return self
+ else:
+ return LexerActionExecutor(updatedLexerActions)
+
+
+ # Execute the actions encapsulated by this executor within the context of a
+ # particular {@link Lexer}.
+ #
+ # This method calls {@link IntStream#seek} to set the position of the
+ # {@code input} {@link CharStream} prior to calling
+ # {@link LexerAction#execute} on a position-dependent action. Before the
+ # method returns, the input position will be restored to the same position
+ # it was in when the method was invoked.
+ #
+ # @param lexer The lexer instance.
+ # @param input The input stream which is the source for the current token.
+ # When this method is called, the current {@link IntStream#index} for
+ # {@code input} should be the start of the following token, i.e. 1
+ # character past the end of the current token.
+ # @param startIndex The token start index. This value may be passed to
+ # {@link IntStream#seek} to set the {@code input} position to the beginning
+ # of the token.
+ #/
+ def execute(self, lexer:Lexer, input:InputStream, startIndex:int):
+ requiresSeek = False
+ stopIndex = input.index
+ try:
+ for lexerAction in self.lexerActions:
+ if isinstance(lexerAction, LexerIndexedCustomAction):
+ offset = lexerAction.offset
+ input.seek(startIndex + offset)
+ lexerAction = lexerAction.action
+ requiresSeek = (startIndex + offset) != stopIndex
+ elif lexerAction.isPositionDependent:
+ input.seek(stopIndex)
+ requiresSeek = False
+ lexerAction.execute(lexer)
+ finally:
+ if requiresSeek:
+ input.seek(stopIndex)
+
+ def __hash__(self):
+ return self.hashCode
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, LexerActionExecutor):
+ return False
+ else:
+ return self.hashCode == other.hashCode \
+ and self.lexerActions == other.lexerActions
+
+del Lexer
diff --git a/lib/python3.12/site-packages/antlr4/atn/ParserATNSimulator.py b/lib/python3.12/site-packages/antlr4/atn/ParserATNSimulator.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1fb3d7ed8bab47fb851d85da88d20dc39715627
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/ParserATNSimulator.py
@@ -0,0 +1,1649 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# The embodiment of the adaptive LL(*), ALL(*), parsing strategy.
+#
+#
+# The basic complexity of the adaptive strategy makes it harder to understand.
+# We begin with ATN simulation to build paths in a DFA. Subsequent prediction
+# requests go through the DFA first. If they reach a state without an edge for
+# the current symbol, the algorithm fails over to the ATN simulation to
+# complete the DFA path for the current input (until it finds a conflict state
+# or uniquely predicting state).
+#
+#
+# All of that is done without using the outer context because we want to create
+# a DFA that is not dependent upon the rule invocation stack when we do a
+# prediction. One DFA works in all contexts. We avoid using context not
+# necessarily because it's slower, although it can be, but because of the DFA
+# caching problem. The closure routine only considers the rule invocation stack
+# created during prediction beginning in the decision rule. For example, if
+# prediction occurs without invoking another rule's ATN, there are no context
+# stacks in the configurations. When lack of context leads to a conflict, we
+# don't know if it's an ambiguity or a weakness in the strong LL(*) parsing
+# strategy (versus full LL(*)).
+#
+#
+# When SLL yields a configuration set with conflict, we rewind the input and
+# retry the ATN simulation, this time using full outer context without adding
+# to the DFA. Configuration context stacks will be the full invocation stacks
+# from the start rule. If we get a conflict using full context, then we can
+# definitively say we have a true ambiguity for that input sequence. If we
+# don't get a conflict, it implies that the decision is sensitive to the outer
+# context. (It is not context-sensitive in the sense of context-sensitive
+# grammars.)
+#
+#
+# The next time we reach this DFA state with an SLL conflict, through DFA
+# simulation, we will again retry the ATN simulation using full context mode.
+# This is slow because we can't save the results and have to "interpret" the
+# ATN each time we get that input.
+#
+#
+# CACHING FULL CONTEXT PREDICTIONS
+#
+#
+# We could cache results from full context to predicted alternative easily and
+# that saves a lot of time but doesn't work in presence of predicates. The set
+# of visible predicates from the ATN start state changes depending on the
+# context, because closure can fall off the end of a rule. I tried to cache
+# tuples (stack context, semantic context, predicted alt) but it was slower
+# than interpreting and much more complicated. Also required a huge amount of
+# memory. The goal is not to create the world's fastest parser anyway. I'd like
+# to keep this algorithm simple. By launching multiple threads, we can improve
+# the speed of parsing across a large number of files.
+#
+#
+# There is no strict ordering between the amount of input used by SLL vs LL,
+# which makes it really hard to build a cache for full context. Let's say that
+# we have input A B C that leads to an SLL conflict with full context X. That
+# implies that using X we might only use A B but we could also use A B C D to
+# resolve conflict. Input A B C D could predict alternative 1 in one position
+# in the input and A B C E could predict alternative 2 in another position in
+# input. The conflicting SLL configurations could still be non-unique in the
+# full context prediction, which would lead us to requiring more input than the
+# original A B C. To make a prediction cache work, we have to track the exact
+# input used during the previous prediction. That amounts to a cache that maps
+# X to a specific DFA for that context.
+#
+#
+# Something should be done for left-recursive expression predictions. They are
+# likely LL(1) + pred eval. Easier to do the whole SLL unless error and retry
+# with full LL thing Sam does.
+#
+#
+# AVOIDING FULL CONTEXT PREDICTION
+#
+#
+# We avoid doing full context retry when the outer context is empty, we did not
+# dip into the outer context by falling off the end of the decision state rule,
+# or when we force SLL mode.
+#
+#
+# As an example of the not dip into outer context case, consider as super
+# constructor calls versus function calls. One grammar might look like
+# this:
+#
+#
+# ctorBody
+# : '{' superCall? stat* '}'
+# ;
+#
+#
+#
+# Or, you might see something like
+#
+#
+# stat
+# : superCall ';'
+# | expression ';'
+# | ...
+# ;
+#
+#
+#
+# In both cases I believe that no closure operations will dip into the outer
+# context. In the first case ctorBody in the worst case will stop at the '}'.
+# In the 2nd case it should stop at the ';'. Both cases should stay within the
+# entry rule and not dip into the outer context.
+#
+#
+# PREDICATES
+#
+#
+# Predicates are always evaluated if present in either SLL or LL both. SLL and
+# LL simulation deals with predicates differently. SLL collects predicates as
+# it performs closure operations like ANTLR v3 did. It delays predicate
+# evaluation until it reaches and accept state. This allows us to cache the SLL
+# ATN simulation whereas, if we had evaluated predicates on-the-fly during
+# closure, the DFA state configuration sets would be different and we couldn't
+# build up a suitable DFA.
+#
+#
+# When building a DFA accept state during ATN simulation, we evaluate any
+# predicates and return the sole semantically valid alternative. If there is
+# more than 1 alternative, we report an ambiguity. If there are 0 alternatives,
+# we throw an exception. Alternatives without predicates act like they have
+# true predicates. The simple way to think about it is to strip away all
+# alternatives with false predicates and choose the minimum alternative that
+# remains.
+#
+#
+# When we start in the DFA and reach an accept state that's predicated, we test
+# those and return the minimum semantically viable alternative. If no
+# alternatives are viable, we throw an exception.
+#
+#
+# During full LL ATN simulation, closure always evaluates predicates and
+# on-the-fly. This is crucial to reducing the configuration set size during
+# closure. It hits a landmine when parsing with the Java grammar, for example,
+# without this on-the-fly evaluation.
+#
+#
+# SHARING DFA
+#
+#
+# All instances of the same parser share the same decision DFAs through a
+# static field. Each instance gets its own ATN simulator but they share the
+# same {@link #decisionToDFA} field. They also share a
+# {@link PredictionContextCache} object that makes sure that all
+# {@link PredictionContext} objects are shared among the DFA states. This makes
+# a big size difference.
+#
+#
+# THREAD SAFETY
+#
+#
+# The {@link ParserATNSimulator} locks on the {@link #decisionToDFA} field when
+# it adds a new DFA object to that array. {@link #addDFAEdge}
+# locks on the DFA for the current decision when setting the
+# {@link DFAState#edges} field. {@link #addDFAState} locks on
+# the DFA for the current decision when looking up a DFA state to see if it
+# already exists. We must make sure that all requests to add DFA states that
+# are equivalent result in the same shared DFA object. This is because lots of
+# threads will be trying to update the DFA at once. The
+# {@link #addDFAState} method also locks inside the DFA lock
+# but this time on the shared context cache when it rebuilds the
+# configurations' {@link PredictionContext} objects using cached
+# subgraphs/nodes. No other locking occurs, even during DFA simulation. This is
+# safe as long as we can guarantee that all threads referencing
+# {@code s.edge[t]} get the same physical target {@link DFAState}, or
+# {@code null}. Once into the DFA, the DFA simulation does not reference the
+# {@link DFA#states} map. It follows the {@link DFAState#edges} field to new
+# targets. The DFA simulator will either find {@link DFAState#edges} to be
+# {@code null}, to be non-{@code null} and {@code dfa.edges[t]} null, or
+# {@code dfa.edges[t]} to be non-null. The
+# {@link #addDFAEdge} method could be racing to set the field
+# but in either case the DFA simulator works; if {@code null}, and requests ATN
+# simulation. It could also race trying to get {@code dfa.edges[t]}, but either
+# way it will work because it's not doing a test and set operation.
+#
+#
+# Starting with SLL then failing to combined SLL/LL (Two-Stage
+# Parsing)
+#
+#
+# Sam pointed out that if SLL does not give a syntax error, then there is no
+# point in doing full LL, which is slower. We only have to try LL if we get a
+# syntax error. For maximum speed, Sam starts the parser set to pure SLL
+# mode with the {@link BailErrorStrategy}:
+#
+#
+# parser.{@link Parser#getInterpreter() getInterpreter()}.{@link #setPredictionMode setPredictionMode}{@code (}{@link PredictionMode#SLL}{@code )};
+# parser.{@link Parser#setErrorHandler setErrorHandler}(new {@link BailErrorStrategy}());
+#
+#
+#
+# If it does not get a syntax error, then we're done. If it does get a syntax
+# error, we need to retry with the combined SLL/LL strategy.
+#
+#
+# The reason this works is as follows. If there are no SLL conflicts, then the
+# grammar is SLL (at least for that input set). If there is an SLL conflict,
+# the full LL analysis must yield a set of viable alternatives which is a
+# subset of the alternatives reported by SLL. If the LL set is a singleton,
+# then the grammar is LL but not SLL. If the LL set is the same size as the SLL
+# set, the decision is SLL. If the LL set has size > 1, then that decision
+# is truly ambiguous on the current input. If the LL set is smaller, then the
+# SLL conflict resolution might choose an alternative that the full LL would
+# rule out as a possibility based upon better context information. If that's
+# the case, then the SLL parse will definitely get an error because the full LL
+# analysis says it's not viable. If SLL conflict resolution chooses an
+# alternative within the LL set, them both SLL and LL would choose the same
+# alternative because they both choose the minimum of multiple conflicting
+# alternatives.
+#
+#
+# Let's say we have a set of SLL conflicting alternatives {@code {1, 2, 3}} and
+# a smaller LL set called s. If s is {@code {2, 3}}, then SLL
+# parsing will get an error because SLL will pursue alternative 1. If
+# s is {@code {1, 2}} or {@code {1, 3}} then both SLL and LL will
+# choose the same alternative because alternative one is the minimum of either
+# set. If s is {@code {2}} or {@code {3}} then SLL will get a syntax
+# error. If s is {@code {1}} then SLL will succeed.
+#
+#
+# Of course, if the input is invalid, then we will get an error for sure in
+# both SLL and LL parsing. Erroneous input will therefore require 2 passes over
+# the input.
+#
+import sys
+from antlr4 import DFA
+from antlr4.PredictionContext import PredictionContextCache, PredictionContext, SingletonPredictionContext, \
+ PredictionContextFromRuleContext
+from antlr4.BufferedTokenStream import TokenStream
+from antlr4.Parser import Parser
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.Utils import str_list
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNSimulator import ATNSimulator
+from antlr4.atn.ATNState import StarLoopEntryState, DecisionState, RuleStopState, ATNState
+from antlr4.atn.PredictionMode import PredictionMode
+from antlr4.atn.SemanticContext import SemanticContext, AND, andContext, orContext
+from antlr4.atn.Transition import Transition, RuleTransition, ActionTransition, PrecedencePredicateTransition, \
+ PredicateTransition, AtomTransition, SetTransition, NotSetTransition
+from antlr4.dfa.DFAState import DFAState, PredPrediction
+from antlr4.error.Errors import NoViableAltException
+
+
+class ParserATNSimulator(ATNSimulator):
+ __slots__ = (
+ 'parser', 'decisionToDFA', 'predictionMode', '_input', '_startIndex',
+ '_outerContext', '_dfa', 'mergeCache'
+ )
+
+ debug = False
+ debug_list_atn_decisions = False
+ dfa_debug = False
+ retry_debug = False
+
+
+ def __init__(self, parser:Parser, atn:ATN, decisionToDFA:list, sharedContextCache:PredictionContextCache):
+ super().__init__(atn, sharedContextCache)
+ self.parser = parser
+ self.decisionToDFA = decisionToDFA
+ # SLL, LL, or LL + exact ambig detection?#
+ self.predictionMode = PredictionMode.LL
+ # LAME globals to avoid parameters!!!!! I need these down deep in predTransition
+ self._input = None
+ self._startIndex = 0
+ self._outerContext = None
+ self._dfa = None
+ # Each prediction operation uses a cache for merge of prediction contexts.
+ # Don't keep around as it wastes huge amounts of memory. DoubleKeyMap
+ # isn't synchronized but we're ok since two threads shouldn't reuse same
+ # parser/atnsim object because it can only handle one input at a time.
+ # This maps graphs a and b to merged result c. (a,b)→c. We can avoid
+ # the merge if we ever see a and b again. Note that (b,a)→c should
+ # also be examined during cache lookup.
+ #
+ self.mergeCache = None
+
+
+ def reset(self):
+ pass
+
+ def adaptivePredict(self, input:TokenStream, decision:int, outerContext:ParserRuleContext):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("adaptivePredict decision " + str(decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ " line " + str(input.LT(1).line) + ":" +
+ str(input.LT(1).column))
+ self._input = input
+ self._startIndex = input.index
+ self._outerContext = outerContext
+
+ dfa = self.decisionToDFA[decision]
+ self._dfa = dfa
+ m = input.mark()
+ index = input.index
+
+ # Now we are certain to have a specific decision's DFA
+ # But, do we still need an initial state?
+ try:
+ if dfa.precedenceDfa:
+ # the start state for a precedence DFA depends on the current
+ # parser precedence, and is provided by a DFA method.
+ s0 = dfa.getPrecedenceStartState(self.parser.getPrecedence())
+ else:
+ # the start state for a "regular" DFA is just s0
+ s0 = dfa.s0
+
+ if s0 is None:
+ if outerContext is None:
+ outerContext = ParserRuleContext.EMPTY
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("predictATN decision " + str(dfa.decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ ", outerContext=" + outerContext.toString(self.parser.literalNames, None))
+
+ fullCtx = False
+ s0_closure = self.computeStartState(dfa.atnStartState, ParserRuleContext.EMPTY, fullCtx)
+
+ if dfa.precedenceDfa:
+ # If this is a precedence DFA, we use applyPrecedenceFilter
+ # to convert the computed start state to a precedence start
+ # state. We then use DFA.setPrecedenceStartState to set the
+ # appropriate start state for the precedence level rather
+ # than simply setting DFA.s0.
+ #
+ dfa.s0.configs = s0_closure # not used for prediction but useful to know start configs anyway
+ s0_closure = self.applyPrecedenceFilter(s0_closure)
+ s0 = self.addDFAState(dfa, DFAState(configs=s0_closure))
+ dfa.setPrecedenceStartState(self.parser.getPrecedence(), s0)
+ else:
+ s0 = self.addDFAState(dfa, DFAState(configs=s0_closure))
+ dfa.s0 = s0
+
+ alt = self.execATN(dfa, s0, input, index, outerContext)
+ if ParserATNSimulator.debug:
+ print("DFA after predictATN: " + dfa.toString(self.parser.literalNames))
+ return alt
+ finally:
+ self._dfa = None
+ self.mergeCache = None # wack cache after each prediction
+ input.seek(index)
+ input.release(m)
+
+ # Performs ATN simulation to compute a predicted alternative based
+ # upon the remaining input, but also updates the DFA cache to avoid
+ # having to traverse the ATN again for the same input sequence.
+
+ # There are some key conditions we're looking for after computing a new
+ # set of ATN configs (proposed DFA state):
+ # if the set is empty, there is no viable alternative for current symbol
+ # does the state uniquely predict an alternative?
+ # does the state have a conflict that would prevent us from
+ # putting it on the work list?
+
+ # We also have some key operations to do:
+ # add an edge from previous DFA state to potentially new DFA state, D,
+ # upon current symbol but only if adding to work list, which means in all
+ # cases except no viable alternative (and possibly non-greedy decisions?)
+ # collecting predicates and adding semantic context to DFA accept states
+ # adding rule context to context-sensitive DFA accept states
+ # consuming an input symbol
+ # reporting a conflict
+ # reporting an ambiguity
+ # reporting a context sensitivity
+ # reporting insufficient predicates
+
+ # cover these cases:
+ # dead end
+ # single alt
+ # single alt + preds
+ # conflict
+ # conflict + preds
+ #
+ def execATN(self, dfa:DFA, s0:DFAState, input:TokenStream, startIndex:int, outerContext:ParserRuleContext ):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("execATN decision " + str(dfa.decision) +
+ " exec LA(1)==" + self.getLookaheadName(input) +
+ " line " + str(input.LT(1).line) + ":" + str(input.LT(1).column))
+
+ previousD = s0
+
+ if ParserATNSimulator.debug:
+ print("s0 = " + str(s0))
+
+ t = input.LA(1)
+
+ while True: # while more work
+ D = self.getExistingTargetState(previousD, t)
+ if D is None:
+ D = self.computeTargetState(dfa, previousD, t)
+ if D is self.ERROR:
+ # if any configs in previous dipped into outer context, that
+ # means that input up to t actually finished entry rule
+ # at least for SLL decision. Full LL doesn't dip into outer
+ # so don't need special case.
+ # We will get an error no matter what so delay until after
+ # decision; better error message. Also, no reachable target
+ # ATN states in SLL implies LL will also get nowhere.
+ # If conflict in states that dip out, choose min since we
+ # will get error no matter what.
+ e = self.noViableAlt(input, outerContext, previousD.configs, startIndex)
+ input.seek(startIndex)
+ alt = self.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previousD.configs, outerContext)
+ if alt!=ATN.INVALID_ALT_NUMBER:
+ return alt
+ raise e
+
+ if D.requiresFullContext and self.predictionMode != PredictionMode.SLL:
+ # IF PREDS, MIGHT RESOLVE TO SINGLE ALT => SLL (or syntax error)
+ conflictingAlts = D.configs.conflictingAlts
+ if D.predicates is not None:
+ if ParserATNSimulator.debug:
+ print("DFA state has preds in DFA sim LL failover")
+ conflictIndex = input.index
+ if conflictIndex != startIndex:
+ input.seek(startIndex)
+
+ conflictingAlts = self.evalSemanticContext(D.predicates, outerContext, True)
+ if len(conflictingAlts)==1:
+ if ParserATNSimulator.debug:
+ print("Full LL avoided")
+ return min(conflictingAlts)
+
+ if conflictIndex != startIndex:
+ # restore the index so reporting the fallback to full
+ # context occurs with the index at the correct spot
+ input.seek(conflictIndex)
+
+ if ParserATNSimulator.dfa_debug:
+ print("ctx sensitive state " + str(outerContext) +" in " + str(D))
+ fullCtx = True
+ s0_closure = self.computeStartState(dfa.atnStartState, outerContext, fullCtx)
+ self.reportAttemptingFullContext(dfa, conflictingAlts, D.configs, startIndex, input.index)
+ alt = self.execATNWithFullContext(dfa, D, s0_closure, input, startIndex, outerContext)
+ return alt
+
+ if D.isAcceptState:
+ if D.predicates is None:
+ return D.prediction
+
+ stopIndex = input.index
+ input.seek(startIndex)
+ alts = self.evalSemanticContext(D.predicates, outerContext, True)
+ if len(alts)==0:
+ raise self.noViableAlt(input, outerContext, D.configs, startIndex)
+ elif len(alts)==1:
+ return min(alts)
+ else:
+ # report ambiguity after predicate evaluation to make sure the correct
+ # set of ambig alts is reported.
+ self.reportAmbiguity(dfa, D, startIndex, stopIndex, False, alts, D.configs)
+ return min(alts)
+
+ previousD = D
+
+ if t != Token.EOF:
+ input.consume()
+ t = input.LA(1)
+
+ #
+ # Get an existing target state for an edge in the DFA. If the target state
+ # for the edge has not yet been computed or is otherwise not available,
+ # this method returns {@code null}.
+ #
+ # @param previousD The current DFA state
+ # @param t The next input symbol
+ # @return The existing target DFA state for the given input symbol
+ # {@code t}, or {@code null} if the target state for this edge is not
+ # already cached
+ #
+ def getExistingTargetState(self, previousD:DFAState, t:int):
+ edges = previousD.edges
+ if edges is None or t + 1 < 0 or t + 1 >= len(edges):
+ return None
+ else:
+ return edges[t + 1]
+
+ #
+ # Compute a target state for an edge in the DFA, and attempt to add the
+ # computed state and corresponding edge to the DFA.
+ #
+ # @param dfa The DFA
+ # @param previousD The current DFA state
+ # @param t The next input symbol
+ #
+ # @return The computed target DFA state for the given input symbol
+ # {@code t}. If {@code t} does not lead to a valid DFA state, this method
+ # returns {@link #ERROR}.
+ #
+ def computeTargetState(self, dfa:DFA, previousD:DFAState, t:int):
+ reach = self.computeReachSet(previousD.configs, t, False)
+ if reach is None:
+ self.addDFAEdge(dfa, previousD, t, self.ERROR)
+ return self.ERROR
+
+ # create new target state; we'll add to DFA after it's complete
+ D = DFAState(configs=reach)
+
+ predictedAlt = self.getUniqueAlt(reach)
+
+ if ParserATNSimulator.debug:
+ altSubSets = PredictionMode.getConflictingAltSubsets(reach)
+ print("SLL altSubSets=" + str(altSubSets) + ", configs=" + str(reach) +
+ ", predict=" + str(predictedAlt) + ", allSubsetsConflict=" +
+ str(PredictionMode.allSubsetsConflict(altSubSets)) + ", conflictingAlts=" +
+ str(self.getConflictingAlts(reach)))
+
+ if predictedAlt!=ATN.INVALID_ALT_NUMBER:
+ # NO CONFLICT, UNIQUELY PREDICTED ALT
+ D.isAcceptState = True
+ D.configs.uniqueAlt = predictedAlt
+ D.prediction = predictedAlt
+ elif PredictionMode.hasSLLConflictTerminatingPrediction(self.predictionMode, reach):
+ # MORE THAN ONE VIABLE ALTERNATIVE
+ D.configs.conflictingAlts = self.getConflictingAlts(reach)
+ D.requiresFullContext = True
+ # in SLL-only mode, we will stop at this state and return the minimum alt
+ D.isAcceptState = True
+ D.prediction = min(D.configs.conflictingAlts)
+
+ if D.isAcceptState and D.configs.hasSemanticContext:
+ self.predicateDFAState(D, self.atn.getDecisionState(dfa.decision))
+ if D.predicates is not None:
+ D.prediction = ATN.INVALID_ALT_NUMBER
+
+ # all adds to dfa are done after we've created full D state
+ D = self.addDFAEdge(dfa, previousD, t, D)
+ return D
+
+ def predicateDFAState(self, dfaState:DFAState, decisionState:DecisionState):
+ # We need to test all predicates, even in DFA states that
+ # uniquely predict alternative.
+ nalts = len(decisionState.transitions)
+ # Update DFA so reach becomes accept state with (predicate,alt)
+ # pairs if preds found for conflicting alts
+ altsToCollectPredsFrom = self.getConflictingAltsOrUniqueAlt(dfaState.configs)
+ altToPred = self.getPredsForAmbigAlts(altsToCollectPredsFrom, dfaState.configs, nalts)
+ if altToPred is not None:
+ dfaState.predicates = self.getPredicatePredictions(altsToCollectPredsFrom, altToPred)
+ dfaState.prediction = ATN.INVALID_ALT_NUMBER # make sure we use preds
+ else:
+ # There are preds in configs but they might go away
+ # when OR'd together like {p}? || NONE == NONE. If neither
+ # alt has preds, resolve to min alt
+ dfaState.prediction = min(altsToCollectPredsFrom)
+
+ # comes back with reach.uniqueAlt set to a valid alt
+ def execATNWithFullContext(self, dfa:DFA, D:DFAState, # how far we got before failing over
+ s0:ATNConfigSet,
+ input:TokenStream,
+ startIndex:int,
+ outerContext:ParserRuleContext):
+ if ParserATNSimulator.debug or ParserATNSimulator.debug_list_atn_decisions:
+ print("execATNWithFullContext", str(s0))
+ fullCtx = True
+ foundExactAmbig = False
+ reach = None
+ previous = s0
+ input.seek(startIndex)
+ t = input.LA(1)
+ predictedAlt = -1
+ while (True): # while more work
+ reach = self.computeReachSet(previous, t, fullCtx)
+ if reach is None:
+ # if any configs in previous dipped into outer context, that
+ # means that input up to t actually finished entry rule
+ # at least for LL decision. Full LL doesn't dip into outer
+ # so don't need special case.
+ # We will get an error no matter what so delay until after
+ # decision; better error message. Also, no reachable target
+ # ATN states in SLL implies LL will also get nowhere.
+ # If conflict in states that dip out, choose min since we
+ # will get error no matter what.
+ e = self.noViableAlt(input, outerContext, previous, startIndex)
+ input.seek(startIndex)
+ alt = self.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previous, outerContext)
+ if alt!=ATN.INVALID_ALT_NUMBER:
+ return alt
+ else:
+ raise e
+
+ altSubSets = PredictionMode.getConflictingAltSubsets(reach)
+ if ParserATNSimulator.debug:
+ print("LL altSubSets=" + str(altSubSets) + ", predict=" +
+ str(PredictionMode.getUniqueAlt(altSubSets)) + ", resolvesToJustOneViableAlt=" +
+ str(PredictionMode.resolvesToJustOneViableAlt(altSubSets)))
+
+ reach.uniqueAlt = self.getUniqueAlt(reach)
+ # unique prediction?
+ if reach.uniqueAlt!=ATN.INVALID_ALT_NUMBER:
+ predictedAlt = reach.uniqueAlt
+ break
+ elif self.predictionMode is not PredictionMode.LL_EXACT_AMBIG_DETECTION:
+ predictedAlt = PredictionMode.resolvesToJustOneViableAlt(altSubSets)
+ if predictedAlt != ATN.INVALID_ALT_NUMBER:
+ break
+ else:
+ # In exact ambiguity mode, we never try to terminate early.
+ # Just keeps scarfing until we know what the conflict is
+ if PredictionMode.allSubsetsConflict(altSubSets) and PredictionMode.allSubsetsEqual(altSubSets):
+ foundExactAmbig = True
+ predictedAlt = PredictionMode.getSingleViableAlt(altSubSets)
+ break
+ # else there are multiple non-conflicting subsets or
+ # we're not sure what the ambiguity is yet.
+ # So, keep going.
+
+ previous = reach
+ if t != Token.EOF:
+ input.consume()
+ t = input.LA(1)
+
+ # If the configuration set uniquely predicts an alternative,
+ # without conflict, then we know that it's a full LL decision
+ # not SLL.
+ if reach.uniqueAlt != ATN.INVALID_ALT_NUMBER :
+ self.reportContextSensitivity(dfa, predictedAlt, reach, startIndex, input.index)
+ return predictedAlt
+
+ # We do not check predicates here because we have checked them
+ # on-the-fly when doing full context prediction.
+
+ #
+ # In non-exact ambiguity detection mode, we might actually be able to
+ # detect an exact ambiguity, but I'm not going to spend the cycles
+ # needed to check. We only emit ambiguity warnings in exact ambiguity
+ # mode.
+ #
+ # For example, we might know that we have conflicting configurations.
+ # But, that does not mean that there is no way forward without a
+ # conflict. It's possible to have nonconflicting alt subsets as in:
+
+ # altSubSets=[{1, 2}, {1, 2}, {1}, {1, 2}]
+
+ # from
+ #
+ # [(17,1,[5 $]), (13,1,[5 10 $]), (21,1,[5 10 $]), (11,1,[$]),
+ # (13,2,[5 10 $]), (21,2,[5 10 $]), (11,2,[$])]
+ #
+ # In this case, (17,1,[5 $]) indicates there is some next sequence that
+ # would resolve this without conflict to alternative 1. Any other viable
+ # next sequence, however, is associated with a conflict. We stop
+ # looking for input because no amount of further lookahead will alter
+ # the fact that we should predict alternative 1. We just can't say for
+ # sure that there is an ambiguity without looking further.
+
+ self.reportAmbiguity(dfa, D, startIndex, input.index, foundExactAmbig, None, reach)
+
+ return predictedAlt
+
+ def computeReachSet(self, closure:ATNConfigSet, t:int, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("in computeReachSet, starting closure: " + str(closure))
+
+ if self.mergeCache is None:
+ self.mergeCache = dict()
+
+ intermediate = ATNConfigSet(fullCtx)
+
+ # Configurations already in a rule stop state indicate reaching the end
+ # of the decision rule (local context) or end of the start rule (full
+ # context). Once reached, these configurations are never updated by a
+ # closure operation, so they are handled separately for the performance
+ # advantage of having a smaller intermediate set when calling closure.
+ #
+ # For full-context reach operations, separate handling is required to
+ # ensure that the alternative matching the longest overall sequence is
+ # chosen when multiple such configurations can match the input.
+
+ skippedStopStates = None
+
+ # First figure out where we can reach on input t
+ for c in closure:
+ if ParserATNSimulator.debug:
+ print("testing " + self.getTokenName(t) + " at " + str(c))
+
+ if isinstance(c.state, RuleStopState):
+ if fullCtx or t == Token.EOF:
+ if skippedStopStates is None:
+ skippedStopStates = list()
+ skippedStopStates.append(c)
+ continue
+
+ for trans in c.state.transitions:
+ target = self.getReachableTarget(trans, t)
+ if target is not None:
+ intermediate.add(ATNConfig(state=target, config=c), self.mergeCache)
+
+ # Now figure out where the reach operation can take us...
+
+ reach = None
+
+ # This block optimizes the reach operation for intermediate sets which
+ # trivially indicate a termination state for the overall
+ # adaptivePredict operation.
+ #
+ # The conditions assume that intermediate
+ # contains all configurations relevant to the reach set, but this
+ # condition is not true when one or more configurations have been
+ # withheld in skippedStopStates, or when the current symbol is EOF.
+ #
+ if skippedStopStates is None and t!=Token.EOF:
+ if len(intermediate)==1:
+ # Don't pursue the closure if there is just one state.
+ # It can only have one alternative; just add to result
+ # Also don't pursue the closure if there is unique alternative
+ # among the configurations.
+ reach = intermediate
+ elif self.getUniqueAlt(intermediate)!=ATN.INVALID_ALT_NUMBER:
+ # Also don't pursue the closure if there is unique alternative
+ # among the configurations.
+ reach = intermediate
+
+ # If the reach set could not be trivially determined, perform a closure
+ # operation on the intermediate set to compute its initial value.
+ #
+ if reach is None:
+ reach = ATNConfigSet(fullCtx)
+ closureBusy = set()
+ treatEofAsEpsilon = t == Token.EOF
+ for c in intermediate:
+ self.closure(c, reach, closureBusy, False, fullCtx, treatEofAsEpsilon)
+
+ if t == Token.EOF:
+ # After consuming EOF no additional input is possible, so we are
+ # only interested in configurations which reached the end of the
+ # decision rule (local context) or end of the start rule (full
+ # context). Update reach to contain only these configurations. This
+ # handles both explicit EOF transitions in the grammar and implicit
+ # EOF transitions following the end of the decision or start rule.
+ #
+ # When reach==intermediate, no closure operation was performed. In
+ # this case, removeAllConfigsNotInRuleStopState needs to check for
+ # reachable rule stop states as well as configurations already in
+ # a rule stop state.
+ #
+ # This is handled before the configurations in skippedStopStates,
+ # because any configurations potentially added from that list are
+ # already guaranteed to meet this condition whether or not it's
+ # required.
+ #
+ reach = self.removeAllConfigsNotInRuleStopState(reach, reach is intermediate)
+
+ # If skippedStopStates is not null, then it contains at least one
+ # configuration. For full-context reach operations, these
+ # configurations reached the end of the start rule, in which case we
+ # only add them back to reach if no configuration during the current
+ # closure operation reached such a state. This ensures adaptivePredict
+ # chooses an alternative matching the longest overall sequence when
+ # multiple alternatives are viable.
+ #
+ if skippedStopStates is not None and ( (not fullCtx) or (not PredictionMode.hasConfigInRuleStopState(reach))):
+ for c in skippedStopStates:
+ reach.add(c, self.mergeCache)
+ if len(reach)==0:
+ return None
+ else:
+ return reach
+
+ #
+ # Return a configuration set containing only the configurations from
+ # {@code configs} which are in a {@link RuleStopState}. If all
+ # configurations in {@code configs} are already in a rule stop state, this
+ # method simply returns {@code configs}.
+ #
+ # When {@code lookToEndOfRule} is true, this method uses
+ # {@link ATN#nextTokens} for each configuration in {@code configs} which is
+ # not already in a rule stop state to see if a rule stop state is reachable
+ # from the configuration via epsilon-only transitions.
+ #
+ # @param configs the configuration set to update
+ # @param lookToEndOfRule when true, this method checks for rule stop states
+ # reachable by epsilon-only transitions from each configuration in
+ # {@code configs}.
+ #
+ # @return {@code configs} if all configurations in {@code configs} are in a
+ # rule stop state, otherwise return a new configuration set containing only
+ # the configurations from {@code configs} which are in a rule stop state
+ #
+ def removeAllConfigsNotInRuleStopState(self, configs:ATNConfigSet, lookToEndOfRule:bool):
+ if PredictionMode.allConfigsInRuleStopStates(configs):
+ return configs
+ result = ATNConfigSet(configs.fullCtx)
+ for config in configs:
+ if isinstance(config.state, RuleStopState):
+ result.add(config, self.mergeCache)
+ continue
+ if lookToEndOfRule and config.state.epsilonOnlyTransitions:
+ nextTokens = self.atn.nextTokens(config.state)
+ if Token.EPSILON in nextTokens:
+ endOfRuleState = self.atn.ruleToStopState[config.state.ruleIndex]
+ result.add(ATNConfig(state=endOfRuleState, config=config), self.mergeCache)
+ return result
+
+ def computeStartState(self, p:ATNState, ctx:RuleContext, fullCtx:bool):
+ # always at least the implicit call to start rule
+ initialContext = PredictionContextFromRuleContext(self.atn, ctx)
+ configs = ATNConfigSet(fullCtx)
+
+ for i in range(0, len(p.transitions)):
+ target = p.transitions[i].target
+ c = ATNConfig(target, i+1, initialContext)
+ closureBusy = set()
+ self.closure(c, configs, closureBusy, True, fullCtx, False)
+ return configs
+
+ #
+ # This method transforms the start state computed by
+ # {@link #computeStartState} to the special start state used by a
+ # precedence DFA for a particular precedence value. The transformation
+ # process applies the following changes to the start state's configuration
+ # set.
+ #
+ #
+ # - Evaluate the precedence predicates for each configuration using
+ # {@link SemanticContext#evalPrecedence}.
+ # - Remove all configurations which predict an alternative greater than
+ # 1, for which another configuration that predicts alternative 1 is in the
+ # same ATN state with the same prediction context. This transformation is
+ # valid for the following reasons:
+ #
+ # - The closure block cannot contain any epsilon transitions which bypass
+ # the body of the closure, so all states reachable via alternative 1 are
+ # part of the precedence alternatives of the transformed left-recursive
+ # rule.
+ # - The "primary" portion of a left recursive rule cannot contain an
+ # epsilon transition, so the only way an alternative other than 1 can exist
+ # in a state that is also reachable via alternative 1 is by nesting calls
+ # to the left-recursive rule, with the outer calls not being at the
+ # preferred precedence level.
+ #
+ #
+ #
+ #
+ #
+ # The prediction context must be considered by this filter to address
+ # situations like the following.
+ #
+ #
+ #
+ # grammar TA;
+ # prog: statement* EOF;
+ # statement: letterA | statement letterA 'b' ;
+ # letterA: 'a';
+ #
+ #
+ #
+ # If the above grammar, the ATN state immediately before the token
+ # reference {@code 'a'} in {@code letterA} is reachable from the left edge
+ # of both the primary and closure blocks of the left-recursive rule
+ # {@code statement}. The prediction context associated with each of these
+ # configurations distinguishes between them, and prevents the alternative
+ # which stepped out to {@code prog} (and then back in to {@code statement}
+ # from being eliminated by the filter.
+ #
+ #
+ # @param configs The configuration set computed by
+ # {@link #computeStartState} as the start state for the DFA.
+ # @return The transformed configuration set representing the start state
+ # for a precedence DFA at a particular precedence level (determined by
+ # calling {@link Parser#getPrecedence}).
+ #
+ def applyPrecedenceFilter(self, configs:ATNConfigSet):
+ statesFromAlt1 = dict()
+ configSet = ATNConfigSet(configs.fullCtx)
+ for config in configs:
+ # handle alt 1 first
+ if config.alt != 1:
+ continue
+ updatedContext = config.semanticContext.evalPrecedence(self.parser, self._outerContext)
+ if updatedContext is None:
+ # the configuration was eliminated
+ continue
+
+ statesFromAlt1[config.state.stateNumber] = config.context
+ if updatedContext is not config.semanticContext:
+ configSet.add(ATNConfig(config=config, semantic=updatedContext), self.mergeCache)
+ else:
+ configSet.add(config, self.mergeCache)
+
+ for config in configs:
+ if config.alt == 1:
+ # already handled
+ continue
+
+ # In the future, this elimination step could be updated to also
+ # filter the prediction context for alternatives predicting alt>1
+ # (basically a graph subtraction algorithm).
+ #
+ if not config.precedenceFilterSuppressed:
+ context = statesFromAlt1.get(config.state.stateNumber, None)
+ if context==config.context:
+ # eliminated
+ continue
+
+ configSet.add(config, self.mergeCache)
+
+ return configSet
+
+ def getReachableTarget(self, trans:Transition, ttype:int):
+ if trans.matches(ttype, 0, self.atn.maxTokenType):
+ return trans.target
+ else:
+ return None
+
+ def getPredsForAmbigAlts(self, ambigAlts:set, configs:ATNConfigSet, nalts:int):
+ # REACH=[1|1|[]|0:0, 1|2|[]|0:1]
+ # altToPred starts as an array of all null contexts. The entry at index i
+ # corresponds to alternative i. altToPred[i] may have one of three values:
+ # 1. null: no ATNConfig c is found such that c.alt==i
+ # 2. SemanticContext.NONE: At least one ATNConfig c exists such that
+ # c.alt==i and c.semanticContext==SemanticContext.NONE. In other words,
+ # alt i has at least one unpredicated config.
+ # 3. Non-NONE Semantic Context: There exists at least one, and for all
+ # ATNConfig c such that c.alt==i, c.semanticContext!=SemanticContext.NONE.
+ #
+ # From this, it is clear that NONE||anything==NONE.
+ #
+ altToPred = [None] * (nalts + 1)
+ for c in configs:
+ if c.alt in ambigAlts:
+ altToPred[c.alt] = orContext(altToPred[c.alt], c.semanticContext)
+
+ nPredAlts = 0
+ for i in range(1, nalts+1):
+ if altToPred[i] is None:
+ altToPred[i] = SemanticContext.NONE
+ elif altToPred[i] is not SemanticContext.NONE:
+ nPredAlts += 1
+
+ # nonambig alts are null in altToPred
+ if nPredAlts==0:
+ altToPred = None
+ if ParserATNSimulator.debug:
+ print("getPredsForAmbigAlts result " + str_list(altToPred))
+ return altToPred
+
+ def getPredicatePredictions(self, ambigAlts:set, altToPred:list):
+ pairs = []
+ containsPredicate = False
+ for i in range(1, len(altToPred)):
+ pred = altToPred[i]
+ # unpredicated is indicated by SemanticContext.NONE
+ if ambigAlts is not None and i in ambigAlts:
+ pairs.append(PredPrediction(pred, i))
+ if pred is not SemanticContext.NONE:
+ containsPredicate = True
+
+ if not containsPredicate:
+ return None
+
+ return pairs
+
+ #
+ # This method is used to improve the localization of error messages by
+ # choosing an alternative rather than throwing a
+ # {@link NoViableAltException} in particular prediction scenarios where the
+ # {@link #ERROR} state was reached during ATN simulation.
+ #
+ #
+ # The default implementation of this method uses the following
+ # algorithm to identify an ATN configuration which successfully parsed the
+ # decision entry rule. Choosing such an alternative ensures that the
+ # {@link ParserRuleContext} returned by the calling rule will be complete
+ # and valid, and the syntax error will be reported later at a more
+ # localized location.
+ #
+ #
+ # - If a syntactically valid path or paths reach the end of the decision rule and
+ # they are semantically valid if predicated, return the min associated alt.
+ # - Else, if a semantically invalid but syntactically valid path exist
+ # or paths exist, return the minimum associated alt.
+ #
+ # - Otherwise, return {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ #
+ #
+ # In some scenarios, the algorithm described above could predict an
+ # alternative which will result in a {@link FailedPredicateException} in
+ # the parser. Specifically, this could occur if the only configuration
+ # capable of successfully parsing to the end of the decision rule is
+ # blocked by a semantic predicate. By choosing this alternative within
+ # {@link #adaptivePredict} instead of throwing a
+ # {@link NoViableAltException}, the resulting
+ # {@link FailedPredicateException} in the parser will identify the specific
+ # predicate which is preventing the parser from successfully parsing the
+ # decision rule, which helps developers identify and correct logic errors
+ # in semantic predicates.
+ #
+ #
+ # @param configs The ATN configurations which were valid immediately before
+ # the {@link #ERROR} state was reached
+ # @param outerContext The is the \gamma_0 initial parser context from the paper
+ # or the parser stack at the instant before prediction commences.
+ #
+ # @return The value to return from {@link #adaptivePredict}, or
+ # {@link ATN#INVALID_ALT_NUMBER} if a suitable alternative was not
+ # identified and {@link #adaptivePredict} should report an error instead.
+ #
+ def getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(self, configs:ATNConfigSet, outerContext:ParserRuleContext):
+ semValidConfigs, semInvalidConfigs = self.splitAccordingToSemanticValidity(configs, outerContext)
+ alt = self.getAltThatFinishedDecisionEntryRule(semValidConfigs)
+ if alt!=ATN.INVALID_ALT_NUMBER: # semantically/syntactically viable path exists
+ return alt
+ # Is there a syntactically valid path with a failed pred?
+ if len(semInvalidConfigs)>0:
+ alt = self.getAltThatFinishedDecisionEntryRule(semInvalidConfigs)
+ if alt!=ATN.INVALID_ALT_NUMBER: # syntactically viable path exists
+ return alt
+ return ATN.INVALID_ALT_NUMBER
+
+ def getAltThatFinishedDecisionEntryRule(self, configs:ATNConfigSet):
+ alts = set()
+ for c in configs:
+ if c.reachesIntoOuterContext>0 or (isinstance(c.state, RuleStopState) and c.context.hasEmptyPath() ):
+ alts.add(c.alt)
+ if len(alts)==0:
+ return ATN.INVALID_ALT_NUMBER
+ else:
+ return min(alts)
+
+ # Walk the list of configurations and split them according to
+ # those that have preds evaluating to true/false. If no pred, assume
+ # true pred and include in succeeded set. Returns Pair of sets.
+ #
+ # Create a new set so as not to alter the incoming parameter.
+ #
+ # Assumption: the input stream has been restored to the starting point
+ # prediction, which is where predicates need to evaluate.
+ #
+ def splitAccordingToSemanticValidity(self, configs:ATNConfigSet, outerContext:ParserRuleContext):
+ succeeded = ATNConfigSet(configs.fullCtx)
+ failed = ATNConfigSet(configs.fullCtx)
+ for c in configs:
+ if c.semanticContext is not SemanticContext.NONE:
+ predicateEvaluationResult = c.semanticContext.eval(self.parser, outerContext)
+ if predicateEvaluationResult:
+ succeeded.add(c)
+ else:
+ failed.add(c)
+ else:
+ succeeded.add(c)
+ return (succeeded,failed)
+
+ # Look through a list of predicate/alt pairs, returning alts for the
+ # pairs that win. A {@code NONE} predicate indicates an alt containing an
+ # unpredicated config which behaves as "always true." If !complete
+ # then we stop at the first predicate that evaluates to true. This
+ # includes pairs with null predicates.
+ #
+ def evalSemanticContext(self, predPredictions:list, outerContext:ParserRuleContext, complete:bool):
+ predictions = set()
+ for pair in predPredictions:
+ if pair.pred is SemanticContext.NONE:
+ predictions.add(pair.alt)
+ if not complete:
+ break
+ continue
+ predicateEvaluationResult = pair.pred.eval(self.parser, outerContext)
+ if ParserATNSimulator.debug or ParserATNSimulator.dfa_debug:
+ print("eval pred " + str(pair) + "=" + str(predicateEvaluationResult))
+
+ if predicateEvaluationResult:
+ if ParserATNSimulator.debug or ParserATNSimulator.dfa_debug:
+ print("PREDICT " + str(pair.alt))
+ predictions.add(pair.alt)
+ if not complete:
+ break
+ return predictions
+
+
+ # TODO: If we are doing predicates, there is no point in pursuing
+ # closure operations if we reach a DFA state that uniquely predicts
+ # alternative. We will not be caching that DFA state and it is a
+ # waste to pursue the closure. Might have to advance when we do
+ # ambig detection thought :(
+ #
+
+ def closure(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, treatEofAsEpsilon:bool):
+ initialDepth = 0
+ self.closureCheckingStopState(config, configs, closureBusy, collectPredicates,
+ fullCtx, initialDepth, treatEofAsEpsilon)
+
+
+ def closureCheckingStopState(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, depth:int, treatEofAsEpsilon:bool):
+ if ParserATNSimulator.debug:
+ print("closure(" + str(config) + ")")
+
+ if isinstance(config.state, RuleStopState):
+ # We hit rule end. If we have context info, use it
+ # run thru all possible stack tops in ctx
+ if not config.context.isEmpty():
+ for i in range(0, len(config.context)):
+ state = config.context.getReturnState(i)
+ if state is PredictionContext.EMPTY_RETURN_STATE:
+ if fullCtx:
+ configs.add(ATNConfig(state=config.state, context=PredictionContext.EMPTY, config=config), self.mergeCache)
+ continue
+ else:
+ # we have no context info, just chase follow links (if greedy)
+ if ParserATNSimulator.debug:
+ print("FALLING off rule " + self.getRuleName(config.state.ruleIndex))
+ self.closure_(config, configs, closureBusy, collectPredicates,
+ fullCtx, depth, treatEofAsEpsilon)
+ continue
+ returnState = self.atn.states[state]
+ newContext = config.context.getParent(i) # "pop" return state
+ c = ATNConfig(state=returnState, alt=config.alt, context=newContext, semantic=config.semanticContext)
+ # While we have context to pop back from, we may have
+ # gotten that context AFTER having falling off a rule.
+ # Make sure we track that we are now out of context.
+ c.reachesIntoOuterContext = config.reachesIntoOuterContext
+ self.closureCheckingStopState(c, configs, closureBusy, collectPredicates, fullCtx, depth - 1, treatEofAsEpsilon)
+ return
+ elif fullCtx:
+ # reached end of start rule
+ configs.add(config, self.mergeCache)
+ return
+ else:
+ # else if we have no context info, just chase follow links (if greedy)
+ if ParserATNSimulator.debug:
+ print("FALLING off rule " + self.getRuleName(config.state.ruleIndex))
+
+ self.closure_(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEofAsEpsilon)
+
+ # Do the actual work of walking epsilon edges#
+ def closure_(self, config:ATNConfig, configs:ATNConfigSet, closureBusy:set, collectPredicates:bool, fullCtx:bool, depth:int, treatEofAsEpsilon:bool):
+ p = config.state
+ # optimization
+ if not p.epsilonOnlyTransitions:
+ configs.add(config, self.mergeCache)
+ # make sure to not return here, because EOF transitions can act as
+ # both epsilon transitions and non-epsilon transitions.
+
+ first = True
+ for t in p.transitions:
+ if first:
+ first = False
+ if self.canDropLoopEntryEdgeInLeftRecursiveRule(config):
+ continue
+
+ continueCollecting = collectPredicates and not isinstance(t, ActionTransition)
+ c = self.getEpsilonTarget(config, t, continueCollecting, depth == 0, fullCtx, treatEofAsEpsilon)
+ if c is not None:
+ newDepth = depth
+ if isinstance( config.state, RuleStopState):
+ # target fell off end of rule; mark resulting c as having dipped into outer context
+ # We can't get here if incoming config was rule stop and we had context
+ # track how far we dip into outer context. Might
+ # come in handy and we avoid evaluating context dependent
+ # preds if this is > 0.
+ if self._dfa is not None and self._dfa.precedenceDfa:
+ if t.outermostPrecedenceReturn == self._dfa.atnStartState.ruleIndex:
+ c.precedenceFilterSuppressed = True
+ c.reachesIntoOuterContext += 1
+ if c in closureBusy:
+ # avoid infinite recursion for right-recursive rules
+ continue
+ closureBusy.add(c)
+ configs.dipsIntoOuterContext = True # TODO: can remove? only care when we add to set per middle of this method
+ newDepth -= 1
+ if ParserATNSimulator.debug:
+ print("dips into outer ctx: " + str(c))
+ else:
+ if not t.isEpsilon:
+ if c in closureBusy:
+ # avoid infinite recursion for EOF* and EOF+
+ continue
+ closureBusy.add(c)
+ if isinstance(t, RuleTransition):
+ # latch when newDepth goes negative - once we step out of the entry context we can't return
+ if newDepth >= 0:
+ newDepth += 1
+
+ self.closureCheckingStopState(c, configs, closureBusy, continueCollecting, fullCtx, newDepth, treatEofAsEpsilon)
+
+
+
+ # Implements first-edge (loop entry) elimination as an optimization
+ # during closure operations. See antlr/antlr4#1398.
+ #
+ # The optimization is to avoid adding the loop entry config when
+ # the exit path can only lead back to the same
+ # StarLoopEntryState after popping context at the rule end state
+ # (traversing only epsilon edges, so we're still in closure, in
+ # this same rule).
+ #
+ # We need to detect any state that can reach loop entry on
+ # epsilon w/o exiting rule. We don't have to look at FOLLOW
+ # links, just ensure that all stack tops for config refer to key
+ # states in LR rule.
+ #
+ # To verify we are in the right situation we must first check
+ # closure is at a StarLoopEntryState generated during LR removal.
+ # Then we check that each stack top of context is a return state
+ # from one of these cases:
+ #
+ # 1. 'not' expr, '(' type ')' expr. The return state points at loop entry state
+ # 2. expr op expr. The return state is the block end of internal block of (...)*
+ # 3. 'between' expr 'and' expr. The return state of 2nd expr reference.
+ # That state points at block end of internal block of (...)*.
+ # 4. expr '?' expr ':' expr. The return state points at block end,
+ # which points at loop entry state.
+ #
+ # If any is true for each stack top, then closure does not add a
+ # config to the current config set for edge[0], the loop entry branch.
+ #
+ # Conditions fail if any context for the current config is:
+ #
+ # a. empty (we'd fall out of expr to do a global FOLLOW which could
+ # even be to some weird spot in expr) or,
+ # b. lies outside of expr or,
+ # c. lies within expr but at a state not the BlockEndState
+ # generated during LR removal
+ #
+ # Do we need to evaluate predicates ever in closure for this case?
+ #
+ # No. Predicates, including precedence predicates, are only
+ # evaluated when computing a DFA start state. I.e., only before
+ # the lookahead (but not parser) consumes a token.
+ #
+ # There are no epsilon edges allowed in LR rule alt blocks or in
+ # the "primary" part (ID here). If closure is in
+ # StarLoopEntryState any lookahead operation will have consumed a
+ # token as there are no epsilon-paths that lead to
+ # StarLoopEntryState. We do not have to evaluate predicates
+ # therefore if we are in the generated StarLoopEntryState of a LR
+ # rule. Note that when making a prediction starting at that
+ # decision point, decision d=2, compute-start-state performs
+ # closure starting at edges[0], edges[1] emanating from
+ # StarLoopEntryState. That means it is not performing closure on
+ # StarLoopEntryState during compute-start-state.
+ #
+ # How do we know this always gives same prediction answer?
+ #
+ # Without predicates, loop entry and exit paths are ambiguous
+ # upon remaining input +b (in, say, a+b). Either paths lead to
+ # valid parses. Closure can lead to consuming + immediately or by
+ # falling out of this call to expr back into expr and loop back
+ # again to StarLoopEntryState to match +b. In this special case,
+ # we choose the more efficient path, which is to take the bypass
+ # path.
+ #
+ # The lookahead language has not changed because closure chooses
+ # one path over the other. Both paths lead to consuming the same
+ # remaining input during a lookahead operation. If the next token
+ # is an operator, lookahead will enter the choice block with
+ # operators. If it is not, lookahead will exit expr. Same as if
+ # closure had chosen to enter the choice block immediately.
+ #
+ # Closure is examining one config (some loopentrystate, some alt,
+ # context) which means it is considering exactly one alt. Closure
+ # always copies the same alt to any derived configs.
+ #
+ # How do we know this optimization doesn't mess up precedence in
+ # our parse trees?
+ #
+ # Looking through expr from left edge of stat only has to confirm
+ # that an input, say, a+b+c; begins with any valid interpretation
+ # of an expression. The precedence actually doesn't matter when
+ # making a decision in stat seeing through expr. It is only when
+ # parsing rule expr that we must use the precedence to get the
+ # right interpretation and, hence, parse tree.
+ #
+ # @since 4.6
+ #
+ def canDropLoopEntryEdgeInLeftRecursiveRule(self, config):
+ # return False
+ p = config.state
+ # First check to see if we are in StarLoopEntryState generated during
+ # left-recursion elimination. For efficiency, also check if
+ # the context has an empty stack case. If so, it would mean
+ # global FOLLOW so we can't perform optimization
+ # Are we the special loop entry/exit state? or SLL wildcard
+ if p.stateType != ATNState.STAR_LOOP_ENTRY \
+ or not p.isPrecedenceDecision \
+ or config.context.isEmpty() \
+ or config.context.hasEmptyPath():
+ return False
+
+ # Require all return states to return back to the same rule
+ # that p is in.
+ numCtxs = len(config.context)
+ for i in range(0, numCtxs): # for each stack context
+ returnState = self.atn.states[config.context.getReturnState(i)]
+ if returnState.ruleIndex != p.ruleIndex:
+ return False
+
+ decisionStartState = p.transitions[0].target
+ blockEndStateNum = decisionStartState.endState.stateNumber
+ blockEndState = self.atn.states[blockEndStateNum]
+
+ # Verify that the top of each stack context leads to loop entry/exit
+ # state through epsilon edges and w/o leaving rule.
+ for i in range(0, numCtxs): # for each stack context
+ returnStateNumber = config.context.getReturnState(i)
+ returnState = self.atn.states[returnStateNumber]
+ # all states must have single outgoing epsilon edge
+ if len(returnState.transitions) != 1 or not returnState.transitions[0].isEpsilon:
+ return False
+
+ # Look for prefix op case like 'not expr', (' type ')' expr
+ returnStateTarget = returnState.transitions[0].target
+ if returnState.stateType == ATNState.BLOCK_END and returnStateTarget is p:
+ continue
+
+ # Look for 'expr op expr' or case where expr's return state is block end
+ # of (...)* internal block; the block end points to loop back
+ # which points to p but we don't need to check that
+ if returnState is blockEndState:
+ continue
+
+ # Look for ternary expr ? expr : expr. The return state points at block end,
+ # which points at loop entry state
+ if returnStateTarget is blockEndState:
+ continue
+
+ # Look for complex prefix 'between expr and expr' case where 2nd expr's
+ # return state points at block end state of (...)* internal block
+ if returnStateTarget.stateType == ATNState.BLOCK_END \
+ and len(returnStateTarget.transitions) == 1 \
+ and returnStateTarget.transitions[0].isEpsilon \
+ and returnStateTarget.transitions[0].target is p:
+ continue
+
+ # anything else ain't conforming
+ return False
+
+ return True
+
+
+ def getRuleName(self, index:int):
+ if self.parser is not None and index>=0:
+ return self.parser.ruleNames[index]
+ else:
+ return ""
+
+ epsilonTargetMethods = dict()
+ epsilonTargetMethods[Transition.RULE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.ruleTransition(config, t)
+ epsilonTargetMethods[Transition.PRECEDENCE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.precedenceTransition(config, t, collectPredicates, inContext, fullCtx)
+ epsilonTargetMethods[Transition.PREDICATE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.predTransition(config, t, collectPredicates, inContext, fullCtx)
+ epsilonTargetMethods[Transition.ACTION] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ sim.actionTransition(config, t)
+ epsilonTargetMethods[Transition.EPSILON] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config)
+ epsilonTargetMethods[Transition.ATOM] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+ epsilonTargetMethods[Transition.RANGE] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+ epsilonTargetMethods[Transition.SET] = lambda sim, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon: \
+ ATNConfig(state=t.target, config=config) if treatEofAsEpsilon and t.matches(Token.EOF, 0, 1) else None
+
+ def getEpsilonTarget(self, config:ATNConfig, t:Transition, collectPredicates:bool, inContext:bool, fullCtx:bool, treatEofAsEpsilon:bool):
+ m = self.epsilonTargetMethods.get(t.serializationType, None)
+ if m is None:
+ return None
+ else:
+ return m(self, config, t, collectPredicates, inContext, fullCtx, treatEofAsEpsilon)
+
+ def actionTransition(self, config:ATNConfig, t:ActionTransition):
+ if ParserATNSimulator.debug:
+ print("ACTION edge " + str(t.ruleIndex) + ":" + str(t.actionIndex))
+ return ATNConfig(state=t.target, config=config)
+
+ def precedenceTransition(self, config:ATNConfig, pt:PrecedencePredicateTransition, collectPredicates:bool, inContext:bool, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("PRED (collectPredicates=" + str(collectPredicates) + ") " +
+ str(pt.precedence) + ">=_p, ctx dependent=true")
+ if self.parser is not None:
+ print("context surrounding pred is " + str(self.parser.getRuleInvocationStack()))
+
+ c = None
+ if collectPredicates and inContext:
+ if fullCtx:
+ # In full context mode, we can evaluate predicates on-the-fly
+ # during closure, which dramatically reduces the size of
+ # the config sets. It also obviates the need to test predicates
+ # later during conflict resolution.
+ currentPosition = self._input.index
+ self._input.seek(self._startIndex)
+ predSucceeds = pt.getPredicate().eval(self.parser, self._outerContext)
+ self._input.seek(currentPosition)
+ if predSucceeds:
+ c = ATNConfig(state=pt.target, config=config) # no pred context
+ else:
+ newSemCtx = andContext(config.semanticContext, pt.getPredicate())
+ c = ATNConfig(state=pt.target, semantic=newSemCtx, config=config)
+ else:
+ c = ATNConfig(state=pt.target, config=config)
+
+ if ParserATNSimulator.debug:
+ print("config from pred transition=" + str(c))
+ return c
+
+ def predTransition(self, config:ATNConfig, pt:PredicateTransition, collectPredicates:bool, inContext:bool, fullCtx:bool):
+ if ParserATNSimulator.debug:
+ print("PRED (collectPredicates=" + str(collectPredicates) + ") " + str(pt.ruleIndex) +
+ ":" + str(pt.predIndex) + ", ctx dependent=" + str(pt.isCtxDependent))
+ if self.parser is not None:
+ print("context surrounding pred is " + str(self.parser.getRuleInvocationStack()))
+
+ c = None
+ if collectPredicates and (not pt.isCtxDependent or (pt.isCtxDependent and inContext)):
+ if fullCtx:
+ # In full context mode, we can evaluate predicates on-the-fly
+ # during closure, which dramatically reduces the size of
+ # the config sets. It also obviates the need to test predicates
+ # later during conflict resolution.
+ currentPosition = self._input.index
+ self._input.seek(self._startIndex)
+ predSucceeds = pt.getPredicate().eval(self.parser, self._outerContext)
+ self._input.seek(currentPosition)
+ if predSucceeds:
+ c = ATNConfig(state=pt.target, config=config) # no pred context
+ else:
+ newSemCtx = andContext(config.semanticContext, pt.getPredicate())
+ c = ATNConfig(state=pt.target, semantic=newSemCtx, config=config)
+ else:
+ c = ATNConfig(state=pt.target, config=config)
+
+ if ParserATNSimulator.debug:
+ print("config from pred transition=" + str(c))
+ return c
+
+ def ruleTransition(self, config:ATNConfig, t:RuleTransition):
+ if ParserATNSimulator.debug:
+ print("CALL rule " + self.getRuleName(t.target.ruleIndex) + ", ctx=" + str(config.context))
+ returnState = t.followState
+ newContext = SingletonPredictionContext.create(config.context, returnState.stateNumber)
+ return ATNConfig(state=t.target, context=newContext, config=config )
+
+ def getConflictingAlts(self, configs:ATNConfigSet):
+ altsets = PredictionMode.getConflictingAltSubsets(configs)
+ return PredictionMode.getAlts(altsets)
+
+ # Sam pointed out a problem with the previous definition, v3, of
+ # ambiguous states. If we have another state associated with conflicting
+ # alternatives, we should keep going. For example, the following grammar
+ #
+ # s : (ID | ID ID?) ';' ;
+ #
+ # When the ATN simulation reaches the state before ';', it has a DFA
+ # state that looks like: [12|1|[], 6|2|[], 12|2|[]]. Naturally
+ # 12|1|[] and 12|2|[] conflict, but we cannot stop processing this node
+ # because alternative to has another way to continue, via [6|2|[]].
+ # The key is that we have a single state that has config's only associated
+ # with a single alternative, 2, and crucially the state transitions
+ # among the configurations are all non-epsilon transitions. That means
+ # we don't consider any conflicts that include alternative 2. So, we
+ # ignore the conflict between alts 1 and 2. We ignore a set of
+ # conflicting alts when there is an intersection with an alternative
+ # associated with a single alt state in the state→config-list map.
+ #
+ # It's also the case that we might have two conflicting configurations but
+ # also a 3rd nonconflicting configuration for a different alternative:
+ # [1|1|[], 1|2|[], 8|3|[]]. This can come about from grammar:
+ #
+ # a : A | A | A B ;
+ #
+ # After matching input A, we reach the stop state for rule A, state 1.
+ # State 8 is the state right before B. Clearly alternatives 1 and 2
+ # conflict and no amount of further lookahead will separate the two.
+ # However, alternative 3 will be able to continue and so we do not
+ # stop working on this state. In the previous example, we're concerned
+ # with states associated with the conflicting alternatives. Here alt
+ # 3 is not associated with the conflicting configs, but since we can continue
+ # looking for input reasonably, I don't declare the state done. We
+ # ignore a set of conflicting alts when we have an alternative
+ # that we still need to pursue.
+ #
+
+ def getConflictingAltsOrUniqueAlt(self, configs:ATNConfigSet):
+ conflictingAlts = None
+ if configs.uniqueAlt!= ATN.INVALID_ALT_NUMBER:
+ conflictingAlts = set()
+ conflictingAlts.add(configs.uniqueAlt)
+ else:
+ conflictingAlts = configs.conflictingAlts
+ return conflictingAlts
+
+ def getTokenName(self, t:int):
+ if t==Token.EOF:
+ return "EOF"
+ if self.parser is not None and \
+ self.parser.literalNames is not None and \
+ t < len(self.parser.literalNames):
+ return self.parser.literalNames[t] + "<" + str(t) + ">"
+ else:
+ return str(t)
+
+ def getLookaheadName(self, input:TokenStream):
+ return self.getTokenName(input.LA(1))
+
+ # Used for debugging in adaptivePredict around execATN but I cut
+ # it out for clarity now that alg. works well. We can leave this
+ # "dead" code for a bit.
+ #
+ def dumpDeadEndConfigs(self, nvae:NoViableAltException):
+ print("dead end configs: ")
+ for c in nvae.getDeadEndConfigs():
+ trans = "no edges"
+ if len(c.state.transitions)>0:
+ t = c.state.transitions[0]
+ if isinstance(t, AtomTransition):
+ trans = "Atom "+ self.getTokenName(t.label)
+ elif isinstance(t, SetTransition):
+ neg = isinstance(t, NotSetTransition)
+ trans = ("~" if neg else "")+"Set "+ str(t.set)
+ print(c.toString(self.parser, True) + ":" + trans, file=sys.stderr)
+
+ def noViableAlt(self, input:TokenStream, outerContext:ParserRuleContext, configs:ATNConfigSet, startIndex:int):
+ return NoViableAltException(self.parser, input, input.get(startIndex), input.LT(1), configs, outerContext)
+
+ def getUniqueAlt(self, configs:ATNConfigSet):
+ alt = ATN.INVALID_ALT_NUMBER
+ for c in configs:
+ if alt == ATN.INVALID_ALT_NUMBER:
+ alt = c.alt # found first alt
+ elif c.alt!=alt:
+ return ATN.INVALID_ALT_NUMBER
+ return alt
+
+ #
+ # Add an edge to the DFA, if possible. This method calls
+ # {@link #addDFAState} to ensure the {@code to} state is present in the
+ # DFA. If {@code from} is {@code null}, or if {@code t} is outside the
+ # range of edges that can be represented in the DFA tables, this method
+ # returns without adding the edge to the DFA.
+ #
+ # If {@code to} is {@code null}, this method returns {@code null}.
+ # Otherwise, this method returns the {@link DFAState} returned by calling
+ # {@link #addDFAState} for the {@code to} state.
+ #
+ # @param dfa The DFA
+ # @param from The source state for the edge
+ # @param t The input symbol
+ # @param to The target state for the edge
+ #
+ # @return If {@code to} is {@code null}, this method returns {@code null};
+ # otherwise this method returns the result of calling {@link #addDFAState}
+ # on {@code to}
+ #
+ def addDFAEdge(self, dfa:DFA, from_:DFAState, t:int, to:DFAState):
+ if ParserATNSimulator.debug:
+ print("EDGE " + str(from_) + " -> " + str(to) + " upon " + self.getTokenName(t))
+
+ if to is None:
+ return None
+
+ to = self.addDFAState(dfa, to) # used existing if possible not incoming
+ if from_ is None or t < -1 or t > self.atn.maxTokenType:
+ return to
+
+ if from_.edges is None:
+ from_.edges = [None] * (self.atn.maxTokenType + 2)
+ from_.edges[t+1] = to # connect
+
+ if ParserATNSimulator.debug:
+ names = None if self.parser is None else self.parser.literalNames
+ print("DFA=\n" + dfa.toString(names))
+
+ return to
+
+ #
+ # Add state {@code D} to the DFA if it is not already present, and return
+ # the actual instance stored in the DFA. If a state equivalent to {@code D}
+ # is already in the DFA, the existing state is returned. Otherwise this
+ # method returns {@code D} after adding it to the DFA.
+ #
+ # If {@code D} is {@link #ERROR}, this method returns {@link #ERROR} and
+ # does not change the DFA.
+ #
+ # @param dfa The dfa
+ # @param D The DFA state to add
+ # @return The state stored in the DFA. This will be either the existing
+ # state if {@code D} is already in the DFA, or {@code D} itself if the
+ # state was not already present.
+ #
+ def addDFAState(self, dfa:DFA, D:DFAState):
+ if D is self.ERROR:
+ return D
+
+
+ existing = dfa.states.get(D, None)
+ if existing is not None:
+ return existing
+
+ D.stateNumber = len(dfa.states)
+ if not D.configs.readonly:
+ D.configs.optimizeConfigs(self)
+ D.configs.setReadonly(True)
+ dfa.states[D] = D
+ if ParserATNSimulator.debug:
+ print("adding new DFA state: " + str(D))
+ return D
+
+ def reportAttemptingFullContext(self, dfa:DFA, conflictingAlts:set, configs:ATNConfigSet, startIndex:int, stopIndex:int):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+ print("reportAttemptingFullContext decision=" + str(dfa.decision) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportAttemptingFullContext(self.parser, dfa, startIndex, stopIndex, conflictingAlts, configs)
+
+ def reportContextSensitivity(self, dfa:DFA, prediction:int, configs:ATNConfigSet, startIndex:int, stopIndex:int):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+ print("reportContextSensitivity decision=" + str(dfa.decision) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportContextSensitivity(self.parser, dfa, startIndex, stopIndex, prediction, configs)
+
+ # If context sensitive parsing, we know it's ambiguity not conflict#
+ def reportAmbiguity(self, dfa:DFA, D:DFAState, startIndex:int, stopIndex:int,
+ exact:bool, ambigAlts:set, configs:ATNConfigSet ):
+ if ParserATNSimulator.debug or ParserATNSimulator.retry_debug:
+# ParserATNPathFinder finder = new ParserATNPathFinder(parser, atn);
+# int i = 1;
+# for (Transition t : dfa.atnStartState.transitions) {
+# print("ALT "+i+"=");
+# print(startIndex+".."+stopIndex+", len(input)="+parser.getInputStream().size());
+# TraceTree path = finder.trace(t.target, parser.getContext(), (TokenStream)parser.getInputStream(),
+# startIndex, stopIndex);
+# if ( path!=null ) {
+# print("path = "+path.toStringTree());
+# for (TraceTree leaf : path.leaves) {
+# List states = path.getPathToNode(leaf);
+# print("states="+states);
+# }
+# }
+# i++;
+# }
+ print("reportAmbiguity " + str(ambigAlts) + ":" + str(configs) +
+ ", input=" + self.parser.getTokenStream().getText(startIndex, stopIndex))
+ if self.parser is not None:
+ self.parser.getErrorListenerDispatch().reportAmbiguity(self.parser, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
diff --git a/lib/python3.12/site-packages/antlr4/atn/PredictionMode.py b/lib/python3.12/site-packages/antlr4/atn/PredictionMode.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e5c73bb47f329d519f4e574ba3a36fc6c4ac29f
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/PredictionMode.py
@@ -0,0 +1,499 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+#
+# This enumeration defines the prediction modes available in ANTLR 4 along with
+# utility methods for analyzing configuration sets for conflicts and/or
+# ambiguities.
+
+
+from enum import Enum
+from antlr4.atn.ATN import ATN
+from antlr4.atn.ATNConfig import ATNConfig
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNState import RuleStopState
+from antlr4.atn.SemanticContext import SemanticContext
+
+PredictionMode = None
+
+class PredictionMode(Enum):
+ #
+ # The SLL(*) prediction mode. This prediction mode ignores the current
+ # parser context when making predictions. This is the fastest prediction
+ # mode, and provides correct results for many grammars. This prediction
+ # mode is more powerful than the prediction mode provided by ANTLR 3, but
+ # may result in syntax errors for grammar and input combinations which are
+ # not SLL.
+ #
+ #
+ # When using this prediction mode, the parser will either return a correct
+ # parse tree (i.e. the same parse tree that would be returned with the
+ # {@link #LL} prediction mode), or it will report a syntax error. If a
+ # syntax error is encountered when using the {@link #SLL} prediction mode,
+ # it may be due to either an actual syntax error in the input or indicate
+ # that the particular combination of grammar and input requires the more
+ # powerful {@link #LL} prediction abilities to complete successfully.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ #
+ SLL = 0
+ #
+ # The LL(*) prediction mode. This prediction mode allows the current parser
+ # context to be used for resolving SLL conflicts that occur during
+ # prediction. This is the fastest prediction mode that guarantees correct
+ # parse results for all combinations of grammars with syntactically correct
+ # inputs.
+ #
+ #
+ # When using this prediction mode, the parser will make correct decisions
+ # for all syntactically-correct grammar and input combinations. However, in
+ # cases where the grammar is truly ambiguous this prediction mode might not
+ # report a precise answer for exactly which alternatives are
+ # ambiguous.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ #
+ LL = 1
+ #
+ # The LL(*) prediction mode with exact ambiguity detection. In addition to
+ # the correctness guarantees provided by the {@link #LL} prediction mode,
+ # this prediction mode instructs the prediction algorithm to determine the
+ # complete and exact set of ambiguous alternatives for every ambiguous
+ # decision encountered while parsing.
+ #
+ #
+ # This prediction mode may be used for diagnosing ambiguities during
+ # grammar development. Due to the performance overhead of calculating sets
+ # of ambiguous alternatives, this prediction mode should be avoided when
+ # the exact results are not necessary.
+ #
+ #
+ # This prediction mode does not provide any guarantees for prediction
+ # behavior for syntactically-incorrect inputs.
+ #
+ LL_EXACT_AMBIG_DETECTION = 2
+
+
+ #
+ # Computes the SLL prediction termination condition.
+ #
+ #
+ # This method computes the SLL prediction termination condition for both of
+ # the following cases.
+ #
+ #
+ # - The usual SLL+LL fallback upon SLL conflict
+ # - Pure SLL without LL fallback
+ #
+ #
+ # COMBINED SLL+LL PARSING
+ #
+ # When LL-fallback is enabled upon SLL conflict, correct predictions are
+ # ensured regardless of how the termination condition is computed by this
+ # method. Due to the substantially higher cost of LL prediction, the
+ # prediction should only fall back to LL when the additional lookahead
+ # cannot lead to a unique SLL prediction.
+ #
+ # Assuming combined SLL+LL parsing, an SLL configuration set with only
+ # conflicting subsets should fall back to full LL, even if the
+ # configuration sets don't resolve to the same alternative (e.g.
+ # {@code {1,2}} and {@code {3,4}}. If there is at least one non-conflicting
+ # configuration, SLL could continue with the hopes that more lookahead will
+ # resolve via one of those non-conflicting configurations.
+ #
+ # Here's the prediction termination rule them: SLL (for SLL+LL parsing)
+ # stops when it sees only conflicting configuration subsets. In contrast,
+ # full LL keeps going when there is uncertainty.
+ #
+ # HEURISTIC
+ #
+ # As a heuristic, we stop prediction when we see any conflicting subset
+ # unless we see a state that only has one alternative associated with it.
+ # The single-alt-state thing lets prediction continue upon rules like
+ # (otherwise, it would admit defeat too soon):
+ #
+ # {@code [12|1|[], 6|2|[], 12|2|[]]. s : (ID | ID ID?) ';' ;}
+ #
+ # When the ATN simulation reaches the state before {@code ';'}, it has a
+ # DFA state that looks like: {@code [12|1|[], 6|2|[], 12|2|[]]}. Naturally
+ # {@code 12|1|[]} and {@code 12|2|[]} conflict, but we cannot stop
+ # processing this node because alternative to has another way to continue,
+ # via {@code [6|2|[]]}.
+ #
+ # It also let's us continue for this rule:
+ #
+ # {@code [1|1|[], 1|2|[], 8|3|[]] a : A | A | A B ;}
+ #
+ # After matching input A, we reach the stop state for rule A, state 1.
+ # State 8 is the state right before B. Clearly alternatives 1 and 2
+ # conflict and no amount of further lookahead will separate the two.
+ # However, alternative 3 will be able to continue and so we do not stop
+ # working on this state. In the previous example, we're concerned with
+ # states associated with the conflicting alternatives. Here alt 3 is not
+ # associated with the conflicting configs, but since we can continue
+ # looking for input reasonably, don't declare the state done.
+ #
+ # PURE SLL PARSING
+ #
+ # To handle pure SLL parsing, all we have to do is make sure that we
+ # combine stack contexts for configurations that differ only by semantic
+ # predicate. From there, we can do the usual SLL termination heuristic.
+ #
+ # PREDICATES IN SLL+LL PARSING
+ #
+ # SLL decisions don't evaluate predicates until after they reach DFA stop
+ # states because they need to create the DFA cache that works in all
+ # semantic situations. In contrast, full LL evaluates predicates collected
+ # during start state computation so it can ignore predicates thereafter.
+ # This means that SLL termination detection can totally ignore semantic
+ # predicates.
+ #
+ # Implementation-wise, {@link ATNConfigSet} combines stack contexts but not
+ # semantic predicate contexts so we might see two configurations like the
+ # following.
+ #
+ # {@code (s, 1, x, {}), (s, 1, x', {p})}
+ #
+ # Before testing these configurations against others, we have to merge
+ # {@code x} and {@code x'} (without modifying the existing configurations).
+ # For example, we test {@code (x+x')==x''} when looking for conflicts in
+ # the following configurations.
+ #
+ # {@code (s, 1, x, {}), (s, 1, x', {p}), (s, 2, x'', {})}
+ #
+ # If the configuration set has predicates (as indicated by
+ # {@link ATNConfigSet#hasSemanticContext}), this algorithm makes a copy of
+ # the configurations to strip out all of the predicates so that a standard
+ # {@link ATNConfigSet} will merge everything ignoring predicates.
+ #
+ @classmethod
+ def hasSLLConflictTerminatingPrediction(cls, mode:PredictionMode, configs:ATNConfigSet):
+ # Configs in rule stop states indicate reaching the end of the decision
+ # rule (local context) or end of start rule (full context). If all
+ # configs meet this condition, then none of the configurations is able
+ # to match additional input so we terminate prediction.
+ #
+ if cls.allConfigsInRuleStopStates(configs):
+ return True
+
+ # pure SLL mode parsing
+ if mode == PredictionMode.SLL:
+ # Don't bother with combining configs from different semantic
+ # contexts if we can fail over to full LL; costs more time
+ # since we'll often fail over anyway.
+ if configs.hasSemanticContext:
+ # dup configs, tossing out semantic predicates
+ dup = ATNConfigSet()
+ for c in configs:
+ c = ATNConfig(config=c, semantic=SemanticContext.NONE)
+ dup.add(c)
+ configs = dup
+ # now we have combined contexts for configs with dissimilar preds
+
+ # pure SLL or combined SLL+LL mode parsing
+ altsets = cls.getConflictingAltSubsets(configs)
+ return cls.hasConflictingAltSet(altsets) and not cls.hasStateAssociatedWithOneAlt(configs)
+
+ # Checks if any configuration in {@code configs} is in a
+ # {@link RuleStopState}. Configurations meeting this condition have reached
+ # the end of the decision rule (local context) or end of start rule (full
+ # context).
+ #
+ # @param configs the configuration set to test
+ # @return {@code true} if any configuration in {@code configs} is in a
+ # {@link RuleStopState}, otherwise {@code false}
+ @classmethod
+ def hasConfigInRuleStopState(cls, configs:ATNConfigSet):
+ return any(isinstance(cfg.state, RuleStopState) for cfg in configs)
+
+ # Checks if all configurations in {@code configs} are in a
+ # {@link RuleStopState}. Configurations meeting this condition have reached
+ # the end of the decision rule (local context) or end of start rule (full
+ # context).
+ #
+ # @param configs the configuration set to test
+ # @return {@code true} if all configurations in {@code configs} are in a
+ # {@link RuleStopState}, otherwise {@code false}
+ @classmethod
+ def allConfigsInRuleStopStates(cls, configs:ATNConfigSet):
+ return all(isinstance(cfg.state, RuleStopState) for cfg in configs)
+
+ #
+ # Full LL prediction termination.
+ #
+ # Can we stop looking ahead during ATN simulation or is there some
+ # uncertainty as to which alternative we will ultimately pick, after
+ # consuming more input? Even if there are partial conflicts, we might know
+ # that everything is going to resolve to the same minimum alternative. That
+ # means we can stop since no more lookahead will change that fact. On the
+ # other hand, there might be multiple conflicts that resolve to different
+ # minimums. That means we need more look ahead to decide which of those
+ # alternatives we should predict.
+ #
+ # The basic idea is to split the set of configurations {@code C}, into
+ # conflicting subsets {@code (s, _, ctx, _)} and singleton subsets with
+ # non-conflicting configurations. Two configurations conflict if they have
+ # identical {@link ATNConfig#state} and {@link ATNConfig#context} values
+ # but different {@link ATNConfig#alt} value, e.g. {@code (s, i, ctx, _)}
+ # and {@code (s, j, ctx, _)} for {@code i!=j}.
+ #
+ # Reduce these configuration subsets to the set of possible alternatives.
+ # You can compute the alternative subsets in one pass as follows:
+ #
+ # {@code A_s,ctx = {i | (s, i, ctx, _)}} for each configuration in
+ # {@code C} holding {@code s} and {@code ctx} fixed.
+ #
+ # Or in pseudo-code, for each configuration {@code c} in {@code C}:
+ #
+ #
+ # map[c] U= c.{@link ATNConfig#alt alt} # map hash/equals uses s and x, not
+ # alt and not pred
+ #
+ #
+ # The values in {@code map} are the set of {@code A_s,ctx} sets.
+ #
+ # If {@code |A_s,ctx|=1} then there is no conflict associated with
+ # {@code s} and {@code ctx}.
+ #
+ # Reduce the subsets to singletons by choosing a minimum of each subset. If
+ # the union of these alternative subsets is a singleton, then no amount of
+ # more lookahead will help us. We will always pick that alternative. If,
+ # however, there is more than one alternative, then we are uncertain which
+ # alternative to predict and must continue looking for resolution. We may
+ # or may not discover an ambiguity in the future, even if there are no
+ # conflicting subsets this round.
+ #
+ # The biggest sin is to terminate early because it means we've made a
+ # decision but were uncertain as to the eventual outcome. We haven't used
+ # enough lookahead. On the other hand, announcing a conflict too late is no
+ # big deal; you will still have the conflict. It's just inefficient. It
+ # might even look until the end of file.
+ #
+ # No special consideration for semantic predicates is required because
+ # predicates are evaluated on-the-fly for full LL prediction, ensuring that
+ # no configuration contains a semantic context during the termination
+ # check.
+ #
+ # CONFLICTING CONFIGS
+ #
+ # Two configurations {@code (s, i, x)} and {@code (s, j, x')}, conflict
+ # when {@code i!=j} but {@code x=x'}. Because we merge all
+ # {@code (s, i, _)} configurations together, that means that there are at
+ # most {@code n} configurations associated with state {@code s} for
+ # {@code n} possible alternatives in the decision. The merged stacks
+ # complicate the comparison of configuration contexts {@code x} and
+ # {@code x'}. Sam checks to see if one is a subset of the other by calling
+ # merge and checking to see if the merged result is either {@code x} or
+ # {@code x'}. If the {@code x} associated with lowest alternative {@code i}
+ # is the superset, then {@code i} is the only possible prediction since the
+ # others resolve to {@code min(i)} as well. However, if {@code x} is
+ # associated with {@code j>i} then at least one stack configuration for
+ # {@code j} is not in conflict with alternative {@code i}. The algorithm
+ # should keep going, looking for more lookahead due to the uncertainty.
+ #
+ # For simplicity, I'm doing a equality check between {@code x} and
+ # {@code x'} that lets the algorithm continue to consume lookahead longer
+ # than necessary. The reason I like the equality is of course the
+ # simplicity but also because that is the test you need to detect the
+ # alternatives that are actually in conflict.
+ #
+ # CONTINUE/STOP RULE
+ #
+ # Continue if union of resolved alternative sets from non-conflicting and
+ # conflicting alternative subsets has more than one alternative. We are
+ # uncertain about which alternative to predict.
+ #
+ # The complete set of alternatives, {@code [i for (_,i,_)]}, tells us which
+ # alternatives are still in the running for the amount of input we've
+ # consumed at this point. The conflicting sets let us to strip away
+ # configurations that won't lead to more states because we resolve
+ # conflicts to the configuration with a minimum alternate for the
+ # conflicting set.
+ #
+ # CASES
+ #
+ #
+ #
+ # - no conflicts and more than 1 alternative in set => continue
+ #
+ # - {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s, 3, z)},
+ # {@code (s', 1, y)}, {@code (s', 2, y)} yields non-conflicting set
+ # {@code {3}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} =
+ # {@code {1,3}} => continue
+ #
+ #
+ # - {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)},
+ # {@code (s', 2, y)}, {@code (s'', 1, z)} yields non-conflicting set
+ # {@code {1}} U conflicting sets {@code min({1,2})} U {@code min({1,2})} =
+ # {@code {1}} => stop and predict 1
+ #
+ # - {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 1, y)},
+ # {@code (s', 2, y)} yields conflicting, reduced sets {@code {1}} U
+ # {@code {1}} = {@code {1}} => stop and predict 1, can announce
+ # ambiguity {@code {1,2}}
+ #
+ # - {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 2, y)},
+ # {@code (s', 3, y)} yields conflicting, reduced sets {@code {1}} U
+ # {@code {2}} = {@code {1,2}} => continue
+ #
+ # - {@code (s, 1, x)}, {@code (s, 2, x)}, {@code (s', 3, y)},
+ # {@code (s', 4, y)} yields conflicting, reduced sets {@code {1}} U
+ # {@code {3}} = {@code {1,3}} => continue
+ #
+ #
+ #
+ # EXACT AMBIGUITY DETECTION
+ #
+ # If all states report the same conflicting set of alternatives, then we
+ # know we have the exact ambiguity set.
+ #
+ # |A_i|>1 and
+ # A_i = A_j for all i, j.
+ #
+ # In other words, we continue examining lookahead until all {@code A_i}
+ # have more than one alternative and all {@code A_i} are the same. If
+ # {@code A={{1,2}, {1,3}}}, then regular LL prediction would terminate
+ # because the resolved set is {@code {1}}. To determine what the real
+ # ambiguity is, we have to know whether the ambiguity is between one and
+ # two or one and three so we keep going. We can only stop prediction when
+ # we need exact ambiguity detection when the sets look like
+ # {@code A={{1,2}}} or {@code {{1,2},{1,2}}}, etc...
+ #
+ @classmethod
+ def resolvesToJustOneViableAlt(cls, altsets:list):
+ return cls.getSingleViableAlt(altsets)
+
+ #
+ # Determines if every alternative subset in {@code altsets} contains more
+ # than one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if every {@link BitSet} in {@code altsets} has
+ # {@link BitSet#cardinality cardinality} > 1, otherwise {@code false}
+ #
+ @classmethod
+ def allSubsetsConflict(cls, altsets:list):
+ return not cls.hasNonConflictingAltSet(altsets)
+
+ #
+ # Determines if any single alternative subset in {@code altsets} contains
+ # exactly one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if {@code altsets} contains a {@link BitSet} with
+ # {@link BitSet#cardinality cardinality} 1, otherwise {@code false}
+ #
+ @classmethod
+ def hasNonConflictingAltSet(cls, altsets:list):
+ return any(len(alts) == 1 for alts in altsets)
+
+ #
+ # Determines if any single alternative subset in {@code altsets} contains
+ # more than one alternative.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if {@code altsets} contains a {@link BitSet} with
+ # {@link BitSet#cardinality cardinality} > 1, otherwise {@code false}
+ #
+ @classmethod
+ def hasConflictingAltSet(cls, altsets:list):
+ return any(len(alts) > 1 for alts in altsets)
+
+ #
+ # Determines if every alternative subset in {@code altsets} is equivalent.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return {@code true} if every member of {@code altsets} is equal to the
+ # others, otherwise {@code false}
+ #
+ @classmethod
+ def allSubsetsEqual(cls, altsets:list):
+ if not altsets:
+ return True
+ first = next(iter(altsets))
+ return all(alts == first for alts in iter(altsets))
+
+ #
+ # Returns the unique alternative predicted by all alternative subsets in
+ # {@code altsets}. If no such alternative exists, this method returns
+ # {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ # @param altsets a collection of alternative subsets
+ #
+ @classmethod
+ def getUniqueAlt(cls, altsets:list):
+ all = cls.getAlts(altsets)
+ if len(all)==1:
+ return next(iter(all))
+ return ATN.INVALID_ALT_NUMBER
+
+ # Gets the complete set of represented alternatives for a collection of
+ # alternative subsets. This method returns the union of each {@link BitSet}
+ # in {@code altsets}.
+ #
+ # @param altsets a collection of alternative subsets
+ # @return the set of represented alternatives in {@code altsets}
+ #
+ @classmethod
+ def getAlts(cls, altsets:list):
+ return set.union(*altsets)
+
+ #
+ # This function gets the conflicting alt subsets from a configuration set.
+ # For each configuration {@code c} in {@code configs}:
+ #
+ #
+ # map[c] U= c.{@link ATNConfig#alt alt} # map hash/equals uses s and x, not
+ # alt and not pred
+ #
+ #
+ @classmethod
+ def getConflictingAltSubsets(cls, configs:ATNConfigSet):
+ configToAlts = dict()
+ for c in configs:
+ h = hash((c.state.stateNumber, c.context))
+ alts = configToAlts.get(h, None)
+ if alts is None:
+ alts = set()
+ configToAlts[h] = alts
+ alts.add(c.alt)
+ return configToAlts.values()
+
+ #
+ # Get a map from state to alt subset from a configuration set. For each
+ # configuration {@code c} in {@code configs}:
+ #
+ #
+ # map[c.{@link ATNConfig#state state}] U= c.{@link ATNConfig#alt alt}
+ #
+ #
+ @classmethod
+ def getStateToAltMap(cls, configs:ATNConfigSet):
+ m = dict()
+ for c in configs:
+ alts = m.get(c.state, None)
+ if alts is None:
+ alts = set()
+ m[c.state] = alts
+ alts.add(c.alt)
+ return m
+
+ @classmethod
+ def hasStateAssociatedWithOneAlt(cls, configs:ATNConfigSet):
+ return any(len(alts) == 1 for alts in cls.getStateToAltMap(configs).values())
+
+ @classmethod
+ def getSingleViableAlt(cls, altsets:list):
+ viableAlts = set()
+ for alts in altsets:
+ minAlt = min(alts)
+ viableAlts.add(minAlt)
+ if len(viableAlts)>1 : # more than 1 viable alt
+ return ATN.INVALID_ALT_NUMBER
+ return min(viableAlts)
diff --git a/lib/python3.12/site-packages/antlr4/atn/SemanticContext.py b/lib/python3.12/site-packages/antlr4/atn/SemanticContext.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f4dc31088d35b73304432c46c54e31c1ab92700
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/SemanticContext.py
@@ -0,0 +1,323 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# A tree structure used to record the semantic context in which
+# an ATN configuration is valid. It's either a single predicate,
+# a conjunction {@code p1&&p2}, or a sum of products {@code p1||p2}.
+#
+# I have scoped the {@link AND}, {@link OR}, and {@link Predicate} subclasses of
+# {@link SemanticContext} within the scope of this outer class.
+#
+from antlr4.Recognizer import Recognizer
+from antlr4.RuleContext import RuleContext
+from io import StringIO
+
+
+class SemanticContext(object):
+ #
+ # The default {@link SemanticContext}, which is semantically equivalent to
+ # a predicate of the form {@code {true}?}.
+ #
+ NONE = None
+
+ #
+ # For context independent predicates, we evaluate them without a local
+ # context (i.e., null context). That way, we can evaluate them without
+ # having to create proper rule-specific context during prediction (as
+ # opposed to the parser, which creates them naturally). In a practical
+ # sense, this avoids a cast exception from RuleContext to myruleContext.
+ #
+ # For context dependent predicates, we must pass in a local context so that
+ # references such as $arg evaluate properly as _localctx.arg. We only
+ # capture context dependent predicates in the context in which we begin
+ # prediction, so we passed in the outer context here in case of context
+ # dependent predicate evaluation.
+ #
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ pass
+
+ #
+ # Evaluate the precedence predicates for the context and reduce the result.
+ #
+ # @param parser The parser instance.
+ # @param outerContext The current parser context object.
+ # @return The simplified semantic context after precedence predicates are
+ # evaluated, which will be one of the following values.
+ #
+ # - {@link #NONE}: if the predicate simplifies to {@code true} after
+ # precedence predicates are evaluated.
+ # - {@code null}: if the predicate simplifies to {@code false} after
+ # precedence predicates are evaluated.
+ # - {@code this}: if the semantic context is not changed as a result of
+ # precedence predicate evaluation.
+ # - A non-{@code null} {@link SemanticContext}: the new simplified
+ # semantic context after precedence predicates are evaluated.
+ #
+ #
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ return self
+
+# need forward declaration
+AND = None
+
+def andContext(a:SemanticContext, b:SemanticContext):
+ if a is None or a is SemanticContext.NONE:
+ return b
+ if b is None or b is SemanticContext.NONE:
+ return a
+ result = AND(a, b)
+ if len(result.opnds) == 1:
+ return result.opnds[0]
+ else:
+ return result
+
+# need forward declaration
+OR = None
+
+def orContext(a:SemanticContext, b:SemanticContext):
+ if a is None:
+ return b
+ if b is None:
+ return a
+ if a is SemanticContext.NONE or b is SemanticContext.NONE:
+ return SemanticContext.NONE
+ result = OR(a, b)
+ if len(result.opnds) == 1:
+ return result.opnds[0]
+ else:
+ return result
+
+def filterPrecedencePredicates(collection:set):
+ return [context for context in collection if isinstance(context, PrecedencePredicate)]
+
+
+class Predicate(SemanticContext):
+ __slots__ = ('ruleIndex', 'predIndex', 'isCtxDependent')
+
+ def __init__(self, ruleIndex:int=-1, predIndex:int=-1, isCtxDependent:bool=False):
+ self.ruleIndex = ruleIndex
+ self.predIndex = predIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ localctx = outerContext if self.isCtxDependent else None
+ return parser.sempred(localctx, self.ruleIndex, self.predIndex)
+
+ def __hash__(self):
+ return hash((self.ruleIndex, self.predIndex, self.isCtxDependent))
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, Predicate):
+ return False
+ return self.ruleIndex == other.ruleIndex and \
+ self.predIndex == other.predIndex and \
+ self.isCtxDependent == other.isCtxDependent
+
+ def __str__(self):
+ return "{" + str(self.ruleIndex) + ":" + str(self.predIndex) + "}?"
+
+
+class PrecedencePredicate(SemanticContext):
+
+ def __init__(self, precedence:int=0):
+ self.precedence = precedence
+
+ def eval(self, parser:Recognizer , outerContext:RuleContext ):
+ return parser.precpred(outerContext, self.precedence)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ if parser.precpred(outerContext, self.precedence):
+ return SemanticContext.NONE
+ else:
+ return None
+
+ def __lt__(self, other):
+ return self.precedence < other.precedence
+
+ def __hash__(self):
+ return 31
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, PrecedencePredicate):
+ return False
+ else:
+ return self.precedence == other.precedence
+
+# A semantic context which is true whenever none of the contained contexts
+# is false.
+del AND
+class AND(SemanticContext):
+ __slots__ = 'opnds'
+
+ def __init__(self, a:SemanticContext, b:SemanticContext):
+ operands = set()
+ if isinstance( a, AND ):
+ operands.update(a.opnds)
+ else:
+ operands.add(a)
+ if isinstance( b, AND ):
+ operands.update(b.opnds)
+ else:
+ operands.add(b)
+
+ precedencePredicates = filterPrecedencePredicates(operands)
+ if len(precedencePredicates)>0:
+ # interested in the transition with the lowest precedence
+ reduced = min(precedencePredicates)
+ operands.add(reduced)
+
+ self.opnds = list(operands)
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, AND):
+ return False
+ else:
+ return self.opnds == other.opnds
+
+ def __hash__(self):
+ h = 0
+ for o in self.opnds:
+ h = hash((h, o))
+ return hash((h, "AND"))
+
+ #
+ # {@inheritDoc}
+ #
+ #
+ # The evaluation of predicates by this context is short-circuiting, but
+ # unordered.
+ #
+ def eval(self, parser:Recognizer, outerContext:RuleContext):
+ return all(opnd.eval(parser, outerContext) for opnd in self.opnds)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ differs = False
+ operands = []
+ for context in self.opnds:
+ evaluated = context.evalPrecedence(parser, outerContext)
+ differs |= evaluated is not context
+ if evaluated is None:
+ # The AND context is false if any element is false
+ return None
+ elif evaluated is not SemanticContext.NONE:
+ # Reduce the result by skipping true elements
+ operands.append(evaluated)
+
+ if not differs:
+ return self
+
+ if len(operands)==0:
+ # all elements were true, so the AND context is true
+ return SemanticContext.NONE
+
+ result = None
+ for o in operands:
+ result = o if result is None else andContext(result, o)
+
+ return result
+
+ def __str__(self):
+ with StringIO() as buf:
+ first = True
+ for o in self.opnds:
+ if not first:
+ buf.write("&&")
+ buf.write(str(o))
+ first = False
+ return buf.getvalue()
+
+#
+# A semantic context which is true whenever at least one of the contained
+# contexts is true.
+del OR
+class OR (SemanticContext):
+ __slots__ = 'opnds'
+
+ def __init__(self, a:SemanticContext, b:SemanticContext):
+ operands = set()
+ if isinstance( a, OR ):
+ operands.update(a.opnds)
+ else:
+ operands.add(a)
+ if isinstance( b, OR ):
+ operands.update(b.opnds)
+ else:
+ operands.add(b)
+
+ precedencePredicates = filterPrecedencePredicates(operands)
+ if len(precedencePredicates)>0:
+ # interested in the transition with the highest precedence
+ s = sorted(precedencePredicates)
+ reduced = s[-1]
+ operands.add(reduced)
+
+ self.opnds = list(operands)
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif not isinstance(other, OR):
+ return False
+ else:
+ return self.opnds == other.opnds
+
+ def __hash__(self):
+ h = 0
+ for o in self.opnds:
+ h = hash((h, o))
+ return hash((h, "OR"))
+
+ #
+ # The evaluation of predicates by this context is short-circuiting, but
+ # unordered.
+ #
+ def eval(self, parser:Recognizer, outerContext:RuleContext):
+ return any(opnd.eval(parser, outerContext) for opnd in self.opnds)
+
+ def evalPrecedence(self, parser:Recognizer, outerContext:RuleContext):
+ differs = False
+ operands = []
+ for context in self.opnds:
+ evaluated = context.evalPrecedence(parser, outerContext)
+ differs |= evaluated is not context
+ if evaluated is SemanticContext.NONE:
+ # The OR context is true if any element is true
+ return SemanticContext.NONE
+ elif evaluated is not None:
+ # Reduce the result by skipping false elements
+ operands.append(evaluated)
+
+ if not differs:
+ return self
+
+ if len(operands)==0:
+ # all elements were false, so the OR context is false
+ return None
+
+ result = None
+ for o in operands:
+ result = o if result is None else orContext(result, o)
+
+ return result
+
+ def __str__(self):
+ with StringIO() as buf:
+ first = True
+ for o in self.opnds:
+ if not first:
+ buf.write("||")
+ buf.write(str(o))
+ first = False
+ return buf.getvalue()
+
+
+SemanticContext.NONE = Predicate()
diff --git a/lib/python3.12/site-packages/antlr4/atn/Transition.py b/lib/python3.12/site-packages/antlr4/atn/Transition.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e4c9971763c34dbb2690660434c5c99d44193e1
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/Transition.py
@@ -0,0 +1,268 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# An ATN transition between any two ATN states. Subclasses define
+# atom, set, epsilon, action, predicate, rule transitions.
+#
+# This is a one way link. It emanates from a state (usually via a list of
+# transitions) and has a target state.
+#
+# Since we never have to change the ATN transitions once we construct it,
+# we can fix these transitions as specific classes. The DFA transitions
+# on the other hand need to update the labels as it adds transitions to
+# the states. We'll use the term Edge for the DFA to distinguish them from
+# ATN transitions.
+#
+from antlr4.IntervalSet import IntervalSet
+from antlr4.Token import Token
+
+# need forward declarations
+from antlr4.atn.SemanticContext import Predicate, PrecedencePredicate
+
+ATNState = None
+RuleStartState = None
+
+class Transition (object):
+ __slots__ = ('target','isEpsilon','label')
+
+ # constants for serialization
+ EPSILON = 1
+ RANGE = 2
+ RULE = 3
+ PREDICATE = 4 # e.g., {isType(input.LT(1))}?
+ ATOM = 5
+ ACTION = 6
+ SET = 7 # ~(A|B) or ~atom, wildcard, which convert to next 2
+ NOT_SET = 8
+ WILDCARD = 9
+ PRECEDENCE = 10
+
+ serializationNames = [
+ "INVALID",
+ "EPSILON",
+ "RANGE",
+ "RULE",
+ "PREDICATE",
+ "ATOM",
+ "ACTION",
+ "SET",
+ "NOT_SET",
+ "WILDCARD",
+ "PRECEDENCE"
+ ]
+
+ serializationTypes = dict()
+
+ def __init__(self, target:ATNState):
+ # The target of this transition.
+ if target is None:
+ raise Exception("target cannot be null.")
+ self.target = target
+ # Are we epsilon, action, sempred?
+ self.isEpsilon = False
+ self.label = None
+
+
+# TODO: make all transitions sets? no, should remove set edges
+class AtomTransition(Transition):
+ __slots__ = ('label_', 'serializationType')
+
+ def __init__(self, target:ATNState, label:int):
+ super().__init__(target)
+ self.label_ = label # The token type or character value; or, signifies special label.
+ self.label = self.makeLabel()
+ self.serializationType = self.ATOM
+
+ def makeLabel(self):
+ s = IntervalSet()
+ s.addOne(self.label_)
+ return s
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return self.label_ == symbol
+
+ def __str__(self):
+ return str(self.label_)
+
+class RuleTransition(Transition):
+ __slots__ = ('ruleIndex', 'precedence', 'followState', 'serializationType')
+
+ def __init__(self, ruleStart:RuleStartState, ruleIndex:int, precedence:int, followState:ATNState):
+ super().__init__(ruleStart)
+ self.ruleIndex = ruleIndex # ptr to the rule definition object for this rule ref
+ self.precedence = precedence
+ self.followState = followState # what node to begin computations following ref to rule
+ self.serializationType = self.RULE
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+
+class EpsilonTransition(Transition):
+ __slots__ = ('serializationType', 'outermostPrecedenceReturn')
+
+ def __init__(self, target, outermostPrecedenceReturn=-1):
+ super(EpsilonTransition, self).__init__(target)
+ self.serializationType = self.EPSILON
+ self.isEpsilon = True
+ self.outermostPrecedenceReturn = outermostPrecedenceReturn
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def __str__(self):
+ return "epsilon"
+
+class RangeTransition(Transition):
+ __slots__ = ('serializationType', 'start', 'stop')
+
+ def __init__(self, target:ATNState, start:int, stop:int):
+ super().__init__(target)
+ self.serializationType = self.RANGE
+ self.start = start
+ self.stop = stop
+ self.label = self.makeLabel()
+
+ def makeLabel(self):
+ s = IntervalSet()
+ s.addRange(range(self.start, self.stop + 1))
+ return s
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= self.start and symbol <= self.stop
+
+ def __str__(self):
+ return "'" + chr(self.start) + "'..'" + chr(self.stop) + "'"
+
+class AbstractPredicateTransition(Transition):
+
+ def __init__(self, target:ATNState):
+ super().__init__(target)
+
+
+class PredicateTransition(AbstractPredicateTransition):
+ __slots__ = ('serializationType', 'ruleIndex', 'predIndex', 'isCtxDependent')
+
+ def __init__(self, target:ATNState, ruleIndex:int, predIndex:int, isCtxDependent:bool):
+ super().__init__(target)
+ self.serializationType = self.PREDICATE
+ self.ruleIndex = ruleIndex
+ self.predIndex = predIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def getPredicate(self):
+ return Predicate(self.ruleIndex, self.predIndex, self.isCtxDependent)
+
+ def __str__(self):
+ return "pred_" + str(self.ruleIndex) + ":" + str(self.predIndex)
+
+class ActionTransition(Transition):
+ __slots__ = ('serializationType', 'ruleIndex', 'actionIndex', 'isCtxDependent')
+
+ def __init__(self, target:ATNState, ruleIndex:int, actionIndex:int=-1, isCtxDependent:bool=False):
+ super().__init__(target)
+ self.serializationType = self.ACTION
+ self.ruleIndex = ruleIndex
+ self.actionIndex = actionIndex
+ self.isCtxDependent = isCtxDependent # e.g., $i ref in pred
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+ def __str__(self):
+ return "action_"+self.ruleIndex+":"+self.actionIndex
+
+# A transition containing a set of values.
+class SetTransition(Transition):
+ __slots__ = 'serializationType'
+
+ def __init__(self, target:ATNState, set:IntervalSet):
+ super().__init__(target)
+ self.serializationType = self.SET
+ if set is not None:
+ self.label = set
+ else:
+ self.label = IntervalSet()
+ self.label.addRange(range(Token.INVALID_TYPE, Token.INVALID_TYPE + 1))
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol in self.label
+
+ def __str__(self):
+ return str(self.label)
+
+class NotSetTransition(SetTransition):
+
+ def __init__(self, target:ATNState, set:IntervalSet):
+ super().__init__(target, set)
+ self.serializationType = self.NOT_SET
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= minVocabSymbol \
+ and symbol <= maxVocabSymbol \
+ and not super(type(self), self).matches(symbol, minVocabSymbol, maxVocabSymbol)
+
+ def __str__(self):
+ return '~' + super(type(self), self).__str__()
+
+
+class WildcardTransition(Transition):
+ __slots__ = 'serializationType'
+
+ def __init__(self, target:ATNState):
+ super().__init__(target)
+ self.serializationType = self.WILDCARD
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return symbol >= minVocabSymbol and symbol <= maxVocabSymbol
+
+ def __str__(self):
+ return "."
+
+
+class PrecedencePredicateTransition(AbstractPredicateTransition):
+ __slots__ = ('serializationType', 'precedence')
+
+ def __init__(self, target:ATNState, precedence:int):
+ super().__init__(target)
+ self.serializationType = self.PRECEDENCE
+ self.precedence = precedence
+ self.isEpsilon = True
+
+ def matches( self, symbol:int, minVocabSymbol:int, maxVocabSymbol:int):
+ return False
+
+
+ def getPredicate(self):
+ return PrecedencePredicate(self.precedence)
+
+ def __str__(self):
+ return self.precedence + " >= _p"
+
+
+Transition.serializationTypes = {
+ EpsilonTransition: Transition.EPSILON,
+ RangeTransition: Transition.RANGE,
+ RuleTransition: Transition.RULE,
+ PredicateTransition: Transition.PREDICATE,
+ AtomTransition: Transition.ATOM,
+ ActionTransition: Transition.ACTION,
+ SetTransition: Transition.SET,
+ NotSetTransition: Transition.NOT_SET,
+ WildcardTransition: Transition.WILDCARD,
+ PrecedencePredicateTransition: Transition.PRECEDENCE
+ }
+
+del ATNState
+del RuleStartState
+
+from antlr4.atn.ATNState import *
diff --git a/lib/python3.12/site-packages/antlr4/atn/__init__.py b/lib/python3.12/site-packages/antlr4/atn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/atn/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATN.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATN.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7f5d9714f586c931a4611f8dd7a4de77eb71be94
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATN.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfig.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfig.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a92441f21b7273063b7ecb72cb4d47ed3227d0b5
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfig.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfigSet.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfigSet.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2eddcae68b9873be51600add6f61a3172eadd200
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNConfigSet.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializationOptions.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializationOptions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6edf61d9e5bd69807985fac05c9c572a469d2eb3
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializationOptions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializer.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7a5e9864b590ccaf9c65f5b780c6baa9ffbed21e
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNDeserializer.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNSimulator.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNSimulator.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e8b5618732365b3a0dd5c9e481853df0a1b3dffb
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNSimulator.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNState.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNState.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..61629345fc9a493a2fca97019a9f4b92d447a084
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNState.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNType.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNType.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..10b09d04d37f941456190329dfdc37dd42181ed7
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ATNType.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerATNSimulator.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerATNSimulator.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cc0f13b21d11dc524e305b8a850f7a560e3c0738
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerATNSimulator.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerAction.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerAction.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3559468606efe47f1694f91c8ea178ffeb326c39
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerAction.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerActionExecutor.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerActionExecutor.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8344bab8905ae8b1a871bca52d3401aa4bc93fe6
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/LexerActionExecutor.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/ParserATNSimulator.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ParserATNSimulator.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aac985361cf02c30dd109ba5fa3ed76075520cb3
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/ParserATNSimulator.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/PredictionMode.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/PredictionMode.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..25620f524550e0e7eed66b4385e7faaa44dde8df
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/PredictionMode.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/SemanticContext.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/SemanticContext.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2dd3aea95f2b85f94cf6a4dbb5397cd6c0e14f27
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/SemanticContext.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/Transition.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/Transition.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1cca3cfb115e5d9c6ec6f345797d1db10f893518
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/Transition.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/atn/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/atn/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6a38c064001ec8c6af4682ee27c5948184f2e536
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/atn/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/dfa/DFA.py b/lib/python3.12/site-packages/antlr4/dfa/DFA.py
new file mode 100644
index 0000000000000000000000000000000000000000..d80589a6834a48cc6bb718bf33f57156e32ec934
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/dfa/DFA.py
@@ -0,0 +1,133 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+from antlr4.atn.ATNState import StarLoopEntryState
+
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.ATNState import DecisionState
+from antlr4.dfa.DFAState import DFAState
+from antlr4.error.Errors import IllegalStateException
+
+
+class DFA(object):
+ __slots__ = ('atnStartState', 'decision', '_states', 's0', 'precedenceDfa')
+
+ def __init__(self, atnStartState:DecisionState, decision:int=0):
+ # From which ATN state did we create this DFA?
+ self.atnStartState = atnStartState
+ self.decision = decision
+ # A set of all DFA states. Use {@link Map} so we can get old state back
+ # ({@link Set} only allows you to see if it's there).
+ self._states = dict()
+ self.s0 = None
+ # {@code true} if this DFA is for a precedence decision; otherwise,
+ # {@code false}. This is the backing field for {@link #isPrecedenceDfa},
+ # {@link #setPrecedenceDfa}.
+ self.precedenceDfa = False
+
+ if isinstance(atnStartState, StarLoopEntryState):
+ if atnStartState.isPrecedenceDecision:
+ self.precedenceDfa = True
+ precedenceState = DFAState(configs=ATNConfigSet())
+ precedenceState.edges = []
+ precedenceState.isAcceptState = False
+ precedenceState.requiresFullContext = False
+ self.s0 = precedenceState
+
+
+ # Get the start state for a specific precedence value.
+ #
+ # @param precedence The current precedence.
+ # @return The start state corresponding to the specified precedence, or
+ # {@code null} if no start state exists for the specified precedence.
+ #
+ # @throws IllegalStateException if this is not a precedence DFA.
+ # @see #isPrecedenceDfa()
+
+ def getPrecedenceStartState(self, precedence:int):
+ if not self.precedenceDfa:
+ raise IllegalStateException("Only precedence DFAs may contain a precedence start state.")
+
+ # s0.edges is never null for a precedence DFA
+ if precedence < 0 or precedence >= len(self.s0.edges):
+ return None
+ return self.s0.edges[precedence]
+
+ # Set the start state for a specific precedence value.
+ #
+ # @param precedence The current precedence.
+ # @param startState The start state corresponding to the specified
+ # precedence.
+ #
+ # @throws IllegalStateException if this is not a precedence DFA.
+ # @see #isPrecedenceDfa()
+ #
+ def setPrecedenceStartState(self, precedence:int, startState:DFAState):
+ if not self.precedenceDfa:
+ raise IllegalStateException("Only precedence DFAs may contain a precedence start state.")
+
+ if precedence < 0:
+ return
+
+ # synchronization on s0 here is ok. when the DFA is turned into a
+ # precedence DFA, s0 will be initialized once and not updated again
+ # s0.edges is never null for a precedence DFA
+ if precedence >= len(self.s0.edges):
+ ext = [None] * (precedence + 1 - len(self.s0.edges))
+ self.s0.edges.extend(ext)
+ self.s0.edges[precedence] = startState
+ #
+ # Sets whether this is a precedence DFA. If the specified value differs
+ # from the current DFA configuration, the following actions are taken;
+ # otherwise no changes are made to the current DFA.
+ #
+ #
+ # - The {@link #states} map is cleared
+ # - If {@code precedenceDfa} is {@code false}, the initial state
+ # {@link #s0} is set to {@code null}; otherwise, it is initialized to a new
+ # {@link DFAState} with an empty outgoing {@link DFAState#edges} array to
+ # store the start states for individual precedence values.
+ # - The {@link #precedenceDfa} field is updated
+ #
+ #
+ # @param precedenceDfa {@code true} if this is a precedence DFA; otherwise,
+ # {@code false}
+
+ def setPrecedenceDfa(self, precedenceDfa:bool):
+ if self.precedenceDfa != precedenceDfa:
+ self._states = dict()
+ if precedenceDfa:
+ precedenceState = DFAState(configs=ATNConfigSet())
+ precedenceState.edges = []
+ precedenceState.isAcceptState = False
+ precedenceState.requiresFullContext = False
+ self.s0 = precedenceState
+ else:
+ self.s0 = None
+ self.precedenceDfa = precedenceDfa
+
+ @property
+ def states(self):
+ return self._states
+
+ # Return a list of all states in this DFA, ordered by state number.
+ def sortedStates(self):
+ return sorted(self._states.keys(), key=lambda state: state.stateNumber)
+
+ def __str__(self):
+ return self.toString(None)
+
+ def toString(self, literalNames:list=None, symbolicNames:list=None):
+ if self.s0 is None:
+ return ""
+ from antlr4.dfa.DFASerializer import DFASerializer
+ serializer = DFASerializer(self,literalNames,symbolicNames)
+ return str(serializer)
+
+ def toLexerString(self):
+ if self.s0 is None:
+ return ""
+ from antlr4.dfa.DFASerializer import LexerDFASerializer
+ serializer = LexerDFASerializer(self)
+ return str(serializer)
diff --git a/lib/python3.12/site-packages/antlr4/dfa/DFASerializer.py b/lib/python3.12/site-packages/antlr4/dfa/DFASerializer.py
new file mode 100644
index 0000000000000000000000000000000000000000..bca0727b76dc54909be0bf60b6d636ec8f539927
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/dfa/DFASerializer.py
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# A DFA walker that knows how to dump them to serialized strings.#/
+from io import StringIO
+from antlr4 import DFA
+from antlr4.Utils import str_list
+from antlr4.dfa.DFAState import DFAState
+
+
+class DFASerializer(object):
+ __slots__ = ('dfa', 'literalNames', 'symbolicNames')
+
+ def __init__(self, dfa:DFA, literalNames:list=None, symbolicNames:list=None):
+ self.dfa = dfa
+ self.literalNames = literalNames
+ self.symbolicNames = symbolicNames
+
+ def __str__(self):
+ if self.dfa.s0 is None:
+ return None
+ with StringIO() as buf:
+ for s in self.dfa.sortedStates():
+ n = 0
+ if s.edges is not None:
+ n = len(s.edges)
+ for i in range(0, n):
+ t = s.edges[i]
+ if t is not None and t.stateNumber != 0x7FFFFFFF:
+ buf.write(self.getStateString(s))
+ label = self.getEdgeLabel(i)
+ buf.write("-")
+ buf.write(label)
+ buf.write("->")
+ buf.write(self.getStateString(t))
+ buf.write('\n')
+ output = buf.getvalue()
+ if len(output)==0:
+ return None
+ else:
+ return output
+
+ def getEdgeLabel(self, i:int):
+ if i==0:
+ return "EOF"
+ if self.literalNames is not None and i<=len(self.literalNames):
+ return self.literalNames[i-1]
+ elif self.symbolicNames is not None and i<=len(self.symbolicNames):
+ return self.symbolicNames[i-1]
+ else:
+ return str(i-1)
+
+ def getStateString(self, s:DFAState):
+ n = s.stateNumber
+ baseStateStr = ( ":" if s.isAcceptState else "") + "s" + str(n) + ( "^" if s.requiresFullContext else "")
+ if s.isAcceptState:
+ if s.predicates is not None:
+ return baseStateStr + "=>" + str_list(s.predicates)
+ else:
+ return baseStateStr + "=>" + str(s.prediction)
+ else:
+ return baseStateStr
+
+class LexerDFASerializer(DFASerializer):
+
+ def __init__(self, dfa:DFA):
+ super().__init__(dfa, None)
+
+ def getEdgeLabel(self, i:int):
+ return "'" + chr(i) + "'"
diff --git a/lib/python3.12/site-packages/antlr4/dfa/DFAState.py b/lib/python3.12/site-packages/antlr4/dfa/DFAState.py
new file mode 100644
index 0000000000000000000000000000000000000000..51955a448886ea1fa34f0f4dff7fb0976edd1975
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/dfa/DFAState.py
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+# Map a predicate to a predicted alternative.#/
+from io import StringIO
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.atn.SemanticContext import SemanticContext
+
+
+class PredPrediction(object):
+ __slots__ = ('alt', 'pred')
+
+ def __init__(self, pred:SemanticContext, alt:int):
+ self.alt = alt
+ self.pred = pred
+
+ def __str__(self):
+ return "(" + str(self.pred) + ", " + str(self.alt) + ")"
+
+# A DFA state represents a set of possible ATN configurations.
+# As Aho, Sethi, Ullman p. 117 says "The DFA uses its state
+# to keep track of all possible states the ATN can be in after
+# reading each input symbol. That is to say, after reading
+# input a1a2..an, the DFA is in a state that represents the
+# subset T of the states of the ATN that are reachable from the
+# ATN's start state along some path labeled a1a2..an."
+# In conventional NFA→DFA conversion, therefore, the subset T
+# would be a bitset representing the set of states the
+# ATN could be in. We need to track the alt predicted by each
+# state as well, however. More importantly, we need to maintain
+# a stack of states, tracking the closure operations as they
+# jump from rule to rule, emulating rule invocations (method calls).
+# I have to add a stack to simulate the proper lookahead sequences for
+# the underlying LL grammar from which the ATN was derived.
+#
+# I use a set of ATNConfig objects not simple states. An ATNConfig
+# is both a state (ala normal conversion) and a RuleContext describing
+# the chain of rules (if any) followed to arrive at that state.
+#
+# A DFA state may have multiple references to a particular state,
+# but with different ATN contexts (with same or different alts)
+# meaning that state was reached via a different set of rule invocations.
+#/
+class DFAState(object):
+ __slots__ = (
+ 'stateNumber', 'configs', 'edges', 'isAcceptState', 'prediction',
+ 'lexerActionExecutor', 'requiresFullContext', 'predicates'
+ )
+
+ def __init__(self, stateNumber:int=-1, configs:ATNConfigSet=ATNConfigSet()):
+ self.stateNumber = stateNumber
+ self.configs = configs
+ # {@code edges[symbol]} points to target of symbol. Shift up by 1 so (-1)
+ # {@link Token#EOF} maps to {@code edges[0]}.
+ self.edges = None
+ self.isAcceptState = False
+ # if accept state, what ttype do we match or alt do we predict?
+ # This is set to {@link ATN#INVALID_ALT_NUMBER} when {@link #predicates}{@code !=null} or
+ # {@link #requiresFullContext}.
+ self.prediction = 0
+ self.lexerActionExecutor = None
+ # Indicates that this state was created during SLL prediction that
+ # discovered a conflict between the configurations in the state. Future
+ # {@link ParserATNSimulator#execATN} invocations immediately jumped doing
+ # full context prediction if this field is true.
+ self.requiresFullContext = False
+ # During SLL parsing, this is a list of predicates associated with the
+ # ATN configurations of the DFA state. When we have predicates,
+ # {@link #requiresFullContext} is {@code false} since full context prediction evaluates predicates
+ # on-the-fly. If this is not null, then {@link #prediction} is
+ # {@link ATN#INVALID_ALT_NUMBER}.
+ #
+ # We only use these for non-{@link #requiresFullContext} but conflicting states. That
+ # means we know from the context (it's $ or we don't dip into outer
+ # context) that it's an ambiguity not a conflict.
+ #
+ # This list is computed by {@link ParserATNSimulator#predicateDFAState}.
+ self.predicates = None
+
+
+
+ # Get the set of all alts mentioned by all ATN configurations in this
+ # DFA state.
+ def getAltSet(self):
+ if self.configs is not None:
+ return set(cfg.alt for cfg in self.configs) or None
+ return None
+
+ def __hash__(self):
+ return hash(self.configs)
+
+ # Two {@link DFAState} instances are equal if their ATN configuration sets
+ # are the same. This method is used to see if a state already exists.
+ #
+ # Because the number of alternatives and number of ATN configurations are
+ # finite, there is a finite number of DFA states that can be processed.
+ # This is necessary to show that the algorithm terminates.
+ #
+ # Cannot test the DFA state numbers here because in
+ # {@link ParserATNSimulator#addDFAState} we need to know if any other state
+ # exists that has this exact set of ATN configurations. The
+ # {@link #stateNumber} is irrelevant.
+ def __eq__(self, other):
+ # compare set of ATN configurations in this set with other
+ if self is other:
+ return True
+ elif not isinstance(other, DFAState):
+ return False
+ else:
+ return self.configs==other.configs
+
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write(str(self.stateNumber))
+ buf.write(":")
+ buf.write(str(self.configs))
+ if self.isAcceptState:
+ buf.write("=>")
+ if self.predicates is not None:
+ buf.write(str(self.predicates))
+ else:
+ buf.write(str(self.prediction))
+ return buf.getvalue()
diff --git a/lib/python3.12/site-packages/antlr4/dfa/__init__.py b/lib/python3.12/site-packages/antlr4/dfa/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/dfa/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFA.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFA.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d2ccd457f113d7031659896020019872650a622d
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFA.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFASerializer.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFASerializer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72ddca5d0372ee6658071559830a2d22811f5353
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFASerializer.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFAState.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFAState.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c9b3d7a6ae90e2b351f8b1f36bf8ae6e380f163e
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/DFAState.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/dfa/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6ccb1f73a887de77958aa0feeca27c90be7a4f43
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/dfa/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/error/DiagnosticErrorListener.py b/lib/python3.12/site-packages/antlr4/error/DiagnosticErrorListener.py
new file mode 100644
index 0000000000000000000000000000000000000000..32ac14b63579ce7c984c2e34f2b1c80bebe328ed
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/error/DiagnosticErrorListener.py
@@ -0,0 +1,107 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+#
+# This implementation of {@link ANTLRErrorListener} can be used to identify
+# certain potential correctness and performance problems in grammars. "Reports"
+# are made by calling {@link Parser#notifyErrorListeners} with the appropriate
+# message.
+#
+#
+# - Ambiguities: These are cases where more than one path through the
+# grammar can match the input.
+# - Weak context sensitivity: These are cases where full-context
+# prediction resolved an SLL conflict to a unique alternative which equaled the
+# minimum alternative of the SLL conflict.
+# - Strong (forced) context sensitivity: These are cases where the
+# full-context prediction resolved an SLL conflict to a unique alternative,
+# and the minimum alternative of the SLL conflict was found to not be
+# a truly viable alternative. Two-stage parsing cannot be used for inputs where
+# this situation occurs.
+#
+
+from io import StringIO
+from antlr4 import Parser, DFA
+from antlr4.atn.ATNConfigSet import ATNConfigSet
+from antlr4.error.ErrorListener import ErrorListener
+
+class DiagnosticErrorListener(ErrorListener):
+
+ def __init__(self, exactOnly:bool=True):
+ # whether all ambiguities or only exact ambiguities are reported.
+ self.exactOnly = exactOnly
+
+ def reportAmbiguity(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, exact:bool, ambigAlts:set, configs:ATNConfigSet):
+ if self.exactOnly and not exact:
+ return
+
+ with StringIO() as buf:
+ buf.write("reportAmbiguity d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(": ambigAlts=")
+ buf.write(str(self.getConflictingAlts(ambigAlts, configs)))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+
+ def reportAttemptingFullContext(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, conflictingAlts:set, configs:ATNConfigSet):
+ with StringIO() as buf:
+ buf.write("reportAttemptingFullContext d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+ def reportContextSensitivity(self, recognizer:Parser, dfa:DFA, startIndex:int,
+ stopIndex:int, prediction:int, configs:ATNConfigSet):
+ with StringIO() as buf:
+ buf.write("reportContextSensitivity d=")
+ buf.write(self.getDecisionDescription(recognizer, dfa))
+ buf.write(", input='")
+ buf.write(recognizer.getTokenStream().getText(startIndex, stopIndex))
+ buf.write("'")
+ recognizer.notifyErrorListeners(buf.getvalue())
+
+ def getDecisionDescription(self, recognizer:Parser, dfa:DFA):
+ decision = dfa.decision
+ ruleIndex = dfa.atnStartState.ruleIndex
+
+ ruleNames = recognizer.ruleNames
+ if ruleIndex < 0 or ruleIndex >= len(ruleNames):
+ return str(decision)
+
+ ruleName = ruleNames[ruleIndex]
+ if ruleName is None or len(ruleName)==0:
+ return str(decision)
+
+ return str(decision) + " (" + ruleName + ")"
+
+ #
+ # Computes the set of conflicting or ambiguous alternatives from a
+ # configuration set, if that information was not already provided by the
+ # parser.
+ #
+ # @param reportedAlts The set of conflicting or ambiguous alternatives, as
+ # reported by the parser.
+ # @param configs The conflicting or ambiguous configuration set.
+ # @return Returns {@code reportedAlts} if it is not {@code null}, otherwise
+ # returns the set of alternatives represented in {@code configs}.
+ #
+ def getConflictingAlts(self, reportedAlts:set, configs:ATNConfigSet):
+ if reportedAlts is not None:
+ return reportedAlts
+
+ result = set()
+ for config in configs:
+ result.add(config.alt)
+
+ return result
diff --git a/lib/python3.12/site-packages/antlr4/error/ErrorListener.py b/lib/python3.12/site-packages/antlr4/error/ErrorListener.py
new file mode 100644
index 0000000000000000000000000000000000000000..933264d431b9829f43a38d5f0f07c83bbad703a0
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/error/ErrorListener.py
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+
+# Provides an empty default implementation of {@link ANTLRErrorListener}. The
+# default implementation of each method does nothing, but can be overridden as
+# necessary.
+
+
+import sys
+
+class ErrorListener(object):
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ pass
+
+ def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs):
+ pass
+
+ def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs):
+ pass
+
+ def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs):
+ pass
+
+class ConsoleErrorListener(ErrorListener):
+ #
+ # Provides a default instance of {@link ConsoleErrorListener}.
+ #
+ INSTANCE = None
+
+ #
+ # {@inheritDoc}
+ #
+ #
+ # This implementation prints messages to {@link System#err} containing the
+ # values of {@code line}, {@code charPositionInLine}, and {@code msg} using
+ # the following format.
+ #
+ #
+ # line line:charPositionInLine msg
+ #
+ #
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ print("line " + str(line) + ":" + str(column) + " " + msg, file=sys.stderr)
+
+ConsoleErrorListener.INSTANCE = ConsoleErrorListener()
+
+class ProxyErrorListener(ErrorListener):
+
+ def __init__(self, delegates):
+ super().__init__()
+ if delegates is None:
+ raise ReferenceError("delegates")
+ self.delegates = delegates
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
+ for delegate in self.delegates:
+ delegate.syntaxError(recognizer, offendingSymbol, line, column, msg, e)
+
+ def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs):
+ for delegate in self.delegates:
+ delegate.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
+
+ def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs):
+ for delegate in self.delegates:
+ delegate.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs)
+
+ def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs):
+ for delegate in self.delegates:
+ delegate.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs)
diff --git a/lib/python3.12/site-packages/antlr4/error/ErrorStrategy.py b/lib/python3.12/site-packages/antlr4/error/ErrorStrategy.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f7caadb240445e6d997ad582a51836f95cab5c5
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/error/ErrorStrategy.py
@@ -0,0 +1,709 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+import sys
+from antlr4.IntervalSet import IntervalSet
+
+from antlr4.Token import Token
+from antlr4.atn.ATNState import ATNState
+from antlr4.error.Errors import RecognitionException, NoViableAltException, InputMismatchException, \
+ FailedPredicateException, ParseCancellationException
+
+# need forward declaration
+Parser = None
+
+class ErrorStrategy(object):
+
+ def reset(self, recognizer:Parser):
+ pass
+
+ def recoverInline(self, recognizer:Parser):
+ pass
+
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ pass
+
+ def sync(self, recognizer:Parser):
+ pass
+
+ def inErrorRecoveryMode(self, recognizer:Parser):
+ pass
+
+ def reportError(self, recognizer:Parser, e:RecognitionException):
+ pass
+
+
+# This is the default implementation of {@link ANTLRErrorStrategy} used for
+# error reporting and recovery in ANTLR parsers.
+#
+class DefaultErrorStrategy(ErrorStrategy):
+
+ def __init__(self):
+ super().__init__()
+ # Indicates whether the error strategy is currently "recovering from an
+ # error". This is used to suppress reporting multiple error messages while
+ # attempting to recover from a detected syntax error.
+ #
+ # @see #inErrorRecoveryMode
+ #
+ self.errorRecoveryMode = False
+
+ # The index into the input stream where the last error occurred.
+ # This is used to prevent infinite loops where an error is found
+ # but no token is consumed during recovery...another error is found,
+ # ad nauseum. This is a failsafe mechanism to guarantee that at least
+ # one token/tree node is consumed for two errors.
+ #
+ self.lastErrorIndex = -1
+ self.lastErrorStates = None
+ self.nextTokensContext = None
+ self.nextTokenState = 0
+
+ # The default implementation simply calls {@link #endErrorCondition} to
+ # ensure that the handler is not in error recovery mode.
+ def reset(self, recognizer:Parser):
+ self.endErrorCondition(recognizer)
+
+ #
+ # This method is called to enter error recovery mode when a recognition
+ # exception is reported.
+ #
+ # @param recognizer the parser instance
+ #
+ def beginErrorCondition(self, recognizer:Parser):
+ self.errorRecoveryMode = True
+
+ def inErrorRecoveryMode(self, recognizer:Parser):
+ return self.errorRecoveryMode
+
+ #
+ # This method is called to leave error recovery mode after recovering from
+ # a recognition exception.
+ #
+ # @param recognizer
+ #
+ def endErrorCondition(self, recognizer:Parser):
+ self.errorRecoveryMode = False
+ self.lastErrorStates = None
+ self.lastErrorIndex = -1
+
+ #
+ # {@inheritDoc}
+ #
+ # The default implementation simply calls {@link #endErrorCondition}.
+ #
+ def reportMatch(self, recognizer:Parser):
+ self.endErrorCondition(recognizer)
+
+ #
+ # {@inheritDoc}
+ #
+ # The default implementation returns immediately if the handler is already
+ # in error recovery mode. Otherwise, it calls {@link #beginErrorCondition}
+ # and dispatches the reporting task based on the runtime type of {@code e}
+ # according to the following table.
+ #
+ #
+ # - {@link NoViableAltException}: Dispatches the call to
+ # {@link #reportNoViableAlternative}
+ # - {@link InputMismatchException}: Dispatches the call to
+ # {@link #reportInputMismatch}
+ # - {@link FailedPredicateException}: Dispatches the call to
+ # {@link #reportFailedPredicate}
+ # - All other types: calls {@link Parser#notifyErrorListeners} to report
+ # the exception
+ #
+ #
+ def reportError(self, recognizer:Parser, e:RecognitionException):
+ # if we've already reported an error and have not matched a token
+ # yet successfully, don't report any errors.
+ if self.inErrorRecoveryMode(recognizer):
+ return # don't report spurious errors
+ self.beginErrorCondition(recognizer)
+ if isinstance( e, NoViableAltException ):
+ self.reportNoViableAlternative(recognizer, e)
+ elif isinstance( e, InputMismatchException ):
+ self.reportInputMismatch(recognizer, e)
+ elif isinstance( e, FailedPredicateException ):
+ self.reportFailedPredicate(recognizer, e)
+ else:
+ print("unknown recognition error type: " + type(e).__name__)
+ recognizer.notifyErrorListeners(e.message, e.offendingToken, e)
+
+ #
+ # {@inheritDoc}
+ #
+ # The default implementation resynchronizes the parser by consuming tokens
+ # until we find one in the resynchronization set--loosely the set of tokens
+ # that can follow the current rule.
+ #
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ if self.lastErrorIndex==recognizer.getInputStream().index \
+ and self.lastErrorStates is not None \
+ and recognizer.state in self.lastErrorStates:
+ # uh oh, another error at same token index and previously-visited
+ # state in ATN; must be a case where LT(1) is in the recovery
+ # token set so nothing got consumed. Consume a single token
+ # at least to prevent an infinite loop; this is a failsafe.
+ recognizer.consume()
+
+ self.lastErrorIndex = recognizer._input.index
+ if self.lastErrorStates is None:
+ self.lastErrorStates = []
+ self.lastErrorStates.append(recognizer.state)
+ followSet = self.getErrorRecoverySet(recognizer)
+ self.consumeUntil(recognizer, followSet)
+
+ # The default implementation of {@link ANTLRErrorStrategy#sync} makes sure
+ # that the current lookahead symbol is consistent with what were expecting
+ # at this point in the ATN. You can call this anytime but ANTLR only
+ # generates code to check before subrules/loops and each iteration.
+ #
+ # Implements Jim Idle's magic sync mechanism in closures and optional
+ # subrules. E.g.,
+ #
+ #
+ # a : sync ( stuff sync )* ;
+ # sync : {consume to what can follow sync} ;
+ #
+ #
+ # At the start of a sub rule upon error, {@link #sync} performs single
+ # token deletion, if possible. If it can't do that, it bails on the current
+ # rule and uses the default error recovery, which consumes until the
+ # resynchronization set of the current rule.
+ #
+ # If the sub rule is optional ({@code (...)?}, {@code (...)*}, or block
+ # with an empty alternative), then the expected set includes what follows
+ # the subrule.
+ #
+ # During loop iteration, it consumes until it sees a token that can start a
+ # sub rule or what follows loop. Yes, that is pretty aggressive. We opt to
+ # stay in the loop as long as possible.
+ #
+ # ORIGINS
+ #
+ # Previous versions of ANTLR did a poor job of their recovery within loops.
+ # A single mismatch token or missing token would force the parser to bail
+ # out of the entire rules surrounding the loop. So, for rule
+ #
+ #
+ # classDef : 'class' ID '{' member* '}'
+ #
+ #
+ # input with an extra token between members would force the parser to
+ # consume until it found the next class definition rather than the next
+ # member definition of the current class.
+ #
+ # This functionality cost a little bit of effort because the parser has to
+ # compare token set at the start of the loop and at each iteration. If for
+ # some reason speed is suffering for you, you can turn off this
+ # functionality by simply overriding this method as a blank { }.
+ #
+ def sync(self, recognizer:Parser):
+ # If already recovering, don't try to sync
+ if self.inErrorRecoveryMode(recognizer):
+ return
+
+ s = recognizer._interp.atn.states[recognizer.state]
+ la = recognizer.getTokenStream().LA(1)
+ # try cheaper subset first; might get lucky. seems to shave a wee bit off
+ nextTokens = recognizer.atn.nextTokens(s)
+ if la in nextTokens:
+ self.nextTokensContext = None
+ self.nextTokenState = ATNState.INVALID_STATE_NUMBER
+ return
+ elif Token.EPSILON in nextTokens:
+ if self.nextTokensContext is None:
+ # It's possible the next token won't match information tracked
+ # by sync is restricted for performance.
+ self.nextTokensContext = recognizer._ctx
+ self.nextTokensState = recognizer._stateNumber
+ return
+
+ if s.stateType in [ATNState.BLOCK_START, ATNState.STAR_BLOCK_START,
+ ATNState.PLUS_BLOCK_START, ATNState.STAR_LOOP_ENTRY]:
+ # report error and recover if possible
+ if self.singleTokenDeletion(recognizer)is not None:
+ return
+ else:
+ raise InputMismatchException(recognizer)
+
+ elif s.stateType in [ATNState.PLUS_LOOP_BACK, ATNState.STAR_LOOP_BACK]:
+ self.reportUnwantedToken(recognizer)
+ expecting = recognizer.getExpectedTokens()
+ whatFollowsLoopIterationOrRule = expecting.addSet(self.getErrorRecoverySet(recognizer))
+ self.consumeUntil(recognizer, whatFollowsLoopIterationOrRule)
+
+ else:
+ # do nothing if we can't identify the exact kind of ATN state
+ pass
+
+ # This is called by {@link #reportError} when the exception is a
+ # {@link NoViableAltException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportNoViableAlternative(self, recognizer:Parser, e:NoViableAltException):
+ tokens = recognizer.getTokenStream()
+ if tokens is not None:
+ if e.startToken.type==Token.EOF:
+ input = ""
+ else:
+ input = tokens.getText(e.startToken, e.offendingToken)
+ else:
+ input = ""
+ msg = "no viable alternative at input " + self.escapeWSAndQuote(input)
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ #
+ # This is called by {@link #reportError} when the exception is an
+ # {@link InputMismatchException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportInputMismatch(self, recognizer:Parser, e:InputMismatchException):
+ msg = "mismatched input " + self.getTokenErrorDisplay(e.offendingToken) \
+ + " expecting " + e.getExpectedTokens().toString(recognizer.literalNames, recognizer.symbolicNames)
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ #
+ # This is called by {@link #reportError} when the exception is a
+ # {@link FailedPredicateException}.
+ #
+ # @see #reportError
+ #
+ # @param recognizer the parser instance
+ # @param e the recognition exception
+ #
+ def reportFailedPredicate(self, recognizer, e):
+ ruleName = recognizer.ruleNames[recognizer._ctx.getRuleIndex()]
+ msg = "rule " + ruleName + " " + e.message
+ recognizer.notifyErrorListeners(msg, e.offendingToken, e)
+
+ # This method is called to report a syntax error which requires the removal
+ # of a token from the input stream. At the time this method is called, the
+ # erroneous symbol is current {@code LT(1)} symbol and has not yet been
+ # removed from the input stream. When this method returns,
+ # {@code recognizer} is in error recovery mode.
+ #
+ # This method is called when {@link #singleTokenDeletion} identifies
+ # single-token deletion as a viable recovery strategy for a mismatched
+ # input error.
+ #
+ # The default implementation simply returns if the handler is already in
+ # error recovery mode. Otherwise, it calls {@link #beginErrorCondition} to
+ # enter error recovery mode, followed by calling
+ # {@link Parser#notifyErrorListeners}.
+ #
+ # @param recognizer the parser instance
+ #
+ def reportUnwantedToken(self, recognizer:Parser):
+ if self.inErrorRecoveryMode(recognizer):
+ return
+
+ self.beginErrorCondition(recognizer)
+ t = recognizer.getCurrentToken()
+ tokenName = self.getTokenErrorDisplay(t)
+ expecting = self.getExpectedTokens(recognizer)
+ msg = "extraneous input " + tokenName + " expecting " \
+ + expecting.toString(recognizer.literalNames, recognizer.symbolicNames)
+ recognizer.notifyErrorListeners(msg, t, None)
+
+ # This method is called to report a syntax error which requires the
+ # insertion of a missing token into the input stream. At the time this
+ # method is called, the missing token has not yet been inserted. When this
+ # method returns, {@code recognizer} is in error recovery mode.
+ #
+ # This method is called when {@link #singleTokenInsertion} identifies
+ # single-token insertion as a viable recovery strategy for a mismatched
+ # input error.
+ #
+ # The default implementation simply returns if the handler is already in
+ # error recovery mode. Otherwise, it calls {@link #beginErrorCondition} to
+ # enter error recovery mode, followed by calling
+ # {@link Parser#notifyErrorListeners}.
+ #
+ # @param recognizer the parser instance
+ #
+ def reportMissingToken(self, recognizer:Parser):
+ if self.inErrorRecoveryMode(recognizer):
+ return
+ self.beginErrorCondition(recognizer)
+ t = recognizer.getCurrentToken()
+ expecting = self.getExpectedTokens(recognizer)
+ msg = "missing " + expecting.toString(recognizer.literalNames, recognizer.symbolicNames) \
+ + " at " + self.getTokenErrorDisplay(t)
+ recognizer.notifyErrorListeners(msg, t, None)
+
+ # The default implementation attempts to recover from the mismatched input
+ # by using single token insertion and deletion as described below. If the
+ # recovery attempt fails, this method throws an
+ # {@link InputMismatchException}.
+ #
+ # EXTRA TOKEN (single token deletion)
+ #
+ # {@code LA(1)} is not what we are looking for. If {@code LA(2)} has the
+ # right token, however, then assume {@code LA(1)} is some extra spurious
+ # token and delete it. Then consume and return the next token (which was
+ # the {@code LA(2)} token) as the successful result of the match operation.
+ #
+ # This recovery strategy is implemented by {@link #singleTokenDeletion}.
+ #
+ # MISSING TOKEN (single token insertion)
+ #
+ # If current token (at {@code LA(1)}) is consistent with what could come
+ # after the expected {@code LA(1)} token, then assume the token is missing
+ # and use the parser's {@link TokenFactory} to create it on the fly. The
+ # "insertion" is performed by returning the created token as the successful
+ # result of the match operation.
+ #
+ # This recovery strategy is implemented by {@link #singleTokenInsertion}.
+ #
+ # EXAMPLE
+ #
+ # For example, Input {@code i=(3;} is clearly missing the {@code ')'}. When
+ # the parser returns from the nested call to {@code expr}, it will have
+ # call chain:
+ #
+ #
+ # stat → expr → atom
+ #
+ #
+ # and it will be trying to match the {@code ')'} at this point in the
+ # derivation:
+ #
+ #
+ # => ID '=' '(' INT ')' ('+' atom)* ';'
+ # ^
+ #
+ #
+ # The attempt to match {@code ')'} will fail when it sees {@code ';'} and
+ # call {@link #recoverInline}. To recover, it sees that {@code LA(1)==';'}
+ # is in the set of tokens that can follow the {@code ')'} token reference
+ # in rule {@code atom}. It can assume that you forgot the {@code ')'}.
+ #
+ def recoverInline(self, recognizer:Parser):
+ # SINGLE TOKEN DELETION
+ matchedSymbol = self.singleTokenDeletion(recognizer)
+ if matchedSymbol is not None:
+ # we have deleted the extra token.
+ # now, move past ttype token as if all were ok
+ recognizer.consume()
+ return matchedSymbol
+
+ # SINGLE TOKEN INSERTION
+ if self.singleTokenInsertion(recognizer):
+ return self.getMissingSymbol(recognizer)
+
+ # even that didn't work; must throw the exception
+ raise InputMismatchException(recognizer)
+
+ #
+ # This method implements the single-token insertion inline error recovery
+ # strategy. It is called by {@link #recoverInline} if the single-token
+ # deletion strategy fails to recover from the mismatched input. If this
+ # method returns {@code true}, {@code recognizer} will be in error recovery
+ # mode.
+ #
+ # This method determines whether or not single-token insertion is viable by
+ # checking if the {@code LA(1)} input symbol could be successfully matched
+ # if it were instead the {@code LA(2)} symbol. If this method returns
+ # {@code true}, the caller is responsible for creating and inserting a
+ # token with the correct type to produce this behavior.
+ #
+ # @param recognizer the parser instance
+ # @return {@code true} if single-token insertion is a viable recovery
+ # strategy for the current mismatched input, otherwise {@code false}
+ #
+ def singleTokenInsertion(self, recognizer:Parser):
+ currentSymbolType = recognizer.getTokenStream().LA(1)
+ # if current token is consistent with what could come after current
+ # ATN state, then we know we're missing a token; error recovery
+ # is free to conjure up and insert the missing token
+ atn = recognizer._interp.atn
+ currentState = atn.states[recognizer.state]
+ next = currentState.transitions[0].target
+ expectingAtLL2 = atn.nextTokens(next, recognizer._ctx)
+ if currentSymbolType in expectingAtLL2:
+ self.reportMissingToken(recognizer)
+ return True
+ else:
+ return False
+
+ # This method implements the single-token deletion inline error recovery
+ # strategy. It is called by {@link #recoverInline} to attempt to recover
+ # from mismatched input. If this method returns null, the parser and error
+ # handler state will not have changed. If this method returns non-null,
+ # {@code recognizer} will not be in error recovery mode since the
+ # returned token was a successful match.
+ #
+ # If the single-token deletion is successful, this method calls
+ # {@link #reportUnwantedToken} to report the error, followed by
+ # {@link Parser#consume} to actually "delete" the extraneous token. Then,
+ # before returning {@link #reportMatch} is called to signal a successful
+ # match.
+ #
+ # @param recognizer the parser instance
+ # @return the successfully matched {@link Token} instance if single-token
+ # deletion successfully recovers from the mismatched input, otherwise
+ # {@code null}
+ #
+ def singleTokenDeletion(self, recognizer:Parser):
+ nextTokenType = recognizer.getTokenStream().LA(2)
+ expecting = self.getExpectedTokens(recognizer)
+ if nextTokenType in expecting:
+ self.reportUnwantedToken(recognizer)
+ # print("recoverFromMismatchedToken deleting " \
+ # + str(recognizer.getTokenStream().LT(1)) \
+ # + " since " + str(recognizer.getTokenStream().LT(2)) \
+ # + " is what we want", file=sys.stderr)
+ recognizer.consume() # simply delete extra token
+ # we want to return the token we're actually matching
+ matchedSymbol = recognizer.getCurrentToken()
+ self.reportMatch(recognizer) # we know current token is correct
+ return matchedSymbol
+ else:
+ return None
+
+ # Conjure up a missing token during error recovery.
+ #
+ # The recognizer attempts to recover from single missing
+ # symbols. But, actions might refer to that missing symbol.
+ # For example, x=ID {f($x);}. The action clearly assumes
+ # that there has been an identifier matched previously and that
+ # $x points at that token. If that token is missing, but
+ # the next token in the stream is what we want we assume that
+ # this token is missing and we keep going. Because we
+ # have to return some token to replace the missing token,
+ # we have to conjure one up. This method gives the user control
+ # over the tokens returned for missing tokens. Mostly,
+ # you will want to create something special for identifier
+ # tokens. For literals such as '{' and ',', the default
+ # action in the parser or tree parser works. It simply creates
+ # a CommonToken of the appropriate type. The text will be the token.
+ # If you change what tokens must be created by the lexer,
+ # override this method to create the appropriate tokens.
+ #
+ def getMissingSymbol(self, recognizer:Parser):
+ currentSymbol = recognizer.getCurrentToken()
+ expecting = self.getExpectedTokens(recognizer)
+ expectedTokenType = expecting[0] # get any element
+ if expectedTokenType==Token.EOF:
+ tokenText = ""
+ else:
+ name = None
+ if expectedTokenType < len(recognizer.literalNames):
+ name = recognizer.literalNames[expectedTokenType]
+ if name is None and expectedTokenType < len(recognizer.symbolicNames):
+ name = recognizer.symbolicNames[expectedTokenType]
+ tokenText = ""
+ current = currentSymbol
+ lookback = recognizer.getTokenStream().LT(-1)
+ if current.type==Token.EOF and lookback is not None:
+ current = lookback
+ return recognizer.getTokenFactory().create(current.source,
+ expectedTokenType, tokenText, Token.DEFAULT_CHANNEL,
+ -1, -1, current.line, current.column)
+
+ def getExpectedTokens(self, recognizer:Parser):
+ return recognizer.getExpectedTokens()
+
+ # How should a token be displayed in an error message? The default
+ # is to display just the text, but during development you might
+ # want to have a lot of information spit out. Override in that case
+ # to use t.toString() (which, for CommonToken, dumps everything about
+ # the token). This is better than forcing you to override a method in
+ # your token objects because you don't have to go modify your lexer
+ # so that it creates a new Java type.
+ #
+ def getTokenErrorDisplay(self, t:Token):
+ if t is None:
+ return ""
+ s = t.text
+ if s is None:
+ if t.type==Token.EOF:
+ s = ""
+ else:
+ s = "<" + str(t.type) + ">"
+ return self.escapeWSAndQuote(s)
+
+ def escapeWSAndQuote(self, s:str):
+ s = s.replace("\n","\\n")
+ s = s.replace("\r","\\r")
+ s = s.replace("\t","\\t")
+ return "'" + s + "'"
+
+ # Compute the error recovery set for the current rule. During
+ # rule invocation, the parser pushes the set of tokens that can
+ # follow that rule reference on the stack; this amounts to
+ # computing FIRST of what follows the rule reference in the
+ # enclosing rule. See LinearApproximator.FIRST().
+ # This local follow set only includes tokens
+ # from within the rule; i.e., the FIRST computation done by
+ # ANTLR stops at the end of a rule.
+ #
+ # EXAMPLE
+ #
+ # When you find a "no viable alt exception", the input is not
+ # consistent with any of the alternatives for rule r. The best
+ # thing to do is to consume tokens until you see something that
+ # can legally follow a call to r#or* any rule that called r.
+ # You don't want the exact set of viable next tokens because the
+ # input might just be missing a token--you might consume the
+ # rest of the input looking for one of the missing tokens.
+ #
+ # Consider grammar:
+ #
+ # a : '[' b ']'
+ # | '(' b ')'
+ # ;
+ # b : c '^' INT ;
+ # c : ID
+ # | INT
+ # ;
+ #
+ # At each rule invocation, the set of tokens that could follow
+ # that rule is pushed on a stack. Here are the various
+ # context-sensitive follow sets:
+ #
+ # FOLLOW(b1_in_a) = FIRST(']') = ']'
+ # FOLLOW(b2_in_a) = FIRST(')') = ')'
+ # FOLLOW(c_in_b) = FIRST('^') = '^'
+ #
+ # Upon erroneous input "[]", the call chain is
+ #
+ # a -> b -> c
+ #
+ # and, hence, the follow context stack is:
+ #
+ # depth follow set start of rule execution
+ # 0 a (from main())
+ # 1 ']' b
+ # 2 '^' c
+ #
+ # Notice that ')' is not included, because b would have to have
+ # been called from a different context in rule a for ')' to be
+ # included.
+ #
+ # For error recovery, we cannot consider FOLLOW(c)
+ # (context-sensitive or otherwise). We need the combined set of
+ # all context-sensitive FOLLOW sets--the set of all tokens that
+ # could follow any reference in the call chain. We need to
+ # resync to one of those tokens. Note that FOLLOW(c)='^' and if
+ # we resync'd to that token, we'd consume until EOF. We need to
+ # sync to context-sensitive FOLLOWs for a, b, and c: {']','^'}.
+ # In this case, for input "[]", LA(1) is ']' and in the set, so we would
+ # not consume anything. After printing an error, rule c would
+ # return normally. Rule b would not find the required '^' though.
+ # At this point, it gets a mismatched token error and throws an
+ # exception (since LA(1) is not in the viable following token
+ # set). The rule exception handler tries to recover, but finds
+ # the same recovery set and doesn't consume anything. Rule b
+ # exits normally returning to rule a. Now it finds the ']' (and
+ # with the successful match exits errorRecovery mode).
+ #
+ # So, you can see that the parser walks up the call chain looking
+ # for the token that was a member of the recovery set.
+ #
+ # Errors are not generated in errorRecovery mode.
+ #
+ # ANTLR's error recovery mechanism is based upon original ideas:
+ #
+ # "Algorithms + Data Structures = Programs" by Niklaus Wirth
+ #
+ # and
+ #
+ # "A note on error recovery in recursive descent parsers":
+ # http:#portal.acm.org/citation.cfm?id=947902.947905
+ #
+ # Later, Josef Grosch had some good ideas:
+ #
+ # "Efficient and Comfortable Error Recovery in Recursive Descent
+ # Parsers":
+ # ftp:#www.cocolab.com/products/cocktail/doca4.ps/ell.ps.zip
+ #
+ # Like Grosch I implement context-sensitive FOLLOW sets that are combined
+ # at run-time upon error to avoid overhead during parsing.
+ #
+ def getErrorRecoverySet(self, recognizer:Parser):
+ atn = recognizer._interp.atn
+ ctx = recognizer._ctx
+ recoverSet = IntervalSet()
+ while ctx is not None and ctx.invokingState>=0:
+ # compute what follows who invoked us
+ invokingState = atn.states[ctx.invokingState]
+ rt = invokingState.transitions[0]
+ follow = atn.nextTokens(rt.followState)
+ recoverSet.addSet(follow)
+ ctx = ctx.parentCtx
+ recoverSet.removeOne(Token.EPSILON)
+ return recoverSet
+
+ # Consume tokens until one matches the given token set.#
+ def consumeUntil(self, recognizer:Parser, set_:set):
+ ttype = recognizer.getTokenStream().LA(1)
+ while ttype != Token.EOF and not ttype in set_:
+ recognizer.consume()
+ ttype = recognizer.getTokenStream().LA(1)
+
+
+#
+# This implementation of {@link ANTLRErrorStrategy} responds to syntax errors
+# by immediately canceling the parse operation with a
+# {@link ParseCancellationException}. The implementation ensures that the
+# {@link ParserRuleContext#exception} field is set for all parse tree nodes
+# that were not completed prior to encountering the error.
+#
+#
+# This error strategy is useful in the following scenarios.
+#
+#
+# - Two-stage parsing: This error strategy allows the first
+# stage of two-stage parsing to immediately terminate if an error is
+# encountered, and immediately fall back to the second stage. In addition to
+# avoiding wasted work by attempting to recover from errors here, the empty
+# implementation of {@link BailErrorStrategy#sync} improves the performance of
+# the first stage.
+# - Silent validation: When syntax errors are not being
+# reported or logged, and the parse result is simply ignored if errors occur,
+# the {@link BailErrorStrategy} avoids wasting work on recovering from errors
+# when the result will be ignored either way.
+#
+#
+#
+# {@code myparser.setErrorHandler(new BailErrorStrategy());}
+#
+# @see Parser#setErrorHandler(ANTLRErrorStrategy)
+#
+class BailErrorStrategy(DefaultErrorStrategy):
+ # Instead of recovering from exception {@code e}, re-throw it wrapped
+ # in a {@link ParseCancellationException} so it is not caught by the
+ # rule function catches. Use {@link Exception#getCause()} to get the
+ # original {@link RecognitionException}.
+ #
+ def recover(self, recognizer:Parser, e:RecognitionException):
+ context = recognizer._ctx
+ while context is not None:
+ context.exception = e
+ context = context.parentCtx
+ raise ParseCancellationException(e)
+
+ # Make sure we don't attempt to recover inline; if the parser
+ # successfully recovers, it won't throw an exception.
+ #
+ def recoverInline(self, recognizer:Parser):
+ self.recover(recognizer, InputMismatchException(recognizer))
+
+ # Make sure we don't attempt to recover from problems in subrules.#
+ def sync(self, recognizer:Parser):
+ pass
+
+del Parser
\ No newline at end of file
diff --git a/lib/python3.12/site-packages/antlr4/error/Errors.py b/lib/python3.12/site-packages/antlr4/error/Errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..e78ac05911d3c9569441fe376ff7d6c686c05c95
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/error/Errors.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+# need forward declaration
+Token = None
+Lexer = None
+Parser = None
+TokenStream = None
+ATNConfigSet = None
+ParserRulecontext = None
+PredicateTransition = None
+BufferedTokenStream = None
+
+class UnsupportedOperationException(Exception):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+class IllegalStateException(Exception):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+class CancellationException(IllegalStateException):
+
+ def __init__(self, msg:str):
+ super().__init__(msg)
+
+# The root of the ANTLR exception hierarchy. In general, ANTLR tracks just
+# 3 kinds of errors: prediction errors, failed predicate errors, and
+# mismatched input errors. In each case, the parser knows where it is
+# in the input, where it is in the ATN, the rule invocation stack,
+# and what kind of problem occurred.
+
+from antlr4.InputStream import InputStream
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.Recognizer import Recognizer
+
+class RecognitionException(Exception):
+
+
+ def __init__(self, message:str=None, recognizer:Recognizer=None, input:InputStream=None, ctx:ParserRulecontext=None):
+ super().__init__(message)
+ self.message = message
+ self.recognizer = recognizer
+ self.input = input
+ self.ctx = ctx
+ # The current {@link Token} when an error occurred. Since not all streams
+ # support accessing symbols by index, we have to track the {@link Token}
+ # instance itself.
+ self.offendingToken = None
+ # Get the ATN state number the parser was in at the time the error
+ # occurred. For {@link NoViableAltException} and
+ # {@link LexerNoViableAltException} exceptions, this is the
+ # {@link DecisionState} number. For others, it is the state whose outgoing
+ # edge we couldn't match.
+ self.offendingState = -1
+ if recognizer is not None:
+ self.offendingState = recognizer.state
+
+ # If the state number is not known, this method returns -1.
+
+ #
+ # Gets the set of input symbols which could potentially follow the
+ # previously matched symbol at the time this exception was thrown.
+ #
+ # If the set of expected tokens is not known and could not be computed,
+ # this method returns {@code null}.
+ #
+ # @return The set of token types that could potentially follow the current
+ # state in the ATN, or {@code null} if the information is not available.
+ #/
+ def getExpectedTokens(self):
+ if self.recognizer is not None:
+ return self.recognizer.atn.getExpectedTokens(self.offendingState, self.ctx)
+ else:
+ return None
+
+
+class LexerNoViableAltException(RecognitionException):
+
+ def __init__(self, lexer:Lexer, input:InputStream, startIndex:int, deadEndConfigs:ATNConfigSet):
+ super().__init__(message=None, recognizer=lexer, input=input, ctx=None)
+ self.startIndex = startIndex
+ self.deadEndConfigs = deadEndConfigs
+
+ def __str__(self):
+ symbol = ""
+ if self.startIndex >= 0 and self.startIndex < self.input.size:
+ symbol = self.input.getText(self.startIndex, self.startIndex)
+ # TODO symbol = Utils.escapeWhitespace(symbol, false);
+ return "LexerNoViableAltException('" + symbol + "')"
+
+# Indicates that the parser could not decide which of two or more paths
+# to take based upon the remaining input. It tracks the starting token
+# of the offending input and also knows where the parser was
+# in the various paths when the error. Reported by reportNoViableAlternative()
+#
+class NoViableAltException(RecognitionException):
+
+ def __init__(self, recognizer:Parser, input:TokenStream=None, startToken:Token=None,
+ offendingToken:Token=None, deadEndConfigs:ATNConfigSet=None, ctx:ParserRuleContext=None):
+ if ctx is None:
+ ctx = recognizer._ctx
+ if offendingToken is None:
+ offendingToken = recognizer.getCurrentToken()
+ if startToken is None:
+ startToken = recognizer.getCurrentToken()
+ if input is None:
+ input = recognizer.getInputStream()
+ super().__init__(recognizer=recognizer, input=input, ctx=ctx)
+ # Which configurations did we try at input.index() that couldn't match input.LT(1)?#
+ self.deadEndConfigs = deadEndConfigs
+ # The token object at the start index; the input stream might
+ # not be buffering tokens so get a reference to it. (At the
+ # time the error occurred, of course the stream needs to keep a
+ # buffer all of the tokens but later we might not have access to those.)
+ self.startToken = startToken
+ self.offendingToken = offendingToken
+
+# This signifies any kind of mismatched input exceptions such as
+# when the current input does not match the expected token.
+#
+class InputMismatchException(RecognitionException):
+
+ def __init__(self, recognizer:Parser):
+ super().__init__(recognizer=recognizer, input=recognizer.getInputStream(), ctx=recognizer._ctx)
+ self.offendingToken = recognizer.getCurrentToken()
+
+
+# A semantic predicate failed during validation. Validation of predicates
+# occurs when normally parsing the alternative just like matching a token.
+# Disambiguating predicate evaluation occurs when we test a predicate during
+# prediction.
+
+class FailedPredicateException(RecognitionException):
+
+ def __init__(self, recognizer:Parser, predicate:str=None, message:str=None):
+ super().__init__(message=self.formatMessage(predicate,message), recognizer=recognizer,
+ input=recognizer.getInputStream(), ctx=recognizer._ctx)
+ s = recognizer._interp.atn.states[recognizer.state]
+ trans = s.transitions[0]
+ from antlr4.atn.Transition import PredicateTransition
+ if isinstance(trans, PredicateTransition):
+ self.ruleIndex = trans.ruleIndex
+ self.predicateIndex = trans.predIndex
+ else:
+ self.ruleIndex = 0
+ self.predicateIndex = 0
+ self.predicate = predicate
+ self.offendingToken = recognizer.getCurrentToken()
+
+ def formatMessage(self, predicate:str, message:str):
+ if message is not None:
+ return message
+ else:
+ return "failed predicate: {" + predicate + "}?"
+
+class ParseCancellationException(CancellationException):
+
+ pass
+
+del Token
+del Lexer
+del Parser
+del TokenStream
+del ATNConfigSet
+del ParserRulecontext
+del PredicateTransition
+del BufferedTokenStream
diff --git a/lib/python3.12/site-packages/antlr4/error/__init__.py b/lib/python3.12/site-packages/antlr4/error/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/error/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/lib/python3.12/site-packages/antlr4/error/__pycache__/DiagnosticErrorListener.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/error/__pycache__/DiagnosticErrorListener.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..65eb886f5d2caddd1b278688d53319c0832af7cc
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/error/__pycache__/DiagnosticErrorListener.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorListener.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorListener.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7c2f49911a0d6ff9a7b336c7fa37758f4e96672f
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorListener.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorStrategy.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorStrategy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..078069c4b010241d42b0f864be1a0b9ee64df7ac
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/error/__pycache__/ErrorStrategy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/error/__pycache__/Errors.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/error/__pycache__/Errors.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..98d89f4fe0d1dafc652e029336f865ad2cecfe98
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/error/__pycache__/Errors.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/error/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/error/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..29bd07e94d65f785559528064e71cdb32150f430
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/error/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/Chunk.py b/lib/python3.12/site-packages/antlr4/tree/Chunk.py
new file mode 100644
index 0000000000000000000000000000000000000000..081419a34f65463b370b848b141192bfe491befd
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/Chunk.py
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+class Chunk(object):
+ pass
+
+class TagChunk(Chunk):
+ __slots__ = ('tag', 'label')
+
+ def __init__(self, tag:str, label:str=None):
+ self.tag = tag
+ self.label = label
+
+ def __str__(self):
+ if self.label is None:
+ return self.tag
+ else:
+ return self.label + ":" + self.tag
+
+class TextChunk(Chunk):
+ __slots__ = 'text'
+
+ def __init__(self, text:str):
+ self.text = text
+
+ def __str__(self):
+ return "'" + self.text + "'"
diff --git a/lib/python3.12/site-packages/antlr4/tree/ParseTreeMatch.py b/lib/python3.12/site-packages/antlr4/tree/ParseTreeMatch.py
new file mode 100644
index 0000000000000000000000000000000000000000..c02bc0357d26b343a72307cda77ff62fe307a44a
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/ParseTreeMatch.py
@@ -0,0 +1,118 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+#
+# Represents the result of matching a {@link ParseTree} against a tree pattern.
+#
+from io import StringIO
+from antlr4.tree.ParseTreePattern import ParseTreePattern
+from antlr4.tree.Tree import ParseTree
+
+
+class ParseTreeMatch(object):
+ __slots__ = ('tree', 'pattern', 'labels', 'mismatchedNode')
+ #
+ # Constructs a new instance of {@link ParseTreeMatch} from the specified
+ # parse tree and pattern.
+ #
+ # @param tree The parse tree to match against the pattern.
+ # @param pattern The parse tree pattern.
+ # @param labels A mapping from label names to collections of
+ # {@link ParseTree} objects located by the tree pattern matching process.
+ # @param mismatchedNode The first node which failed to match the tree
+ # pattern during the matching process.
+ #
+ # @exception IllegalArgumentException if {@code tree} is {@code null}
+ # @exception IllegalArgumentException if {@code pattern} is {@code null}
+ # @exception IllegalArgumentException if {@code labels} is {@code null}
+ #
+ def __init__(self, tree:ParseTree, pattern:ParseTreePattern, labels:dict, mismatchedNode:ParseTree):
+ if tree is None:
+ raise Exception("tree cannot be null")
+ if pattern is None:
+ raise Exception("pattern cannot be null")
+ if labels is None:
+ raise Exception("labels cannot be null")
+ self.tree = tree
+ self.pattern = pattern
+ self.labels = labels
+ self.mismatchedNode = mismatchedNode
+
+ #
+ # Get the last node associated with a specific {@code label}.
+ #
+ # For example, for pattern {@code }, {@code get("id")} returns the
+ # node matched for that {@code ID}. If more than one node
+ # matched the specified label, only the last is returned. If there is
+ # no node associated with the label, this returns {@code null}.
+ #
+ # Pattern tags like {@code } and {@code } without labels are
+ # considered to be labeled with {@code ID} and {@code expr}, respectively.
+ #
+ # @param label The label to check.
+ #
+ # @return The last {@link ParseTree} to match a tag with the specified
+ # label, or {@code null} if no parse tree matched a tag with the label.
+ #
+ def get(self, label:str):
+ parseTrees = self.labels.get(label, None)
+ if parseTrees is None or len(parseTrees)==0:
+ return None
+ else:
+ return parseTrees[len(parseTrees)-1]
+
+ #
+ # Return all nodes matching a rule or token tag with the specified label.
+ #
+ # If the {@code label} is the name of a parser rule or token in the
+ # grammar, the resulting list will contain both the parse trees matching
+ # rule or tags explicitly labeled with the label and the complete set of
+ # parse trees matching the labeled and unlabeled tags in the pattern for
+ # the parser rule or token. For example, if {@code label} is {@code "foo"},
+ # the result will contain all of the following.
+ #
+ #
+ # - Parse tree nodes matching tags of the form {@code } and
+ # {@code }.
+ # - Parse tree nodes matching tags of the form {@code }.
+ # - Parse tree nodes matching tags of the form {@code }.
+ #
+ #
+ # @param label The label.
+ #
+ # @return A collection of all {@link ParseTree} nodes matching tags with
+ # the specified {@code label}. If no nodes matched the label, an empty list
+ # is returned.
+ #
+ def getAll(self, label:str):
+ nodes = self.labels.get(label, None)
+ if nodes is None:
+ return list()
+ else:
+ return nodes
+
+
+ #
+ # Gets a value indicating whether the match operation succeeded.
+ #
+ # @return {@code true} if the match operation succeeded; otherwise,
+ # {@code false}.
+ #
+ def succeeded(self):
+ return self.mismatchedNode is None
+
+ #
+ # {@inheritDoc}
+ #
+ def __str__(self):
+ with StringIO() as buf:
+ buf.write("Match ")
+ buf.write("succeeded" if self.succeeded() else "failed")
+ buf.write("; found ")
+ buf.write(str(len(self.labels)))
+ buf.write(" labels")
+ return buf.getvalue()
diff --git a/lib/python3.12/site-packages/antlr4/tree/ParseTreePattern.py b/lib/python3.12/site-packages/antlr4/tree/ParseTreePattern.py
new file mode 100644
index 0000000000000000000000000000000000000000..37fd0bf09f478d47f927b3fdf7d7a32da1c0b795
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/ParseTreePattern.py
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A pattern like {@code = ;} converted to a {@link ParseTree} by
+# {@link ParseTreePatternMatcher#compile(String, int)}.
+#
+from antlr4.tree.ParseTreePatternMatcher import ParseTreePatternMatcher
+from antlr4.tree.Tree import ParseTree
+from antlr4.xpath.XPath import XPath
+
+
+class ParseTreePattern(object):
+ __slots__ = ('matcher', 'patternRuleIndex', 'pattern', 'patternTree')
+
+ # Construct a new instance of the {@link ParseTreePattern} class.
+ #
+ # @param matcher The {@link ParseTreePatternMatcher} which created this
+ # tree pattern.
+ # @param pattern The tree pattern in concrete syntax form.
+ # @param patternRuleIndex The parser rule which serves as the root of the
+ # tree pattern.
+ # @param patternTree The tree pattern in {@link ParseTree} form.
+ #
+ def __init__(self, matcher:ParseTreePatternMatcher, pattern:str, patternRuleIndex:int , patternTree:ParseTree):
+ self.matcher = matcher
+ self.patternRuleIndex = patternRuleIndex
+ self.pattern = pattern
+ self.patternTree = patternTree
+
+ #
+ # Match a specific parse tree against this tree pattern.
+ #
+ # @param tree The parse tree to match against this tree pattern.
+ # @return A {@link ParseTreeMatch} object describing the result of the
+ # match operation. The {@link ParseTreeMatch#succeeded()} method can be
+ # used to determine whether or not the match was successful.
+ #
+ def match(self, tree:ParseTree):
+ return self.matcher.match(tree, self)
+
+ #
+ # Determine whether or not a parse tree matches this tree pattern.
+ #
+ # @param tree The parse tree to match against this tree pattern.
+ # @return {@code true} if {@code tree} is a match for the current tree
+ # pattern; otherwise, {@code false}.
+ #
+ def matches(self, tree:ParseTree):
+ return self.matcher.match(tree, self).succeeded()
+
+ # Find all nodes using XPath and then try to match those subtrees against
+ # this tree pattern.
+ #
+ # @param tree The {@link ParseTree} to match against this pattern.
+ # @param xpath An expression matching the nodes
+ #
+ # @return A collection of {@link ParseTreeMatch} objects describing the
+ # successful matches. Unsuccessful matches are omitted from the result,
+ # regardless of the reason for the failure.
+ #
+ def findAll(self, tree:ParseTree, xpath:str):
+ subtrees = XPath.findAll(tree, xpath, self.matcher.parser)
+ matches = list()
+ for t in subtrees:
+ match = self.match(t)
+ if match.succeeded():
+ matches.append(match)
+ return matches
diff --git a/lib/python3.12/site-packages/antlr4/tree/ParseTreePatternMatcher.py b/lib/python3.12/site-packages/antlr4/tree/ParseTreePatternMatcher.py
new file mode 100644
index 0000000000000000000000000000000000000000..62fd197b0d143393fa187ead9b0c576112b486be
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/ParseTreePatternMatcher.py
@@ -0,0 +1,374 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A tree pattern matching mechanism for ANTLR {@link ParseTree}s.
+#
+# Patterns are strings of source input text with special tags representing
+# token or rule references such as:
+#
+# {@code = ;}
+#
+# Given a pattern start rule such as {@code statement}, this object constructs
+# a {@link ParseTree} with placeholders for the {@code ID} and {@code expr}
+# subtree. Then the {@link #match} routines can compare an actual
+# {@link ParseTree} from a parse with this pattern. Tag {@code } matches
+# any {@code ID} token and tag {@code } references the result of the
+# {@code expr} rule (generally an instance of {@code ExprContext}.
+#
+# Pattern {@code x = 0;} is a similar pattern that matches the same pattern
+# except that it requires the identifier to be {@code x} and the expression to
+# be {@code 0}.
+#
+# The {@link #matches} routines return {@code true} or {@code false} based
+# upon a match for the tree rooted at the parameter sent in. The
+# {@link #match} routines return a {@link ParseTreeMatch} object that
+# contains the parse tree, the parse tree pattern, and a map from tag name to
+# matched nodes (more below). A subtree that fails to match, returns with
+# {@link ParseTreeMatch#mismatchedNode} set to the first tree node that did not
+# match.
+#
+# For efficiency, you can compile a tree pattern in string form to a
+# {@link ParseTreePattern} object.
+#
+# See {@code TestParseTreeMatcher} for lots of examples.
+# {@link ParseTreePattern} has two static helper methods:
+# {@link ParseTreePattern#findAll} and {@link ParseTreePattern#match} that
+# are easy to use but not super efficient because they create new
+# {@link ParseTreePatternMatcher} objects each time and have to compile the
+# pattern in string form before using it.
+#
+# The lexer and parser that you pass into the {@link ParseTreePatternMatcher}
+# constructor are used to parse the pattern in string form. The lexer converts
+# the {@code = ;} into a sequence of four tokens (assuming lexer
+# throws out whitespace or puts it on a hidden channel). Be aware that the
+# input stream is reset for the lexer (but not the parser; a
+# {@link ParserInterpreter} is created to parse the input.). Any user-defined
+# fields you have put into the lexer might get changed when this mechanism asks
+# it to scan the pattern string.
+#
+# Normally a parser does not accept token {@code } as a valid
+# {@code expr} but, from the parser passed in, we create a special version of
+# the underlying grammar representation (an {@link ATN}) that allows imaginary
+# tokens representing rules ({@code }) to match entire rules. We call
+# these bypass alternatives.
+#
+# Delimiters are {@code <} and {@code >}, with {@code \} as the escape string
+# by default, but you can set them to whatever you want using
+# {@link #setDelimiters}. You must escape both start and stop strings
+# {@code \<} and {@code \>}.
+#
+from antlr4.CommonTokenStream import CommonTokenStream
+from antlr4.InputStream import InputStream
+from antlr4.ParserRuleContext import ParserRuleContext
+from antlr4.Lexer import Lexer
+from antlr4.ListTokenSource import ListTokenSource
+from antlr4.Token import Token
+from antlr4.error.ErrorStrategy import BailErrorStrategy
+from antlr4.error.Errors import RecognitionException, ParseCancellationException
+from antlr4.tree.Chunk import TagChunk, TextChunk
+from antlr4.tree.RuleTagToken import RuleTagToken
+from antlr4.tree.TokenTagToken import TokenTagToken
+from antlr4.tree.Tree import ParseTree, TerminalNode, RuleNode
+
+# need forward declaration
+Parser = None
+ParseTreePattern = None
+
+class CannotInvokeStartRule(Exception):
+
+ def __init__(self, e:Exception):
+ super().__init__(e)
+
+class StartRuleDoesNotConsumeFullPattern(Exception):
+
+ pass
+
+
+class ParseTreePatternMatcher(object):
+ __slots__ = ('lexer', 'parser', 'start', 'stop', 'escape')
+
+ # Constructs a {@link ParseTreePatternMatcher} or from a {@link Lexer} and
+ # {@link Parser} object. The lexer input stream is altered for tokenizing
+ # the tree patterns. The parser is used as a convenient mechanism to get
+ # the grammar name, plus token, rule names.
+ def __init__(self, lexer:Lexer, parser:Parser):
+ self.lexer = lexer
+ self.parser = parser
+ self.start = "<"
+ self.stop = ">"
+ self.escape = "\\" # e.g., \< and \> must escape BOTH!
+
+ # Set the delimiters used for marking rule and token tags within concrete
+ # syntax used by the tree pattern parser.
+ #
+ # @param start The start delimiter.
+ # @param stop The stop delimiter.
+ # @param escapeLeft The escape sequence to use for escaping a start or stop delimiter.
+ #
+ # @exception IllegalArgumentException if {@code start} is {@code null} or empty.
+ # @exception IllegalArgumentException if {@code stop} is {@code null} or empty.
+ #
+ def setDelimiters(self, start:str, stop:str, escapeLeft:str):
+ if start is None or len(start)==0:
+ raise Exception("start cannot be null or empty")
+ if stop is None or len(stop)==0:
+ raise Exception("stop cannot be null or empty")
+ self.start = start
+ self.stop = stop
+ self.escape = escapeLeft
+
+ # Does {@code pattern} matched as rule {@code patternRuleIndex} match {@code tree}?#
+ def matchesRuleIndex(self, tree:ParseTree, pattern:str, patternRuleIndex:int):
+ p = self.compileTreePattern(pattern, patternRuleIndex)
+ return self.matches(tree, p)
+
+ # Does {@code pattern} matched as rule patternRuleIndex match tree? Pass in a
+ # compiled pattern instead of a string representation of a tree pattern.
+ #
+ def matchesPattern(self, tree:ParseTree, pattern:ParseTreePattern):
+ mismatchedNode = self.matchImpl(tree, pattern.patternTree, dict())
+ return mismatchedNode is None
+
+ #
+ # Compare {@code pattern} matched as rule {@code patternRuleIndex} against
+ # {@code tree} and return a {@link ParseTreeMatch} object that contains the
+ # matched elements, or the node at which the match failed.
+ #
+ def matchRuleIndex(self, tree:ParseTree, pattern:str, patternRuleIndex:int):
+ p = self.compileTreePattern(pattern, patternRuleIndex)
+ return self.matchPattern(tree, p)
+
+ #
+ # Compare {@code pattern} matched against {@code tree} and return a
+ # {@link ParseTreeMatch} object that contains the matched elements, or the
+ # node at which the match failed. Pass in a compiled pattern instead of a
+ # string representation of a tree pattern.
+ #
+ def matchPattern(self, tree:ParseTree, pattern:ParseTreePattern):
+ labels = dict()
+ mismatchedNode = self.matchImpl(tree, pattern.patternTree, labels)
+ from antlr4.tree.ParseTreeMatch import ParseTreeMatch
+ return ParseTreeMatch(tree, pattern, labels, mismatchedNode)
+
+ #
+ # For repeated use of a tree pattern, compile it to a
+ # {@link ParseTreePattern} using this method.
+ #
+ def compileTreePattern(self, pattern:str, patternRuleIndex:int):
+ tokenList = self.tokenize(pattern)
+ tokenSrc = ListTokenSource(tokenList)
+ tokens = CommonTokenStream(tokenSrc)
+ from antlr4.ParserInterpreter import ParserInterpreter
+ parserInterp = ParserInterpreter(self.parser.grammarFileName, self.parser.tokenNames,
+ self.parser.ruleNames, self.parser.getATNWithBypassAlts(),tokens)
+ tree = None
+ try:
+ parserInterp.setErrorHandler(BailErrorStrategy())
+ tree = parserInterp.parse(patternRuleIndex)
+ except ParseCancellationException as e:
+ raise e.cause
+ except RecognitionException as e:
+ raise e
+ except Exception as e:
+ raise CannotInvokeStartRule(e)
+
+ # Make sure tree pattern compilation checks for a complete parse
+ if tokens.LA(1)!=Token.EOF:
+ raise StartRuleDoesNotConsumeFullPattern()
+
+ from antlr4.tree.ParseTreePattern import ParseTreePattern
+ return ParseTreePattern(self, pattern, patternRuleIndex, tree)
+
+ #
+ # Recursively walk {@code tree} against {@code patternTree}, filling
+ # {@code match.}{@link ParseTreeMatch#labels labels}.
+ #
+ # @return the first node encountered in {@code tree} which does not match
+ # a corresponding node in {@code patternTree}, or {@code null} if the match
+ # was successful. The specific node returned depends on the matching
+ # algorithm used by the implementation, and may be overridden.
+ #
+ def matchImpl(self, tree:ParseTree, patternTree:ParseTree, labels:dict):
+ if tree is None:
+ raise Exception("tree cannot be null")
+ if patternTree is None:
+ raise Exception("patternTree cannot be null")
+
+ # x and , x and y, or x and x; or could be mismatched types
+ if isinstance(tree, TerminalNode) and isinstance(patternTree, TerminalNode ):
+ mismatchedNode = None
+ # both are tokens and they have same type
+ if tree.symbol.type == patternTree.symbol.type:
+ if isinstance( patternTree.symbol, TokenTagToken ): # x and
+ tokenTagToken = patternTree.symbol
+ # track label->list-of-nodes for both token name and label (if any)
+ self.map(labels, tokenTagToken.tokenName, tree)
+ if tokenTagToken.label is not None:
+ self.map(labels, tokenTagToken.label, tree)
+ elif tree.getText()==patternTree.getText():
+ # x and x
+ pass
+ else:
+ # x and y
+ if mismatchedNode is None:
+ mismatchedNode = tree
+ else:
+ if mismatchedNode is None:
+ mismatchedNode = tree
+
+ return mismatchedNode
+
+ if isinstance(tree, ParserRuleContext) and isinstance(patternTree, ParserRuleContext):
+ mismatchedNode = None
+ # (expr ...) and
+ ruleTagToken = self.getRuleTagToken(patternTree)
+ if ruleTagToken is not None:
+ m = None
+ if tree.ruleContext.ruleIndex == patternTree.ruleContext.ruleIndex:
+ # track label->list-of-nodes for both rule name and label (if any)
+ self.map(labels, ruleTagToken.ruleName, tree)
+ if ruleTagToken.label is not None:
+ self.map(labels, ruleTagToken.label, tree)
+ else:
+ if mismatchedNode is None:
+ mismatchedNode = tree
+
+ return mismatchedNode
+
+ # (expr ...) and (expr ...)
+ if tree.getChildCount()!=patternTree.getChildCount():
+ if mismatchedNode is None:
+ mismatchedNode = tree
+ return mismatchedNode
+
+ n = tree.getChildCount()
+ for i in range(0, n):
+ childMatch = self.matchImpl(tree.getChild(i), patternTree.getChild(i), labels)
+ if childMatch is not None:
+ return childMatch
+
+ return mismatchedNode
+
+ # if nodes aren't both tokens or both rule nodes, can't match
+ return tree
+
+ def map(self, labels, label, tree):
+ v = labels.get(label, None)
+ if v is None:
+ v = list()
+ labels[label] = v
+ v.append(tree)
+
+ # Is {@code t} {@code (expr )} subtree?#
+ def getRuleTagToken(self, tree:ParseTree):
+ if isinstance( tree, RuleNode ):
+ if tree.getChildCount()==1 and isinstance(tree.getChild(0), TerminalNode ):
+ c = tree.getChild(0)
+ if isinstance( c.symbol, RuleTagToken ):
+ return c.symbol
+ return None
+
+ def tokenize(self, pattern:str):
+ # split pattern into chunks: sea (raw input) and islands (, )
+ chunks = self.split(pattern)
+
+ # create token stream from text and tags
+ tokens = list()
+ for chunk in chunks:
+ if isinstance( chunk, TagChunk ):
+ # add special rule token or conjure up new token from name
+ if chunk.tag[0].isupper():
+ ttype = self.parser.getTokenType(chunk.tag)
+ if ttype==Token.INVALID_TYPE:
+ raise Exception("Unknown token " + str(chunk.tag) + " in pattern: " + pattern)
+ tokens.append(TokenTagToken(chunk.tag, ttype, chunk.label))
+ elif chunk.tag[0].islower():
+ ruleIndex = self.parser.getRuleIndex(chunk.tag)
+ if ruleIndex==-1:
+ raise Exception("Unknown rule " + str(chunk.tag) + " in pattern: " + pattern)
+ ruleImaginaryTokenType = self.parser.getATNWithBypassAlts().ruleToTokenType[ruleIndex]
+ tokens.append(RuleTagToken(chunk.tag, ruleImaginaryTokenType, chunk.label))
+ else:
+ raise Exception("invalid tag: " + str(chunk.tag) + " in pattern: " + pattern)
+ else:
+ self.lexer.setInputStream(InputStream(chunk.text))
+ t = self.lexer.nextToken()
+ while t.type!=Token.EOF:
+ tokens.append(t)
+ t = self.lexer.nextToken()
+ return tokens
+
+ # Split {@code = ;} into 4 chunks for tokenizing by {@link #tokenize}.#
+ def split(self, pattern:str):
+ p = 0
+ n = len(pattern)
+ chunks = list()
+ # find all start and stop indexes first, then collect
+ starts = list()
+ stops = list()
+ while p < n :
+ if p == pattern.find(self.escape + self.start, p):
+ p += len(self.escape) + len(self.start)
+ elif p == pattern.find(self.escape + self.stop, p):
+ p += len(self.escape) + len(self.stop)
+ elif p == pattern.find(self.start, p):
+ starts.append(p)
+ p += len(self.start)
+ elif p == pattern.find(self.stop, p):
+ stops.append(p)
+ p += len(self.stop)
+ else:
+ p += 1
+
+ nt = len(starts)
+
+ if nt > len(stops):
+ raise Exception("unterminated tag in pattern: " + pattern)
+ if nt < len(stops):
+ raise Exception("missing start tag in pattern: " + pattern)
+
+ for i in range(0, nt):
+ if starts[i] >= stops[i]:
+ raise Exception("tag delimiters out of order in pattern: " + pattern)
+
+ # collect into chunks now
+ if nt==0:
+ chunks.append(TextChunk(pattern))
+
+ if nt>0 and starts[0]>0: # copy text up to first tag into chunks
+ text = pattern[0:starts[0]]
+ chunks.add(TextChunk(text))
+
+ for i in range(0, nt):
+ # copy inside of
+ tag = pattern[starts[i] + len(self.start) : stops[i]]
+ ruleOrToken = tag
+ label = None
+ colon = tag.find(':')
+ if colon >= 0:
+ label = tag[0:colon]
+ ruleOrToken = tag[colon+1 : len(tag)]
+ chunks.append(TagChunk(label, ruleOrToken))
+ if i+1 < len(starts):
+ # copy from end of to start of next
+ text = pattern[stops[i] + len(self.stop) : starts[i + 1]]
+ chunks.append(TextChunk(text))
+
+ if nt > 0 :
+ afterLastTag = stops[nt - 1] + len(self.stop)
+ if afterLastTag < n : # copy text from end of last tag to end
+ text = pattern[afterLastTag : n]
+ chunks.append(TextChunk(text))
+
+ # strip out the escape sequences from text chunks but not tags
+ for i in range(0, len(chunks)):
+ c = chunks[i]
+ if isinstance( c, TextChunk ):
+ unescaped = c.text.replace(self.escape, "")
+ if len(unescaped) < len(c.text):
+ chunks[i] = TextChunk(unescaped)
+ return chunks
diff --git a/lib/python3.12/site-packages/antlr4/tree/RuleTagToken.py b/lib/python3.12/site-packages/antlr4/tree/RuleTagToken.py
new file mode 100644
index 0000000000000000000000000000000000000000..a198f7da13643d538ce96aeeb6a8ff4f757f1ecd
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/RuleTagToken.py
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A {@link Token} object representing an entire subtree matched by a parser
+# rule; e.g., {@code }. These tokens are created for {@link TagChunk}
+# chunks where the tag corresponds to a parser rule.
+#
+from antlr4.Token import Token
+
+
+class RuleTagToken(Token):
+ __slots__ = ('label', 'ruleName')
+ #
+ # Constructs a new instance of {@link RuleTagToken} with the specified rule
+ # name, bypass token type, and label.
+ #
+ # @param ruleName The name of the parser rule this rule tag matches.
+ # @param bypassTokenType The bypass token type assigned to the parser rule.
+ # @param label The label associated with the rule tag, or {@code null} if
+ # the rule tag is unlabeled.
+ #
+ # @exception IllegalArgumentException if {@code ruleName} is {@code null}
+ # or empty.
+
+ def __init__(self, ruleName:str, bypassTokenType:int, label:str=None):
+ if ruleName is None or len(ruleName)==0:
+ raise Exception("ruleName cannot be null or empty.")
+ self.source = None
+ self.type = bypassTokenType # token type of the token
+ self.channel = Token.DEFAULT_CHANNEL # The parser ignores everything not on DEFAULT_CHANNEL
+ self.start = -1 # optional; return -1 if not implemented.
+ self.stop = -1 # optional; return -1 if not implemented.
+ self.tokenIndex = -1 # from 0..n-1 of the token object in the input stream
+ self.line = 0 # line=1..n of the 1st character
+ self.column = -1 # beginning of the line at which it occurs, 0..n-1
+ self.label = label
+ self._text = self.getText() # text of the token.
+
+ self.ruleName = ruleName
+
+
+ def getText(self):
+ if self.label is None:
+ return "<" + self.ruleName + ">"
+ else:
+ return "<" + self.label + ":" + self.ruleName + ">"
diff --git a/lib/python3.12/site-packages/antlr4/tree/TokenTagToken.py b/lib/python3.12/site-packages/antlr4/tree/TokenTagToken.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7beeb87684c06606e17053f0f74fcae36876959
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/TokenTagToken.py
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# A {@link Token} object representing a token of a particular type; e.g.,
+# {@code }. These tokens are created for {@link TagChunk} chunks where the
+# tag corresponds to a lexer rule or token type.
+#
+from antlr4.Token import CommonToken
+
+
+class TokenTagToken(CommonToken):
+ __slots__ = ('tokenName', 'label')
+ # Constructs a new instance of {@link TokenTagToken} with the specified
+ # token name, type, and label.
+ #
+ # @param tokenName The token name.
+ # @param type The token type.
+ # @param label The label associated with the token tag, or {@code null} if
+ # the token tag is unlabeled.
+ #
+ def __init__(self, tokenName:str, type:int, label:str=None):
+ super().__init__(type=type)
+ self.tokenName = tokenName
+ self.label = label
+ self._text = self.getText()
+
+ #
+ # {@inheritDoc}
+ #
+ # The implementation for {@link TokenTagToken} returns the token tag
+ # formatted with {@code <} and {@code >} delimiters.
+ #
+ def getText(self):
+ if self.label is None:
+ return "<" + self.tokenName + ">"
+ else:
+ return "<" + self.label + ":" + self.tokenName + ">"
+
+ # The implementation for {@link TokenTagToken} returns a string of the form
+ # {@code tokenName:type}.
+ #
+ def __str__(self):
+ return self.tokenName + ":" + str(self.type)
diff --git a/lib/python3.12/site-packages/antlr4/tree/Tree.py b/lib/python3.12/site-packages/antlr4/tree/Tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..812acc96bbee97860bc8a914feedcd0584def050
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/Tree.py
@@ -0,0 +1,191 @@
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#/
+
+
+# The basic notion of a tree has a parent, a payload, and a list of children.
+# It is the most abstract interface for all the trees used by ANTLR.
+#/
+from antlr4.Token import Token
+
+INVALID_INTERVAL = (-1, -2)
+
+class Tree(object):
+ pass
+
+class SyntaxTree(Tree):
+ pass
+
+class ParseTree(SyntaxTree):
+ pass
+
+class RuleNode(ParseTree):
+ pass
+
+class TerminalNode(ParseTree):
+ pass
+
+class ErrorNode(TerminalNode):
+ pass
+
+class ParseTreeVisitor(object):
+ def visit(self, tree):
+ return tree.accept(self)
+
+ def visitChildren(self, node):
+ result = self.defaultResult()
+ n = node.getChildCount()
+ for i in range(n):
+ if not self.shouldVisitNextChild(node, result):
+ return result
+
+ c = node.getChild(i)
+ childResult = c.accept(self)
+ result = self.aggregateResult(result, childResult)
+
+ return result
+
+ def visitTerminal(self, node):
+ return self.defaultResult()
+
+ def visitErrorNode(self, node):
+ return self.defaultResult()
+
+ def defaultResult(self):
+ return None
+
+ def aggregateResult(self, aggregate, nextResult):
+ return nextResult
+
+ def shouldVisitNextChild(self, node, currentResult):
+ return True
+
+ParserRuleContext = None
+
+class ParseTreeListener(object):
+
+ def visitTerminal(self, node:TerminalNode):
+ pass
+
+ def visitErrorNode(self, node:ErrorNode):
+ pass
+
+ def enterEveryRule(self, ctx:ParserRuleContext):
+ pass
+
+ def exitEveryRule(self, ctx:ParserRuleContext):
+ pass
+
+del ParserRuleContext
+
+class TerminalNodeImpl(TerminalNode):
+ __slots__ = ('parentCtx', 'symbol')
+
+ def __init__(self, symbol:Token):
+ self.parentCtx = None
+ self.symbol = symbol
+ def __setattr__(self, key, value):
+ super().__setattr__(key, value)
+
+ def getChild(self, i:int):
+ return None
+
+ def getSymbol(self):
+ return self.symbol
+
+ def getParent(self):
+ return self.parentCtx
+
+ def getPayload(self):
+ return self.symbol
+
+ def getSourceInterval(self):
+ if self.symbol is None:
+ return INVALID_INTERVAL
+ tokenIndex = self.symbol.tokenIndex
+ return (tokenIndex, tokenIndex)
+
+ def getChildCount(self):
+ return 0
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitTerminal(self)
+
+ def getText(self):
+ return self.symbol.text
+
+ def __str__(self):
+ if self.symbol.type == Token.EOF:
+ return ""
+ else:
+ return self.symbol.text
+
+# Represents a token that was consumed during resynchronization
+# rather than during a valid match operation. For example,
+# we will create this kind of a node during single token insertion
+# and deletion as well as during "consume until error recovery set"
+# upon no viable alternative exceptions.
+
+class ErrorNodeImpl(TerminalNodeImpl,ErrorNode):
+
+ def __init__(self, token:Token):
+ super().__init__(token)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ return visitor.visitErrorNode(self)
+
+
+class ParseTreeWalker(object):
+
+ DEFAULT = None
+
+ def walk(self, listener:ParseTreeListener, t:ParseTree):
+ """
+ Performs a walk on the given parse tree starting at the root and going down recursively
+ with depth-first search. On each node, {@link ParseTreeWalker#enterRule} is called before
+ recursively walking down into child nodes, then
+ {@link ParseTreeWalker#exitRule} is called after the recursive call to wind up.
+ @param listener The listener used by the walker to process grammar rules
+ @param t The parse tree to be walked on
+ """
+ if isinstance(t, ErrorNode):
+ listener.visitErrorNode(t)
+ return
+ elif isinstance(t, TerminalNode):
+ listener.visitTerminal(t)
+ return
+ self.enterRule(listener, t)
+ for child in t.getChildren():
+ self.walk(listener, child)
+ self.exitRule(listener, t)
+
+ #
+ # The discovery of a rule node, involves sending two events: the generic
+ # {@link ParseTreeListener#enterEveryRule} and a
+ # {@link RuleContext}-specific event. First we trigger the generic and then
+ # the rule specific. We to them in reverse order upon finishing the node.
+ #
+ def enterRule(self, listener:ParseTreeListener, r:RuleNode):
+ """
+ Enters a grammar rule by first triggering the generic event {@link ParseTreeListener#enterEveryRule}
+ then by triggering the event specific to the given parse tree node
+ @param listener The listener responding to the trigger events
+ @param r The grammar rule containing the rule context
+ """
+ ctx = r.getRuleContext()
+ listener.enterEveryRule(ctx)
+ ctx.enterRule(listener)
+
+ def exitRule(self, listener:ParseTreeListener, r:RuleNode):
+ """
+ Exits a grammar rule by first triggering the event specific to the given parse tree node
+ then by triggering the generic event {@link ParseTreeListener#exitEveryRule}
+ @param listener The listener responding to the trigger events
+ @param r The grammar rule containing the rule context
+ """
+ ctx = r.getRuleContext()
+ ctx.exitRule(listener)
+ listener.exitEveryRule(ctx)
+
+ParseTreeWalker.DEFAULT = ParseTreeWalker()
diff --git a/lib/python3.12/site-packages/antlr4/tree/Trees.py b/lib/python3.12/site-packages/antlr4/tree/Trees.py
new file mode 100644
index 0000000000000000000000000000000000000000..686b8cb287b3058c2e0b33dfb1567320299f214e
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/tree/Trees.py
@@ -0,0 +1,111 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+
+# A set of utility routines useful for all kinds of ANTLR trees.#
+from io import StringIO
+from antlr4.Token import Token
+from antlr4.Utils import escapeWhitespace
+from antlr4.tree.Tree import RuleNode, ErrorNode, TerminalNode, Tree, ParseTree
+
+# need forward declaration
+Parser = None
+
+class Trees(object):
+
+ # Print out a whole tree in LISP form. {@link #getNodeText} is used on the
+ # node payloads to get the text for the nodes. Detect
+ # parse trees and extract data appropriately.
+ @classmethod
+ def toStringTree(cls, t:Tree, ruleNames:list=None, recog:Parser=None):
+ if recog is not None:
+ ruleNames = recog.ruleNames
+ s = escapeWhitespace(cls.getNodeText(t, ruleNames), False)
+ if t.getChildCount()==0:
+ return s
+ with StringIO() as buf:
+ buf.write("(")
+ buf.write(s)
+ buf.write(' ')
+ for i in range(0, t.getChildCount()):
+ if i > 0:
+ buf.write(' ')
+ buf.write(cls.toStringTree(t.getChild(i), ruleNames))
+ buf.write(")")
+ return buf.getvalue()
+
+ @classmethod
+ def getNodeText(cls, t:Tree, ruleNames:list=None, recog:Parser=None):
+ if recog is not None:
+ ruleNames = recog.ruleNames
+ if ruleNames is not None:
+ if isinstance(t, RuleNode):
+ if t.getAltNumber()!=0: # should use ATN.INVALID_ALT_NUMBER but won't compile
+ return ruleNames[t.getRuleIndex()]+":"+str(t.getAltNumber())
+ return ruleNames[t.getRuleIndex()]
+ elif isinstance( t, ErrorNode):
+ return str(t)
+ elif isinstance(t, TerminalNode):
+ if t.symbol is not None:
+ return t.symbol.text
+ # no recog for rule names
+ payload = t.getPayload()
+ if isinstance(payload, Token ):
+ return payload.text
+ return str(t.getPayload())
+
+
+ # Return ordered list of all children of this node
+ @classmethod
+ def getChildren(cls, t:Tree):
+ return [ t.getChild(i) for i in range(0, t.getChildCount()) ]
+
+ # Return a list of all ancestors of this node. The first node of
+ # list is the root and the last is the parent of this node.
+ #
+ @classmethod
+ def getAncestors(cls, t:Tree):
+ ancestors = []
+ t = t.getParent()
+ while t is not None:
+ ancestors.insert(0, t) # insert at start
+ t = t.getParent()
+ return ancestors
+
+ @classmethod
+ def findAllTokenNodes(cls, t:ParseTree, ttype:int):
+ return cls.findAllNodes(t, ttype, True)
+
+ @classmethod
+ def findAllRuleNodes(cls, t:ParseTree, ruleIndex:int):
+ return cls.findAllNodes(t, ruleIndex, False)
+
+ @classmethod
+ def findAllNodes(cls, t:ParseTree, index:int, findTokens:bool):
+ nodes = []
+ cls._findAllNodes(t, index, findTokens, nodes)
+ return nodes
+
+ @classmethod
+ def _findAllNodes(cls, t:ParseTree, index:int, findTokens:bool, nodes:list):
+ from antlr4.ParserRuleContext import ParserRuleContext
+ # check this node (the root) first
+ if findTokens and isinstance(t, TerminalNode):
+ if t.symbol.type==index:
+ nodes.append(t)
+ elif not findTokens and isinstance(t, ParserRuleContext):
+ if t.ruleIndex == index:
+ nodes.append(t)
+ # check children
+ for i in range(0, t.getChildCount()):
+ cls._findAllNodes(t.getChild(i), index, findTokens, nodes)
+
+ @classmethod
+ def descendants(cls, t:ParseTree):
+ nodes = [t]
+ for i in range(0, t.getChildCount()):
+ nodes.extend(cls.descendants(t.getChild(i)))
+ return nodes
diff --git a/lib/python3.12/site-packages/antlr4/tree/__init__.py b/lib/python3.12/site-packages/antlr4/tree/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/Chunk.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Chunk.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..69403b088e2b49e91d369a8346a58ffa32bf20b7
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Chunk.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreeMatch.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreeMatch.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b54a223391df40ff5339a6bf91f0ecdf7d9d0342
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreeMatch.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePattern.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePattern.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c83520a269309d32bae649c8dbded349c55b4ace
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePattern.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePatternMatcher.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePatternMatcher.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2778e9bfb45b181fff14574ff14ea1501bc4fcd5
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/ParseTreePatternMatcher.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/RuleTagToken.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/RuleTagToken.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6c1f68a13755d6a8501b33dbfc39c598ffb3c8b8
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/RuleTagToken.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/TokenTagToken.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/TokenTagToken.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..65610c472ba6787c8cfb75c8789eac093f563c06
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/TokenTagToken.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/Tree.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Tree.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b03a403dc7f8d2c884dfe09b2caca14a66e7daa1
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Tree.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/Trees.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Trees.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..34eb0b3d9c3302989b3548b2b1ab32f015d400c9
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/Trees.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/tree/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/tree/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d0b13d083063719f9c7fd753af282e4f519b187b
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/tree/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/xpath/XPath.py b/lib/python3.12/site-packages/antlr4/xpath/XPath.py
new file mode 100644
index 0000000000000000000000000000000000000000..24029e7cf2bfa9798538211bc7599b49dbb70191
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/xpath/XPath.py
@@ -0,0 +1,352 @@
+#
+# Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
+# Use of this file is governed by the BSD 3-clause license that
+# can be found in the LICENSE.txt file in the project root.
+#
+
+#
+# Represent a subset of XPath XML path syntax for use in identifying nodes in
+# parse trees.
+#
+#
+# Split path into words and separators {@code /} and {@code //} via ANTLR
+# itself then walk path elements from left to right. At each separator-word
+# pair, find set of nodes. Next stage uses those as work list.
+#
+#
+# The basic interface is
+# {@link XPath#findAll ParseTree.findAll}{@code (tree, pathString, parser)}.
+# But that is just shorthand for:
+#
+#
+# {@link XPath} p = new {@link XPath#XPath XPath}(parser, pathString);
+# return p.{@link #evaluate evaluate}(tree);
+#
+#
+#
+# See {@code org.antlr.v4.test.TestXPath} for descriptions. In short, this
+# allows operators:
+#
+#
+# - /
- root
+# - //
- anywhere
+# - !
- invert; this must appear directly after root or anywhere
+# operator
+#
+#
+#
+# and path elements:
+#
+#
+# - ID
- token name
+# - 'string'
- any string literal token from the grammar
+# - expr
- rule name
+# - *
- wildcard matching any node
+#
+#
+#
+# Whitespace is not allowed.
+#
+from antlr4 import CommonTokenStream, DFA, PredictionContextCache, Lexer, LexerATNSimulator, ParserRuleContext, TerminalNode
+from antlr4.InputStream import InputStream
+from antlr4.Parser import Parser
+from antlr4.RuleContext import RuleContext
+from antlr4.Token import Token
+from antlr4.atn.ATNDeserializer import ATNDeserializer
+from antlr4.error.ErrorListener import ErrorListener
+from antlr4.error.Errors import LexerNoViableAltException
+from antlr4.tree.Tree import ParseTree
+from antlr4.tree.Trees import Trees
+from io import StringIO
+
+
+def serializedATN():
+ with StringIO() as buf:
+ buf.write("\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2\n")
+ buf.write("\64\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t")
+ buf.write("\7\4\b\t\b\4\t\t\t\3\2\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5")
+ buf.write("\3\6\3\6\7\6\37\n\6\f\6\16\6\"\13\6\3\6\3\6\3\7\3\7\5")
+ buf.write("\7(\n\7\3\b\3\b\3\t\3\t\7\t.\n\t\f\t\16\t\61\13\t\3\t")
+ buf.write("\3\t\3/\2\n\3\5\5\6\7\7\t\b\13\t\r\2\17\2\21\n\3\2\4\7")
+ buf.write("\2\62;aa\u00b9\u00b9\u0302\u0371\u2041\u2042\17\2C\\c")
+ buf.write("|\u00c2\u00d8\u00da\u00f8\u00fa\u0301\u0372\u037f\u0381")
+ buf.write("\u2001\u200e\u200f\u2072\u2191\u2c02\u2ff1\u3003\ud801")
+ buf.write("\uf902\ufdd1\ufdf2\uffff\64\2\3\3\2\2\2\2\5\3\2\2\2\2")
+ buf.write("\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\21\3\2\2\2\3\23")
+ buf.write("\3\2\2\2\5\26\3\2\2\2\7\30\3\2\2\2\t\32\3\2\2\2\13\34")
+ buf.write("\3\2\2\2\r\'\3\2\2\2\17)\3\2\2\2\21+\3\2\2\2\23\24\7\61")
+ buf.write("\2\2\24\25\7\61\2\2\25\4\3\2\2\2\26\27\7\61\2\2\27\6\3")
+ buf.write("\2\2\2\30\31\7,\2\2\31\b\3\2\2\2\32\33\7#\2\2\33\n\3\2")
+ buf.write("\2\2\34 \5\17\b\2\35\37\5\r\7\2\36\35\3\2\2\2\37\"\3\2")
+ buf.write("\2\2 \36\3\2\2\2 !\3\2\2\2!#\3\2\2\2\" \3\2\2\2#$\b\6")
+ buf.write("\2\2$\f\3\2\2\2%(\5\17\b\2&(\t\2\2\2\'%\3\2\2\2\'&\3\2")
+ buf.write("\2\2(\16\3\2\2\2)*\t\3\2\2*\20\3\2\2\2+/\7)\2\2,.\13\2")
+ buf.write("\2\2-,\3\2\2\2.\61\3\2\2\2/\60\3\2\2\2/-\3\2\2\2\60\62")
+ buf.write("\3\2\2\2\61/\3\2\2\2\62\63\7)\2\2\63\22\3\2\2\2\6\2 \'")
+ buf.write("/\3\3\6\2")
+ return buf.getvalue()
+
+
+class XPathLexer(Lexer):
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+
+ TOKEN_REF = 1
+ RULE_REF = 2
+ ANYWHERE = 3
+ ROOT = 4
+ WILDCARD = 5
+ BANG = 6
+ ID = 7
+ STRING = 8
+
+ modeNames = [ "DEFAULT_MODE" ]
+
+ literalNames = [ "",
+ "'//'", "'/'", "'*'", "'!'" ]
+
+ symbolicNames = [ "",
+ "TOKEN_REF", "RULE_REF", "ANYWHERE", "ROOT", "WILDCARD", "BANG",
+ "ID", "STRING" ]
+
+ ruleNames = [ "ANYWHERE", "ROOT", "WILDCARD", "BANG", "ID", "NameChar",
+ "NameStartChar", "STRING" ]
+
+ grammarFileName = "XPathLexer.g4"
+
+ def __init__(self, input=None):
+ super().__init__(input)
+ self.checkVersion("4.9.1")
+ self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
+ self._actions = None
+ self._predicates = None
+
+
+ def action(self, localctx:RuleContext, ruleIndex:int, actionIndex:int):
+ if self._actions is None:
+ actions = dict()
+ actions[4] = self.ID_action
+ self._actions = actions
+ _action = self._actions.get(ruleIndex, None)
+ if _action is not None:
+ _action(localctx, actionIndex)
+ else:
+ raise Exception("No registered action for: %d" % ruleIndex)
+
+ def ID_action(self, localctx:RuleContext , actionIndex:int):
+ if actionIndex == 0:
+ char = self.text[0]
+ if char.isupper():
+ self.type = XPathLexer.TOKEN_REF
+ else:
+ self.type = XPathLexer.RULE_REF
+
+class XPath(object):
+
+ WILDCARD = "*" # word not operator/separator
+ NOT = "!" # word for invert operator
+
+ def __init__(self, parser:Parser, path:str):
+ self.parser = parser
+ self.path = path
+ self.elements = self.split(path)
+
+ def split(self, path:str):
+ input = InputStream(path)
+ lexer = XPathLexer(input)
+ def recover(self, e):
+ raise e
+ lexer.recover = recover
+ lexer.removeErrorListeners()
+ lexer.addErrorListener(ErrorListener()) # XPathErrorListener does no more
+ tokenStream = CommonTokenStream(lexer)
+ try:
+ tokenStream.fill()
+ except LexerNoViableAltException as e:
+ pos = lexer.column
+ msg = "Invalid tokens or characters at index %d in path '%s'" % (pos, path)
+ raise Exception(msg, e)
+
+ tokens = iter(tokenStream.tokens)
+ elements = list()
+ for el in tokens:
+ invert = False
+ anywhere = False
+ # Check for path separators, if none assume root
+ if el.type in [XPathLexer.ROOT, XPathLexer.ANYWHERE]:
+ anywhere = el.type == XPathLexer.ANYWHERE
+ next_el = next(tokens, None)
+ if not next_el:
+ raise Exception('Missing element after %s' % el.getText())
+ else:
+ el = next_el
+ # Check for bangs
+ if el.type == XPathLexer.BANG:
+ invert = True
+ next_el = next(tokens, None)
+ if not next_el:
+ raise Exception('Missing element after %s' % el.getText())
+ else:
+ el = next_el
+ # Add searched element
+ if el.type in [XPathLexer.TOKEN_REF, XPathLexer.RULE_REF, XPathLexer.WILDCARD, XPathLexer.STRING]:
+ element = self.getXPathElement(el, anywhere)
+ element.invert = invert
+ elements.append(element)
+ elif el.type==Token.EOF:
+ break
+ else:
+ raise Exception("Unknown path element %s" % lexer.symbolicNames[el.type])
+ return elements
+
+ #
+ # Convert word like {@code#} or {@code ID} or {@code expr} to a path
+ # element. {@code anywhere} is {@code true} if {@code //} precedes the
+ # word.
+ #
+ def getXPathElement(self, wordToken:Token, anywhere:bool):
+ if wordToken.type==Token.EOF:
+ raise Exception("Missing path element at end of path")
+
+ word = wordToken.text
+ if wordToken.type==XPathLexer.WILDCARD :
+ return XPathWildcardAnywhereElement() if anywhere else XPathWildcardElement()
+
+ elif wordToken.type in [XPathLexer.TOKEN_REF, XPathLexer.STRING]:
+ tsource = self.parser.getTokenStream().tokenSource
+
+ ttype = Token.INVALID_TYPE
+ if wordToken.type == XPathLexer.TOKEN_REF:
+ if word in tsource.ruleNames:
+ ttype = tsource.ruleNames.index(word) + 1
+ else:
+ if word in tsource.literalNames:
+ ttype = tsource.literalNames.index(word)
+
+ if ttype == Token.INVALID_TYPE:
+ raise Exception("%s at index %d isn't a valid token name" % (word, wordToken.tokenIndex))
+ return XPathTokenAnywhereElement(word, ttype) if anywhere else XPathTokenElement(word, ttype)
+
+ else:
+ ruleIndex = self.parser.ruleNames.index(word) if word in self.parser.ruleNames else -1
+
+ if ruleIndex == -1:
+ raise Exception("%s at index %d isn't a valid rule name" % (word, wordToken.tokenIndex))
+ return XPathRuleAnywhereElement(word, ruleIndex) if anywhere else XPathRuleElement(word, ruleIndex)
+
+
+ @staticmethod
+ def findAll(tree:ParseTree, xpath:str, parser:Parser):
+ p = XPath(parser, xpath)
+ return p.evaluate(tree)
+
+ #
+ # Return a list of all nodes starting at {@code t} as root that satisfy the
+ # path. The root {@code /} is relative to the node passed to
+ # {@link #evaluate}.
+ #
+ def evaluate(self, t:ParseTree):
+ dummyRoot = ParserRuleContext()
+ dummyRoot.children = [t] # don't set t's parent.
+
+ work = [dummyRoot]
+ for element in self.elements:
+ work_next = list()
+ for node in work:
+ if not isinstance(node, TerminalNode) and node.children:
+ # only try to match next element if it has children
+ # e.g., //func/*/stat might have a token node for which
+ # we can't go looking for stat nodes.
+ matching = element.evaluate(node)
+
+ # See issue antlr#370 - Prevents XPath from returning the
+ # same node multiple times
+ matching = filter(lambda m: m not in work_next, matching)
+
+ work_next.extend(matching)
+ work = work_next
+
+ return work
+
+
+class XPathElement(object):
+
+ def __init__(self, nodeName:str):
+ self.nodeName = nodeName
+ self.invert = False
+
+ def __str__(self):
+ return type(self).__name__ + "[" + ("!" if self.invert else "") + self.nodeName + "]"
+
+
+
+#
+# Either {@code ID} at start of path or {@code ...//ID} in middle of path.
+#
+class XPathRuleAnywhereElement(XPathElement):
+
+ def __init__(self, ruleName:str, ruleIndex:int):
+ super().__init__(ruleName)
+ self.ruleIndex = ruleIndex
+
+ def evaluate(self, t:ParseTree):
+ # return all ParserRuleContext descendants of t that match ruleIndex (or do not match if inverted)
+ return filter(lambda c: isinstance(c, ParserRuleContext) and (self.invert ^ (c.getRuleIndex() == self.ruleIndex)), Trees.descendants(t))
+
+class XPathRuleElement(XPathElement):
+
+ def __init__(self, ruleName:str, ruleIndex:int):
+ super().__init__(ruleName)
+ self.ruleIndex = ruleIndex
+
+ def evaluate(self, t:ParseTree):
+ # return all ParserRuleContext children of t that match ruleIndex (or do not match if inverted)
+ return filter(lambda c: isinstance(c, ParserRuleContext) and (self.invert ^ (c.getRuleIndex() == self.ruleIndex)), Trees.getChildren(t))
+
+class XPathTokenAnywhereElement(XPathElement):
+
+ def __init__(self, ruleName:str, tokenType:int):
+ super().__init__(ruleName)
+ self.tokenType = tokenType
+
+ def evaluate(self, t:ParseTree):
+ # return all TerminalNode descendants of t that match tokenType (or do not match if inverted)
+ return filter(lambda c: isinstance(c, TerminalNode) and (self.invert ^ (c.symbol.type == self.tokenType)), Trees.descendants(t))
+
+class XPathTokenElement(XPathElement):
+
+ def __init__(self, ruleName:str, tokenType:int):
+ super().__init__(ruleName)
+ self.tokenType = tokenType
+
+ def evaluate(self, t:ParseTree):
+ # return all TerminalNode children of t that match tokenType (or do not match if inverted)
+ return filter(lambda c: isinstance(c, TerminalNode) and (self.invert ^ (c.symbol.type == self.tokenType)), Trees.getChildren(t))
+
+
+class XPathWildcardAnywhereElement(XPathElement):
+
+ def __init__(self):
+ super().__init__(XPath.WILDCARD)
+
+ def evaluate(self, t:ParseTree):
+ if self.invert:
+ return list() # !* is weird but valid (empty)
+ else:
+ return Trees.descendants(t)
+
+
+class XPathWildcardElement(XPathElement):
+
+ def __init__(self):
+ super().__init__(XPath.WILDCARD)
+
+
+ def evaluate(self, t:ParseTree):
+ if self.invert:
+ return list() # !* is weird but valid (empty)
+ else:
+ return Trees.getChildren(t)
diff --git a/lib/python3.12/site-packages/antlr4/xpath/__init__.py b/lib/python3.12/site-packages/antlr4/xpath/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..216c000dc5ffc8e53cc9c596e420c1e67604d1aa
--- /dev/null
+++ b/lib/python3.12/site-packages/antlr4/xpath/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ericvergnaud'
diff --git a/lib/python3.12/site-packages/antlr4/xpath/__pycache__/XPath.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/xpath/__pycache__/XPath.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ece67a311cfe4fbfadab875fcfd21096fb357e76
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/xpath/__pycache__/XPath.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/antlr4/xpath/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/antlr4/xpath/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a103fb7402469bd05b6d006467ea0ed3de555b54
Binary files /dev/null and b/lib/python3.12/site-packages/antlr4/xpath/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__init__.py b/lib/python3.12/site-packages/attr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c6e0650bc4bf53806420d7ef5f881ecd2bd77ea
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/__init__.py
@@ -0,0 +1,104 @@
+# SPDX-License-Identifier: MIT
+
+"""
+Classes Without Boilerplate
+"""
+
+from functools import partial
+from typing import Callable, Literal, Protocol
+
+from . import converters, exceptions, filters, setters, validators
+from ._cmp import cmp_using
+from ._config import get_run_validators, set_run_validators
+from ._funcs import asdict, assoc, astuple, has, resolve_types
+from ._make import (
+ NOTHING,
+ Attribute,
+ Converter,
+ Factory,
+ _Nothing,
+ attrib,
+ attrs,
+ evolve,
+ fields,
+ fields_dict,
+ make_class,
+ validate,
+)
+from ._next_gen import define, field, frozen, mutable
+from ._version_info import VersionInfo
+
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
+
+
+class AttrsInstance(Protocol):
+ pass
+
+
+NothingType = Literal[_Nothing.NOTHING]
+
+__all__ = [
+ "NOTHING",
+ "Attribute",
+ "AttrsInstance",
+ "Converter",
+ "Factory",
+ "NothingType",
+ "asdict",
+ "assoc",
+ "astuple",
+ "attr",
+ "attrib",
+ "attributes",
+ "attrs",
+ "cmp_using",
+ "converters",
+ "define",
+ "evolve",
+ "exceptions",
+ "field",
+ "fields",
+ "fields_dict",
+ "filters",
+ "frozen",
+ "get_run_validators",
+ "has",
+ "ib",
+ "make_class",
+ "mutable",
+ "resolve_types",
+ "s",
+ "set_run_validators",
+ "setters",
+ "validate",
+ "validators",
+]
+
+
+def _make_getattr(mod_name: str) -> Callable:
+ """
+ Create a metadata proxy for packaging information that uses *mod_name* in
+ its warnings and errors.
+ """
+
+ def __getattr__(name: str) -> str:
+ if name not in ("__version__", "__version_info__"):
+ msg = f"module {mod_name} has no attribute {name}"
+ raise AttributeError(msg)
+
+ from importlib.metadata import metadata
+
+ meta = metadata("attrs")
+
+ if name == "__version_info__":
+ return VersionInfo._from_version_string(meta["version"])
+
+ return meta["version"]
+
+ return __getattr__
+
+
+__getattr__ = _make_getattr(__name__)
diff --git a/lib/python3.12/site-packages/attr/__init__.pyi b/lib/python3.12/site-packages/attr/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8d78fa19abe1a529af98889eb40017c32d5b62a2
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/__init__.pyi
@@ -0,0 +1,389 @@
+import enum
+import sys
+
+from typing import (
+ Any,
+ Callable,
+ Generic,
+ Literal,
+ Mapping,
+ Protocol,
+ Sequence,
+ TypeVar,
+ overload,
+)
+
+# `import X as X` is required to make these public
+from . import converters as converters
+from . import exceptions as exceptions
+from . import filters as filters
+from . import setters as setters
+from . import validators as validators
+from ._cmp import cmp_using as cmp_using
+from ._typing_compat import AttrsInstance_
+from ._version_info import VersionInfo
+from attrs import (
+ define as define,
+ field as field,
+ mutable as mutable,
+ frozen as frozen,
+ _EqOrderType,
+ _ValidatorType,
+ _ConverterType,
+ _ReprArgType,
+ _OnSetAttrType,
+ _OnSetAttrArgType,
+ _FieldTransformer,
+ _ValidatorArgType,
+)
+
+if sys.version_info >= (3, 10):
+ from typing import TypeGuard, TypeAlias
+else:
+ from typing_extensions import TypeGuard, TypeAlias
+
+if sys.version_info >= (3, 11):
+ from typing import dataclass_transform
+else:
+ from typing_extensions import dataclass_transform
+
+__version__: str
+__version_info__: VersionInfo
+__title__: str
+__description__: str
+__url__: str
+__uri__: str
+__author__: str
+__email__: str
+__license__: str
+__copyright__: str
+
+_T = TypeVar("_T")
+_C = TypeVar("_C", bound=type)
+
+_FilterType = Callable[["Attribute[_T]", _T], bool]
+
+# We subclass this here to keep the protocol's qualified name clean.
+class AttrsInstance(AttrsInstance_, Protocol):
+ pass
+
+_A = TypeVar("_A", bound=type[AttrsInstance])
+
+class _Nothing(enum.Enum):
+ NOTHING = enum.auto()
+
+NOTHING = _Nothing.NOTHING
+NothingType: TypeAlias = Literal[_Nothing.NOTHING]
+
+# NOTE: Factory lies about its return type to make this possible:
+# `x: List[int] # = Factory(list)`
+# Work around mypy issue #4554 in the common case by using an overload.
+
+@overload
+def Factory(factory: Callable[[], _T]) -> _T: ...
+@overload
+def Factory(
+ factory: Callable[[Any], _T],
+ takes_self: Literal[True],
+) -> _T: ...
+@overload
+def Factory(
+ factory: Callable[[], _T],
+ takes_self: Literal[False],
+) -> _T: ...
+
+In = TypeVar("In")
+Out = TypeVar("Out")
+
+class Converter(Generic[In, Out]):
+ @overload
+ def __init__(self, converter: Callable[[In], Out]) -> None: ...
+ @overload
+ def __init__(
+ self,
+ converter: Callable[[In, AttrsInstance, Attribute], Out],
+ *,
+ takes_self: Literal[True],
+ takes_field: Literal[True],
+ ) -> None: ...
+ @overload
+ def __init__(
+ self,
+ converter: Callable[[In, Attribute], Out],
+ *,
+ takes_field: Literal[True],
+ ) -> None: ...
+ @overload
+ def __init__(
+ self,
+ converter: Callable[[In, AttrsInstance], Out],
+ *,
+ takes_self: Literal[True],
+ ) -> None: ...
+
+class Attribute(Generic[_T]):
+ name: str
+ default: _T | None
+ validator: _ValidatorType[_T] | None
+ repr: _ReprArgType
+ cmp: _EqOrderType
+ eq: _EqOrderType
+ order: _EqOrderType
+ hash: bool | None
+ init: bool
+ converter: Converter | None
+ metadata: dict[Any, Any]
+ type: type[_T] | None
+ kw_only: bool
+ on_setattr: _OnSetAttrType
+ alias: str | None
+
+ def evolve(self, **changes: Any) -> "Attribute[Any]": ...
+
+# NOTE: We had several choices for the annotation to use for type arg:
+# 1) Type[_T]
+# - Pros: Handles simple cases correctly
+# - Cons: Might produce less informative errors in the case of conflicting
+# TypeVars e.g. `attr.ib(default='bad', type=int)`
+# 2) Callable[..., _T]
+# - Pros: Better error messages than #1 for conflicting TypeVars
+# - Cons: Terrible error messages for validator checks.
+# e.g. attr.ib(type=int, validator=validate_str)
+# -> error: Cannot infer function type argument
+# 3) type (and do all of the work in the mypy plugin)
+# - Pros: Simple here, and we could customize the plugin with our own errors.
+# - Cons: Would need to write mypy plugin code to handle all the cases.
+# We chose option #1.
+
+# `attr` lies about its return type to make the following possible:
+# attr() -> Any
+# attr(8) -> int
+# attr(validator=) -> Whatever the callable expects.
+# This makes this type of assignments possible:
+# x: int = attr(8)
+#
+# This form catches explicit None or no default but with no other arguments
+# returns Any.
+@overload
+def attrib(
+ default: None = ...,
+ validator: None = ...,
+ repr: _ReprArgType = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ metadata: Mapping[Any, Any] | None = ...,
+ type: None = ...,
+ converter: None = ...,
+ factory: None = ...,
+ kw_only: bool | None = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ alias: str | None = ...,
+) -> Any: ...
+
+# This form catches an explicit None or no default and infers the type from the
+# other arguments.
+@overload
+def attrib(
+ default: None = ...,
+ validator: _ValidatorArgType[_T] | None = ...,
+ repr: _ReprArgType = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ metadata: Mapping[Any, Any] | None = ...,
+ type: type[_T] | None = ...,
+ converter: _ConverterType
+ | list[_ConverterType]
+ | tuple[_ConverterType]
+ | None = ...,
+ factory: Callable[[], _T] | None = ...,
+ kw_only: bool | None = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ alias: str | None = ...,
+) -> _T: ...
+
+# This form catches an explicit default argument.
+@overload
+def attrib(
+ default: _T,
+ validator: _ValidatorArgType[_T] | None = ...,
+ repr: _ReprArgType = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ metadata: Mapping[Any, Any] | None = ...,
+ type: type[_T] | None = ...,
+ converter: _ConverterType
+ | list[_ConverterType]
+ | tuple[_ConverterType]
+ | None = ...,
+ factory: Callable[[], _T] | None = ...,
+ kw_only: bool | None = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ alias: str | None = ...,
+) -> _T: ...
+
+# This form covers type=non-Type: e.g. forward references (str), Any
+@overload
+def attrib(
+ default: _T | None = ...,
+ validator: _ValidatorArgType[_T] | None = ...,
+ repr: _ReprArgType = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ metadata: Mapping[Any, Any] | None = ...,
+ type: object = ...,
+ converter: _ConverterType
+ | list[_ConverterType]
+ | tuple[_ConverterType]
+ | None = ...,
+ factory: Callable[[], _T] | None = ...,
+ kw_only: bool | None = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ alias: str | None = ...,
+) -> Any: ...
+@overload
+@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
+def attrs(
+ maybe_cls: _C,
+ these: dict[str, Any] | None = ...,
+ repr_ns: str | None = ...,
+ repr: bool = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+ auto_exc: bool = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ auto_detect: bool = ...,
+ collect_by_mro: bool = ...,
+ getstate_setstate: bool | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ field_transformer: _FieldTransformer | None = ...,
+ match_args: bool = ...,
+ unsafe_hash: bool | None = ...,
+) -> _C: ...
+@overload
+@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
+def attrs(
+ maybe_cls: None = ...,
+ these: dict[str, Any] | None = ...,
+ repr_ns: str | None = ...,
+ repr: bool = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+ auto_exc: bool = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ auto_detect: bool = ...,
+ collect_by_mro: bool = ...,
+ getstate_setstate: bool | None = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ field_transformer: _FieldTransformer | None = ...,
+ match_args: bool = ...,
+ unsafe_hash: bool | None = ...,
+) -> Callable[[_C], _C]: ...
+def fields(cls: type[AttrsInstance]) -> Any: ...
+def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
+def validate(inst: AttrsInstance) -> None: ...
+def resolve_types(
+ cls: _A,
+ globalns: dict[str, Any] | None = ...,
+ localns: dict[str, Any] | None = ...,
+ attribs: list[Attribute[Any]] | None = ...,
+ include_extras: bool = ...,
+) -> _A: ...
+
+# TODO: add support for returning a proper attrs class from the mypy plugin
+# we use Any instead of _CountingAttr so that e.g. `make_class('Foo',
+# [attr.ib()])` is valid
+def make_class(
+ name: str,
+ attrs: list[str] | tuple[str, ...] | dict[str, Any],
+ bases: tuple[type, ...] = ...,
+ class_body: dict[str, Any] | None = ...,
+ repr_ns: str | None = ...,
+ repr: bool = ...,
+ cmp: _EqOrderType | None = ...,
+ hash: bool | None = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+ auto_exc: bool = ...,
+ eq: _EqOrderType | None = ...,
+ order: _EqOrderType | None = ...,
+ collect_by_mro: bool = ...,
+ on_setattr: _OnSetAttrArgType | None = ...,
+ field_transformer: _FieldTransformer | None = ...,
+) -> type: ...
+
+# _funcs --
+
+# TODO: add support for returning TypedDict from the mypy plugin
+# FIXME: asdict/astuple do not honor their factory args. Waiting on one of
+# these:
+# https://github.com/python/mypy/issues/4236
+# https://github.com/python/typing/issues/253
+# XXX: remember to fix attrs.asdict/astuple too!
+def asdict(
+ inst: AttrsInstance,
+ recurse: bool = ...,
+ filter: _FilterType[Any] | None = ...,
+ dict_factory: type[Mapping[Any, Any]] = ...,
+ retain_collection_types: bool = ...,
+ value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ...,
+ tuple_keys: bool | None = ...,
+) -> dict[str, Any]: ...
+
+# TODO: add support for returning NamedTuple from the mypy plugin
+def astuple(
+ inst: AttrsInstance,
+ recurse: bool = ...,
+ filter: _FilterType[Any] | None = ...,
+ tuple_factory: type[Sequence[Any]] = ...,
+ retain_collection_types: bool = ...,
+) -> tuple[Any, ...]: ...
+def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ...
+def assoc(inst: _T, **changes: Any) -> _T: ...
+def evolve(inst: _T, **changes: Any) -> _T: ...
+
+# _config --
+
+def set_run_validators(run: bool) -> None: ...
+def get_run_validators() -> bool: ...
+
+# aliases --
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;)
diff --git a/lib/python3.12/site-packages/attr/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2e8da84d2a5c875bbb841f5b4f0ef0604dc8434c
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_cmp.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_cmp.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3b16bd49364caeec4e7fe54991d94c2d629e7629
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_cmp.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_compat.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_compat.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..93882c3670cf096e7ed5263f9d5a69d8e798f2d8
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_compat.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_config.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_config.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ac3be2a86231bec6104052c4c8bb94f36bf0641a
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_config.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_funcs.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_funcs.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0f56c0213fa474b21071ca5978b3587702115c19
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_funcs.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_next_gen.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_next_gen.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..39bb48431fc89f07899854cd122d69ddde2d5663
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_next_gen.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/_version_info.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/_version_info.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..48c5657bc69d0e9120dea95c60162e965b5d67d1
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/_version_info.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/converters.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/converters.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..04c4d3bcf3c2843a5f5c6ed0128db755125a9833
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/converters.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/exceptions.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/exceptions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..81a08d0b9475e38bebd1216bf0656e9878a0d0cb
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/exceptions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/filters.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/filters.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..99569bc779162d5cacce9c4fa53862c883346fd5
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/filters.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/setters.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/setters.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..83d1d4c608379d99f43571eed93e4821656a986b
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/setters.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/__pycache__/validators.cpython-312.pyc b/lib/python3.12/site-packages/attr/__pycache__/validators.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..01c4d4dd44abb1fd02500753fcc12778e901776d
Binary files /dev/null and b/lib/python3.12/site-packages/attr/__pycache__/validators.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/attr/_cmp.py b/lib/python3.12/site-packages/attr/_cmp.py
new file mode 100644
index 0000000000000000000000000000000000000000..09bab491f83ef4d15129f34b5f5a9e69bb34d63c
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_cmp.py
@@ -0,0 +1,160 @@
+# SPDX-License-Identifier: MIT
+
+
+import functools
+import types
+
+from ._make import __ne__
+
+
+_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
+
+
+def cmp_using(
+ eq=None,
+ lt=None,
+ le=None,
+ gt=None,
+ ge=None,
+ require_same_type=True,
+ class_name="Comparable",
+):
+ """
+ Create a class that can be passed into `attrs.field`'s ``eq``, ``order``,
+ and ``cmp`` arguments to customize field comparison.
+
+ The resulting class will have a full set of ordering methods if at least
+ one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
+
+ Args:
+ eq (typing.Callable | None):
+ Callable used to evaluate equality of two objects.
+
+ lt (typing.Callable | None):
+ Callable used to evaluate whether one object is less than another
+ object.
+
+ le (typing.Callable | None):
+ Callable used to evaluate whether one object is less than or equal
+ to another object.
+
+ gt (typing.Callable | None):
+ Callable used to evaluate whether one object is greater than
+ another object.
+
+ ge (typing.Callable | None):
+ Callable used to evaluate whether one object is greater than or
+ equal to another object.
+
+ require_same_type (bool):
+ When `True`, equality and ordering methods will return
+ `NotImplemented` if objects are not of the same type.
+
+ class_name (str | None): Name of class. Defaults to "Comparable".
+
+ See `comparison` for more details.
+
+ .. versionadded:: 21.1.0
+ """
+
+ body = {
+ "__slots__": ["value"],
+ "__init__": _make_init(),
+ "_requirements": [],
+ "_is_comparable_to": _is_comparable_to,
+ }
+
+ # Add operations.
+ num_order_functions = 0
+ has_eq_function = False
+
+ if eq is not None:
+ has_eq_function = True
+ body["__eq__"] = _make_operator("eq", eq)
+ body["__ne__"] = __ne__
+
+ if lt is not None:
+ num_order_functions += 1
+ body["__lt__"] = _make_operator("lt", lt)
+
+ if le is not None:
+ num_order_functions += 1
+ body["__le__"] = _make_operator("le", le)
+
+ if gt is not None:
+ num_order_functions += 1
+ body["__gt__"] = _make_operator("gt", gt)
+
+ if ge is not None:
+ num_order_functions += 1
+ body["__ge__"] = _make_operator("ge", ge)
+
+ type_ = types.new_class(
+ class_name, (object,), {}, lambda ns: ns.update(body)
+ )
+
+ # Add same type requirement.
+ if require_same_type:
+ type_._requirements.append(_check_same_type)
+
+ # Add total ordering if at least one operation was defined.
+ if 0 < num_order_functions < 4:
+ if not has_eq_function:
+ # functools.total_ordering requires __eq__ to be defined,
+ # so raise early error here to keep a nice stack.
+ msg = "eq must be define is order to complete ordering from lt, le, gt, ge."
+ raise ValueError(msg)
+ type_ = functools.total_ordering(type_)
+
+ return type_
+
+
+def _make_init():
+ """
+ Create __init__ method.
+ """
+
+ def __init__(self, value):
+ """
+ Initialize object with *value*.
+ """
+ self.value = value
+
+ return __init__
+
+
+def _make_operator(name, func):
+ """
+ Create operator method.
+ """
+
+ def method(self, other):
+ if not self._is_comparable_to(other):
+ return NotImplemented
+
+ result = func(self.value, other.value)
+ if result is NotImplemented:
+ return NotImplemented
+
+ return result
+
+ method.__name__ = f"__{name}__"
+ method.__doc__ = (
+ f"Return a {_operation_names[name]} b. Computed by attrs."
+ )
+
+ return method
+
+
+def _is_comparable_to(self, other):
+ """
+ Check whether `other` is comparable to `self`.
+ """
+ return all(func(self, other) for func in self._requirements)
+
+
+def _check_same_type(self, other):
+ """
+ Return True if *self* and *other* are of the same type, False otherwise.
+ """
+ return other.value.__class__ is self.value.__class__
diff --git a/lib/python3.12/site-packages/attr/_cmp.pyi b/lib/python3.12/site-packages/attr/_cmp.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..cc7893b04520afa719b1412c7646c3c1b39bf94b
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_cmp.pyi
@@ -0,0 +1,13 @@
+from typing import Any, Callable
+
+_CompareWithType = Callable[[Any, Any], bool]
+
+def cmp_using(
+ eq: _CompareWithType | None = ...,
+ lt: _CompareWithType | None = ...,
+ le: _CompareWithType | None = ...,
+ gt: _CompareWithType | None = ...,
+ ge: _CompareWithType | None = ...,
+ require_same_type: bool = ...,
+ class_name: str = ...,
+) -> type: ...
diff --git a/lib/python3.12/site-packages/attr/_compat.py b/lib/python3.12/site-packages/attr/_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc68ed9eaf9853bcc6065e7691209a787bf2eccc
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_compat.py
@@ -0,0 +1,99 @@
+# SPDX-License-Identifier: MIT
+
+import inspect
+import platform
+import sys
+import threading
+
+from collections.abc import Mapping, Sequence # noqa: F401
+from typing import _GenericAlias
+
+
+PYPY = platform.python_implementation() == "PyPy"
+PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
+PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
+PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
+PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
+PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
+
+
+if PY_3_14_PLUS:
+ import annotationlib
+
+ # We request forward-ref annotations to not break in the presence of
+ # forward references.
+
+ def _get_annotations(cls):
+ return annotationlib.get_annotations(
+ cls, format=annotationlib.Format.FORWARDREF
+ )
+
+else:
+
+ def _get_annotations(cls):
+ """
+ Get annotations for *cls*.
+ """
+ return cls.__dict__.get("__annotations__", {})
+
+
+class _AnnotationExtractor:
+ """
+ Extract type annotations from a callable, returning None whenever there
+ is none.
+ """
+
+ __slots__ = ["sig"]
+
+ def __init__(self, callable):
+ try:
+ self.sig = inspect.signature(callable)
+ except (ValueError, TypeError): # inspect failed
+ self.sig = None
+
+ def get_first_param_type(self):
+ """
+ Return the type annotation of the first argument if it's not empty.
+ """
+ if not self.sig:
+ return None
+
+ params = list(self.sig.parameters.values())
+ if params and params[0].annotation is not inspect.Parameter.empty:
+ return params[0].annotation
+
+ return None
+
+ def get_return_type(self):
+ """
+ Return the return type if it's not empty.
+ """
+ if (
+ self.sig
+ and self.sig.return_annotation is not inspect.Signature.empty
+ ):
+ return self.sig.return_annotation
+
+ return None
+
+
+# Thread-local global to track attrs instances which are already being repr'd.
+# This is needed because there is no other (thread-safe) way to pass info
+# about the instances that are already being repr'd through the call stack
+# in order to ensure we don't perform infinite recursion.
+#
+# For instance, if an instance contains a dict which contains that instance,
+# we need to know that we're already repr'ing the outside instance from within
+# the dict's repr() call.
+#
+# This lives here rather than in _make.py so that the functions in _make.py
+# don't have a direct reference to the thread-local in their globals dict.
+# If they have such a reference, it breaks cloudpickle.
+repr_context = threading.local()
+
+
+def get_generic_base(cl):
+ """If this is a generic class (A[str]), return the generic base for it."""
+ if cl.__class__ is _GenericAlias:
+ return cl.__origin__
+ return None
diff --git a/lib/python3.12/site-packages/attr/_config.py b/lib/python3.12/site-packages/attr/_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b257726fb1e8b95583ecc3eee8d153336dc4089
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_config.py
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: MIT
+
+__all__ = ["get_run_validators", "set_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+ """
+ Set whether or not validators are run. By default, they are run.
+
+ .. deprecated:: 21.3.0 It will not be removed, but it also will not be
+ moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()`
+ instead.
+ """
+ if not isinstance(run, bool):
+ msg = "'run' must be bool."
+ raise TypeError(msg)
+ global _run_validators
+ _run_validators = run
+
+
+def get_run_validators():
+ """
+ Return whether or not validators are run.
+
+ .. deprecated:: 21.3.0 It will not be removed, but it also will not be
+ moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()`
+ instead.
+ """
+ return _run_validators
diff --git a/lib/python3.12/site-packages/attr/_funcs.py b/lib/python3.12/site-packages/attr/_funcs.py
new file mode 100644
index 0000000000000000000000000000000000000000..1adb50021373d9c09fcb9db0641bbc03248d54a3
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_funcs.py
@@ -0,0 +1,497 @@
+# SPDX-License-Identifier: MIT
+
+
+import copy
+
+from ._compat import get_generic_base
+from ._make import _OBJ_SETATTR, NOTHING, fields
+from .exceptions import AttrsAttributeNotFoundError
+
+
+_ATOMIC_TYPES = frozenset(
+ {
+ type(None),
+ bool,
+ int,
+ float,
+ str,
+ complex,
+ bytes,
+ type(...),
+ type,
+ range,
+ property,
+ }
+)
+
+
+def asdict(
+ inst,
+ recurse=True,
+ filter=None,
+ dict_factory=dict,
+ retain_collection_types=False,
+ value_serializer=None,
+):
+ """
+ Return the *attrs* attribute values of *inst* as a dict.
+
+ Optionally recurse into other *attrs*-decorated classes.
+
+ Args:
+ inst: Instance of an *attrs*-decorated class.
+
+ recurse (bool): Recurse into classes that are also *attrs*-decorated.
+
+ filter (~typing.Callable):
+ A callable whose return code determines whether an attribute or
+ element is included (`True`) or dropped (`False`). Is called with
+ the `attrs.Attribute` as the first argument and the value as the
+ second argument.
+
+ dict_factory (~typing.Callable):
+ A callable to produce dictionaries from. For example, to produce
+ ordered dictionaries instead of normal Python dictionaries, pass in
+ ``collections.OrderedDict``.
+
+ retain_collection_types (bool):
+ Do not convert to `list` when encountering an attribute whose type
+ is `tuple` or `set`. Only meaningful if *recurse* is `True`.
+
+ value_serializer (typing.Callable | None):
+ A hook that is called for every attribute or dict key/value. It
+ receives the current instance, field and value and must return the
+ (updated) value. The hook is run *after* the optional *filter* has
+ been applied.
+
+ Returns:
+ Return type of *dict_factory*.
+
+ Raises:
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ .. versionadded:: 16.0.0 *dict_factory*
+ .. versionadded:: 16.1.0 *retain_collection_types*
+ .. versionadded:: 20.3.0 *value_serializer*
+ .. versionadded:: 21.3.0
+ If a dict has a collection for a key, it is serialized as a tuple.
+ """
+ attrs = fields(inst.__class__)
+ rv = dict_factory()
+ for a in attrs:
+ v = getattr(inst, a.name)
+ if filter is not None and not filter(a, v):
+ continue
+
+ if value_serializer is not None:
+ v = value_serializer(inst, a, v)
+
+ if recurse is True:
+ value_type = type(v)
+ if value_type in _ATOMIC_TYPES:
+ rv[a.name] = v
+ elif has(value_type):
+ rv[a.name] = asdict(
+ v,
+ recurse=True,
+ filter=filter,
+ dict_factory=dict_factory,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ )
+ elif issubclass(value_type, (tuple, list, set, frozenset)):
+ cf = value_type if retain_collection_types is True else list
+ items = [
+ _asdict_anything(
+ i,
+ is_key=False,
+ filter=filter,
+ dict_factory=dict_factory,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ )
+ for i in v
+ ]
+ try:
+ rv[a.name] = cf(items)
+ except TypeError:
+ if not issubclass(cf, tuple):
+ raise
+ # Workaround for TypeError: cf.__new__() missing 1 required
+ # positional argument (which appears, for a namedturle)
+ rv[a.name] = cf(*items)
+ elif issubclass(value_type, dict):
+ df = dict_factory
+ rv[a.name] = df(
+ (
+ _asdict_anything(
+ kk,
+ is_key=True,
+ filter=filter,
+ dict_factory=df,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ ),
+ _asdict_anything(
+ vv,
+ is_key=False,
+ filter=filter,
+ dict_factory=df,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ ),
+ )
+ for kk, vv in v.items()
+ )
+ else:
+ rv[a.name] = v
+ else:
+ rv[a.name] = v
+ return rv
+
+
+def _asdict_anything(
+ val,
+ is_key,
+ filter,
+ dict_factory,
+ retain_collection_types,
+ value_serializer,
+):
+ """
+ ``asdict`` only works on attrs instances, this works on anything.
+ """
+ val_type = type(val)
+ if val_type in _ATOMIC_TYPES:
+ rv = val
+ if value_serializer is not None:
+ rv = value_serializer(None, None, rv)
+ elif getattr(val_type, "__attrs_attrs__", None) is not None:
+ # Attrs class.
+ rv = asdict(
+ val,
+ recurse=True,
+ filter=filter,
+ dict_factory=dict_factory,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ )
+ elif issubclass(val_type, (tuple, list, set, frozenset)):
+ if retain_collection_types is True:
+ cf = val.__class__
+ elif is_key:
+ cf = tuple
+ else:
+ cf = list
+
+ rv = cf(
+ [
+ _asdict_anything(
+ i,
+ is_key=False,
+ filter=filter,
+ dict_factory=dict_factory,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ )
+ for i in val
+ ]
+ )
+ elif issubclass(val_type, dict):
+ df = dict_factory
+ rv = df(
+ (
+ _asdict_anything(
+ kk,
+ is_key=True,
+ filter=filter,
+ dict_factory=df,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ ),
+ _asdict_anything(
+ vv,
+ is_key=False,
+ filter=filter,
+ dict_factory=df,
+ retain_collection_types=retain_collection_types,
+ value_serializer=value_serializer,
+ ),
+ )
+ for kk, vv in val.items()
+ )
+ else:
+ rv = val
+ if value_serializer is not None:
+ rv = value_serializer(None, None, rv)
+
+ return rv
+
+
+def astuple(
+ inst,
+ recurse=True,
+ filter=None,
+ tuple_factory=tuple,
+ retain_collection_types=False,
+):
+ """
+ Return the *attrs* attribute values of *inst* as a tuple.
+
+ Optionally recurse into other *attrs*-decorated classes.
+
+ Args:
+ inst: Instance of an *attrs*-decorated class.
+
+ recurse (bool):
+ Recurse into classes that are also *attrs*-decorated.
+
+ filter (~typing.Callable):
+ A callable whose return code determines whether an attribute or
+ element is included (`True`) or dropped (`False`). Is called with
+ the `attrs.Attribute` as the first argument and the value as the
+ second argument.
+
+ tuple_factory (~typing.Callable):
+ A callable to produce tuples from. For example, to produce lists
+ instead of tuples.
+
+ retain_collection_types (bool):
+ Do not convert to `list` or `dict` when encountering an attribute
+ which type is `tuple`, `dict` or `set`. Only meaningful if
+ *recurse* is `True`.
+
+ Returns:
+ Return type of *tuple_factory*
+
+ Raises:
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ .. versionadded:: 16.2.0
+ """
+ attrs = fields(inst.__class__)
+ rv = []
+ retain = retain_collection_types # Very long. :/
+ for a in attrs:
+ v = getattr(inst, a.name)
+ if filter is not None and not filter(a, v):
+ continue
+ value_type = type(v)
+ if recurse is True:
+ if value_type in _ATOMIC_TYPES:
+ rv.append(v)
+ elif has(value_type):
+ rv.append(
+ astuple(
+ v,
+ recurse=True,
+ filter=filter,
+ tuple_factory=tuple_factory,
+ retain_collection_types=retain,
+ )
+ )
+ elif issubclass(value_type, (tuple, list, set, frozenset)):
+ cf = v.__class__ if retain is True else list
+ items = [
+ (
+ astuple(
+ j,
+ recurse=True,
+ filter=filter,
+ tuple_factory=tuple_factory,
+ retain_collection_types=retain,
+ )
+ if has(j.__class__)
+ else j
+ )
+ for j in v
+ ]
+ try:
+ rv.append(cf(items))
+ except TypeError:
+ if not issubclass(cf, tuple):
+ raise
+ # Workaround for TypeError: cf.__new__() missing 1 required
+ # positional argument (which appears, for a namedturle)
+ rv.append(cf(*items))
+ elif issubclass(value_type, dict):
+ df = value_type if retain is True else dict
+ rv.append(
+ df(
+ (
+ (
+ astuple(
+ kk,
+ tuple_factory=tuple_factory,
+ retain_collection_types=retain,
+ )
+ if has(kk.__class__)
+ else kk
+ ),
+ (
+ astuple(
+ vv,
+ tuple_factory=tuple_factory,
+ retain_collection_types=retain,
+ )
+ if has(vv.__class__)
+ else vv
+ ),
+ )
+ for kk, vv in v.items()
+ )
+ )
+ else:
+ rv.append(v)
+ else:
+ rv.append(v)
+
+ return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+ """
+ Check whether *cls* is a class with *attrs* attributes.
+
+ Args:
+ cls (type): Class to introspect.
+
+ Raises:
+ TypeError: If *cls* is not a class.
+
+ Returns:
+ bool:
+ """
+ attrs = getattr(cls, "__attrs_attrs__", None)
+ if attrs is not None:
+ return True
+
+ # No attrs, maybe it's a specialized generic (A[str])?
+ generic_base = get_generic_base(cls)
+ if generic_base is not None:
+ generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
+ if generic_attrs is not None:
+ # Stick it on here for speed next time.
+ cls.__attrs_attrs__ = generic_attrs
+ return generic_attrs is not None
+ return False
+
+
+def assoc(inst, **changes):
+ """
+ Copy *inst* and apply *changes*.
+
+ This is different from `evolve` that applies the changes to the arguments
+ that create the new instance.
+
+ `evolve`'s behavior is preferable, but there are `edge cases`_ where it
+ doesn't work. Therefore `assoc` is deprecated, but will not be removed.
+
+ .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
+
+ Args:
+ inst: Instance of a class with *attrs* attributes.
+
+ changes: Keyword changes in the new copy.
+
+ Returns:
+ A copy of inst with *changes* incorporated.
+
+ Raises:
+ attrs.exceptions.AttrsAttributeNotFoundError:
+ If *attr_name* couldn't be found on *cls*.
+
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ .. deprecated:: 17.1.0
+ Use `attrs.evolve` instead if you can. This function will not be
+ removed du to the slightly different approach compared to
+ `attrs.evolve`, though.
+ """
+ new = copy.copy(inst)
+ attrs = fields(inst.__class__)
+ for k, v in changes.items():
+ a = getattr(attrs, k, NOTHING)
+ if a is NOTHING:
+ msg = f"{k} is not an attrs attribute on {new.__class__}."
+ raise AttrsAttributeNotFoundError(msg)
+ _OBJ_SETATTR(new, k, v)
+ return new
+
+
+def resolve_types(
+ cls, globalns=None, localns=None, attribs=None, include_extras=True
+):
+ """
+ Resolve any strings and forward annotations in type annotations.
+
+ This is only required if you need concrete types in :class:`Attribute`'s
+ *type* field. In other words, you don't need to resolve your types if you
+ only use them for static type checking.
+
+ With no arguments, names will be looked up in the module in which the class
+ was created. If this is not what you want, for example, if the name only
+ exists inside a method, you may pass *globalns* or *localns* to specify
+ other dictionaries in which to look up these names. See the docs of
+ `typing.get_type_hints` for more details.
+
+ Args:
+ cls (type): Class to resolve.
+
+ globalns (dict | None): Dictionary containing global variables.
+
+ localns (dict | None): Dictionary containing local variables.
+
+ attribs (list | None):
+ List of attribs for the given class. This is necessary when calling
+ from inside a ``field_transformer`` since *cls* is not an *attrs*
+ class yet.
+
+ include_extras (bool):
+ Resolve more accurately, if possible. Pass ``include_extras`` to
+ ``typing.get_hints``, if supported by the typing module. On
+ supported Python versions (3.9+), this resolves the types more
+ accurately.
+
+ Raises:
+ TypeError: If *cls* is not a class.
+
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class and you didn't pass any attribs.
+
+ NameError: If types cannot be resolved because of missing variables.
+
+ Returns:
+ *cls* so you can use this function also as a class decorator. Please
+ note that you have to apply it **after** `attrs.define`. That means the
+ decorator has to come in the line **before** `attrs.define`.
+
+ .. versionadded:: 20.1.0
+ .. versionadded:: 21.1.0 *attribs*
+ .. versionadded:: 23.1.0 *include_extras*
+ """
+ # Since calling get_type_hints is expensive we cache whether we've
+ # done it already.
+ if getattr(cls, "__attrs_types_resolved__", None) != cls:
+ import typing
+
+ kwargs = {
+ "globalns": globalns,
+ "localns": localns,
+ "include_extras": include_extras,
+ }
+
+ hints = typing.get_type_hints(cls, **kwargs)
+ for field in fields(cls) if attribs is None else attribs:
+ if field.name in hints:
+ # Since fields have been frozen we must work around it.
+ _OBJ_SETATTR(field, "type", hints[field.name])
+ # We store the class we resolved so that subclasses know they haven't
+ # been resolved.
+ cls.__attrs_types_resolved__ = cls
+
+ # Return the class so you can use it as a decorator too.
+ return cls
diff --git a/lib/python3.12/site-packages/attr/_make.py b/lib/python3.12/site-packages/attr/_make.py
new file mode 100644
index 0000000000000000000000000000000000000000..d24d9ba98575c3c4beb3186ca0335d2175663c7f
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_make.py
@@ -0,0 +1,3362 @@
+# SPDX-License-Identifier: MIT
+
+from __future__ import annotations
+
+import abc
+import contextlib
+import copy
+import enum
+import inspect
+import itertools
+import linecache
+import sys
+import types
+import unicodedata
+import weakref
+
+from collections.abc import Callable, Mapping
+from functools import cached_property
+from typing import Any, NamedTuple, TypeVar
+
+# We need to import _compat itself in addition to the _compat members to avoid
+# having the thread-local in the globals here.
+from . import _compat, _config, setters
+from ._compat import (
+ PY_3_10_PLUS,
+ PY_3_11_PLUS,
+ PY_3_13_PLUS,
+ _AnnotationExtractor,
+ _get_annotations,
+ get_generic_base,
+)
+from .exceptions import (
+ DefaultAlreadySetError,
+ FrozenInstanceError,
+ NotAnAttrsClassError,
+ UnannotatedAttributeError,
+)
+
+
+# This is used at least twice, so cache it here.
+_OBJ_SETATTR = object.__setattr__
+_INIT_FACTORY_PAT = "__attr_factory_%s"
+_CLASSVAR_PREFIXES = (
+ "typing.ClassVar",
+ "t.ClassVar",
+ "ClassVar",
+ "typing_extensions.ClassVar",
+)
+# we don't use a double-underscore prefix because that triggers
+# name mangling when trying to create a slot for the field
+# (when slots=True)
+_HASH_CACHE_FIELD = "_attrs_cached_hash"
+
+_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})
+
+# Unique object for unequivocal getattr() defaults.
+_SENTINEL = object()
+
+_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)
+
+
+class _Nothing(enum.Enum):
+ """
+ Sentinel to indicate the lack of a value when `None` is ambiguous.
+
+ If extending attrs, you can use ``typing.Literal[NOTHING]`` to show
+ that a value may be ``NOTHING``.
+
+ .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.
+ .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.
+ """
+
+ NOTHING = enum.auto()
+
+ def __repr__(self):
+ return "NOTHING"
+
+ def __bool__(self):
+ return False
+
+
+NOTHING = _Nothing.NOTHING
+"""
+Sentinel to indicate the lack of a value when `None` is ambiguous.
+
+When using in 3rd party code, use `attrs.NothingType` for type annotations.
+"""
+
+
+class _CacheHashWrapper(int):
+ """
+ An integer subclass that pickles / copies as None
+
+ This is used for non-slots classes with ``cache_hash=True``, to avoid
+ serializing a potentially (even likely) invalid hash value. Since `None`
+ is the default value for uncalculated hashes, whenever this is copied,
+ the copy's value for the hash should automatically reset.
+
+ See GH #613 for more details.
+ """
+
+ def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
+ return _none_constructor, _args
+
+
+def attrib(
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ hash=None,
+ init=True,
+ metadata=None,
+ type=None,
+ converter=None,
+ factory=None,
+ kw_only=None,
+ eq=None,
+ order=None,
+ on_setattr=None,
+ alias=None,
+):
+ """
+ Create a new field / attribute on a class.
+
+ Identical to `attrs.field`, except it's not keyword-only.
+
+ Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,
+ though).
+
+ .. warning::
+
+ Does **nothing** unless the class is also decorated with
+ `attr.s` (or similar)!
+
+
+ .. versionadded:: 15.2.0 *convert*
+ .. versionadded:: 16.3.0 *metadata*
+ .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+ .. versionchanged:: 17.1.0
+ *hash* is `None` and therefore mirrors *eq* by default.
+ .. versionadded:: 17.3.0 *type*
+ .. deprecated:: 17.4.0 *convert*
+ .. versionadded:: 17.4.0
+ *converter* as a replacement for the deprecated *convert* to achieve
+ consistency with other noun-based arguments.
+ .. versionadded:: 18.1.0
+ ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
+ .. versionadded:: 18.2.0 *kw_only*
+ .. versionchanged:: 19.2.0 *convert* keyword argument removed.
+ .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.
+ .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
+ .. versionadded:: 19.2.0 *eq* and *order*
+ .. versionadded:: 20.1.0 *on_setattr*
+ .. versionchanged:: 20.3.0 *kw_only* backported to Python 2
+ .. versionchanged:: 21.1.0
+ *eq*, *order*, and *cmp* also accept a custom callable
+ .. versionchanged:: 21.1.0 *cmp* undeprecated
+ .. versionadded:: 22.2.0 *alias*
+ .. versionchanged:: 25.4.0
+ *kw_only* can now be None, and its default is also changed from False to
+ None.
+ """
+ eq, eq_key, order, order_key = _determine_attrib_eq_order(
+ cmp, eq, order, True
+ )
+
+ if hash is not None and hash is not True and hash is not False:
+ msg = "Invalid value for hash. Must be True, False, or None."
+ raise TypeError(msg)
+
+ if factory is not None:
+ if default is not NOTHING:
+ msg = (
+ "The `default` and `factory` arguments are mutually exclusive."
+ )
+ raise ValueError(msg)
+ if not callable(factory):
+ msg = "The `factory` argument must be a callable."
+ raise ValueError(msg)
+ default = Factory(factory)
+
+ if metadata is None:
+ metadata = {}
+
+ # Apply syntactic sugar by auto-wrapping.
+ if isinstance(on_setattr, (list, tuple)):
+ on_setattr = setters.pipe(*on_setattr)
+
+ if validator and isinstance(validator, (list, tuple)):
+ validator = and_(*validator)
+
+ if converter and isinstance(converter, (list, tuple)):
+ converter = pipe(*converter)
+
+ return _CountingAttr(
+ default=default,
+ validator=validator,
+ repr=repr,
+ cmp=None,
+ hash=hash,
+ init=init,
+ converter=converter,
+ metadata=metadata,
+ type=type,
+ kw_only=kw_only,
+ eq=eq,
+ eq_key=eq_key,
+ order=order,
+ order_key=order_key,
+ on_setattr=on_setattr,
+ alias=alias,
+ )
+
+
+def _compile_and_eval(
+ script: str,
+ globs: dict[str, Any] | None,
+ locs: Mapping[str, object] | None = None,
+ filename: str = "",
+) -> None:
+ """
+ Evaluate the script with the given global (globs) and local (locs)
+ variables.
+ """
+ bytecode = compile(script, filename, "exec")
+ eval(bytecode, globs, locs)
+
+
+def _linecache_and_compile(
+ script: str,
+ filename: str,
+ globs: dict[str, Any] | None,
+ locals: Mapping[str, object] | None = None,
+) -> dict[str, Any]:
+ """
+ Cache the script with _linecache_, compile it and return the _locals_.
+ """
+
+ locs = {} if locals is None else locals
+
+ # In order of debuggers like PDB being able to step through the code,
+ # we add a fake linecache entry.
+ count = 1
+ base_filename = filename
+ while True:
+ linecache_tuple = (
+ len(script),
+ None,
+ script.splitlines(True),
+ filename,
+ )
+ old_val = linecache.cache.setdefault(filename, linecache_tuple)
+ if old_val == linecache_tuple:
+ break
+
+ filename = f"{base_filename[:-1]}-{count}>"
+ count += 1
+
+ _compile_and_eval(script, globs, locs, filename)
+
+ return locs
+
+
+def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type:
+ """
+ Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+ The subclass is a bare tuple with properties for names.
+
+ class MyClassAttributes(tuple):
+ __slots__ = ()
+ x = property(itemgetter(0))
+ """
+ attr_class_name = f"{cls_name}Attributes"
+ body = {}
+ for i, attr_name in enumerate(attr_names):
+
+ def getter(self, i=i):
+ return self[i]
+
+ body[attr_name] = property(getter)
+ return type(attr_class_name, (tuple,), body)
+
+
+# Tuple class for extracted attributes from a class definition.
+# `base_attrs` is a subset of `attrs`.
+class _Attributes(NamedTuple):
+ attrs: type
+ base_attrs: list[Attribute]
+ base_attrs_map: dict[str, type]
+
+
+def _is_class_var(annot):
+ """
+ Check whether *annot* is a typing.ClassVar.
+
+ The string comparison hack is used to avoid evaluating all string
+ annotations which would put attrs-based classes at a performance
+ disadvantage compared to plain old classes.
+ """
+ annot = str(annot)
+
+ # Annotation can be quoted.
+ if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):
+ annot = annot[1:-1]
+
+ return annot.startswith(_CLASSVAR_PREFIXES)
+
+
+def _has_own_attribute(cls, attrib_name):
+ """
+ Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
+ """
+ return attrib_name in cls.__dict__
+
+
+def _collect_base_attrs(
+ cls, taken_attr_names
+) -> tuple[list[Attribute], dict[str, type]]:
+ """
+ Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
+ """
+ base_attrs = []
+ base_attr_map = {} # A dictionary of base attrs to their classes.
+
+ # Traverse the MRO and collect attributes.
+ for base_cls in reversed(cls.__mro__[1:-1]):
+ for a in getattr(base_cls, "__attrs_attrs__", []):
+ if a.inherited or a.name in taken_attr_names:
+ continue
+
+ a = a.evolve(inherited=True) # noqa: PLW2901
+ base_attrs.append(a)
+ base_attr_map[a.name] = base_cls
+
+ # For each name, only keep the freshest definition i.e. the furthest at the
+ # back. base_attr_map is fine because it gets overwritten with every new
+ # instance.
+ filtered = []
+ seen = set()
+ for a in reversed(base_attrs):
+ if a.name in seen:
+ continue
+ filtered.insert(0, a)
+ seen.add(a.name)
+
+ return filtered, base_attr_map
+
+
+def _collect_base_attrs_broken(cls, taken_attr_names):
+ """
+ Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.
+
+ N.B. *taken_attr_names* will be mutated.
+
+ Adhere to the old incorrect behavior.
+
+ Notably it collects from the front and considers inherited attributes which
+ leads to the buggy behavior reported in #428.
+ """
+ base_attrs = []
+ base_attr_map = {} # A dictionary of base attrs to their classes.
+
+ # Traverse the MRO and collect attributes.
+ for base_cls in cls.__mro__[1:-1]:
+ for a in getattr(base_cls, "__attrs_attrs__", []):
+ if a.name in taken_attr_names:
+ continue
+
+ a = a.evolve(inherited=True) # noqa: PLW2901
+ taken_attr_names.add(a.name)
+ base_attrs.append(a)
+ base_attr_map[a.name] = base_cls
+
+ return base_attrs, base_attr_map
+
+
+def _transform_attrs(
+ cls,
+ these,
+ auto_attribs,
+ kw_only,
+ collect_by_mro,
+ field_transformer,
+) -> _Attributes:
+ """
+ Transform all `_CountingAttr`s on a class into `Attribute`s.
+
+ If *these* is passed, use that and don't look for them on the class.
+
+ If *collect_by_mro* is True, collect them in the correct MRO order,
+ otherwise use the old -- incorrect -- order. See #428.
+
+ Return an `_Attributes`.
+ """
+ cd = cls.__dict__
+ anns = _get_annotations(cls)
+
+ if these is not None:
+ ca_list = list(these.items())
+ elif auto_attribs is True:
+ ca_names = {
+ name
+ for name, attr in cd.items()
+ if attr.__class__ is _CountingAttr
+ }
+ ca_list = []
+ annot_names = set()
+ for attr_name, type in anns.items():
+ if _is_class_var(type):
+ continue
+ annot_names.add(attr_name)
+ a = cd.get(attr_name, NOTHING)
+
+ if a.__class__ is not _CountingAttr:
+ a = attrib(a)
+ ca_list.append((attr_name, a))
+
+ unannotated = ca_names - annot_names
+ if unannotated:
+ raise UnannotatedAttributeError(
+ "The following `attr.ib`s lack a type annotation: "
+ + ", ".join(
+ sorted(unannotated, key=lambda n: cd.get(n).counter)
+ )
+ + "."
+ )
+ else:
+ ca_list = sorted(
+ (
+ (name, attr)
+ for name, attr in cd.items()
+ if attr.__class__ is _CountingAttr
+ ),
+ key=lambda e: e[1].counter,
+ )
+
+ fca = Attribute.from_counting_attr
+ no = ClassProps.KeywordOnly.NO
+ own_attrs = [
+ fca(
+ attr_name,
+ ca,
+ kw_only is not no,
+ anns.get(attr_name),
+ )
+ for attr_name, ca in ca_list
+ ]
+
+ if collect_by_mro:
+ base_attrs, base_attr_map = _collect_base_attrs(
+ cls, {a.name for a in own_attrs}
+ )
+ else:
+ base_attrs, base_attr_map = _collect_base_attrs_broken(
+ cls, {a.name for a in own_attrs}
+ )
+
+ if kw_only is ClassProps.KeywordOnly.FORCE:
+ own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
+ base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
+
+ attrs = base_attrs + own_attrs
+
+ if field_transformer is not None:
+ attrs = tuple(field_transformer(cls, attrs))
+
+ # Check attr order after executing the field_transformer.
+ # Mandatory vs non-mandatory attr order only matters when they are part of
+ # the __init__ signature and when they aren't kw_only (which are moved to
+ # the end and can be mandatory or non-mandatory in any order, as they will
+ # be specified as keyword args anyway). Check the order of those attrs:
+ had_default = False
+ for a in (a for a in attrs if a.init is not False and a.kw_only is False):
+ if had_default is True and a.default is NOTHING:
+ msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}"
+ raise ValueError(msg)
+
+ if had_default is False and a.default is not NOTHING:
+ had_default = True
+
+ # Resolve default field alias after executing field_transformer.
+ # This allows field_transformer to differentiate between explicit vs
+ # default aliases and supply their own defaults.
+ for a in attrs:
+ if not a.alias:
+ # Evolve is very slow, so we hold our nose and do it dirty.
+ _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
+
+ # Create AttrsClass *after* applying the field_transformer since it may
+ # add or remove attributes!
+ attr_names = [a.name for a in attrs]
+ AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+ return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)
+
+
+def _make_cached_property_getattr(cached_properties, original_getattr, cls):
+ lines = [
+ # Wrapped to get `__class__` into closure cell for super()
+ # (It will be replaced with the newly constructed class after construction).
+ "def wrapper(_cls):",
+ " __class__ = _cls",
+ " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",
+ " func = cached_properties.get(item)",
+ " if func is not None:",
+ " result = func(self)",
+ " _setter = _cached_setattr_get(self)",
+ " _setter(item, result)",
+ " return result",
+ ]
+ if original_getattr is not None:
+ lines.append(
+ " return original_getattr(self, item)",
+ )
+ else:
+ lines.extend(
+ [
+ " try:",
+ " return super().__getattribute__(item)",
+ " except AttributeError:",
+ " if not hasattr(super(), '__getattr__'):",
+ " raise",
+ " return super().__getattr__(item)",
+ " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
+ " raise AttributeError(original_error)",
+ ]
+ )
+
+ lines.extend(
+ [
+ " return __getattr__",
+ "__getattr__ = wrapper(_cls)",
+ ]
+ )
+
+ unique_filename = _generate_unique_filename(cls, "getattr")
+
+ glob = {
+ "cached_properties": cached_properties,
+ "_cached_setattr_get": _OBJ_SETATTR.__get__,
+ "original_getattr": original_getattr,
+ }
+
+ return _linecache_and_compile(
+ "\n".join(lines), unique_filename, glob, locals={"_cls": cls}
+ )["__getattr__"]
+
+
+def _frozen_setattrs(self, name, value):
+ """
+ Attached to frozen classes as __setattr__.
+ """
+ if isinstance(self, BaseException) and name in (
+ "__cause__",
+ "__context__",
+ "__traceback__",
+ "__suppress_context__",
+ "__notes__",
+ ):
+ BaseException.__setattr__(self, name, value)
+ return
+
+ raise FrozenInstanceError
+
+
+def _frozen_delattrs(self, name):
+ """
+ Attached to frozen classes as __delattr__.
+ """
+ if isinstance(self, BaseException) and name in ("__notes__",):
+ BaseException.__delattr__(self, name)
+ return
+
+ raise FrozenInstanceError
+
+
+def evolve(*args, **changes):
+ """
+ Create a new instance, based on the first positional argument with
+ *changes* applied.
+
+ .. tip::
+
+ On Python 3.13 and later, you can also use `copy.replace` instead.
+
+ Args:
+
+ inst:
+ Instance of a class with *attrs* attributes. *inst* must be passed
+ as a positional argument.
+
+ changes:
+ Keyword changes in the new copy.
+
+ Returns:
+ A copy of inst with *changes* incorporated.
+
+ Raises:
+ TypeError:
+ If *attr_name* couldn't be found in the class ``__init__``.
+
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ .. versionadded:: 17.1.0
+ .. deprecated:: 23.1.0
+ It is now deprecated to pass the instance using the keyword argument
+ *inst*. It will raise a warning until at least April 2024, after which
+ it will become an error. Always pass the instance as a positional
+ argument.
+ .. versionchanged:: 24.1.0
+ *inst* can't be passed as a keyword argument anymore.
+ """
+ try:
+ (inst,) = args
+ except ValueError:
+ msg = (
+ f"evolve() takes 1 positional argument, but {len(args)} were given"
+ )
+ raise TypeError(msg) from None
+
+ cls = inst.__class__
+ attrs = fields(cls)
+ for a in attrs:
+ if not a.init:
+ continue
+ attr_name = a.name # To deal with private attributes.
+ init_name = a.alias
+ if init_name not in changes:
+ changes[init_name] = getattr(inst, attr_name)
+
+ return cls(**changes)
+
+
+class _ClassBuilder:
+ """
+ Iteratively build *one* class.
+ """
+
+ __slots__ = (
+ "_add_method_dunders",
+ "_attr_names",
+ "_attrs",
+ "_base_attr_map",
+ "_base_names",
+ "_cache_hash",
+ "_cls",
+ "_cls_dict",
+ "_delete_attribs",
+ "_frozen",
+ "_has_custom_setattr",
+ "_has_post_init",
+ "_has_pre_init",
+ "_is_exc",
+ "_on_setattr",
+ "_pre_init_has_args",
+ "_repr_added",
+ "_script_snippets",
+ "_slots",
+ "_weakref_slot",
+ "_wrote_own_setattr",
+ )
+
+ def __init__(
+ self,
+ cls: type,
+ these,
+ auto_attribs: bool,
+ props: ClassProps,
+ has_custom_setattr: bool,
+ ):
+ attrs, base_attrs, base_map = _transform_attrs(
+ cls,
+ these,
+ auto_attribs,
+ props.kw_only,
+ props.collected_fields_by_mro,
+ props.field_transformer,
+ )
+
+ self._cls = cls
+ self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
+ self._attrs = attrs
+ self._base_names = {a.name for a in base_attrs}
+ self._base_attr_map = base_map
+ self._attr_names = tuple(a.name for a in attrs)
+ self._slots = props.is_slotted
+ self._frozen = props.is_frozen
+ self._weakref_slot = props.has_weakref_slot
+ self._cache_hash = (
+ props.hashability is ClassProps.Hashability.HASHABLE_CACHED
+ )
+ self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
+ self._pre_init_has_args = False
+ if self._has_pre_init:
+ # Check if the pre init method has more arguments than just `self`
+ # We want to pass arguments if pre init expects arguments
+ pre_init_func = cls.__attrs_pre_init__
+ pre_init_signature = inspect.signature(pre_init_func)
+ self._pre_init_has_args = len(pre_init_signature.parameters) > 1
+ self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
+ self._delete_attribs = not bool(these)
+ self._is_exc = props.is_exception
+ self._on_setattr = props.on_setattr_hook
+
+ self._has_custom_setattr = has_custom_setattr
+ self._wrote_own_setattr = False
+
+ self._cls_dict["__attrs_attrs__"] = self._attrs
+ self._cls_dict["__attrs_props__"] = props
+
+ if props.is_frozen:
+ self._cls_dict["__setattr__"] = _frozen_setattrs
+ self._cls_dict["__delattr__"] = _frozen_delattrs
+
+ self._wrote_own_setattr = True
+ elif self._on_setattr in (
+ _DEFAULT_ON_SETATTR,
+ setters.validate,
+ setters.convert,
+ ):
+ has_validator = has_converter = False
+ for a in attrs:
+ if a.validator is not None:
+ has_validator = True
+ if a.converter is not None:
+ has_converter = True
+
+ if has_validator and has_converter:
+ break
+ if (
+ (
+ self._on_setattr == _DEFAULT_ON_SETATTR
+ and not (has_validator or has_converter)
+ )
+ or (self._on_setattr == setters.validate and not has_validator)
+ or (self._on_setattr == setters.convert and not has_converter)
+ ):
+ # If class-level on_setattr is set to convert + validate, but
+ # there's no field to convert or validate, pretend like there's
+ # no on_setattr.
+ self._on_setattr = None
+
+ if props.added_pickling:
+ (
+ self._cls_dict["__getstate__"],
+ self._cls_dict["__setstate__"],
+ ) = self._make_getstate_setstate()
+
+ # tuples of script, globs, hook
+ self._script_snippets: list[
+ tuple[str, dict, Callable[[dict, dict], Any]]
+ ] = []
+ self._repr_added = False
+
+ # We want to only do this check once; in 99.9% of cases these
+ # exist.
+ if not hasattr(self._cls, "__module__") or not hasattr(
+ self._cls, "__qualname__"
+ ):
+ self._add_method_dunders = self._add_method_dunders_safe
+ else:
+ self._add_method_dunders = self._add_method_dunders_unsafe
+
+ def __repr__(self):
+ return f"<_ClassBuilder(cls={self._cls.__name__})>"
+
+ def _eval_snippets(self) -> None:
+ """
+ Evaluate any registered snippets in one go.
+ """
+ script = "\n".join([snippet[0] for snippet in self._script_snippets])
+ globs = {}
+ for _, snippet_globs, _ in self._script_snippets:
+ globs.update(snippet_globs)
+
+ locs = _linecache_and_compile(
+ script,
+ _generate_unique_filename(self._cls, "methods"),
+ globs,
+ )
+
+ for _, _, hook in self._script_snippets:
+ hook(self._cls_dict, locs)
+
+ def build_class(self):
+ """
+ Finalize class based on the accumulated configuration.
+
+ Builder cannot be used after calling this method.
+ """
+ self._eval_snippets()
+ if self._slots is True:
+ cls = self._create_slots_class()
+ self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
+ else:
+ cls = self._patch_original_class()
+ if PY_3_10_PLUS:
+ cls = abc.update_abstractmethods(cls)
+
+ # The method gets only called if it's not inherited from a base class.
+ # _has_own_attribute does NOT work properly for classmethods.
+ if (
+ getattr(cls, "__attrs_init_subclass__", None)
+ and "__attrs_init_subclass__" not in cls.__dict__
+ ):
+ cls.__attrs_init_subclass__()
+
+ return cls
+
+ def _patch_original_class(self):
+ """
+ Apply accumulated methods and return the class.
+ """
+ cls = self._cls
+ base_names = self._base_names
+
+ # Clean class of attribute definitions (`attr.ib()`s).
+ if self._delete_attribs:
+ for name in self._attr_names:
+ if (
+ name not in base_names
+ and getattr(cls, name, _SENTINEL) is not _SENTINEL
+ ):
+ # An AttributeError can happen if a base class defines a
+ # class variable and we want to set an attribute with the
+ # same name by using only a type annotation.
+ with contextlib.suppress(AttributeError):
+ delattr(cls, name)
+
+ # Attach our dunder methods.
+ for name, value in self._cls_dict.items():
+ setattr(cls, name, value)
+
+ # If we've inherited an attrs __setattr__ and don't write our own,
+ # reset it to object's.
+ if not self._wrote_own_setattr and getattr(
+ cls, "__attrs_own_setattr__", False
+ ):
+ cls.__attrs_own_setattr__ = False
+
+ if not self._has_custom_setattr:
+ cls.__setattr__ = _OBJ_SETATTR
+
+ return cls
+
+ def _create_slots_class(self):
+ """
+ Build and return a new class with a `__slots__` attribute.
+ """
+ cd = {
+ k: v
+ for k, v in self._cls_dict.items()
+ if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
+ }
+
+ # 3.14.0rc2+
+ if hasattr(sys, "_clear_type_descriptors"):
+ sys._clear_type_descriptors(self._cls)
+
+ # If our class doesn't have its own implementation of __setattr__
+ # (either from the user or by us), check the bases, if one of them has
+ # an attrs-made __setattr__, that needs to be reset. We don't walk the
+ # MRO because we only care about our immediate base classes.
+ # XXX: This can be confused by subclassing a slotted attrs class with
+ # XXX: a non-attrs class and subclass the resulting class with an attrs
+ # XXX: class. See `test_slotted_confused` for details. For now that's
+ # XXX: OK with us.
+ if not self._wrote_own_setattr:
+ cd["__attrs_own_setattr__"] = False
+
+ if not self._has_custom_setattr:
+ for base_cls in self._cls.__bases__:
+ if base_cls.__dict__.get("__attrs_own_setattr__", False):
+ cd["__setattr__"] = _OBJ_SETATTR
+ break
+
+ # Traverse the MRO to collect existing slots
+ # and check for an existing __weakref__.
+ existing_slots = {}
+ weakref_inherited = False
+ for base_cls in self._cls.__mro__[1:-1]:
+ if base_cls.__dict__.get("__weakref__", None) is not None:
+ weakref_inherited = True
+ existing_slots.update(
+ {
+ name: getattr(base_cls, name)
+ for name in getattr(base_cls, "__slots__", [])
+ }
+ )
+
+ base_names = set(self._base_names)
+
+ names = self._attr_names
+ if (
+ self._weakref_slot
+ and "__weakref__" not in getattr(self._cls, "__slots__", ())
+ and "__weakref__" not in names
+ and not weakref_inherited
+ ):
+ names += ("__weakref__",)
+
+ cached_properties = {
+ name: cached_prop.func
+ for name, cached_prop in cd.items()
+ if isinstance(cached_prop, cached_property)
+ }
+
+ # Collect methods with a `__class__` reference that are shadowed in the new class.
+ # To know to update them.
+ additional_closure_functions_to_update = []
+ if cached_properties:
+ class_annotations = _get_annotations(self._cls)
+ for name, func in cached_properties.items():
+ # Add cached properties to names for slotting.
+ names += (name,)
+ # Clear out function from class to avoid clashing.
+ del cd[name]
+ additional_closure_functions_to_update.append(func)
+ annotation = inspect.signature(func).return_annotation
+ if annotation is not inspect.Parameter.empty:
+ class_annotations[name] = annotation
+
+ original_getattr = cd.get("__getattr__")
+ if original_getattr is not None:
+ additional_closure_functions_to_update.append(original_getattr)
+
+ cd["__getattr__"] = _make_cached_property_getattr(
+ cached_properties, original_getattr, self._cls
+ )
+
+ # We only add the names of attributes that aren't inherited.
+ # Setting __slots__ to inherited attributes wastes memory.
+ slot_names = [name for name in names if name not in base_names]
+
+ # There are slots for attributes from current class
+ # that are defined in parent classes.
+ # As their descriptors may be overridden by a child class,
+ # we collect them here and update the class dict
+ reused_slots = {
+ slot: slot_descriptor
+ for slot, slot_descriptor in existing_slots.items()
+ if slot in slot_names
+ }
+ slot_names = [name for name in slot_names if name not in reused_slots]
+ cd.update(reused_slots)
+ if self._cache_hash:
+ slot_names.append(_HASH_CACHE_FIELD)
+
+ cd["__slots__"] = tuple(slot_names)
+
+ cd["__qualname__"] = self._cls.__qualname__
+
+ # Create new class based on old class and our methods.
+ cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
+
+ # The following is a fix for
+ # .
+ # If a method mentions `__class__` or uses the no-arg super(), the
+ # compiler will bake a reference to the class in the method itself
+ # as `method.__closure__`. Since we replace the class with a
+ # clone, we rewrite these references so it keeps working.
+ for item in itertools.chain(
+ cls.__dict__.values(), additional_closure_functions_to_update
+ ):
+ if isinstance(item, (classmethod, staticmethod)):
+ # Class- and staticmethods hide their functions inside.
+ # These might need to be rewritten as well.
+ closure_cells = getattr(item.__func__, "__closure__", None)
+ elif isinstance(item, property):
+ # Workaround for property `super()` shortcut (PY3-only).
+ # There is no universal way for other descriptors.
+ closure_cells = getattr(item.fget, "__closure__", None)
+ else:
+ closure_cells = getattr(item, "__closure__", None)
+
+ if not closure_cells: # Catch None or the empty list.
+ continue
+ for cell in closure_cells:
+ try:
+ match = cell.cell_contents is self._cls
+ except ValueError: # noqa: PERF203
+ # ValueError: Cell is empty
+ pass
+ else:
+ if match:
+ cell.cell_contents = cls
+ return cls
+
+ def add_repr(self, ns):
+ script, globs = _make_repr_script(self._attrs, ns)
+
+ def _attach_repr(cls_dict, globs):
+ cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])
+
+ self._script_snippets.append((script, globs, _attach_repr))
+ self._repr_added = True
+ return self
+
+ def add_str(self):
+ if not self._repr_added:
+ msg = "__str__ can only be generated if a __repr__ exists."
+ raise ValueError(msg)
+
+ def __str__(self):
+ return self.__repr__()
+
+ self._cls_dict["__str__"] = self._add_method_dunders(__str__)
+ return self
+
+ def _make_getstate_setstate(self):
+ """
+ Create custom __setstate__ and __getstate__ methods.
+ """
+ # __weakref__ is not writable.
+ state_attr_names = tuple(
+ an for an in self._attr_names if an != "__weakref__"
+ )
+
+ def slots_getstate(self):
+ """
+ Automatically created by attrs.
+ """
+ return {name: getattr(self, name) for name in state_attr_names}
+
+ hash_caching_enabled = self._cache_hash
+
+ def slots_setstate(self, state):
+ """
+ Automatically created by attrs.
+ """
+ __bound_setattr = _OBJ_SETATTR.__get__(self)
+ if isinstance(state, tuple):
+ # Backward compatibility with attrs instances pickled with
+ # attrs versions before v22.2.0 which stored tuples.
+ for name, value in zip(state_attr_names, state):
+ __bound_setattr(name, value)
+ else:
+ for name in state_attr_names:
+ if name in state:
+ __bound_setattr(name, state[name])
+
+ # The hash code cache is not included when the object is
+ # serialized, but it still needs to be initialized to None to
+ # indicate that the first call to __hash__ should be a cache
+ # miss.
+ if hash_caching_enabled:
+ __bound_setattr(_HASH_CACHE_FIELD, None)
+
+ return slots_getstate, slots_setstate
+
+ def make_unhashable(self):
+ self._cls_dict["__hash__"] = None
+ return self
+
+ def add_hash(self):
+ script, globs = _make_hash_script(
+ self._cls,
+ self._attrs,
+ frozen=self._frozen,
+ cache_hash=self._cache_hash,
+ )
+
+ def attach_hash(cls_dict: dict, locs: dict) -> None:
+ cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])
+
+ self._script_snippets.append((script, globs, attach_hash))
+
+ return self
+
+ def add_init(self):
+ script, globs, annotations = _make_init_script(
+ self._cls,
+ self._attrs,
+ self._has_pre_init,
+ self._pre_init_has_args,
+ self._has_post_init,
+ self._frozen,
+ self._slots,
+ self._cache_hash,
+ self._base_attr_map,
+ self._is_exc,
+ self._on_setattr,
+ attrs_init=False,
+ )
+
+ def _attach_init(cls_dict, globs):
+ init = globs["__init__"]
+ init.__annotations__ = annotations
+ cls_dict["__init__"] = self._add_method_dunders(init)
+
+ self._script_snippets.append((script, globs, _attach_init))
+
+ return self
+
+ def add_replace(self):
+ self._cls_dict["__replace__"] = self._add_method_dunders(
+ lambda self, **changes: evolve(self, **changes)
+ )
+ return self
+
+ def add_match_args(self):
+ self._cls_dict["__match_args__"] = tuple(
+ field.name
+ for field in self._attrs
+ if field.init and not field.kw_only
+ )
+
+ def add_attrs_init(self):
+ script, globs, annotations = _make_init_script(
+ self._cls,
+ self._attrs,
+ self._has_pre_init,
+ self._pre_init_has_args,
+ self._has_post_init,
+ self._frozen,
+ self._slots,
+ self._cache_hash,
+ self._base_attr_map,
+ self._is_exc,
+ self._on_setattr,
+ attrs_init=True,
+ )
+
+ def _attach_attrs_init(cls_dict, globs):
+ init = globs["__attrs_init__"]
+ init.__annotations__ = annotations
+ cls_dict["__attrs_init__"] = self._add_method_dunders(init)
+
+ self._script_snippets.append((script, globs, _attach_attrs_init))
+
+ return self
+
+ def add_eq(self):
+ cd = self._cls_dict
+
+ script, globs = _make_eq_script(self._attrs)
+
+ def _attach_eq(cls_dict, globs):
+ cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])
+
+ self._script_snippets.append((script, globs, _attach_eq))
+
+ cd["__ne__"] = __ne__
+
+ return self
+
+ def add_order(self):
+ cd = self._cls_dict
+
+ cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (
+ self._add_method_dunders(meth)
+ for meth in _make_order(self._cls, self._attrs)
+ )
+
+ return self
+
+ def add_setattr(self):
+ sa_attrs = {}
+ for a in self._attrs:
+ on_setattr = a.on_setattr or self._on_setattr
+ if on_setattr and on_setattr is not setters.NO_OP:
+ sa_attrs[a.name] = a, on_setattr
+
+ if not sa_attrs:
+ return self
+
+ if self._has_custom_setattr:
+ # We need to write a __setattr__ but there already is one!
+ msg = "Can't combine custom __setattr__ with on_setattr hooks."
+ raise ValueError(msg)
+
+ # docstring comes from _add_method_dunders
+ def __setattr__(self, name, val):
+ try:
+ a, hook = sa_attrs[name]
+ except KeyError:
+ nval = val
+ else:
+ nval = hook(self, a, val)
+
+ _OBJ_SETATTR(self, name, nval)
+
+ self._cls_dict["__attrs_own_setattr__"] = True
+ self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)
+ self._wrote_own_setattr = True
+
+ return self
+
+ def _add_method_dunders_unsafe(self, method: Callable) -> Callable:
+ """
+ Add __module__ and __qualname__ to a *method*.
+ """
+ method.__module__ = self._cls.__module__
+
+ method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
+
+ method.__doc__ = (
+ f"Method generated by attrs for class {self._cls.__qualname__}."
+ )
+
+ return method
+
+ def _add_method_dunders_safe(self, method: Callable) -> Callable:
+ """
+ Add __module__ and __qualname__ to a *method* if possible.
+ """
+ with contextlib.suppress(AttributeError):
+ method.__module__ = self._cls.__module__
+
+ with contextlib.suppress(AttributeError):
+ method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}"
+
+ with contextlib.suppress(AttributeError):
+ method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}."
+
+ return method
+
+
+def _determine_attrs_eq_order(cmp, eq, order, default_eq):
+ """
+ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
+ values of eq and order. If *eq* is None, set it to *default_eq*.
+ """
+ if cmp is not None and any((eq is not None, order is not None)):
+ msg = "Don't mix `cmp` with `eq' and `order`."
+ raise ValueError(msg)
+
+ # cmp takes precedence due to bw-compatibility.
+ if cmp is not None:
+ return cmp, cmp
+
+ # If left None, equality is set to the specified default and ordering
+ # mirrors equality.
+ if eq is None:
+ eq = default_eq
+
+ if order is None:
+ order = eq
+
+ if eq is False and order is True:
+ msg = "`order` can only be True if `eq` is True too."
+ raise ValueError(msg)
+
+ return eq, order
+
+
+def _determine_attrib_eq_order(cmp, eq, order, default_eq):
+ """
+ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
+ values of eq and order. If *eq* is None, set it to *default_eq*.
+ """
+ if cmp is not None and any((eq is not None, order is not None)):
+ msg = "Don't mix `cmp` with `eq' and `order`."
+ raise ValueError(msg)
+
+ def decide_callable_or_boolean(value):
+ """
+ Decide whether a key function is used.
+ """
+ if callable(value):
+ value, key = True, value
+ else:
+ key = None
+ return value, key
+
+ # cmp takes precedence due to bw-compatibility.
+ if cmp is not None:
+ cmp, cmp_key = decide_callable_or_boolean(cmp)
+ return cmp, cmp_key, cmp, cmp_key
+
+ # If left None, equality is set to the specified default and ordering
+ # mirrors equality.
+ if eq is None:
+ eq, eq_key = default_eq, None
+ else:
+ eq, eq_key = decide_callable_or_boolean(eq)
+
+ if order is None:
+ order, order_key = eq, eq_key
+ else:
+ order, order_key = decide_callable_or_boolean(order)
+
+ if eq is False and order is True:
+ msg = "`order` can only be True if `eq` is True too."
+ raise ValueError(msg)
+
+ return eq, eq_key, order, order_key
+
+
+def _determine_whether_to_implement(
+ cls, flag, auto_detect, dunders, default=True
+):
+ """
+ Check whether we should implement a set of methods for *cls*.
+
+ *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
+ same as passed into @attr.s and *dunders* is a tuple of attribute names
+ whose presence signal that the user has implemented it themselves.
+
+ Return *default* if no reason for either for or against is found.
+ """
+ if flag is True or flag is False:
+ return flag
+
+ if flag is None and auto_detect is False:
+ return default
+
+ # Logically, flag is None and auto_detect is True here.
+ for dunder in dunders:
+ if _has_own_attribute(cls, dunder):
+ return False
+
+ return default
+
+
+def attrs(
+ maybe_cls=None,
+ these=None,
+ repr_ns=None,
+ repr=None,
+ cmp=None,
+ hash=None,
+ init=None,
+ slots=False,
+ frozen=False,
+ weakref_slot=True,
+ str=False,
+ auto_attribs=False,
+ kw_only=False,
+ cache_hash=False,
+ auto_exc=False,
+ eq=None,
+ order=None,
+ auto_detect=False,
+ collect_by_mro=False,
+ getstate_setstate=None,
+ on_setattr=None,
+ field_transformer=None,
+ match_args=True,
+ unsafe_hash=None,
+ force_kw_only=True,
+):
+ r"""
+ A class decorator that adds :term:`dunder methods` according to the
+ specified attributes using `attr.ib` or the *these* argument.
+
+ Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will
+ *never* go away, though).
+
+ Args:
+ repr_ns (str):
+ When using nested classes, there was no way in Python 2 to
+ automatically detect that. This argument allows to set a custom
+ name for a more meaningful ``repr`` output. This argument is
+ pointless in Python 3 and is therefore deprecated.
+
+ .. caution::
+ Refer to `attrs.define` for the rest of the parameters, but note that they
+ can have different defaults.
+
+ Notably, leaving *on_setattr* as `None` will **not** add any hooks.
+
+ .. versionadded:: 16.0.0 *slots*
+ .. versionadded:: 16.1.0 *frozen*
+ .. versionadded:: 16.3.0 *str*
+ .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
+ .. versionchanged:: 17.1.0
+ *hash* supports `None` as value which is also the default now.
+ .. versionadded:: 17.3.0 *auto_attribs*
+ .. versionchanged:: 18.1.0
+ If *these* is passed, no attributes are deleted from the class body.
+ .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
+ .. versionadded:: 18.2.0 *weakref_slot*
+ .. deprecated:: 18.2.0
+ ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a
+ `DeprecationWarning` if the classes compared are subclasses of
+ each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
+ to each other.
+ .. versionchanged:: 19.2.0
+ ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
+ subclasses comparable anymore.
+ .. versionadded:: 18.2.0 *kw_only*
+ .. versionadded:: 18.2.0 *cache_hash*
+ .. versionadded:: 19.1.0 *auto_exc*
+ .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
+ .. versionadded:: 19.2.0 *eq* and *order*
+ .. versionadded:: 20.1.0 *auto_detect*
+ .. versionadded:: 20.1.0 *collect_by_mro*
+ .. versionadded:: 20.1.0 *getstate_setstate*
+ .. versionadded:: 20.1.0 *on_setattr*
+ .. versionadded:: 20.3.0 *field_transformer*
+ .. versionchanged:: 21.1.0
+ ``init=False`` injects ``__attrs_init__``
+ .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__``
+ .. versionchanged:: 21.1.0 *cmp* undeprecated
+ .. versionadded:: 21.3.0 *match_args*
+ .. versionadded:: 22.2.0
+ *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
+ .. deprecated:: 24.1.0 *repr_ns*
+ .. versionchanged:: 24.1.0
+ Instances are not compared as tuples of attributes anymore, but using a
+ big ``and`` condition. This is faster and has more correct behavior for
+ uncomparable values like `math.nan`.
+ .. versionadded:: 24.1.0
+ If a class has an *inherited* classmethod called
+ ``__attrs_init_subclass__``, it is executed after the class is created.
+ .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
+ .. versionchanged:: 25.4.0
+ *kw_only* now only applies to attributes defined in the current class,
+ and respects attribute-level ``kw_only=False`` settings.
+ .. versionadded:: 25.4.0 *force_kw_only*
+ """
+ if repr_ns is not None:
+ import warnings
+
+ warnings.warn(
+ DeprecationWarning(
+ "The `repr_ns` argument is deprecated and will be removed in or after August 2025."
+ ),
+ stacklevel=2,
+ )
+
+ eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None)
+
+ # unsafe_hash takes precedence due to PEP 681.
+ if unsafe_hash is not None:
+ hash = unsafe_hash
+
+ if isinstance(on_setattr, (list, tuple)):
+ on_setattr = setters.pipe(*on_setattr)
+
+ def wrap(cls):
+ nonlocal hash
+ is_frozen = frozen or _has_frozen_base_class(cls)
+ is_exc = auto_exc is True and issubclass(cls, BaseException)
+ has_own_setattr = auto_detect and _has_own_attribute(
+ cls, "__setattr__"
+ )
+
+ if has_own_setattr and is_frozen:
+ msg = "Can't freeze a class with a custom __setattr__."
+ raise ValueError(msg)
+
+ eq = not is_exc and _determine_whether_to_implement(
+ cls, eq_, auto_detect, ("__eq__", "__ne__")
+ )
+
+ Hashability = ClassProps.Hashability
+
+ if is_exc:
+ hashability = Hashability.LEAVE_ALONE
+ elif hash is True:
+ hashability = (
+ Hashability.HASHABLE_CACHED
+ if cache_hash
+ else Hashability.HASHABLE
+ )
+ elif hash is False:
+ hashability = Hashability.LEAVE_ALONE
+ elif hash is None:
+ if auto_detect is True and _has_own_attribute(cls, "__hash__"):
+ hashability = Hashability.LEAVE_ALONE
+ elif eq is True and is_frozen is True:
+ hashability = (
+ Hashability.HASHABLE_CACHED
+ if cache_hash
+ else Hashability.HASHABLE
+ )
+ elif eq is False:
+ hashability = Hashability.LEAVE_ALONE
+ else:
+ hashability = Hashability.UNHASHABLE
+ else:
+ msg = "Invalid value for hash. Must be True, False, or None."
+ raise TypeError(msg)
+
+ KeywordOnly = ClassProps.KeywordOnly
+ if kw_only:
+ kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
+ else:
+ kwo = KeywordOnly.NO
+
+ props = ClassProps(
+ is_exception=is_exc,
+ is_frozen=is_frozen,
+ is_slotted=slots,
+ collected_fields_by_mro=collect_by_mro,
+ added_init=_determine_whether_to_implement(
+ cls, init, auto_detect, ("__init__",)
+ ),
+ added_repr=_determine_whether_to_implement(
+ cls, repr, auto_detect, ("__repr__",)
+ ),
+ added_eq=eq,
+ added_ordering=not is_exc
+ and _determine_whether_to_implement(
+ cls,
+ order_,
+ auto_detect,
+ ("__lt__", "__le__", "__gt__", "__ge__"),
+ ),
+ hashability=hashability,
+ added_match_args=match_args,
+ kw_only=kwo,
+ has_weakref_slot=weakref_slot,
+ added_str=str,
+ added_pickling=_determine_whether_to_implement(
+ cls,
+ getstate_setstate,
+ auto_detect,
+ ("__getstate__", "__setstate__"),
+ default=slots,
+ ),
+ on_setattr_hook=on_setattr,
+ field_transformer=field_transformer,
+ )
+
+ if not props.is_hashable and cache_hash:
+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
+ raise TypeError(msg)
+
+ builder = _ClassBuilder(
+ cls,
+ these,
+ auto_attribs=auto_attribs,
+ props=props,
+ has_custom_setattr=has_own_setattr,
+ )
+
+ if props.added_repr:
+ builder.add_repr(repr_ns)
+
+ if props.added_str:
+ builder.add_str()
+
+ if props.added_eq:
+ builder.add_eq()
+ if props.added_ordering:
+ builder.add_order()
+
+ if not frozen:
+ builder.add_setattr()
+
+ if props.is_hashable:
+ builder.add_hash()
+ elif props.hashability is Hashability.UNHASHABLE:
+ builder.make_unhashable()
+
+ if props.added_init:
+ builder.add_init()
+ else:
+ builder.add_attrs_init()
+ if cache_hash:
+ msg = "Invalid value for cache_hash. To use hash caching, init must be True."
+ raise TypeError(msg)
+
+ if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"):
+ builder.add_replace()
+
+ if (
+ PY_3_10_PLUS
+ and match_args
+ and not _has_own_attribute(cls, "__match_args__")
+ ):
+ builder.add_match_args()
+
+ return builder.build_class()
+
+ # maybe_cls's type depends on the usage of the decorator. It's a class
+ # if it's used as `@attrs` but `None` if used as `@attrs()`.
+ if maybe_cls is None:
+ return wrap
+
+ return wrap(maybe_cls)
+
+
+_attrs = attrs
+"""
+Internal alias so we can use it in functions that take an argument called
+*attrs*.
+"""
+
+
+def _has_frozen_base_class(cls):
+ """
+ Check whether *cls* has a frozen ancestor by looking at its
+ __setattr__.
+ """
+ return cls.__setattr__ is _frozen_setattrs
+
+
+def _generate_unique_filename(cls: type, func_name: str) -> str:
+ """
+ Create a "filename" suitable for a function being generated.
+ """
+ return (
+ f""
+ )
+
+
+def _make_hash_script(
+ cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool
+) -> tuple[str, dict]:
+ attrs = tuple(
+ a for a in attrs if a.hash is True or (a.hash is None and a.eq is True)
+ )
+
+ tab = " "
+
+ type_hash = hash(_generate_unique_filename(cls, "hash"))
+ # If eq is custom generated, we need to include the functions in globs
+ globs = {}
+
+ hash_def = "def __hash__(self"
+ hash_func = "hash(("
+ closing_braces = "))"
+ if not cache_hash:
+ hash_def += "):"
+ else:
+ hash_def += ", *"
+
+ hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):"
+ hash_func = "_cache_wrapper(" + hash_func
+ closing_braces += ")"
+
+ method_lines = [hash_def]
+
+ def append_hash_computation_lines(prefix, indent):
+ """
+ Generate the code for actually computing the hash code.
+ Below this will either be returned directly or used to compute
+ a value which is then cached, depending on the value of cache_hash
+ """
+
+ method_lines.extend(
+ [
+ indent + prefix + hash_func,
+ indent + f" {type_hash},",
+ ]
+ )
+
+ for a in attrs:
+ if a.eq_key:
+ cmp_name = f"_{a.name}_key"
+ globs[cmp_name] = a.eq_key
+ method_lines.append(
+ indent + f" {cmp_name}(self.{a.name}),"
+ )
+ else:
+ method_lines.append(indent + f" self.{a.name},")
+
+ method_lines.append(indent + " " + closing_braces)
+
+ if cache_hash:
+ method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:")
+ if frozen:
+ append_hash_computation_lines(
+ f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2
+ )
+ method_lines.append(tab * 2 + ")") # close __setattr__
+ else:
+ append_hash_computation_lines(
+ f"self.{_HASH_CACHE_FIELD} = ", tab * 2
+ )
+ method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}")
+ else:
+ append_hash_computation_lines("return ", tab)
+
+ script = "\n".join(method_lines)
+ return script, globs
+
+
+def _add_hash(cls: type, attrs: list[Attribute]):
+ """
+ Add a hash method to *cls*.
+ """
+ script, globs = _make_hash_script(
+ cls, attrs, frozen=False, cache_hash=False
+ )
+ _compile_and_eval(
+ script, globs, filename=_generate_unique_filename(cls, "__hash__")
+ )
+ cls.__hash__ = globs["__hash__"]
+ return cls
+
+
+def __ne__(self, other):
+ """
+ Check equality and either forward a NotImplemented or
+ return the result negated.
+ """
+ result = self.__eq__(other)
+ if result is NotImplemented:
+ return NotImplemented
+
+ return not result
+
+
+def _make_eq_script(attrs: list) -> tuple[str, dict]:
+ """
+ Create __eq__ method for *cls* with *attrs*.
+ """
+ attrs = [a for a in attrs if a.eq]
+
+ lines = [
+ "def __eq__(self, other):",
+ " if other.__class__ is not self.__class__:",
+ " return NotImplemented",
+ ]
+
+ globs = {}
+ if attrs:
+ lines.append(" return (")
+ for a in attrs:
+ if a.eq_key:
+ cmp_name = f"_{a.name}_key"
+ # Add the key function to the global namespace
+ # of the evaluated function.
+ globs[cmp_name] = a.eq_key
+ lines.append(
+ f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})"
+ )
+ else:
+ lines.append(f" self.{a.name} == other.{a.name}")
+ if a is not attrs[-1]:
+ lines[-1] = f"{lines[-1]} and"
+ lines.append(" )")
+ else:
+ lines.append(" return True")
+
+ script = "\n".join(lines)
+
+ return script, globs
+
+
+def _make_order(cls, attrs):
+ """
+ Create ordering methods for *cls* with *attrs*.
+ """
+ attrs = [a for a in attrs if a.order]
+
+ def attrs_to_tuple(obj):
+ """
+ Save us some typing.
+ """
+ return tuple(
+ key(value) if key else value
+ for value, key in (
+ (getattr(obj, a.name), a.order_key) for a in attrs
+ )
+ )
+
+ def __lt__(self, other):
+ """
+ Automatically created by attrs.
+ """
+ if other.__class__ is self.__class__:
+ return attrs_to_tuple(self) < attrs_to_tuple(other)
+
+ return NotImplemented
+
+ def __le__(self, other):
+ """
+ Automatically created by attrs.
+ """
+ if other.__class__ is self.__class__:
+ return attrs_to_tuple(self) <= attrs_to_tuple(other)
+
+ return NotImplemented
+
+ def __gt__(self, other):
+ """
+ Automatically created by attrs.
+ """
+ if other.__class__ is self.__class__:
+ return attrs_to_tuple(self) > attrs_to_tuple(other)
+
+ return NotImplemented
+
+ def __ge__(self, other):
+ """
+ Automatically created by attrs.
+ """
+ if other.__class__ is self.__class__:
+ return attrs_to_tuple(self) >= attrs_to_tuple(other)
+
+ return NotImplemented
+
+ return __lt__, __le__, __gt__, __ge__
+
+
+def _add_eq(cls, attrs=None):
+ """
+ Add equality methods to *cls* with *attrs*.
+ """
+ if attrs is None:
+ attrs = cls.__attrs_attrs__
+
+ script, globs = _make_eq_script(attrs)
+ _compile_and_eval(
+ script, globs, filename=_generate_unique_filename(cls, "__eq__")
+ )
+ cls.__eq__ = globs["__eq__"]
+ cls.__ne__ = __ne__
+
+ return cls
+
+
+def _make_repr_script(attrs, ns) -> tuple[str, dict]:
+ """
+ Create the source and globs for a __repr__ and return it.
+ """
+ # Figure out which attributes to include, and which function to use to
+ # format them. The a.repr value can be either bool or a custom
+ # callable.
+ attr_names_with_reprs = tuple(
+ (a.name, (repr if a.repr is True else a.repr), a.init)
+ for a in attrs
+ if a.repr is not False
+ )
+ globs = {
+ name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr
+ }
+ globs["_compat"] = _compat
+ globs["AttributeError"] = AttributeError
+ globs["NOTHING"] = NOTHING
+ attribute_fragments = []
+ for name, r, i in attr_names_with_reprs:
+ accessor = (
+ "self." + name if i else 'getattr(self, "' + name + '", NOTHING)'
+ )
+ fragment = (
+ "%s={%s!r}" % (name, accessor)
+ if r == repr
+ else "%s={%s_repr(%s)}" % (name, name, accessor)
+ )
+ attribute_fragments.append(fragment)
+ repr_fragment = ", ".join(attribute_fragments)
+
+ if ns is None:
+ cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}'
+ else:
+ cls_name_fragment = ns + ".{self.__class__.__name__}"
+
+ lines = [
+ "def __repr__(self):",
+ " try:",
+ " already_repring = _compat.repr_context.already_repring",
+ " except AttributeError:",
+ " already_repring = {id(self),}",
+ " _compat.repr_context.already_repring = already_repring",
+ " else:",
+ " if id(self) in already_repring:",
+ " return '...'",
+ " else:",
+ " already_repring.add(id(self))",
+ " try:",
+ f" return f'{cls_name_fragment}({repr_fragment})'",
+ " finally:",
+ " already_repring.remove(id(self))",
+ ]
+
+ return "\n".join(lines), globs
+
+
+def _add_repr(cls, ns=None, attrs=None):
+ """
+ Add a repr method to *cls*.
+ """
+ if attrs is None:
+ attrs = cls.__attrs_attrs__
+
+ script, globs = _make_repr_script(attrs, ns)
+ _compile_and_eval(
+ script, globs, filename=_generate_unique_filename(cls, "__repr__")
+ )
+ cls.__repr__ = globs["__repr__"]
+ return cls
+
+
+def fields(cls):
+ """
+ Return the tuple of *attrs* attributes for a class.
+
+ The tuple also allows accessing the fields by their names (see below for
+ examples).
+
+ Args:
+ cls (type): Class to introspect.
+
+ Raises:
+ TypeError: If *cls* is not a class.
+
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ Returns:
+ tuple (with name accessors) of `attrs.Attribute`
+
+ .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+ by name.
+ .. versionchanged:: 23.1.0 Add support for generic classes.
+ """
+ generic_base = get_generic_base(cls)
+
+ if generic_base is None and not isinstance(cls, type):
+ msg = "Passed object must be a class."
+ raise TypeError(msg)
+
+ attrs = getattr(cls, "__attrs_attrs__", None)
+
+ if attrs is None:
+ if generic_base is not None:
+ attrs = getattr(generic_base, "__attrs_attrs__", None)
+ if attrs is not None:
+ # Even though this is global state, stick it on here to speed
+ # it up. We rely on `cls` being cached for this to be
+ # efficient.
+ cls.__attrs_attrs__ = attrs
+ return attrs
+ msg = f"{cls!r} is not an attrs-decorated class."
+ raise NotAnAttrsClassError(msg)
+
+ return attrs
+
+
+def fields_dict(cls):
+ """
+ Return an ordered dictionary of *attrs* attributes for a class, whose keys
+ are the attribute names.
+
+ Args:
+ cls (type): Class to introspect.
+
+ Raises:
+ TypeError: If *cls* is not a class.
+
+ attrs.exceptions.NotAnAttrsClassError:
+ If *cls* is not an *attrs* class.
+
+ Returns:
+ dict[str, attrs.Attribute]: Dict of attribute name to definition
+
+ .. versionadded:: 18.1.0
+ """
+ if not isinstance(cls, type):
+ msg = "Passed object must be a class."
+ raise TypeError(msg)
+ attrs = getattr(cls, "__attrs_attrs__", None)
+ if attrs is None:
+ msg = f"{cls!r} is not an attrs-decorated class."
+ raise NotAnAttrsClassError(msg)
+ return {a.name: a for a in attrs}
+
+
+def validate(inst):
+ """
+ Validate all attributes on *inst* that have a validator.
+
+ Leaves all exceptions through.
+
+ Args:
+ inst: Instance of a class with *attrs* attributes.
+ """
+ if _config._run_validators is False:
+ return
+
+ for a in fields(inst.__class__):
+ v = a.validator
+ if v is not None:
+ v(inst, a, getattr(inst, a.name))
+
+
+def _is_slot_attr(a_name, base_attr_map):
+ """
+ Check if the attribute name comes from a slot class.
+ """
+ cls = base_attr_map.get(a_name)
+ return cls and "__slots__" in cls.__dict__
+
+
+def _make_init_script(
+ cls,
+ attrs,
+ pre_init,
+ pre_init_has_args,
+ post_init,
+ frozen,
+ slots,
+ cache_hash,
+ base_attr_map,
+ is_exc,
+ cls_on_setattr,
+ attrs_init,
+) -> tuple[str, dict, dict]:
+ has_cls_on_setattr = (
+ cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP
+ )
+
+ if frozen and has_cls_on_setattr:
+ msg = "Frozen classes can't use on_setattr."
+ raise ValueError(msg)
+
+ needs_cached_setattr = cache_hash or frozen
+ filtered_attrs = []
+ attr_dict = {}
+ for a in attrs:
+ if not a.init and a.default is NOTHING:
+ continue
+
+ filtered_attrs.append(a)
+ attr_dict[a.name] = a
+
+ if a.on_setattr is not None:
+ if frozen is True:
+ msg = "Frozen classes can't use on_setattr."
+ raise ValueError(msg)
+
+ needs_cached_setattr = True
+ elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP:
+ needs_cached_setattr = True
+
+ script, globs, annotations = _attrs_to_init_script(
+ filtered_attrs,
+ frozen,
+ slots,
+ pre_init,
+ pre_init_has_args,
+ post_init,
+ cache_hash,
+ base_attr_map,
+ is_exc,
+ needs_cached_setattr,
+ has_cls_on_setattr,
+ "__attrs_init__" if attrs_init else "__init__",
+ )
+ if cls.__module__ in sys.modules:
+ # This makes typing.get_type_hints(CLS.__init__) resolve string types.
+ globs.update(sys.modules[cls.__module__].__dict__)
+
+ globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
+
+ if needs_cached_setattr:
+ # Save the lookup overhead in __init__ if we need to circumvent
+ # setattr hooks.
+ globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__
+
+ return script, globs, annotations
+
+
+def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str:
+ """
+ Use the cached object.setattr to set *attr_name* to *value_var*.
+ """
+ return f"_setattr('{attr_name}', {value_var})"
+
+
+def _setattr_with_converter(
+ attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
+) -> str:
+ """
+ Use the cached object.setattr to set *attr_name* to *value_var*, but run
+ its converter first.
+ """
+ return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})"
+
+
+def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str:
+ """
+ Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise
+ relegate to _setattr.
+ """
+ if has_on_setattr:
+ return _setattr(attr_name, value, True)
+
+ return f"self.{attr_name} = {value}"
+
+
+def _assign_with_converter(
+ attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter
+) -> str:
+ """
+ Unless *attr_name* has an on_setattr hook, use normal assignment after
+ conversion. Otherwise relegate to _setattr_with_converter.
+ """
+ if has_on_setattr:
+ return _setattr_with_converter(attr_name, value_var, True, converter)
+
+ return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}"
+
+
+def _determine_setters(
+ frozen: bool, slots: bool, base_attr_map: dict[str, type]
+):
+ """
+ Determine the correct setter functions based on whether a class is frozen
+ and/or slotted.
+ """
+ if frozen is True:
+ if slots is True:
+ return (), _setattr, _setattr_with_converter
+
+ # Dict frozen classes assign directly to __dict__.
+ # But only if the attribute doesn't come from an ancestor slot
+ # class.
+ # Note _inst_dict will be used again below if cache_hash is True
+
+ def fmt_setter(
+ attr_name: str, value_var: str, has_on_setattr: bool
+ ) -> str:
+ if _is_slot_attr(attr_name, base_attr_map):
+ return _setattr(attr_name, value_var, has_on_setattr)
+
+ return f"_inst_dict['{attr_name}'] = {value_var}"
+
+ def fmt_setter_with_converter(
+ attr_name: str,
+ value_var: str,
+ has_on_setattr: bool,
+ converter: Converter,
+ ) -> str:
+ if has_on_setattr or _is_slot_attr(attr_name, base_attr_map):
+ return _setattr_with_converter(
+ attr_name, value_var, has_on_setattr, converter
+ )
+
+ return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}"
+
+ return (
+ ("_inst_dict = self.__dict__",),
+ fmt_setter,
+ fmt_setter_with_converter,
+ )
+
+ # Not frozen -- we can just assign directly.
+ return (), _assign, _assign_with_converter
+
+
+def _attrs_to_init_script(
+ attrs: list[Attribute],
+ is_frozen: bool,
+ is_slotted: bool,
+ call_pre_init: bool,
+ pre_init_has_args: bool,
+ call_post_init: bool,
+ does_cache_hash: bool,
+ base_attr_map: dict[str, type],
+ is_exc: bool,
+ needs_cached_setattr: bool,
+ has_cls_on_setattr: bool,
+ method_name: str,
+) -> tuple[str, dict, dict]:
+ """
+ Return a script of an initializer for *attrs*, a dict of globals, and
+ annotations for the initializer.
+
+ The globals are required by the generated script.
+ """
+ lines = ["self.__attrs_pre_init__()"] if call_pre_init else []
+
+ if needs_cached_setattr:
+ lines.append(
+ # Circumvent the __setattr__ descriptor to save one lookup per
+ # assignment. Note _setattr will be used again below if
+ # does_cache_hash is True.
+ "_setattr = _cached_setattr_get(self)"
+ )
+
+ extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters(
+ is_frozen, is_slotted, base_attr_map
+ )
+ lines.extend(extra_lines)
+
+ args = [] # Parameters in the definition of __init__
+ pre_init_args = [] # Parameters in the call to __attrs_pre_init__
+ kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
+ attrs_to_validate = []
+
+ # This is a dictionary of names to validator and converter callables.
+ # Injecting this into __init__ globals lets us avoid lookups.
+ names_for_globals = {}
+ annotations = {"return": None}
+
+ for a in attrs:
+ if a.validator:
+ attrs_to_validate.append(a)
+
+ attr_name = a.name
+ has_on_setattr = a.on_setattr is not None or (
+ a.on_setattr is not setters.NO_OP and has_cls_on_setattr
+ )
+ # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not
+ # explicitly provided
+ arg_name = a.alias
+
+ has_factory = isinstance(a.default, Factory)
+ maybe_self = "self" if has_factory and a.default.takes_self else ""
+
+ if a.converter is not None and not isinstance(a.converter, Converter):
+ converter = Converter(a.converter)
+ else:
+ converter = a.converter
+
+ if a.init is False:
+ if has_factory:
+ init_factory_name = _INIT_FACTORY_PAT % (a.name,)
+ if converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name,
+ init_factory_name + f"({maybe_self})",
+ has_on_setattr,
+ converter,
+ )
+ )
+ names_for_globals[converter._get_global_name(a.name)] = (
+ converter.converter
+ )
+ else:
+ lines.append(
+ fmt_setter(
+ attr_name,
+ init_factory_name + f"({maybe_self})",
+ has_on_setattr,
+ )
+ )
+ names_for_globals[init_factory_name] = a.default.factory
+ elif converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name,
+ f"attr_dict['{attr_name}'].default",
+ has_on_setattr,
+ converter,
+ )
+ )
+ names_for_globals[converter._get_global_name(a.name)] = (
+ converter.converter
+ )
+ else:
+ lines.append(
+ fmt_setter(
+ attr_name,
+ f"attr_dict['{attr_name}'].default",
+ has_on_setattr,
+ )
+ )
+ elif a.default is not NOTHING and not has_factory:
+ arg = f"{arg_name}=attr_dict['{attr_name}'].default"
+ if a.kw_only:
+ kw_only_args.append(arg)
+ else:
+ args.append(arg)
+ pre_init_args.append(arg_name)
+
+ if converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name, arg_name, has_on_setattr, converter
+ )
+ )
+ names_for_globals[converter._get_global_name(a.name)] = (
+ converter.converter
+ )
+ else:
+ lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
+
+ elif has_factory:
+ arg = f"{arg_name}=NOTHING"
+ if a.kw_only:
+ kw_only_args.append(arg)
+ else:
+ args.append(arg)
+ pre_init_args.append(arg_name)
+ lines.append(f"if {arg_name} is not NOTHING:")
+
+ init_factory_name = _INIT_FACTORY_PAT % (a.name,)
+ if converter is not None:
+ lines.append(
+ " "
+ + fmt_setter_with_converter(
+ attr_name, arg_name, has_on_setattr, converter
+ )
+ )
+ lines.append("else:")
+ lines.append(
+ " "
+ + fmt_setter_with_converter(
+ attr_name,
+ init_factory_name + "(" + maybe_self + ")",
+ has_on_setattr,
+ converter,
+ )
+ )
+ names_for_globals[converter._get_global_name(a.name)] = (
+ converter.converter
+ )
+ else:
+ lines.append(
+ " " + fmt_setter(attr_name, arg_name, has_on_setattr)
+ )
+ lines.append("else:")
+ lines.append(
+ " "
+ + fmt_setter(
+ attr_name,
+ init_factory_name + "(" + maybe_self + ")",
+ has_on_setattr,
+ )
+ )
+ names_for_globals[init_factory_name] = a.default.factory
+ else:
+ if a.kw_only:
+ kw_only_args.append(arg_name)
+ else:
+ args.append(arg_name)
+ pre_init_args.append(arg_name)
+
+ if converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name, arg_name, has_on_setattr, converter
+ )
+ )
+ names_for_globals[converter._get_global_name(a.name)] = (
+ converter.converter
+ )
+ else:
+ lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
+
+ if a.init is True:
+ if a.type is not None and converter is None:
+ annotations[arg_name] = a.type
+ elif converter is not None and converter._first_param_type:
+ # Use the type from the converter if present.
+ annotations[arg_name] = converter._first_param_type
+
+ if attrs_to_validate: # we can skip this if there are no validators.
+ names_for_globals["_config"] = _config
+ lines.append("if _config._run_validators is True:")
+ for a in attrs_to_validate:
+ val_name = "__attr_validator_" + a.name
+ attr_name = "__attr_" + a.name
+ lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
+ names_for_globals[val_name] = a.validator
+ names_for_globals[attr_name] = a
+
+ if call_post_init:
+ lines.append("self.__attrs_post_init__()")
+
+ # Because this is set only after __attrs_post_init__ is called, a crash
+ # will result if post-init tries to access the hash code. This seemed
+ # preferable to setting this beforehand, in which case alteration to field
+ # values during post-init combined with post-init accessing the hash code
+ # would result in silent bugs.
+ if does_cache_hash:
+ if is_frozen:
+ if is_slotted:
+ init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)"
+ else:
+ init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None"
+ else:
+ init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None"
+ lines.append(init_hash_cache)
+
+ # For exceptions we rely on BaseException.__init__ for proper
+ # initialization.
+ if is_exc:
+ vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
+
+ lines.append(f"BaseException.__init__(self, {vals})")
+
+ args = ", ".join(args)
+ pre_init_args = ", ".join(pre_init_args)
+ if kw_only_args:
+ # leading comma & kw_only args
+ args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
+ pre_init_kw_only_args = ", ".join(
+ [
+ f"{kw_arg_name}={kw_arg_name}"
+ # We need to remove the defaults from the kw_only_args.
+ for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args)
+ ]
+ )
+ pre_init_args += ", " if pre_init_args else ""
+ pre_init_args += pre_init_kw_only_args
+
+ if call_pre_init and pre_init_has_args:
+ # If pre init method has arguments, pass the values given to __init__.
+ lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
+
+ # Python <3.12 doesn't allow backslashes in f-strings.
+ NL = "\n "
+ return (
+ f"""def {method_name}(self, {args}):
+ {NL.join(lines) if lines else "pass"}
+""",
+ names_for_globals,
+ annotations,
+ )
+
+
+def _default_init_alias_for(name: str) -> str:
+ """
+ The default __init__ parameter name for a field.
+
+ This performs private-name adjustment via leading-unscore stripping,
+ and is the default value of Attribute.alias if not provided.
+ """
+
+ return name.lstrip("_")
+
+
+class Attribute:
+ """
+ *Read-only* representation of an attribute.
+
+ .. warning::
+
+ You should never instantiate this class yourself.
+
+ The class has *all* arguments of `attr.ib` (except for ``factory`` which is
+ only syntactic sugar for ``default=Factory(...)`` plus the following:
+
+ - ``name`` (`str`): The name of the attribute.
+ - ``alias`` (`str`): The __init__ parameter name of the attribute, after
+ any explicit overrides and default private-attribute-name handling.
+ - ``inherited`` (`bool`): Whether or not that attribute has been inherited
+ from a base class.
+ - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
+ callables that are used for comparing and ordering objects by this
+ attribute, respectively. These are set by passing a callable to
+ `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also
+ :ref:`comparison customization `.
+
+ Instances of this class are frequently used for introspection purposes
+ like:
+
+ - `fields` returns a tuple of them.
+ - Validators get them passed as the first argument.
+ - The :ref:`field transformer ` hook receives a list of
+ them.
+ - The ``alias`` property exposes the __init__ parameter name of the field,
+ with any overrides and default private-attribute handling applied.
+
+
+ .. versionadded:: 20.1.0 *inherited*
+ .. versionadded:: 20.1.0 *on_setattr*
+ .. versionchanged:: 20.2.0 *inherited* is not taken into account for
+ equality checks and hashing anymore.
+ .. versionadded:: 21.1.0 *eq_key* and *order_key*
+ .. versionadded:: 22.2.0 *alias*
+
+ For the full version history of the fields, see `attr.ib`.
+ """
+
+ # These slots must NOT be reordered because we use them later for
+ # instantiation.
+ __slots__ = ( # noqa: RUF023
+ "name",
+ "default",
+ "validator",
+ "repr",
+ "eq",
+ "eq_key",
+ "order",
+ "order_key",
+ "hash",
+ "init",
+ "metadata",
+ "type",
+ "converter",
+ "kw_only",
+ "inherited",
+ "on_setattr",
+ "alias",
+ )
+
+ def __init__(
+ self,
+ name,
+ default,
+ validator,
+ repr,
+ cmp, # XXX: unused, remove along with other cmp code.
+ hash,
+ init,
+ inherited,
+ metadata=None,
+ type=None,
+ converter=None,
+ kw_only=False,
+ eq=None,
+ eq_key=None,
+ order=None,
+ order_key=None,
+ on_setattr=None,
+ alias=None,
+ ):
+ eq, eq_key, order, order_key = _determine_attrib_eq_order(
+ cmp, eq_key or eq, order_key or order, True
+ )
+
+ # Cache this descriptor here to speed things up later.
+ bound_setattr = _OBJ_SETATTR.__get__(self)
+
+ # Despite the big red warning, people *do* instantiate `Attribute`
+ # themselves.
+ bound_setattr("name", name)
+ bound_setattr("default", default)
+ bound_setattr("validator", validator)
+ bound_setattr("repr", repr)
+ bound_setattr("eq", eq)
+ bound_setattr("eq_key", eq_key)
+ bound_setattr("order", order)
+ bound_setattr("order_key", order_key)
+ bound_setattr("hash", hash)
+ bound_setattr("init", init)
+ bound_setattr("converter", converter)
+ bound_setattr(
+ "metadata",
+ (
+ types.MappingProxyType(dict(metadata)) # Shallow copy
+ if metadata
+ else _EMPTY_METADATA_SINGLETON
+ ),
+ )
+ bound_setattr("type", type)
+ bound_setattr("kw_only", kw_only)
+ bound_setattr("inherited", inherited)
+ bound_setattr("on_setattr", on_setattr)
+ bound_setattr("alias", alias)
+
+ def __setattr__(self, name, value):
+ raise FrozenInstanceError
+
+ @classmethod
+ def from_counting_attr(
+ cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
+ ):
+ # The 'kw_only' argument is the class-level setting, and is used if the
+ # attribute itself does not explicitly set 'kw_only'.
+ # type holds the annotated value. deal with conflicts:
+ if type is None:
+ type = ca.type
+ elif ca.type is not None:
+ msg = f"Type annotation and type argument cannot both be present for '{name}'."
+ raise ValueError(msg)
+ return cls(
+ name,
+ ca._default,
+ ca._validator,
+ ca.repr,
+ None,
+ ca.hash,
+ ca.init,
+ False,
+ ca.metadata,
+ type,
+ ca.converter,
+ kw_only if ca.kw_only is None else ca.kw_only,
+ ca.eq,
+ ca.eq_key,
+ ca.order,
+ ca.order_key,
+ ca.on_setattr,
+ ca.alias,
+ )
+
+ # Don't use attrs.evolve since fields(Attribute) doesn't work
+ def evolve(self, **changes):
+ """
+ Copy *self* and apply *changes*.
+
+ This works similarly to `attrs.evolve` but that function does not work
+ with :class:`attrs.Attribute`.
+
+ It is mainly meant to be used for `transform-fields`.
+
+ .. versionadded:: 20.3.0
+ """
+ new = copy.copy(self)
+
+ new._setattrs(changes.items())
+
+ return new
+
+ # Don't use _add_pickle since fields(Attribute) doesn't work
+ def __getstate__(self):
+ """
+ Play nice with pickle.
+ """
+ return tuple(
+ getattr(self, name) if name != "metadata" else dict(self.metadata)
+ for name in self.__slots__
+ )
+
+ def __setstate__(self, state):
+ """
+ Play nice with pickle.
+ """
+ self._setattrs(zip(self.__slots__, state))
+
+ def _setattrs(self, name_values_pairs):
+ bound_setattr = _OBJ_SETATTR.__get__(self)
+ for name, value in name_values_pairs:
+ if name != "metadata":
+ bound_setattr(name, value)
+ else:
+ bound_setattr(
+ name,
+ (
+ types.MappingProxyType(dict(value))
+ if value
+ else _EMPTY_METADATA_SINGLETON
+ ),
+ )
+
+
+_a = [
+ Attribute(
+ name=name,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ eq=True,
+ order=False,
+ hash=(name != "metadata"),
+ init=True,
+ inherited=False,
+ alias=_default_init_alias_for(name),
+ )
+ for name in Attribute.__slots__
+]
+
+Attribute = _add_hash(
+ _add_eq(
+ _add_repr(Attribute, attrs=_a),
+ attrs=[a for a in _a if a.name != "inherited"],
+ ),
+ attrs=[a for a in _a if a.hash and a.name != "inherited"],
+)
+
+
+class _CountingAttr:
+ """
+ Intermediate representation of attributes that uses a counter to preserve
+ the order in which the attributes have been defined.
+
+ *Internal* data structure of the attrs library. Running into is most
+ likely the result of a bug like a forgotten `@attr.s` decorator.
+ """
+
+ __slots__ = (
+ "_default",
+ "_validator",
+ "alias",
+ "converter",
+ "counter",
+ "eq",
+ "eq_key",
+ "hash",
+ "init",
+ "kw_only",
+ "metadata",
+ "on_setattr",
+ "order",
+ "order_key",
+ "repr",
+ "type",
+ )
+ __attrs_attrs__ = (
+ *tuple(
+ Attribute(
+ name=name,
+ alias=_default_init_alias_for(name),
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ hash=True,
+ init=True,
+ kw_only=False,
+ eq=True,
+ eq_key=None,
+ order=False,
+ order_key=None,
+ inherited=False,
+ on_setattr=None,
+ )
+ for name in (
+ "counter",
+ "_default",
+ "repr",
+ "eq",
+ "order",
+ "hash",
+ "init",
+ "on_setattr",
+ "alias",
+ )
+ ),
+ Attribute(
+ name="metadata",
+ alias="metadata",
+ default=None,
+ validator=None,
+ repr=True,
+ cmp=None,
+ hash=False,
+ init=True,
+ kw_only=False,
+ eq=True,
+ eq_key=None,
+ order=False,
+ order_key=None,
+ inherited=False,
+ on_setattr=None,
+ ),
+ )
+ cls_counter = 0
+
+ def __init__(
+ self,
+ default,
+ validator,
+ repr,
+ cmp,
+ hash,
+ init,
+ converter,
+ metadata,
+ type,
+ kw_only,
+ eq,
+ eq_key,
+ order,
+ order_key,
+ on_setattr,
+ alias,
+ ):
+ _CountingAttr.cls_counter += 1
+ self.counter = _CountingAttr.cls_counter
+ self._default = default
+ self._validator = validator
+ self.converter = converter
+ self.repr = repr
+ self.eq = eq
+ self.eq_key = eq_key
+ self.order = order
+ self.order_key = order_key
+ self.hash = hash
+ self.init = init
+ self.metadata = metadata
+ self.type = type
+ self.kw_only = kw_only
+ self.on_setattr = on_setattr
+ self.alias = alias
+
+ def validator(self, meth):
+ """
+ Decorator that adds *meth* to the list of validators.
+
+ Returns *meth* unchanged.
+
+ .. versionadded:: 17.1.0
+ """
+ if self._validator is None:
+ self._validator = meth
+ else:
+ self._validator = and_(self._validator, meth)
+ return meth
+
+ def default(self, meth):
+ """
+ Decorator that allows to set the default for an attribute.
+
+ Returns *meth* unchanged.
+
+ Raises:
+ DefaultAlreadySetError: If default has been set before.
+
+ .. versionadded:: 17.1.0
+ """
+ if self._default is not NOTHING:
+ raise DefaultAlreadySetError
+
+ self._default = Factory(meth, takes_self=True)
+
+ return meth
+
+
+_CountingAttr = _add_eq(_add_repr(_CountingAttr))
+
+
+class ClassProps:
+ """
+ Effective class properties as derived from parameters to `attr.s()` or
+ `define()` decorators.
+
+ This is the same data structure that *attrs* uses internally to decide how
+ to construct the final class.
+
+ Warning:
+
+ This feature is currently **experimental** and is not covered by our
+ strict backwards-compatibility guarantees.
+
+
+ Attributes:
+ is_exception (bool):
+ Whether the class is treated as an exception class.
+
+ is_slotted (bool):
+ Whether the class is `slotted `.
+
+ has_weakref_slot (bool):
+ Whether the class has a slot for weak references.
+
+ is_frozen (bool):
+ Whether the class is frozen.
+
+ kw_only (KeywordOnly):
+ Whether / how the class enforces keyword-only arguments on the
+ ``__init__`` method.
+
+ collected_fields_by_mro (bool):
+ Whether the class fields were collected by method resolution order.
+ That is, correctly but unlike `dataclasses`.
+
+ added_init (bool):
+ Whether the class has an *attrs*-generated ``__init__`` method.
+
+ added_repr (bool):
+ Whether the class has an *attrs*-generated ``__repr__`` method.
+
+ added_eq (bool):
+ Whether the class has *attrs*-generated equality methods.
+
+ added_ordering (bool):
+ Whether the class has *attrs*-generated ordering methods.
+
+ hashability (Hashability): How `hashable ` the class is.
+
+ added_match_args (bool):
+ Whether the class supports positional `match ` over its
+ fields.
+
+ added_str (bool):
+ Whether the class has an *attrs*-generated ``__str__`` method.
+
+ added_pickling (bool):
+ Whether the class has *attrs*-generated ``__getstate__`` and
+ ``__setstate__`` methods for `pickle`.
+
+ on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
+ The class's ``__setattr__`` hook.
+
+ field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
+ The class's `field transformers `.
+
+ .. versionadded:: 25.4.0
+ """
+
+ class Hashability(enum.Enum):
+ """
+ The hashability of a class.
+
+ .. versionadded:: 25.4.0
+ """
+
+ HASHABLE = "hashable"
+ """Write a ``__hash__``."""
+ HASHABLE_CACHED = "hashable_cache"
+ """Write a ``__hash__`` and cache the hash."""
+ UNHASHABLE = "unhashable"
+ """Set ``__hash__`` to ``None``."""
+ LEAVE_ALONE = "leave_alone"
+ """Don't touch ``__hash__``."""
+
+ class KeywordOnly(enum.Enum):
+ """
+ How attributes should be treated regarding keyword-only parameters.
+
+ .. versionadded:: 25.4.0
+ """
+
+ NO = "no"
+ """Attributes are not keyword-only."""
+ YES = "yes"
+ """Attributes in current class without kw_only=False are keyword-only."""
+ FORCE = "force"
+ """All attributes are keyword-only."""
+
+ __slots__ = ( # noqa: RUF023 -- order matters for __init__
+ "is_exception",
+ "is_slotted",
+ "has_weakref_slot",
+ "is_frozen",
+ "kw_only",
+ "collected_fields_by_mro",
+ "added_init",
+ "added_repr",
+ "added_eq",
+ "added_ordering",
+ "hashability",
+ "added_match_args",
+ "added_str",
+ "added_pickling",
+ "on_setattr_hook",
+ "field_transformer",
+ )
+
+ def __init__(
+ self,
+ is_exception,
+ is_slotted,
+ has_weakref_slot,
+ is_frozen,
+ kw_only,
+ collected_fields_by_mro,
+ added_init,
+ added_repr,
+ added_eq,
+ added_ordering,
+ hashability,
+ added_match_args,
+ added_str,
+ added_pickling,
+ on_setattr_hook,
+ field_transformer,
+ ):
+ self.is_exception = is_exception
+ self.is_slotted = is_slotted
+ self.has_weakref_slot = has_weakref_slot
+ self.is_frozen = is_frozen
+ self.kw_only = kw_only
+ self.collected_fields_by_mro = collected_fields_by_mro
+ self.added_init = added_init
+ self.added_repr = added_repr
+ self.added_eq = added_eq
+ self.added_ordering = added_ordering
+ self.hashability = hashability
+ self.added_match_args = added_match_args
+ self.added_str = added_str
+ self.added_pickling = added_pickling
+ self.on_setattr_hook = on_setattr_hook
+ self.field_transformer = field_transformer
+
+ @property
+ def is_hashable(self):
+ return (
+ self.hashability is ClassProps.Hashability.HASHABLE
+ or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
+ )
+
+
+_cas = [
+ Attribute(
+ name=name,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ eq=True,
+ order=False,
+ hash=True,
+ init=True,
+ inherited=False,
+ alias=_default_init_alias_for(name),
+ )
+ for name in ClassProps.__slots__
+]
+
+ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
+
+
+class Factory:
+ """
+ Stores a factory callable.
+
+ If passed as the default value to `attrs.field`, the factory is used to
+ generate a new value.
+
+ Args:
+ factory (typing.Callable):
+ A callable that takes either none or exactly one mandatory
+ positional argument depending on *takes_self*.
+
+ takes_self (bool):
+ Pass the partially initialized instance that is being initialized
+ as a positional argument.
+
+ .. versionadded:: 17.1.0 *takes_self*
+ """
+
+ __slots__ = ("factory", "takes_self")
+
+ def __init__(self, factory, takes_self=False):
+ self.factory = factory
+ self.takes_self = takes_self
+
+ def __getstate__(self):
+ """
+ Play nice with pickle.
+ """
+ return tuple(getattr(self, name) for name in self.__slots__)
+
+ def __setstate__(self, state):
+ """
+ Play nice with pickle.
+ """
+ for name, value in zip(self.__slots__, state):
+ setattr(self, name, value)
+
+
+_f = [
+ Attribute(
+ name=name,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ eq=True,
+ order=False,
+ hash=True,
+ init=True,
+ inherited=False,
+ )
+ for name in Factory.__slots__
+]
+
+Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f)
+
+
+class Converter:
+ """
+ Stores a converter callable.
+
+ Allows for the wrapped converter to take additional arguments. The
+ arguments are passed in the order they are documented.
+
+ Args:
+ converter (Callable): A callable that converts the passed value.
+
+ takes_self (bool):
+ Pass the partially initialized instance that is being initialized
+ as a positional argument. (default: `False`)
+
+ takes_field (bool):
+ Pass the field definition (an :class:`Attribute`) into the
+ converter as a positional argument. (default: `False`)
+
+ .. versionadded:: 24.1.0
+ """
+
+ __slots__ = (
+ "__call__",
+ "_first_param_type",
+ "_global_name",
+ "converter",
+ "takes_field",
+ "takes_self",
+ )
+
+ def __init__(self, converter, *, takes_self=False, takes_field=False):
+ self.converter = converter
+ self.takes_self = takes_self
+ self.takes_field = takes_field
+
+ ex = _AnnotationExtractor(converter)
+ self._first_param_type = ex.get_first_param_type()
+
+ if not (self.takes_self or self.takes_field):
+ self.__call__ = lambda value, _, __: self.converter(value)
+ elif self.takes_self and not self.takes_field:
+ self.__call__ = lambda value, instance, __: self.converter(
+ value, instance
+ )
+ elif not self.takes_self and self.takes_field:
+ self.__call__ = lambda value, __, field: self.converter(
+ value, field
+ )
+ else:
+ self.__call__ = lambda value, instance, field: self.converter(
+ value, instance, field
+ )
+
+ rt = ex.get_return_type()
+ if rt is not None:
+ self.__call__.__annotations__["return"] = rt
+
+ @staticmethod
+ def _get_global_name(attr_name: str) -> str:
+ """
+ Return the name that a converter for an attribute name *attr_name*
+ would have.
+ """
+ return f"__attr_converter_{attr_name}"
+
+ def _fmt_converter_call(self, attr_name: str, value_var: str) -> str:
+ """
+ Return a string that calls the converter for an attribute name
+ *attr_name* and the value in variable named *value_var* according to
+ `self.takes_self` and `self.takes_field`.
+ """
+ if not (self.takes_self or self.takes_field):
+ return f"{self._get_global_name(attr_name)}({value_var})"
+
+ if self.takes_self and self.takes_field:
+ return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])"
+
+ if self.takes_self:
+ return f"{self._get_global_name(attr_name)}({value_var}, self)"
+
+ return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])"
+
+ def __getstate__(self):
+ """
+ Return a dict containing only converter and takes_self -- the rest gets
+ computed when loading.
+ """
+ return {
+ "converter": self.converter,
+ "takes_self": self.takes_self,
+ "takes_field": self.takes_field,
+ }
+
+ def __setstate__(self, state):
+ """
+ Load instance from state.
+ """
+ self.__init__(**state)
+
+
+_f = [
+ Attribute(
+ name=name,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=None,
+ eq=True,
+ order=False,
+ hash=True,
+ init=True,
+ inherited=False,
+ )
+ for name in ("converter", "takes_self", "takes_field")
+]
+
+Converter = _add_hash(
+ _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f
+)
+
+
+def make_class(
+ name, attrs, bases=(object,), class_body=None, **attributes_arguments
+):
+ r"""
+ A quick way to create a new class called *name* with *attrs*.
+
+ .. note::
+
+ ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define`
+ which means that it doesn't come with some of the improved defaults.
+
+ For example, if you want the same ``on_setattr`` behavior as in
+ `attrs.define`, you have to pass the hooks yourself: ``make_class(...,
+ on_setattr=setters.pipe(setters.convert, setters.validate)``
+
+ .. warning::
+
+ It is *your* duty to ensure that the class name and the attribute names
+ are valid identifiers. ``make_class()`` will *not* validate them for
+ you.
+
+ Args:
+ name (str): The name for the new class.
+
+ attrs (list | dict):
+ A list of names or a dictionary of mappings of names to `attr.ib`\
+ s / `attrs.field`\ s.
+
+ The order is deduced from the order of the names or attributes
+ inside *attrs*. Otherwise the order of the definition of the
+ attributes is used.
+
+ bases (tuple[type, ...]): Classes that the new class will subclass.
+
+ class_body (dict):
+ An optional dictionary of class attributes for the new class.
+
+ attributes_arguments: Passed unmodified to `attr.s`.
+
+ Returns:
+ type: A new class with *attrs*.
+
+ .. versionadded:: 17.1.0 *bases*
+ .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
+ .. versionchanged:: 23.2.0 *class_body*
+ .. versionchanged:: 25.2.0 Class names can now be unicode.
+ """
+ # Class identifiers are converted into the normal form NFKC while parsing
+ name = unicodedata.normalize("NFKC", name)
+
+ if isinstance(attrs, dict):
+ cls_dict = attrs
+ elif isinstance(attrs, (list, tuple)):
+ cls_dict = {a: attrib() for a in attrs}
+ else:
+ msg = "attrs argument must be a dict or a list."
+ raise TypeError(msg)
+
+ pre_init = cls_dict.pop("__attrs_pre_init__", None)
+ post_init = cls_dict.pop("__attrs_post_init__", None)
+ user_init = cls_dict.pop("__init__", None)
+
+ body = {}
+ if class_body is not None:
+ body.update(class_body)
+ if pre_init is not None:
+ body["__attrs_pre_init__"] = pre_init
+ if post_init is not None:
+ body["__attrs_post_init__"] = post_init
+ if user_init is not None:
+ body["__init__"] = user_init
+
+ type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body))
+
+ # For pickling to work, the __module__ variable needs to be set to the
+ # frame where the class is created. Bypass this step in environments where
+ # sys._getframe is not defined (Jython for example) or sys._getframe is not
+ # defined for arguments greater than 0 (IronPython).
+ with contextlib.suppress(AttributeError, ValueError):
+ type_.__module__ = sys._getframe(1).f_globals.get(
+ "__name__", "__main__"
+ )
+
+ # We do it here for proper warnings with meaningful stacklevel.
+ cmp = attributes_arguments.pop("cmp", None)
+ (
+ attributes_arguments["eq"],
+ attributes_arguments["order"],
+ ) = _determine_attrs_eq_order(
+ cmp,
+ attributes_arguments.get("eq"),
+ attributes_arguments.get("order"),
+ True,
+ )
+
+ cls = _attrs(these=cls_dict, **attributes_arguments)(type_)
+ # Only add type annotations now or "_attrs()" will complain:
+ cls.__annotations__ = {
+ k: v.type for k, v in cls_dict.items() if v.type is not None
+ }
+ return cls
+
+
+# These are required by within this module so we define them here and merely
+# import into .validators / .converters.
+
+
+@attrs(slots=True, unsafe_hash=True)
+class _AndValidator:
+ """
+ Compose many validators to a single one.
+ """
+
+ _validators = attrib()
+
+ def __call__(self, inst, attr, value):
+ for v in self._validators:
+ v(inst, attr, value)
+
+
+def and_(*validators):
+ """
+ A validator that composes multiple validators into one.
+
+ When called on a value, it runs all wrapped validators.
+
+ Args:
+ validators (~collections.abc.Iterable[typing.Callable]):
+ Arbitrary number of validators.
+
+ .. versionadded:: 17.1.0
+ """
+ vals = []
+ for validator in validators:
+ vals.extend(
+ validator._validators
+ if isinstance(validator, _AndValidator)
+ else [validator]
+ )
+
+ return _AndValidator(tuple(vals))
+
+
+def pipe(*converters):
+ """
+ A converter that composes multiple converters into one.
+
+ When called on a value, it runs all wrapped converters, returning the
+ *last* value.
+
+ Type annotations will be inferred from the wrapped converters', if they
+ have any.
+
+ converters (~collections.abc.Iterable[typing.Callable]):
+ Arbitrary number of converters.
+
+ .. versionadded:: 20.1.0
+ """
+
+ return_instance = any(isinstance(c, Converter) for c in converters)
+
+ if return_instance:
+
+ def pipe_converter(val, inst, field):
+ for c in converters:
+ val = (
+ c(val, inst, field) if isinstance(c, Converter) else c(val)
+ )
+
+ return val
+
+ else:
+
+ def pipe_converter(val):
+ for c in converters:
+ val = c(val)
+
+ return val
+
+ if not converters:
+ # If the converter list is empty, pipe_converter is the identity.
+ A = TypeVar("A")
+ pipe_converter.__annotations__.update({"val": A, "return": A})
+ else:
+ # Get parameter type from first converter.
+ t = _AnnotationExtractor(converters[0]).get_first_param_type()
+ if t:
+ pipe_converter.__annotations__["val"] = t
+
+ last = converters[-1]
+ if not PY_3_11_PLUS and isinstance(last, Converter):
+ last = last.__call__
+
+ # Get return type from last converter.
+ rt = _AnnotationExtractor(last).get_return_type()
+ if rt:
+ pipe_converter.__annotations__["return"] = rt
+
+ if return_instance:
+ return Converter(pipe_converter, takes_self=True, takes_field=True)
+ return pipe_converter
diff --git a/lib/python3.12/site-packages/attr/_next_gen.py b/lib/python3.12/site-packages/attr/_next_gen.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ccd0da2446dc126ce936b054581a527e247cabc
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_next_gen.py
@@ -0,0 +1,674 @@
+# SPDX-License-Identifier: MIT
+
+"""
+These are keyword-only APIs that call `attr.s` and `attr.ib` with different
+default values.
+"""
+
+from functools import partial
+
+from . import setters
+from ._funcs import asdict as _asdict
+from ._funcs import astuple as _astuple
+from ._make import (
+ _DEFAULT_ON_SETATTR,
+ NOTHING,
+ _frozen_setattrs,
+ attrib,
+ attrs,
+)
+from .exceptions import NotAnAttrsClassError, UnannotatedAttributeError
+
+
+def define(
+ maybe_cls=None,
+ *,
+ these=None,
+ repr=None,
+ unsafe_hash=None,
+ hash=None,
+ init=None,
+ slots=True,
+ frozen=False,
+ weakref_slot=True,
+ str=False,
+ auto_attribs=None,
+ kw_only=False,
+ cache_hash=False,
+ auto_exc=True,
+ eq=None,
+ order=False,
+ auto_detect=True,
+ getstate_setstate=None,
+ on_setattr=None,
+ field_transformer=None,
+ match_args=True,
+ force_kw_only=False,
+):
+ r"""
+ A class decorator that adds :term:`dunder methods` according to
+ :term:`fields ` specified using :doc:`type annotations `,
+ `field()` calls, or the *these* argument.
+
+ Since *attrs* patches or replaces an existing class, you cannot use
+ `object.__init_subclass__` with *attrs* classes, because it runs too early.
+ As a replacement, you can define ``__attrs_init_subclass__`` on your class.
+ It will be called by *attrs* classes that subclass it after they're
+ created. See also :ref:`init-subclass`.
+
+ Args:
+ slots (bool):
+ Create a :term:`slotted class ` that's more
+ memory-efficient. Slotted classes are generally superior to the
+ default dict classes, but have some gotchas you should know about,
+ so we encourage you to read the :term:`glossary entry `.
+
+ auto_detect (bool):
+ Instead of setting the *init*, *repr*, *eq*, and *hash* arguments
+ explicitly, assume they are set to True **unless any** of the
+ involved methods for one of the arguments is implemented in the
+ *current* class (meaning, it is *not* inherited from some base
+ class).
+
+ So, for example by implementing ``__eq__`` on a class yourself,
+ *attrs* will deduce ``eq=False`` and will create *neither*
+ ``__eq__`` *nor* ``__ne__`` (but Python classes come with a
+ sensible ``__ne__`` by default, so it *should* be enough to only
+ implement ``__eq__`` in most cases).
+
+ Passing :data:`True` or :data:`False` to *init*, *repr*, *eq*, or *hash*
+ overrides whatever *auto_detect* would determine.
+
+ auto_exc (bool):
+ If the class subclasses `BaseException` (which implicitly includes
+ any subclass of any exception), the following happens to behave
+ like a well-behaved Python exception class:
+
+ - the values for *eq*, *order*, and *hash* are ignored and the
+ instances compare and hash by the instance's ids [#]_ ,
+ - all attributes that are either passed into ``__init__`` or have a
+ default value are additionally available as a tuple in the
+ ``args`` attribute,
+ - the value of *str* is ignored leaving ``__str__`` to base
+ classes.
+
+ .. [#]
+ Note that *attrs* will *not* remove existing implementations of
+ ``__hash__`` or the equality methods. It just won't add own
+ ones.
+
+ on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
+ A callable that is run whenever the user attempts to set an
+ attribute (either by assignment like ``i.x = 42`` or by using
+ `setattr` like ``setattr(i, "x", 42)``). It receives the same
+ arguments as validators: the instance, the attribute that is being
+ modified, and the new value.
+
+ If no exception is raised, the attribute is set to the return value
+ of the callable.
+
+ If a list of callables is passed, they're automatically wrapped in
+ an `attrs.setters.pipe`.
+
+ If left None, the default behavior is to run converters and
+ validators whenever an attribute is set.
+
+ init (bool):
+ Create a ``__init__`` method that initializes the *attrs*
+ attributes. Leading underscores are stripped for the argument name,
+ unless an alias is set on the attribute.
+
+ .. seealso::
+ `init` shows advanced ways to customize the generated
+ ``__init__`` method, including executing code before and after.
+
+ repr(bool):
+ Create a ``__repr__`` method with a human readable representation
+ of *attrs* attributes.
+
+ str (bool):
+ Create a ``__str__`` method that is identical to ``__repr__``. This
+ is usually not necessary except for `Exception`\ s.
+
+ eq (bool | None):
+ If True or None (default), add ``__eq__`` and ``__ne__`` methods
+ that check two instances for equality.
+
+ .. seealso::
+ `comparison` describes how to customize the comparison behavior
+ going as far comparing NumPy arrays.
+
+ order (bool | None):
+ If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__``
+ methods that behave like *eq* above and allow instances to be
+ ordered.
+
+ They compare the instances as if they were tuples of their *attrs*
+ attributes if and only if the types of both classes are
+ *identical*.
+
+ If `None` mirror value of *eq*.
+
+ .. seealso:: `comparison`
+
+ unsafe_hash (bool | None):
+ If None (default), the ``__hash__`` method is generated according
+ how *eq* and *frozen* are set.
+
+ 1. If *both* are True, *attrs* will generate a ``__hash__`` for
+ you.
+ 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set
+ to None, marking it unhashable (which it is).
+ 3. If *eq* is False, ``__hash__`` will be left untouched meaning
+ the ``__hash__`` method of the base class will be used. If the
+ base class is `object`, this means it will fall back to id-based
+ hashing.
+
+ Although not recommended, you can decide for yourself and force
+ *attrs* to create one (for example, if the class is immutable even
+ though you didn't freeze it programmatically) by passing True or
+ not. Both of these cases are rather special and should be used
+ carefully.
+
+ .. seealso::
+
+ - Our documentation on `hashing`,
+ - Python's documentation on `object.__hash__`,
+ - and the `GitHub issue that led to the default \ behavior
+ `_ for more
+ details.
+
+ hash (bool | None):
+ Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence.
+
+ cache_hash (bool):
+ Ensure that the object's hash code is computed only once and stored
+ on the object. If this is set to True, hashing must be either
+ explicitly or implicitly enabled for this class. If the hash code
+ is cached, avoid any reassignments of fields involved in hash code
+ computation or mutations of the objects those fields point to after
+ object creation. If such changes occur, the behavior of the
+ object's hash code is undefined.
+
+ frozen (bool):
+ Make instances immutable after initialization. If someone attempts
+ to modify a frozen instance, `attrs.exceptions.FrozenInstanceError`
+ is raised.
+
+ .. note::
+
+ 1. This is achieved by installing a custom ``__setattr__``
+ method on your class, so you can't implement your own.
+
+ 2. True immutability is impossible in Python.
+
+ 3. This *does* have a minor a runtime performance `impact
+ ` when initializing new instances. In other
+ words: ``__init__`` is slightly slower with ``frozen=True``.
+
+ 4. If a class is frozen, you cannot modify ``self`` in
+ ``__attrs_post_init__`` or a self-written ``__init__``. You
+ can circumvent that limitation by using
+ ``object.__setattr__(self, "attribute_name", value)``.
+
+ 5. Subclasses of a frozen class are frozen too.
+
+ kw_only (bool):
+ Make attributes keyword-only in the generated ``__init__`` (if
+ *init* is False, this parameter is ignored). Attributes that
+ explicitly set ``kw_only=False`` are not affected; base class
+ attributes are also not affected.
+
+ Also see *force_kw_only*.
+
+ weakref_slot (bool):
+ Make instances weak-referenceable. This has no effect unless
+ *slots* is True.
+
+ field_transformer (~typing.Callable | None):
+ A function that is called with the original class object and all
+ fields right before *attrs* finalizes the class. You can use this,
+ for example, to automatically add converters or validators to
+ fields based on their types.
+
+ .. seealso:: `transform-fields`
+
+ match_args (bool):
+ If True (default), set ``__match_args__`` on the class to support
+ :pep:`634` (*Structural Pattern Matching*). It is a tuple of all
+ non-keyword-only ``__init__`` parameter names on Python 3.10 and
+ later. Ignored on older Python versions.
+
+ collect_by_mro (bool):
+ If True, *attrs* collects attributes from base classes correctly
+ according to the `method resolution order
+ `_. If False, *attrs*
+ will mimic the (wrong) behavior of `dataclasses` and :pep:`681`.
+
+ See also `issue #428
+ `_.
+
+ force_kw_only (bool):
+ A back-compat flag for restoring pre-25.4.0 behavior. If True and
+ ``kw_only=True``, all attributes are made keyword-only, including
+ base class attributes, and those set to ``kw_only=False`` at the
+ attribute level. Defaults to False.
+
+ See also `issue #980
+ `_.
+
+ getstate_setstate (bool | None):
+ .. note::
+
+ This is usually only interesting for slotted classes and you
+ should probably just set *auto_detect* to True.
+
+ If True, ``__getstate__`` and ``__setstate__`` are generated and
+ attached to the class. This is necessary for slotted classes to be
+ pickleable. If left None, it's True by default for slotted classes
+ and False for dict classes.
+
+ If *auto_detect* is True, and *getstate_setstate* is left None, and
+ **either** ``__getstate__`` or ``__setstate__`` is detected
+ directly on the class (meaning: not inherited), it is set to False
+ (this is usually what you want).
+
+ auto_attribs (bool | None):
+ If True, look at type annotations to determine which attributes to
+ use, like `dataclasses`. If False, it will only look for explicit
+ :func:`field` class attributes, like classic *attrs*.
+
+ If left None, it will guess:
+
+ 1. If any attributes are annotated and no unannotated
+ `attrs.field`\ s are found, it assumes *auto_attribs=True*.
+ 2. Otherwise it assumes *auto_attribs=False* and tries to collect
+ `attrs.field`\ s.
+
+ If *attrs* decides to look at type annotations, **all** fields
+ **must** be annotated. If *attrs* encounters a field that is set to
+ a :func:`field` / `attr.ib` but lacks a type annotation, an
+ `attrs.exceptions.UnannotatedAttributeError` is raised. Use
+ ``field_name: typing.Any = field(...)`` if you don't want to set a
+ type.
+
+ .. warning::
+
+ For features that use the attribute name to create decorators
+ (for example, :ref:`validators `), you still *must*
+ assign :func:`field` / `attr.ib` to them. Otherwise Python will
+ either not find the name or try to use the default value to
+ call, for example, ``validator`` on it.
+
+ Attributes annotated as `typing.ClassVar`, and attributes that are
+ neither annotated nor set to an `field()` are **ignored**.
+
+ these (dict[str, object]):
+ A dictionary of name to the (private) return value of `field()`
+ mappings. This is useful to avoid the definition of your attributes
+ within the class body because you can't (for example, if you want
+ to add ``__repr__`` methods to Django models) or don't want to.
+
+ If *these* is not `None`, *attrs* will *not* search the class body
+ for attributes and will *not* remove any attributes from it.
+
+ The order is deduced from the order of the attributes inside
+ *these*.
+
+ Arguably, this is a rather obscure feature.
+
+ .. versionadded:: 20.1.0
+ .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``.
+ .. versionadded:: 22.2.0
+ *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
+ .. versionchanged:: 24.1.0
+ Instances are not compared as tuples of attributes anymore, but using a
+ big ``and`` condition. This is faster and has more correct behavior for
+ uncomparable values like `math.nan`.
+ .. versionadded:: 24.1.0
+ If a class has an *inherited* classmethod called
+ ``__attrs_init_subclass__``, it is executed after the class is created.
+ .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
+ .. versionadded:: 24.3.0
+ Unless already present, a ``__replace__`` method is automatically
+ created for `copy.replace` (Python 3.13+ only).
+ .. versionchanged:: 25.4.0
+ *kw_only* now only applies to attributes defined in the current class,
+ and respects attribute-level ``kw_only=False`` settings.
+ .. versionadded:: 25.4.0
+ Added *force_kw_only* to go back to the previous *kw_only* behavior.
+
+ .. note::
+
+ The main differences to the classic `attr.s` are:
+
+ - Automatically detect whether or not *auto_attribs* should be `True`
+ (c.f. *auto_attribs* parameter).
+ - Converters and validators run when attributes are set by default --
+ if *frozen* is `False`.
+ - *slots=True*
+
+ Usually, this has only upsides and few visible effects in everyday
+ programming. But it *can* lead to some surprising behaviors, so
+ please make sure to read :term:`slotted classes`.
+
+ - *auto_exc=True*
+ - *auto_detect=True*
+ - *order=False*
+ - *force_kw_only=False*
+ - Some options that were only relevant on Python 2 or were kept around
+ for backwards-compatibility have been removed.
+
+ """
+
+ def do_it(cls, auto_attribs):
+ return attrs(
+ maybe_cls=cls,
+ these=these,
+ repr=repr,
+ hash=hash,
+ unsafe_hash=unsafe_hash,
+ init=init,
+ slots=slots,
+ frozen=frozen,
+ weakref_slot=weakref_slot,
+ str=str,
+ auto_attribs=auto_attribs,
+ kw_only=kw_only,
+ cache_hash=cache_hash,
+ auto_exc=auto_exc,
+ eq=eq,
+ order=order,
+ auto_detect=auto_detect,
+ collect_by_mro=True,
+ getstate_setstate=getstate_setstate,
+ on_setattr=on_setattr,
+ field_transformer=field_transformer,
+ match_args=match_args,
+ force_kw_only=force_kw_only,
+ )
+
+ def wrap(cls):
+ """
+ Making this a wrapper ensures this code runs during class creation.
+
+ We also ensure that frozen-ness of classes is inherited.
+ """
+ nonlocal frozen, on_setattr
+
+ had_on_setattr = on_setattr not in (None, setters.NO_OP)
+
+ # By default, mutable classes convert & validate on setattr.
+ if frozen is False and on_setattr is None:
+ on_setattr = _DEFAULT_ON_SETATTR
+
+ # However, if we subclass a frozen class, we inherit the immutability
+ # and disable on_setattr.
+ for base_cls in cls.__bases__:
+ if base_cls.__setattr__ is _frozen_setattrs:
+ if had_on_setattr:
+ msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)."
+ raise ValueError(msg)
+
+ on_setattr = setters.NO_OP
+ break
+
+ if auto_attribs is not None:
+ return do_it(cls, auto_attribs)
+
+ try:
+ return do_it(cls, True)
+ except UnannotatedAttributeError:
+ return do_it(cls, False)
+
+ # maybe_cls's type depends on the usage of the decorator. It's a class
+ # if it's used as `@attrs` but `None` if used as `@attrs()`.
+ if maybe_cls is None:
+ return wrap
+
+ return wrap(maybe_cls)
+
+
+mutable = define
+frozen = partial(define, frozen=True, on_setattr=None)
+
+
+def field(
+ *,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ hash=None,
+ init=True,
+ metadata=None,
+ type=None,
+ converter=None,
+ factory=None,
+ kw_only=None,
+ eq=None,
+ order=None,
+ on_setattr=None,
+ alias=None,
+):
+ """
+ Create a new :term:`field` / :term:`attribute` on a class.
+
+ .. warning::
+
+ Does **nothing** unless the class is also decorated with
+ `attrs.define` (or similar)!
+
+ Args:
+ default:
+ A value that is used if an *attrs*-generated ``__init__`` is used
+ and no value is passed while instantiating or the attribute is
+ excluded using ``init=False``.
+
+ If the value is an instance of `attrs.Factory`, its callable will
+ be used to construct a new value (useful for mutable data types
+ like lists or dicts).
+
+ If a default is not set (or set manually to `attrs.NOTHING`), a
+ value *must* be supplied when instantiating; otherwise a
+ `TypeError` will be raised.
+
+ .. seealso:: `defaults`
+
+ factory (~typing.Callable):
+ Syntactic sugar for ``default=attr.Factory(factory)``.
+
+ validator (~typing.Callable | list[~typing.Callable]):
+ Callable that is called by *attrs*-generated ``__init__`` methods
+ after the instance has been initialized. They receive the
+ initialized instance, the :func:`~attrs.Attribute`, and the passed
+ value.
+
+ The return value is *not* inspected so the validator has to throw
+ an exception itself.
+
+ If a `list` is passed, its items are treated as validators and must
+ all pass.
+
+ Validators can be globally disabled and re-enabled using
+ `attrs.validators.get_disabled` / `attrs.validators.set_disabled`.
+
+ The validator can also be set using decorator notation as shown
+ below.
+
+ .. seealso:: :ref:`validators`
+
+ repr (bool | ~typing.Callable):
+ Include this attribute in the generated ``__repr__`` method. If
+ True, include the attribute; if False, omit it. By default, the
+ built-in ``repr()`` function is used. To override how the attribute
+ value is formatted, pass a ``callable`` that takes a single value
+ and returns a string. Note that the resulting string is used as-is,
+ which means it will be used directly *instead* of calling
+ ``repr()`` (the default).
+
+ eq (bool | ~typing.Callable):
+ If True (default), include this attribute in the generated
+ ``__eq__`` and ``__ne__`` methods that check two instances for
+ equality. To override how the attribute value is compared, pass a
+ callable that takes a single value and returns the value to be
+ compared.
+
+ .. seealso:: `comparison`
+
+ order (bool | ~typing.Callable):
+ If True (default), include this attributes in the generated
+ ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To
+ override how the attribute value is ordered, pass a callable that
+ takes a single value and returns the value to be ordered.
+
+ .. seealso:: `comparison`
+
+ hash (bool | None):
+ Include this attribute in the generated ``__hash__`` method. If
+ None (default), mirror *eq*'s value. This is the correct behavior
+ according the Python spec. Setting this value to anything else
+ than None is *discouraged*.
+
+ .. seealso:: `hashing`
+
+ init (bool):
+ Include this attribute in the generated ``__init__`` method.
+
+ It is possible to set this to False and set a default value. In
+ that case this attributed is unconditionally initialized with the
+ specified default value or factory.
+
+ .. seealso:: `init`
+
+ converter (typing.Callable | Converter):
+ A callable that is called by *attrs*-generated ``__init__`` methods
+ to convert attribute's value to the desired format.
+
+ If a vanilla callable is passed, it is given the passed-in value as
+ the only positional argument. It is possible to receive additional
+ arguments by wrapping the callable in a `Converter`.
+
+ Either way, the returned value will be used as the new value of the
+ attribute. The value is converted before being passed to the
+ validator, if any.
+
+ .. seealso:: :ref:`converters`
+
+ metadata (dict | None):
+ An arbitrary mapping, to be used by third-party code.
+
+ .. seealso:: `extending-metadata`.
+
+ type (type):
+ The type of the attribute. Nowadays, the preferred method to
+ specify the type is using a variable annotation (see :pep:`526`).
+ This argument is provided for backwards-compatibility and for usage
+ with `make_class`. Regardless of the approach used, the type will
+ be stored on ``Attribute.type``.
+
+ Please note that *attrs* doesn't do anything with this metadata by
+ itself. You can use it as part of your own code or for `static type
+ checking `.
+
+ kw_only (bool | None):
+ Make this attribute keyword-only in the generated ``__init__`` (if
+ *init* is False, this parameter is ignored). If None (default),
+ mirror the setting from `attrs.define`.
+
+ on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
+ Allows to overwrite the *on_setattr* setting from `attr.s`. If left
+ None, the *on_setattr* value from `attr.s` is used. Set to
+ `attrs.setters.NO_OP` to run **no** `setattr` hooks for this
+ attribute -- regardless of the setting in `define()`.
+
+ alias (str | None):
+ Override this attribute's parameter name in the generated
+ ``__init__`` method. If left None, default to ``name`` stripped
+ of leading underscores. See `private-attributes`.
+
+ .. versionadded:: 20.1.0
+ .. versionchanged:: 21.1.0
+ *eq*, *order*, and *cmp* also accept a custom callable
+ .. versionadded:: 22.2.0 *alias*
+ .. versionadded:: 23.1.0
+ The *type* parameter has been re-added; mostly for `attrs.make_class`.
+ Please note that type checkers ignore this metadata.
+ .. versionchanged:: 25.4.0
+ *kw_only* can now be None, and its default is also changed from False to
+ None.
+
+ .. seealso::
+
+ `attr.ib`
+ """
+ return attrib(
+ default=default,
+ validator=validator,
+ repr=repr,
+ hash=hash,
+ init=init,
+ metadata=metadata,
+ type=type,
+ converter=converter,
+ factory=factory,
+ kw_only=kw_only,
+ eq=eq,
+ order=order,
+ on_setattr=on_setattr,
+ alias=alias,
+ )
+
+
+def asdict(inst, *, recurse=True, filter=None, value_serializer=None):
+ """
+ Same as `attr.asdict`, except that collections types are always retained
+ and dict is always used as *dict_factory*.
+
+ .. versionadded:: 21.3.0
+ """
+ return _asdict(
+ inst=inst,
+ recurse=recurse,
+ filter=filter,
+ value_serializer=value_serializer,
+ retain_collection_types=True,
+ )
+
+
+def astuple(inst, *, recurse=True, filter=None):
+ """
+ Same as `attr.astuple`, except that collections types are always retained
+ and `tuple` is always used as the *tuple_factory*.
+
+ .. versionadded:: 21.3.0
+ """
+ return _astuple(
+ inst=inst, recurse=recurse, filter=filter, retain_collection_types=True
+ )
+
+
+def inspect(cls):
+ """
+ Inspect the class and return its effective build parameters.
+
+ Warning:
+ This feature is currently **experimental** and is not covered by our
+ strict backwards-compatibility guarantees.
+
+ Args:
+ cls: The *attrs*-decorated class to inspect.
+
+ Returns:
+ The effective build parameters of the class.
+
+ Raises:
+ NotAnAttrsClassError: If the class is not an *attrs*-decorated class.
+
+ .. versionadded:: 25.4.0
+ """
+ try:
+ return cls.__dict__["__attrs_props__"]
+ except KeyError:
+ msg = f"{cls!r} is not an attrs-decorated class."
+ raise NotAnAttrsClassError(msg) from None
diff --git a/lib/python3.12/site-packages/attr/_typing_compat.pyi b/lib/python3.12/site-packages/attr/_typing_compat.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..ca7b71e906a28f88726bbd342fdfe636af0281e7
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_typing_compat.pyi
@@ -0,0 +1,15 @@
+from typing import Any, ClassVar, Protocol
+
+# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`.
+MYPY = False
+
+if MYPY:
+ # A protocol to be able to statically accept an attrs class.
+ class AttrsInstance_(Protocol):
+ __attrs_attrs__: ClassVar[Any]
+
+else:
+ # For type checkers without plug-in support use an empty protocol that
+ # will (hopefully) be combined into a union.
+ class AttrsInstance_(Protocol):
+ pass
diff --git a/lib/python3.12/site-packages/attr/_version_info.py b/lib/python3.12/site-packages/attr/_version_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..27f18884ad4d7a42f338e31a8ea19bc5d19999cd
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_version_info.py
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: MIT
+
+
+from functools import total_ordering
+
+from ._funcs import astuple
+from ._make import attrib, attrs
+
+
+@total_ordering
+@attrs(eq=False, order=False, slots=True, frozen=True)
+class VersionInfo:
+ """
+ A version object that can be compared to tuple of length 1--4:
+
+ >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2)
+ True
+ >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1)
+ True
+ >>> vi = attr.VersionInfo(19, 2, 0, "final")
+ >>> vi < (19, 1, 1)
+ False
+ >>> vi < (19,)
+ False
+ >>> vi == (19, 2,)
+ True
+ >>> vi == (19, 2, 1)
+ False
+
+ .. versionadded:: 19.2
+ """
+
+ year = attrib(type=int)
+ minor = attrib(type=int)
+ micro = attrib(type=int)
+ releaselevel = attrib(type=str)
+
+ @classmethod
+ def _from_version_string(cls, s):
+ """
+ Parse *s* and return a _VersionInfo.
+ """
+ v = s.split(".")
+ if len(v) == 3:
+ v.append("final")
+
+ return cls(
+ year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3]
+ )
+
+ def _ensure_tuple(self, other):
+ """
+ Ensure *other* is a tuple of a valid length.
+
+ Returns a possibly transformed *other* and ourselves as a tuple of
+ the same length as *other*.
+ """
+
+ if self.__class__ is other.__class__:
+ other = astuple(other)
+
+ if not isinstance(other, tuple):
+ raise NotImplementedError
+
+ if not (1 <= len(other) <= 4):
+ raise NotImplementedError
+
+ return astuple(self)[: len(other)], other
+
+ def __eq__(self, other):
+ try:
+ us, them = self._ensure_tuple(other)
+ except NotImplementedError:
+ return NotImplemented
+
+ return us == them
+
+ def __lt__(self, other):
+ try:
+ us, them = self._ensure_tuple(other)
+ except NotImplementedError:
+ return NotImplemented
+
+ # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
+ # have to do anything special with releaselevel for now.
+ return us < them
+
+ def __hash__(self):
+ return hash((self.year, self.minor, self.micro, self.releaselevel))
diff --git a/lib/python3.12/site-packages/attr/_version_info.pyi b/lib/python3.12/site-packages/attr/_version_info.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..45ced086337783c4b73b26cd17d2c1c260e24029
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/_version_info.pyi
@@ -0,0 +1,9 @@
+class VersionInfo:
+ @property
+ def year(self) -> int: ...
+ @property
+ def minor(self) -> int: ...
+ @property
+ def micro(self) -> int: ...
+ @property
+ def releaselevel(self) -> str: ...
diff --git a/lib/python3.12/site-packages/attr/converters.py b/lib/python3.12/site-packages/attr/converters.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a79deef04282fb33a42f6aca59563d49e70d4cb
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/converters.py
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: MIT
+
+"""
+Commonly useful converters.
+"""
+
+import typing
+
+from ._compat import _AnnotationExtractor
+from ._make import NOTHING, Converter, Factory, pipe
+
+
+__all__ = [
+ "default_if_none",
+ "optional",
+ "pipe",
+ "to_bool",
+]
+
+
+def optional(converter):
+ """
+ A converter that allows an attribute to be optional. An optional attribute
+ is one which can be set to `None`.
+
+ Type annotations will be inferred from the wrapped converter's, if it has
+ any.
+
+ Args:
+ converter (typing.Callable):
+ the converter that is used for non-`None` values.
+
+ .. versionadded:: 17.1.0
+ """
+
+ if isinstance(converter, Converter):
+
+ def optional_converter(val, inst, field):
+ if val is None:
+ return None
+ return converter(val, inst, field)
+
+ else:
+
+ def optional_converter(val):
+ if val is None:
+ return None
+ return converter(val)
+
+ xtr = _AnnotationExtractor(converter)
+
+ t = xtr.get_first_param_type()
+ if t:
+ optional_converter.__annotations__["val"] = typing.Optional[t]
+
+ rt = xtr.get_return_type()
+ if rt:
+ optional_converter.__annotations__["return"] = typing.Optional[rt]
+
+ if isinstance(converter, Converter):
+ return Converter(optional_converter, takes_self=True, takes_field=True)
+
+ return optional_converter
+
+
+def default_if_none(default=NOTHING, factory=None):
+ """
+ A converter that allows to replace `None` values by *default* or the result
+ of *factory*.
+
+ Args:
+ default:
+ Value to be used if `None` is passed. Passing an instance of
+ `attrs.Factory` is supported, however the ``takes_self`` option is
+ *not*.
+
+ factory (typing.Callable):
+ A callable that takes no parameters whose result is used if `None`
+ is passed.
+
+ Raises:
+ TypeError: If **neither** *default* or *factory* is passed.
+
+ TypeError: If **both** *default* and *factory* are passed.
+
+ ValueError:
+ If an instance of `attrs.Factory` is passed with
+ ``takes_self=True``.
+
+ .. versionadded:: 18.2.0
+ """
+ if default is NOTHING and factory is None:
+ msg = "Must pass either `default` or `factory`."
+ raise TypeError(msg)
+
+ if default is not NOTHING and factory is not None:
+ msg = "Must pass either `default` or `factory` but not both."
+ raise TypeError(msg)
+
+ if factory is not None:
+ default = Factory(factory)
+
+ if isinstance(default, Factory):
+ if default.takes_self:
+ msg = "`takes_self` is not supported by default_if_none."
+ raise ValueError(msg)
+
+ def default_if_none_converter(val):
+ if val is not None:
+ return val
+
+ return default.factory()
+
+ else:
+
+ def default_if_none_converter(val):
+ if val is not None:
+ return val
+
+ return default
+
+ return default_if_none_converter
+
+
+def to_bool(val):
+ """
+ Convert "boolean" strings (for example, from environment variables) to real
+ booleans.
+
+ Values mapping to `True`:
+
+ - ``True``
+ - ``"true"`` / ``"t"``
+ - ``"yes"`` / ``"y"``
+ - ``"on"``
+ - ``"1"``
+ - ``1``
+
+ Values mapping to `False`:
+
+ - ``False``
+ - ``"false"`` / ``"f"``
+ - ``"no"`` / ``"n"``
+ - ``"off"``
+ - ``"0"``
+ - ``0``
+
+ Raises:
+ ValueError: For any other value.
+
+ .. versionadded:: 21.3.0
+ """
+ if isinstance(val, str):
+ val = val.lower()
+
+ if val in (True, "true", "t", "yes", "y", "on", "1", 1):
+ return True
+ if val in (False, "false", "f", "no", "n", "off", "0", 0):
+ return False
+
+ msg = f"Cannot convert value to bool: {val!r}"
+ raise ValueError(msg)
diff --git a/lib/python3.12/site-packages/attr/converters.pyi b/lib/python3.12/site-packages/attr/converters.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..12bd0c4f17bdc60fb8904598af0a3d56d5874a9e
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/converters.pyi
@@ -0,0 +1,19 @@
+from typing import Callable, Any, overload
+
+from attrs import _ConverterType, _CallableConverterType
+
+@overload
+def pipe(*validators: _CallableConverterType) -> _CallableConverterType: ...
+@overload
+def pipe(*validators: _ConverterType) -> _ConverterType: ...
+@overload
+def optional(converter: _CallableConverterType) -> _CallableConverterType: ...
+@overload
+def optional(converter: _ConverterType) -> _ConverterType: ...
+@overload
+def default_if_none(default: Any) -> _CallableConverterType: ...
+@overload
+def default_if_none(
+ *, factory: Callable[[], Any]
+) -> _CallableConverterType: ...
+def to_bool(val: str | int | bool) -> bool: ...
diff --git a/lib/python3.12/site-packages/attr/exceptions.py b/lib/python3.12/site-packages/attr/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b7abb8154108aa1d0ae52fa9ee8e489f05b5563
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/exceptions.py
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: MIT
+
+from __future__ import annotations
+
+from typing import ClassVar
+
+
+class FrozenError(AttributeError):
+ """
+ A frozen/immutable instance or attribute have been attempted to be
+ modified.
+
+ It mirrors the behavior of ``namedtuples`` by using the same error message
+ and subclassing `AttributeError`.
+
+ .. versionadded:: 20.1.0
+ """
+
+ msg = "can't set attribute"
+ args: ClassVar[tuple[str]] = [msg]
+
+
+class FrozenInstanceError(FrozenError):
+ """
+ A frozen instance has been attempted to be modified.
+
+ .. versionadded:: 16.1.0
+ """
+
+
+class FrozenAttributeError(FrozenError):
+ """
+ A frozen attribute has been attempted to be modified.
+
+ .. versionadded:: 20.1.0
+ """
+
+
+class AttrsAttributeNotFoundError(ValueError):
+ """
+ An *attrs* function couldn't find an attribute that the user asked for.
+
+ .. versionadded:: 16.2.0
+ """
+
+
+class NotAnAttrsClassError(ValueError):
+ """
+ A non-*attrs* class has been passed into an *attrs* function.
+
+ .. versionadded:: 16.2.0
+ """
+
+
+class DefaultAlreadySetError(RuntimeError):
+ """
+ A default has been set when defining the field and is attempted to be reset
+ using the decorator.
+
+ .. versionadded:: 17.1.0
+ """
+
+
+class UnannotatedAttributeError(RuntimeError):
+ """
+ A class with ``auto_attribs=True`` has a field without a type annotation.
+
+ .. versionadded:: 17.3.0
+ """
+
+
+class PythonTooOldError(RuntimeError):
+ """
+ It was attempted to use an *attrs* feature that requires a newer Python
+ version.
+
+ .. versionadded:: 18.2.0
+ """
+
+
+class NotCallableError(TypeError):
+ """
+ A field requiring a callable has been set with a value that is not
+ callable.
+
+ .. versionadded:: 19.2.0
+ """
+
+ def __init__(self, msg, value):
+ super(TypeError, self).__init__(msg, value)
+ self.msg = msg
+ self.value = value
+
+ def __str__(self):
+ return str(self.msg)
diff --git a/lib/python3.12/site-packages/attr/exceptions.pyi b/lib/python3.12/site-packages/attr/exceptions.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..f2680118b404db8f5227d04d27e8439331341c4d
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/exceptions.pyi
@@ -0,0 +1,17 @@
+from typing import Any
+
+class FrozenError(AttributeError):
+ msg: str = ...
+
+class FrozenInstanceError(FrozenError): ...
+class FrozenAttributeError(FrozenError): ...
+class AttrsAttributeNotFoundError(ValueError): ...
+class NotAnAttrsClassError(ValueError): ...
+class DefaultAlreadySetError(RuntimeError): ...
+class UnannotatedAttributeError(RuntimeError): ...
+class PythonTooOldError(RuntimeError): ...
+
+class NotCallableError(TypeError):
+ msg: str = ...
+ value: Any = ...
+ def __init__(self, msg: str, value: Any) -> None: ...
diff --git a/lib/python3.12/site-packages/attr/filters.py b/lib/python3.12/site-packages/attr/filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..689b1705a60ff110d6077bab996f8b4588e55b82
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/filters.py
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: MIT
+
+"""
+Commonly useful filters for `attrs.asdict` and `attrs.astuple`.
+"""
+
+from ._make import Attribute
+
+
+def _split_what(what):
+ """
+ Returns a tuple of `frozenset`s of classes and attributes.
+ """
+ return (
+ frozenset(cls for cls in what if isinstance(cls, type)),
+ frozenset(cls for cls in what if isinstance(cls, str)),
+ frozenset(cls for cls in what if isinstance(cls, Attribute)),
+ )
+
+
+def include(*what):
+ """
+ Create a filter that only allows *what*.
+
+ Args:
+ what (list[type, str, attrs.Attribute]):
+ What to include. Can be a type, a name, or an attribute.
+
+ Returns:
+ Callable:
+ A callable that can be passed to `attrs.asdict`'s and
+ `attrs.astuple`'s *filter* argument.
+
+ .. versionchanged:: 23.1.0 Accept strings with field names.
+ """
+ cls, names, attrs = _split_what(what)
+
+ def include_(attribute, value):
+ return (
+ value.__class__ in cls
+ or attribute.name in names
+ or attribute in attrs
+ )
+
+ return include_
+
+
+def exclude(*what):
+ """
+ Create a filter that does **not** allow *what*.
+
+ Args:
+ what (list[type, str, attrs.Attribute]):
+ What to exclude. Can be a type, a name, or an attribute.
+
+ Returns:
+ Callable:
+ A callable that can be passed to `attrs.asdict`'s and
+ `attrs.astuple`'s *filter* argument.
+
+ .. versionchanged:: 23.3.0 Accept field name string as input argument
+ """
+ cls, names, attrs = _split_what(what)
+
+ def exclude_(attribute, value):
+ return not (
+ value.__class__ in cls
+ or attribute.name in names
+ or attribute in attrs
+ )
+
+ return exclude_
diff --git a/lib/python3.12/site-packages/attr/filters.pyi b/lib/python3.12/site-packages/attr/filters.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..974abdcdb51152393d9c9e460c21aa025c45880c
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/filters.pyi
@@ -0,0 +1,6 @@
+from typing import Any
+
+from . import Attribute, _FilterType
+
+def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...
+def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...
diff --git a/lib/python3.12/site-packages/attr/py.typed b/lib/python3.12/site-packages/attr/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/attr/setters.py b/lib/python3.12/site-packages/attr/setters.py
new file mode 100644
index 0000000000000000000000000000000000000000..78b08398a6713fc5fa827c2dc853e0d05de743c4
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/setters.py
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: MIT
+
+"""
+Commonly used hooks for on_setattr.
+"""
+
+from . import _config
+from .exceptions import FrozenAttributeError
+
+
+def pipe(*setters):
+ """
+ Run all *setters* and return the return value of the last one.
+
+ .. versionadded:: 20.1.0
+ """
+
+ def wrapped_pipe(instance, attrib, new_value):
+ rv = new_value
+
+ for setter in setters:
+ rv = setter(instance, attrib, rv)
+
+ return rv
+
+ return wrapped_pipe
+
+
+def frozen(_, __, ___):
+ """
+ Prevent an attribute to be modified.
+
+ .. versionadded:: 20.1.0
+ """
+ raise FrozenAttributeError
+
+
+def validate(instance, attrib, new_value):
+ """
+ Run *attrib*'s validator on *new_value* if it has one.
+
+ .. versionadded:: 20.1.0
+ """
+ if _config._run_validators is False:
+ return new_value
+
+ v = attrib.validator
+ if not v:
+ return new_value
+
+ v(instance, attrib, new_value)
+
+ return new_value
+
+
+def convert(instance, attrib, new_value):
+ """
+ Run *attrib*'s converter -- if it has one -- on *new_value* and return the
+ result.
+
+ .. versionadded:: 20.1.0
+ """
+ c = attrib.converter
+ if c:
+ # This can be removed once we drop 3.8 and use attrs.Converter instead.
+ from ._make import Converter
+
+ if not isinstance(c, Converter):
+ return c(new_value)
+
+ return c(new_value, instance, attrib)
+
+ return new_value
+
+
+# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes.
+# Sphinx's autodata stopped working, so the docstring is inlined in the API
+# docs.
+NO_OP = object()
diff --git a/lib/python3.12/site-packages/attr/setters.pyi b/lib/python3.12/site-packages/attr/setters.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..73abf36e7d5b0f5f56e7fddeee716824c1c60d58
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/setters.pyi
@@ -0,0 +1,20 @@
+from typing import Any, NewType, NoReturn, TypeVar
+
+from . import Attribute
+from attrs import _OnSetAttrType
+
+_T = TypeVar("_T")
+
+def frozen(
+ instance: Any, attribute: Attribute[Any], new_value: Any
+) -> NoReturn: ...
+def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ...
+def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ...
+
+# convert is allowed to return Any, because they can be chained using pipe.
+def convert(
+ instance: Any, attribute: Attribute[Any], new_value: Any
+) -> Any: ...
+
+_NoOpType = NewType("_NoOpType", object)
+NO_OP: _NoOpType
diff --git a/lib/python3.12/site-packages/attr/validators.py b/lib/python3.12/site-packages/attr/validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..837e003b6c2e5ce485d0b1cd19bfa398f18a8d18
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/validators.py
@@ -0,0 +1,748 @@
+# SPDX-License-Identifier: MIT
+
+"""
+Commonly useful validators.
+"""
+
+import operator
+import re
+
+from contextlib import contextmanager
+from re import Pattern
+
+from ._config import get_run_validators, set_run_validators
+from ._make import _AndValidator, and_, attrib, attrs
+from .converters import default_if_none
+from .exceptions import NotCallableError
+
+
+__all__ = [
+ "and_",
+ "deep_iterable",
+ "deep_mapping",
+ "disabled",
+ "ge",
+ "get_disabled",
+ "gt",
+ "in_",
+ "instance_of",
+ "is_callable",
+ "le",
+ "lt",
+ "matches_re",
+ "max_len",
+ "min_len",
+ "not_",
+ "optional",
+ "or_",
+ "set_disabled",
+]
+
+
+def set_disabled(disabled):
+ """
+ Globally disable or enable running validators.
+
+ By default, they are run.
+
+ Args:
+ disabled (bool): If `True`, disable running all validators.
+
+ .. warning::
+
+ This function is not thread-safe!
+
+ .. versionadded:: 21.3.0
+ """
+ set_run_validators(not disabled)
+
+
+def get_disabled():
+ """
+ Return a bool indicating whether validators are currently disabled or not.
+
+ Returns:
+ bool:`True` if validators are currently disabled.
+
+ .. versionadded:: 21.3.0
+ """
+ return not get_run_validators()
+
+
+@contextmanager
+def disabled():
+ """
+ Context manager that disables running validators within its context.
+
+ .. warning::
+
+ This context manager is not thread-safe!
+
+ .. versionadded:: 21.3.0
+ """
+ set_run_validators(False)
+ try:
+ yield
+ finally:
+ set_run_validators(True)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _InstanceOfValidator:
+ type = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not isinstance(value, self.type):
+ msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
+ raise TypeError(
+ msg,
+ attr,
+ self.type,
+ value,
+ )
+
+ def __repr__(self):
+ return f""
+
+
+def instance_of(type):
+ """
+ A validator that raises a `TypeError` if the initializer is called with a
+ wrong type for this particular attribute (checks are performed using
+ `isinstance` therefore it's also valid to pass a tuple of types).
+
+ Args:
+ type (type | tuple[type]): The type to check for.
+
+ Raises:
+ TypeError:
+ With a human readable error message, the attribute (of type
+ `attrs.Attribute`), the expected type, and the value it got.
+ """
+ return _InstanceOfValidator(type)
+
+
+@attrs(repr=False, frozen=True, slots=True)
+class _MatchesReValidator:
+ pattern = attrib()
+ match_func = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not self.match_func(value):
+ msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
+ raise ValueError(
+ msg,
+ attr,
+ self.pattern,
+ value,
+ )
+
+ def __repr__(self):
+ return f""
+
+
+def matches_re(regex, flags=0, func=None):
+ r"""
+ A validator that raises `ValueError` if the initializer is called with a
+ string that doesn't match *regex*.
+
+ Args:
+ regex (str, re.Pattern):
+ A regex string or precompiled pattern to match against
+
+ flags (int):
+ Flags that will be passed to the underlying re function (default 0)
+
+ func (typing.Callable):
+ Which underlying `re` function to call. Valid options are
+ `re.fullmatch`, `re.search`, and `re.match`; the default `None`
+ means `re.fullmatch`. For performance reasons, the pattern is
+ always precompiled using `re.compile`.
+
+ .. versionadded:: 19.2.0
+ .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
+ """
+ valid_funcs = (re.fullmatch, None, re.search, re.match)
+ if func not in valid_funcs:
+ msg = "'func' must be one of {}.".format(
+ ", ".join(
+ sorted((e and e.__name__) or "None" for e in set(valid_funcs))
+ )
+ )
+ raise ValueError(msg)
+
+ if isinstance(regex, Pattern):
+ if flags:
+ msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
+ raise TypeError(msg)
+ pattern = regex
+ else:
+ pattern = re.compile(regex, flags)
+
+ if func is re.match:
+ match_func = pattern.match
+ elif func is re.search:
+ match_func = pattern.search
+ else:
+ match_func = pattern.fullmatch
+
+ return _MatchesReValidator(pattern, match_func)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _OptionalValidator:
+ validator = attrib()
+
+ def __call__(self, inst, attr, value):
+ if value is None:
+ return
+
+ self.validator(inst, attr, value)
+
+ def __repr__(self):
+ return f""
+
+
+def optional(validator):
+ """
+ A validator that makes an attribute optional. An optional attribute is one
+ which can be set to `None` in addition to satisfying the requirements of
+ the sub-validator.
+
+ Args:
+ validator
+ (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
+ A validator (or validators) that is used for non-`None` values.
+
+ .. versionadded:: 15.1.0
+ .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+ .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
+ """
+ if isinstance(validator, (list, tuple)):
+ return _OptionalValidator(_AndValidator(validator))
+
+ return _OptionalValidator(validator)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _InValidator:
+ options = attrib()
+ _original_options = attrib(hash=False)
+
+ def __call__(self, inst, attr, value):
+ try:
+ in_options = value in self.options
+ except TypeError: # e.g. `1 in "abc"`
+ in_options = False
+
+ if not in_options:
+ msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"
+ raise ValueError(
+ msg,
+ attr,
+ self._original_options,
+ value,
+ )
+
+ def __repr__(self):
+ return f""
+
+
+def in_(options):
+ """
+ A validator that raises a `ValueError` if the initializer is called with a
+ value that does not belong in the *options* provided.
+
+ The check is performed using ``value in options``, so *options* has to
+ support that operation.
+
+ To keep the validator hashable, dicts, lists, and sets are transparently
+ transformed into a `tuple`.
+
+ Args:
+ options: Allowed options.
+
+ Raises:
+ ValueError:
+ With a human readable error message, the attribute (of type
+ `attrs.Attribute`), the expected options, and the value it got.
+
+ .. versionadded:: 17.1.0
+ .. versionchanged:: 22.1.0
+ The ValueError was incomplete until now and only contained the human
+ readable error message. Now it contains all the information that has
+ been promised since 17.1.0.
+ .. versionchanged:: 24.1.0
+ *options* that are a list, dict, or a set are now transformed into a
+ tuple to keep the validator hashable.
+ """
+ repr_options = options
+ if isinstance(options, (list, dict, set)):
+ options = tuple(options)
+
+ return _InValidator(options, repr_options)
+
+
+@attrs(repr=False, slots=False, unsafe_hash=True)
+class _IsCallableValidator:
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not callable(value):
+ message = (
+ "'{name}' must be callable "
+ "(got {value!r} that is a {actual!r})."
+ )
+ raise NotCallableError(
+ msg=message.format(
+ name=attr.name, value=value, actual=value.__class__
+ ),
+ value=value,
+ )
+
+ def __repr__(self):
+ return ""
+
+
+def is_callable():
+ """
+ A validator that raises a `attrs.exceptions.NotCallableError` if the
+ initializer is called with a value for this particular attribute that is
+ not callable.
+
+ .. versionadded:: 19.1.0
+
+ Raises:
+ attrs.exceptions.NotCallableError:
+ With a human readable error message containing the attribute
+ (`attrs.Attribute`) name, and the value it got.
+ """
+ return _IsCallableValidator()
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _DeepIterable:
+ member_validator = attrib(validator=is_callable())
+ iterable_validator = attrib(
+ default=None, validator=optional(is_callable())
+ )
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if self.iterable_validator is not None:
+ self.iterable_validator(inst, attr, value)
+
+ for member in value:
+ self.member_validator(inst, attr, member)
+
+ def __repr__(self):
+ iterable_identifier = (
+ ""
+ if self.iterable_validator is None
+ else f" {self.iterable_validator!r}"
+ )
+ return (
+ f""
+ )
+
+
+def deep_iterable(member_validator, iterable_validator=None):
+ """
+ A validator that performs deep validation of an iterable.
+
+ Args:
+ member_validator: Validator(s) to apply to iterable members.
+
+ iterable_validator:
+ Validator(s) to apply to iterable itself (optional).
+
+ Raises
+ TypeError: if any sub-validators fail
+
+ .. versionadded:: 19.1.0
+
+ .. versionchanged:: 25.4.0
+ *member_validator* and *iterable_validator* can now be a list or tuple
+ of validators.
+ """
+ if isinstance(member_validator, (list, tuple)):
+ member_validator = and_(*member_validator)
+ if isinstance(iterable_validator, (list, tuple)):
+ iterable_validator = and_(*iterable_validator)
+ return _DeepIterable(member_validator, iterable_validator)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _DeepMapping:
+ key_validator = attrib(validator=optional(is_callable()))
+ value_validator = attrib(validator=optional(is_callable()))
+ mapping_validator = attrib(validator=optional(is_callable()))
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if self.mapping_validator is not None:
+ self.mapping_validator(inst, attr, value)
+
+ for key in value:
+ if self.key_validator is not None:
+ self.key_validator(inst, attr, key)
+ if self.value_validator is not None:
+ self.value_validator(inst, attr, value[key])
+
+ def __repr__(self):
+ return f""
+
+
+def deep_mapping(
+ key_validator=None, value_validator=None, mapping_validator=None
+):
+ """
+ A validator that performs deep validation of a dictionary.
+
+ All validators are optional, but at least one of *key_validator* or
+ *value_validator* must be provided.
+
+ Args:
+ key_validator: Validator(s) to apply to dictionary keys.
+
+ value_validator: Validator(s) to apply to dictionary values.
+
+ mapping_validator:
+ Validator(s) to apply to top-level mapping attribute.
+
+ .. versionadded:: 19.1.0
+
+ .. versionchanged:: 25.4.0
+ *key_validator* and *value_validator* are now optional, but at least one
+ of them must be provided.
+
+ .. versionchanged:: 25.4.0
+ *key_validator*, *value_validator*, and *mapping_validator* can now be a
+ list or tuple of validators.
+
+ Raises:
+ TypeError: If any sub-validator fails on validation.
+
+ ValueError:
+ If neither *key_validator* nor *value_validator* is provided on
+ instantiation.
+ """
+ if key_validator is None and value_validator is None:
+ msg = (
+ "At least one of key_validator or value_validator must be provided"
+ )
+ raise ValueError(msg)
+
+ if isinstance(key_validator, (list, tuple)):
+ key_validator = and_(*key_validator)
+ if isinstance(value_validator, (list, tuple)):
+ value_validator = and_(*value_validator)
+ if isinstance(mapping_validator, (list, tuple)):
+ mapping_validator = and_(*mapping_validator)
+
+ return _DeepMapping(key_validator, value_validator, mapping_validator)
+
+
+@attrs(repr=False, frozen=True, slots=True)
+class _NumberValidator:
+ bound = attrib()
+ compare_op = attrib()
+ compare_func = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not self.compare_func(value, self.bound):
+ msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
+ raise ValueError(msg)
+
+ def __repr__(self):
+ return f""
+
+
+def lt(val):
+ """
+ A validator that raises `ValueError` if the initializer is called with a
+ number larger or equal to *val*.
+
+ The validator uses `operator.lt` to compare the values.
+
+ Args:
+ val: Exclusive upper bound for values.
+
+ .. versionadded:: 21.3.0
+ """
+ return _NumberValidator(val, "<", operator.lt)
+
+
+def le(val):
+ """
+ A validator that raises `ValueError` if the initializer is called with a
+ number greater than *val*.
+
+ The validator uses `operator.le` to compare the values.
+
+ Args:
+ val: Inclusive upper bound for values.
+
+ .. versionadded:: 21.3.0
+ """
+ return _NumberValidator(val, "<=", operator.le)
+
+
+def ge(val):
+ """
+ A validator that raises `ValueError` if the initializer is called with a
+ number smaller than *val*.
+
+ The validator uses `operator.ge` to compare the values.
+
+ Args:
+ val: Inclusive lower bound for values
+
+ .. versionadded:: 21.3.0
+ """
+ return _NumberValidator(val, ">=", operator.ge)
+
+
+def gt(val):
+ """
+ A validator that raises `ValueError` if the initializer is called with a
+ number smaller or equal to *val*.
+
+ The validator uses `operator.gt` to compare the values.
+
+ Args:
+ val: Exclusive lower bound for values
+
+ .. versionadded:: 21.3.0
+ """
+ return _NumberValidator(val, ">", operator.gt)
+
+
+@attrs(repr=False, frozen=True, slots=True)
+class _MaxLengthValidator:
+ max_length = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if len(value) > self.max_length:
+ msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
+ raise ValueError(msg)
+
+ def __repr__(self):
+ return f""
+
+
+def max_len(length):
+ """
+ A validator that raises `ValueError` if the initializer is called
+ with a string or iterable that is longer than *length*.
+
+ Args:
+ length (int): Maximum length of the string or iterable
+
+ .. versionadded:: 21.3.0
+ """
+ return _MaxLengthValidator(length)
+
+
+@attrs(repr=False, frozen=True, slots=True)
+class _MinLengthValidator:
+ min_length = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if len(value) < self.min_length:
+ msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
+ raise ValueError(msg)
+
+ def __repr__(self):
+ return f""
+
+
+def min_len(length):
+ """
+ A validator that raises `ValueError` if the initializer is called
+ with a string or iterable that is shorter than *length*.
+
+ Args:
+ length (int): Minimum length of the string or iterable
+
+ .. versionadded:: 22.1.0
+ """
+ return _MinLengthValidator(length)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _SubclassOfValidator:
+ type = attrib()
+
+ def __call__(self, inst, attr, value):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not issubclass(value, self.type):
+ msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
+ raise TypeError(
+ msg,
+ attr,
+ self.type,
+ value,
+ )
+
+ def __repr__(self):
+ return f""
+
+
+def _subclass_of(type):
+ """
+ A validator that raises a `TypeError` if the initializer is called with a
+ wrong type for this particular attribute (checks are performed using
+ `issubclass` therefore it's also valid to pass a tuple of types).
+
+ Args:
+ type (type | tuple[type, ...]): The type(s) to check for.
+
+ Raises:
+ TypeError:
+ With a human readable error message, the attribute (of type
+ `attrs.Attribute`), the expected type, and the value it got.
+ """
+ return _SubclassOfValidator(type)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _NotValidator:
+ validator = attrib()
+ msg = attrib(
+ converter=default_if_none(
+ "not_ validator child '{validator!r}' "
+ "did not raise a captured error"
+ )
+ )
+ exc_types = attrib(
+ validator=deep_iterable(
+ member_validator=_subclass_of(Exception),
+ iterable_validator=instance_of(tuple),
+ ),
+ )
+
+ def __call__(self, inst, attr, value):
+ try:
+ self.validator(inst, attr, value)
+ except self.exc_types:
+ pass # suppress error to invert validity
+ else:
+ raise ValueError(
+ self.msg.format(
+ validator=self.validator,
+ exc_types=self.exc_types,
+ ),
+ attr,
+ self.validator,
+ value,
+ self.exc_types,
+ )
+
+ def __repr__(self):
+ return f""
+
+
+def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
+ """
+ A validator that wraps and logically 'inverts' the validator passed to it.
+ It will raise a `ValueError` if the provided validator *doesn't* raise a
+ `ValueError` or `TypeError` (by default), and will suppress the exception
+ if the provided validator *does*.
+
+ Intended to be used with existing validators to compose logic without
+ needing to create inverted variants, for example, ``not_(in_(...))``.
+
+ Args:
+ validator: A validator to be logically inverted.
+
+ msg (str):
+ Message to raise if validator fails. Formatted with keys
+ ``exc_types`` and ``validator``.
+
+ exc_types (tuple[type, ...]):
+ Exception type(s) to capture. Other types raised by child
+ validators will not be intercepted and pass through.
+
+ Raises:
+ ValueError:
+ With a human readable error message, the attribute (of type
+ `attrs.Attribute`), the validator that failed to raise an
+ exception, the value it got, and the expected exception types.
+
+ .. versionadded:: 22.2.0
+ """
+ try:
+ exc_types = tuple(exc_types)
+ except TypeError:
+ exc_types = (exc_types,)
+ return _NotValidator(validator, msg, exc_types)
+
+
+@attrs(repr=False, slots=True, unsafe_hash=True)
+class _OrValidator:
+ validators = attrib()
+
+ def __call__(self, inst, attr, value):
+ for v in self.validators:
+ try:
+ v(inst, attr, value)
+ except Exception: # noqa: BLE001, PERF203, S112
+ continue
+ else:
+ return
+
+ msg = f"None of {self.validators!r} satisfied for value {value!r}"
+ raise ValueError(msg)
+
+ def __repr__(self):
+ return f""
+
+
+def or_(*validators):
+ """
+ A validator that composes multiple validators into one.
+
+ When called on a value, it runs all wrapped validators until one of them is
+ satisfied.
+
+ Args:
+ validators (~collections.abc.Iterable[typing.Callable]):
+ Arbitrary number of validators.
+
+ Raises:
+ ValueError:
+ If no validator is satisfied. Raised with a human-readable error
+ message listing all the wrapped validators and the value that
+ failed all of them.
+
+ .. versionadded:: 24.1.0
+ """
+ vals = []
+ for v in validators:
+ vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
+
+ return _OrValidator(tuple(vals))
diff --git a/lib/python3.12/site-packages/attr/validators.pyi b/lib/python3.12/site-packages/attr/validators.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..36a7e800c237f46755ddfca306f8d867a7f493db
--- /dev/null
+++ b/lib/python3.12/site-packages/attr/validators.pyi
@@ -0,0 +1,140 @@
+from types import UnionType
+from typing import (
+ Any,
+ AnyStr,
+ Callable,
+ Container,
+ ContextManager,
+ Iterable,
+ Mapping,
+ Match,
+ Pattern,
+ TypeVar,
+ overload,
+)
+
+from attrs import _ValidatorType
+from attrs import _ValidatorArgType
+
+_T = TypeVar("_T")
+_T1 = TypeVar("_T1")
+_T2 = TypeVar("_T2")
+_T3 = TypeVar("_T3")
+_T4 = TypeVar("_T4")
+_T5 = TypeVar("_T5")
+_T6 = TypeVar("_T6")
+_I = TypeVar("_I", bound=Iterable)
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+_M = TypeVar("_M", bound=Mapping)
+
+def set_disabled(run: bool) -> None: ...
+def get_disabled() -> bool: ...
+def disabled() -> ContextManager[None]: ...
+
+# To be more precise on instance_of use some overloads.
+# If there are more than 3 items in the tuple then we fall back to Any
+@overload
+def instance_of(type: type[_T]) -> _ValidatorType[_T]: ...
+@overload
+def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ...
+@overload
+def instance_of(
+ type: tuple[type[_T1], type[_T2]],
+) -> _ValidatorType[_T1 | _T2]: ...
+@overload
+def instance_of(
+ type: tuple[type[_T1], type[_T2], type[_T3]],
+) -> _ValidatorType[_T1 | _T2 | _T3]: ...
+@overload
+def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ...
+@overload
+def instance_of(type: UnionType) -> _ValidatorType[Any]: ...
+def optional(
+ validator: (
+ _ValidatorType[_T]
+ | list[_ValidatorType[_T]]
+ | tuple[_ValidatorType[_T]]
+ ),
+) -> _ValidatorType[_T | None]: ...
+def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
+def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
+def matches_re(
+ regex: Pattern[AnyStr] | AnyStr,
+ flags: int = ...,
+ func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ...,
+) -> _ValidatorType[AnyStr]: ...
+def deep_iterable(
+ member_validator: _ValidatorArgType[_T],
+ iterable_validator: _ValidatorArgType[_I] | None = ...,
+) -> _ValidatorType[_I]: ...
+@overload
+def deep_mapping(
+ key_validator: _ValidatorArgType[_K],
+ value_validator: _ValidatorArgType[_V] | None = ...,
+ mapping_validator: _ValidatorArgType[_M] | None = ...,
+) -> _ValidatorType[_M]: ...
+@overload
+def deep_mapping(
+ key_validator: _ValidatorArgType[_K] | None = ...,
+ value_validator: _ValidatorArgType[_V] = ...,
+ mapping_validator: _ValidatorArgType[_M] | None = ...,
+) -> _ValidatorType[_M]: ...
+def is_callable() -> _ValidatorType[_T]: ...
+def lt(val: _T) -> _ValidatorType[_T]: ...
+def le(val: _T) -> _ValidatorType[_T]: ...
+def ge(val: _T) -> _ValidatorType[_T]: ...
+def gt(val: _T) -> _ValidatorType[_T]: ...
+def max_len(length: int) -> _ValidatorType[_T]: ...
+def min_len(length: int) -> _ValidatorType[_T]: ...
+def not_(
+ validator: _ValidatorType[_T],
+ *,
+ msg: str | None = None,
+ exc_types: type[Exception] | Iterable[type[Exception]] = ...,
+) -> _ValidatorType[_T]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[_T1],
+ __v2: _ValidatorType[_T2],
+) -> _ValidatorType[_T1 | _T2]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[_T1],
+ __v2: _ValidatorType[_T2],
+ __v3: _ValidatorType[_T3],
+) -> _ValidatorType[_T1 | _T2 | _T3]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[_T1],
+ __v2: _ValidatorType[_T2],
+ __v3: _ValidatorType[_T3],
+ __v4: _ValidatorType[_T4],
+) -> _ValidatorType[_T1 | _T2 | _T3 | _T4]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[_T1],
+ __v2: _ValidatorType[_T2],
+ __v3: _ValidatorType[_T3],
+ __v4: _ValidatorType[_T4],
+ __v5: _ValidatorType[_T5],
+) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[_T1],
+ __v2: _ValidatorType[_T2],
+ __v3: _ValidatorType[_T3],
+ __v4: _ValidatorType[_T4],
+ __v5: _ValidatorType[_T5],
+ __v6: _ValidatorType[_T6],
+) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5 | _T6]: ...
+@overload
+def or_(
+ __v1: _ValidatorType[Any],
+ __v2: _ValidatorType[Any],
+ __v3: _ValidatorType[Any],
+ __v4: _ValidatorType[Any],
+ __v5: _ValidatorType[Any],
+ __v6: _ValidatorType[Any],
+ *validators: _ValidatorType[Any],
+) -> _ValidatorType[Any]: ...
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER b/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst b/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9d227a0cc43c3268d15722b763bd94ad298645a1
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2010 Pallets
+
+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.
+
+3. Neither the name of the copyright holder 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
+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.
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA b/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..d7c314536239e2b54a4166f1edf315c2a31b7ea6
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA
@@ -0,0 +1,116 @@
+Metadata-Version: 2.1
+Name: Flask
+Version: 2.3.3
+Summary: A simple framework for building complex web applications.
+Maintainer-email: Pallets
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Framework :: Flask
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
+Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
+Requires-Dist: Werkzeug>=2.3.7
+Requires-Dist: Jinja2>=3.1.2
+Requires-Dist: itsdangerous>=2.1.2
+Requires-Dist: click>=8.1.3
+Requires-Dist: blinker>=1.6.2
+Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10'
+Requires-Dist: asgiref>=3.2 ; extra == "async"
+Requires-Dist: python-dotenv ; extra == "dotenv"
+Project-URL: Changes, https://flask.palletsprojects.com/changes/
+Project-URL: Chat, https://discord.gg/pallets
+Project-URL: Documentation, https://flask.palletsprojects.com/
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
+Project-URL: Source Code, https://github.com/pallets/flask/
+Provides-Extra: async
+Provides-Extra: dotenv
+
+Flask
+=====
+
+Flask is a lightweight `WSGI`_ web application framework. It is designed
+to make getting started quick and easy, with the ability to scale up to
+complex applications. It began as a simple wrapper around `Werkzeug`_
+and `Jinja`_ and has become one of the most popular Python web
+application frameworks.
+
+Flask offers suggestions, but doesn't enforce any dependencies or
+project layout. It is up to the developer to choose the tools and
+libraries they want to use. There are many extensions provided by the
+community that make adding new functionality easy.
+
+.. _WSGI: https://wsgi.readthedocs.io/
+.. _Werkzeug: https://werkzeug.palletsprojects.com/
+.. _Jinja: https://jinja.palletsprojects.com/
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ $ pip install -U Flask
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+.. code-block:: python
+
+ # save this as app.py
+ from flask import Flask
+
+ app = Flask(__name__)
+
+ @app.route("/")
+ def hello():
+ return "Hello, World!"
+
+.. code-block:: text
+
+ $ flask run
+ * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
+
+
+Contributing
+------------
+
+For guidance on setting up a development environment and how to make a
+contribution to Flask, see the `contributing guidelines`_.
+
+.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
+
+
+Donate
+------
+
+The Pallets organization develops and supports Flask and the libraries
+it uses. In order to grow the community of contributors and users, and
+allow the maintainers to devote more time to the projects, `please
+donate today`_.
+
+.. _please donate today: https://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://flask.palletsprojects.com/
+- Changes: https://flask.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/Flask/
+- Source Code: https://github.com/pallets/flask/
+- Issue Tracker: https://github.com/pallets/flask/issues/
+- Chat: https://discord.gg/pallets
+
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD b/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..6071e67c47c2f46e8fbe5c69acfcac4c6f31d737
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD
@@ -0,0 +1,53 @@
+../../../bin/flask,sha256=u2KgtjDhP6gWVLV93L8roQIkRwH7epasXVW7zvXR5Xo,244
+flask-2.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+flask-2.3.3.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
+flask-2.3.3.dist-info/METADATA,sha256=-BtXVsnPe7lNA3mcFZHJfsVIiVin1A8LUstChm8qiHo,3588
+flask-2.3.3.dist-info/RECORD,,
+flask-2.3.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask-2.3.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+flask-2.3.3.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40
+flask/__init__.py,sha256=xq09XNKP-Y-fdv6BeGH7RlFaY006tUA3o_llGcl-dno,3731
+flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
+flask/__pycache__/__init__.cpython-312.pyc,,
+flask/__pycache__/__main__.cpython-312.pyc,,
+flask/__pycache__/app.cpython-312.pyc,,
+flask/__pycache__/blueprints.cpython-312.pyc,,
+flask/__pycache__/cli.cpython-312.pyc,,
+flask/__pycache__/config.cpython-312.pyc,,
+flask/__pycache__/ctx.cpython-312.pyc,,
+flask/__pycache__/debughelpers.cpython-312.pyc,,
+flask/__pycache__/globals.cpython-312.pyc,,
+flask/__pycache__/helpers.cpython-312.pyc,,
+flask/__pycache__/logging.cpython-312.pyc,,
+flask/__pycache__/scaffold.cpython-312.pyc,,
+flask/__pycache__/sessions.cpython-312.pyc,,
+flask/__pycache__/signals.cpython-312.pyc,,
+flask/__pycache__/templating.cpython-312.pyc,,
+flask/__pycache__/testing.cpython-312.pyc,,
+flask/__pycache__/typing.cpython-312.pyc,,
+flask/__pycache__/views.cpython-312.pyc,,
+flask/__pycache__/wrappers.cpython-312.pyc,,
+flask/app.py,sha256=ht3Qx9U9z0I1qUfLoS7bYhJcubdpk-i54eHq37LDlN8,87620
+flask/blueprints.py,sha256=ZpVrwa8UY-YnVDsX_1K10XQjDwCUp7Qn2hmKln5icEQ,24332
+flask/cli.py,sha256=PDwZCfPagi5GUzb-D6dEN7y20gWiVAg3ejRnxBKNHPA,33821
+flask/config.py,sha256=YZSZ-xpFj1iW1B1Kj1iDhpc5s7pHncloiRLqXhsU7Hs,12856
+flask/ctx.py,sha256=x2kGzUXtPzVyi2YSKrU_PV1AvtxTmh2iRdriJRTSPGM,14841
+flask/debughelpers.py,sha256=BR0xkd-sAyFuFW07D6NfrqNwSZxk1IrkG5n8zem-3sw,5547
+flask/globals.py,sha256=KUzVvSPh8v28kUasVDi_aQKB9hI2jZSYQHqaDU2P414,2945
+flask/helpers.py,sha256=uVhMwhhfwgjBt8b--zIZTjkfBRK28yPpmNhgVzhP444,25106
+flask/json/__init__.py,sha256=pdtpoK2b0b1u7Sxbx3feM7VWhsI20l1yGAvbYWxaxvc,5572
+flask/json/__pycache__/__init__.cpython-312.pyc,,
+flask/json/__pycache__/provider.cpython-312.pyc,,
+flask/json/__pycache__/tag.cpython-312.pyc,,
+flask/json/provider.py,sha256=Os0frb8oGfyWKL-TDxb0Uy-MY6gDhPdJkRaUl5xAOXI,7637
+flask/json/tag.py,sha256=ihb7QWrNEr0YC3KD4TolZbftgSPCuLk7FAvK49huYC0,8871
+flask/logging.py,sha256=lArx2Bq9oTtUJ-DnZL9t88xU2zytzp4UWSM9Bd72NDQ,2327
+flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask/scaffold.py,sha256=ALGHLcy2qSbJ7ENd1H8dOnq5VDgH5XSFsOkDelcOKV8,33217
+flask/sessions.py,sha256=rFH2QKXG24dEazkKGxAHqUpAUh_30hDHrddhVYgAcY0,14169
+flask/signals.py,sha256=s1H4yKjf3c5dgVr41V6sJpE9dLJvmTJMYuK0rkqx3sw,1146
+flask/templating.py,sha256=XdP2hMFnZ5FCZOG7HUaLjC2VC-b4uHSWlDjwv_1p3qc,7503
+flask/testing.py,sha256=h7AinggrMgGzKlDN66VfB0JjWW4Z1U_OD6FyjqBNiYM,10017
+flask/typing.py,sha256=4Lj-YTxUoYvPYofC9GKu-1o0Ht8lyjp9z3I336J13_o,3005
+flask/views.py,sha256=V5hOGZLx0Bn99QGcM6mh5x_uM-MypVT0-RysEFU84jc,6789
+flask/wrappers.py,sha256=PhMp3teK3SnEmIdog59cO_DHiZ9Btn0qI1EifrTdwP8,5709
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/REQUESTED b/lib/python3.12/site-packages/flask-2.3.3.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL b/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..3b5e64b5e6c4a210201d1676a891fd57b15cda99
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt b/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eec6733e577feb9487435b9722713a820bd4ccc1
--- /dev/null
+++ b/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+flask=flask.cli:main
+
diff --git a/lib/python3.12/site-packages/flask/__init__.py b/lib/python3.12/site-packages/flask/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdca1b0149179a48a8e417b0c0ae912ebb2a0b57
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/__init__.py
@@ -0,0 +1,102 @@
+from . import json as json
+from .app import Flask as Flask
+from .app import Request as Request
+from .app import Response as Response
+from .blueprints import Blueprint as Blueprint
+from .config import Config as Config
+from .ctx import after_this_request as after_this_request
+from .ctx import copy_current_request_context as copy_current_request_context
+from .ctx import has_app_context as has_app_context
+from .ctx import has_request_context as has_request_context
+from .globals import current_app as current_app
+from .globals import g as g
+from .globals import request as request
+from .globals import session as session
+from .helpers import abort as abort
+from .helpers import flash as flash
+from .helpers import get_flashed_messages as get_flashed_messages
+from .helpers import get_template_attribute as get_template_attribute
+from .helpers import make_response as make_response
+from .helpers import redirect as redirect
+from .helpers import send_file as send_file
+from .helpers import send_from_directory as send_from_directory
+from .helpers import stream_with_context as stream_with_context
+from .helpers import url_for as url_for
+from .json import jsonify as jsonify
+from .signals import appcontext_popped as appcontext_popped
+from .signals import appcontext_pushed as appcontext_pushed
+from .signals import appcontext_tearing_down as appcontext_tearing_down
+from .signals import before_render_template as before_render_template
+from .signals import got_request_exception as got_request_exception
+from .signals import message_flashed as message_flashed
+from .signals import request_finished as request_finished
+from .signals import request_started as request_started
+from .signals import request_tearing_down as request_tearing_down
+from .signals import template_rendered as template_rendered
+from .templating import render_template as render_template
+from .templating import render_template_string as render_template_string
+from .templating import stream_template as stream_template
+from .templating import stream_template_string as stream_template_string
+
+__version__ = "2.3.3"
+
+
+def __getattr__(name):
+ if name == "_app_ctx_stack":
+ import warnings
+ from .globals import __app_ctx_stack
+
+ warnings.warn(
+ "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __app_ctx_stack
+
+ if name == "_request_ctx_stack":
+ import warnings
+ from .globals import __request_ctx_stack
+
+ warnings.warn(
+ "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __request_ctx_stack
+
+ if name == "escape":
+ import warnings
+ from markupsafe import escape
+
+ warnings.warn(
+ "'flask.escape' is deprecated and will be removed in Flask 2.4. Import"
+ " 'markupsafe.escape' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return escape
+
+ if name == "Markup":
+ import warnings
+ from markupsafe import Markup
+
+ warnings.warn(
+ "'flask.Markup' is deprecated and will be removed in Flask 2.4. Import"
+ " 'markupsafe.Markup' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return Markup
+
+ if name == "signals_available":
+ import warnings
+
+ warnings.warn(
+ "'signals_available' is deprecated and will be removed in Flask 2.4."
+ " Signals are always available",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return True
+
+ raise AttributeError(name)
diff --git a/lib/python3.12/site-packages/flask/__main__.py b/lib/python3.12/site-packages/flask/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e28416e104515e90fca4b69cc60d0c61fd15d61
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/__main__.py
@@ -0,0 +1,3 @@
+from .cli import main
+
+main()
diff --git a/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f9c7345073c949e641bac12448df9e190ff87076
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f30380477e3bd6f66cb16d7b4eab1fa2c910ae6
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/app.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/app.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6c2556565ab3d39436b20f412f5b8cc06f002c26
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/app.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..79348fd13c9b78e60ad6464157d74df910660c59
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9d897274e7ded2e85e66dd73b7799ffc57c82f8d
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7b6162ea036d4e8f233dff793856737644f40cc5
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f7279004ea661b0c38360776dd60d1e6b642173b
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e2ab3c9f63e8f08b03467dd721571d0817007a30
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..038aaf967cbe5ae94e61b657f7888d5a0cfc78e5
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..75ef6628412954bf200cfeb847ad2078c1426558
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c256e203fa8d6d879909f68ce4124261b7d256c0
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/scaffold.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/scaffold.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d7c70ba91a0f5bd4c3a06fa6fc1e6259542e86ce
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/scaffold.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..87875ab9204a89a63553660db2e7df3b25617781
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/signals.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/signals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5876546dd1784d8d74ae1deb1e1c25835b69c9b8
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/signals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5ab9c2979fdbd34ea09144f96ec12cbc8f92b829
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a85cc4d95f2708de45f48329819f445e6e3947e6
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..173b556570a8c178fbd7c098c7fe31e222f30878
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..82c8ab2924728c66a5d6c194a1c330849fd57d0d
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc b/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3b1651074500d25e54e81f63bff23472c88346fd
Binary files /dev/null and b/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/app.py b/lib/python3.12/site-packages/flask/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b6b38d8ad7f32af70edfead740af1aa313b76f3
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/app.py
@@ -0,0 +1,2213 @@
+from __future__ import annotations
+
+import logging
+import os
+import sys
+import typing as t
+import weakref
+from collections.abc import Iterator as _abc_Iterator
+from datetime import timedelta
+from inspect import iscoroutinefunction
+from itertools import chain
+from types import TracebackType
+from urllib.parse import quote as _url_quote
+
+import click
+from werkzeug.datastructures import Headers
+from werkzeug.datastructures import ImmutableDict
+from werkzeug.exceptions import Aborter
+from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import BadRequestKeyError
+from werkzeug.exceptions import HTTPException
+from werkzeug.exceptions import InternalServerError
+from werkzeug.routing import BuildError
+from werkzeug.routing import Map
+from werkzeug.routing import MapAdapter
+from werkzeug.routing import RequestRedirect
+from werkzeug.routing import RoutingException
+from werkzeug.routing import Rule
+from werkzeug.serving import is_running_from_reloader
+from werkzeug.utils import cached_property
+from werkzeug.utils import redirect as _wz_redirect
+from werkzeug.wrappers import Response as BaseResponse
+
+from . import cli
+from . import typing as ft
+from .config import Config
+from .config import ConfigAttribute
+from .ctx import _AppCtxGlobals
+from .ctx import AppContext
+from .ctx import RequestContext
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import g
+from .globals import request
+from .globals import request_ctx
+from .globals import session
+from .helpers import _split_blueprint_path
+from .helpers import get_debug_flag
+from .helpers import get_flashed_messages
+from .helpers import get_load_dotenv
+from .json.provider import DefaultJSONProvider
+from .json.provider import JSONProvider
+from .logging import create_logger
+from .scaffold import _endpoint_from_view_func
+from .scaffold import _sentinel
+from .scaffold import find_package
+from .scaffold import Scaffold
+from .scaffold import setupmethod
+from .sessions import SecureCookieSessionInterface
+from .sessions import SessionInterface
+from .signals import appcontext_tearing_down
+from .signals import got_request_exception
+from .signals import request_finished
+from .signals import request_started
+from .signals import request_tearing_down
+from .templating import DispatchingJinjaLoader
+from .templating import Environment
+from .wrappers import Request
+from .wrappers import Response
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .blueprints import Blueprint
+ from .testing import FlaskClient
+ from .testing import FlaskCliRunner
+
+T_shell_context_processor = t.TypeVar(
+ "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
+)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+
+
+def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
+ if value is None or isinstance(value, timedelta):
+ return value
+
+ return timedelta(seconds=value)
+
+
+class Flask(Scaffold):
+ """The flask object implements a WSGI application and acts as the central
+ object. It is passed the name of the module or package of the
+ application. Once it is created it will act as a central registry for
+ the view functions, the URL rules, template configuration and much more.
+
+ The name of the package is used to resolve resources from inside the
+ package or the folder the module is contained in depending on if the
+ package parameter resolves to an actual python package (a folder with
+ an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
+
+ For more information about resource loading, see :func:`open_resource`.
+
+ Usually you create a :class:`Flask` instance in your main module or
+ in the :file:`__init__.py` file of your package like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+
+ .. admonition:: About the First Parameter
+
+ The idea of the first parameter is to give Flask an idea of what
+ belongs to your application. This name is used to find resources
+ on the filesystem, can be used by extensions to improve debugging
+ information and a lot more.
+
+ So it's important what you provide there. If you are using a single
+ module, `__name__` is always the correct value. If you however are
+ using a package, it's usually recommended to hardcode the name of
+ your package there.
+
+ For example if your application is defined in :file:`yourapplication/app.py`
+ you should create it with one of the two versions below::
+
+ app = Flask('yourapplication')
+ app = Flask(__name__.split('.')[0])
+
+ Why is that? The application will work even with `__name__`, thanks
+ to how resources are looked up. However it will make debugging more
+ painful. Certain extensions can make assumptions based on the
+ import name of your application. For example the Flask-SQLAlchemy
+ extension will look for the code in your application that triggered
+ an SQL query in debug mode. If the import name is not properly set
+ up, that debugging information is lost. (For example it would only
+ pick up SQL queries in `yourapplication.app` and not
+ `yourapplication.views.frontend`)
+
+ .. versionadded:: 0.7
+ The `static_url_path`, `static_folder`, and `template_folder`
+ parameters were added.
+
+ .. versionadded:: 0.8
+ The `instance_path` and `instance_relative_config` parameters were
+ added.
+
+ .. versionadded:: 0.11
+ The `root_path` parameter was added.
+
+ .. versionadded:: 1.0
+ The ``host_matching`` and ``static_host`` parameters were added.
+
+ .. versionadded:: 1.0
+ The ``subdomain_matching`` parameter was added. Subdomain
+ matching needs to be enabled manually now. Setting
+ :data:`SERVER_NAME` does not implicitly enable it.
+
+ :param import_name: the name of the application package
+ :param static_url_path: can be used to specify a different path for the
+ static files on the web. Defaults to the name
+ of the `static_folder` folder.
+ :param static_folder: The folder with static files that is served at
+ ``static_url_path``. Relative to the application ``root_path``
+ or an absolute path. Defaults to ``'static'``.
+ :param static_host: the host to use when adding the static route.
+ Defaults to None. Required when using ``host_matching=True``
+ with a ``static_folder`` configured.
+ :param host_matching: set ``url_map.host_matching`` attribute.
+ Defaults to False.
+ :param subdomain_matching: consider the subdomain relative to
+ :data:`SERVER_NAME` when matching routes. Defaults to False.
+ :param template_folder: the folder that contains the templates that should
+ be used by the application. Defaults to
+ ``'templates'`` folder in the root path of the
+ application.
+ :param instance_path: An alternative instance path for the application.
+ By default the folder ``'instance'`` next to the
+ package or module is assumed to be the instance
+ path.
+ :param instance_relative_config: if set to ``True`` relative filenames
+ for loading the config are assumed to
+ be relative to the instance path instead
+ of the application root.
+ :param root_path: The path to the root of the application files.
+ This should only be set manually when it can't be detected
+ automatically, such as for namespace packages.
+ """
+
+ #: The class that is used for request objects. See :class:`~flask.Request`
+ #: for more information.
+ request_class = Request
+
+ #: The class that is used for response objects. See
+ #: :class:`~flask.Response` for more information.
+ response_class = Response
+
+ #: The class of the object assigned to :attr:`aborter`, created by
+ #: :meth:`create_aborter`. That object is called by
+ #: :func:`flask.abort` to raise HTTP errors, and can be
+ #: called directly as well.
+ #:
+ #: Defaults to :class:`werkzeug.exceptions.Aborter`.
+ #:
+ #: .. versionadded:: 2.2
+ aborter_class = Aborter
+
+ #: The class that is used for the Jinja environment.
+ #:
+ #: .. versionadded:: 0.11
+ jinja_environment = Environment
+
+ #: The class that is used for the :data:`~flask.g` instance.
+ #:
+ #: Example use cases for a custom class:
+ #:
+ #: 1. Store arbitrary attributes on flask.g.
+ #: 2. Add a property for lazy per-request database connectors.
+ #: 3. Return None instead of AttributeError on unexpected attributes.
+ #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
+ #:
+ #: In Flask 0.9 this property was called `request_globals_class` but it
+ #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
+ #: flask.g object is now application context scoped.
+ #:
+ #: .. versionadded:: 0.10
+ app_ctx_globals_class = _AppCtxGlobals
+
+ #: The class that is used for the ``config`` attribute of this app.
+ #: Defaults to :class:`~flask.Config`.
+ #:
+ #: Example use cases for a custom class:
+ #:
+ #: 1. Default values for certain config options.
+ #: 2. Access to config values through attributes in addition to keys.
+ #:
+ #: .. versionadded:: 0.11
+ config_class = Config
+
+ #: The testing flag. Set this to ``True`` to enable the test mode of
+ #: Flask extensions (and in the future probably also Flask itself).
+ #: For example this might activate test helpers that have an
+ #: additional runtime cost which should not be enabled by default.
+ #:
+ #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
+ #: default it's implicitly enabled.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: ``TESTING`` configuration key. Defaults to ``False``.
+ testing = ConfigAttribute("TESTING")
+
+ #: If a secret key is set, cryptographic components can use this to
+ #: sign cookies and other things. Set this to a complex random value
+ #: when you want to use the secure cookie for instance.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
+ secret_key = ConfigAttribute("SECRET_KEY")
+
+ #: A :class:`~datetime.timedelta` which is used to set the expiration
+ #: date of a permanent session. The default is 31 days which makes a
+ #: permanent session survive for roughly one month.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to
+ #: ``timedelta(days=31)``
+ permanent_session_lifetime = ConfigAttribute(
+ "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
+ )
+
+ json_provider_class: type[JSONProvider] = DefaultJSONProvider
+ """A subclass of :class:`~flask.json.provider.JSONProvider`. An
+ instance is created and assigned to :attr:`app.json` when creating
+ the app.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
+ Python's built-in :mod:`json` library. A different provider can use
+ a different JSON library.
+
+ .. versionadded:: 2.2
+ """
+
+ #: Options that are passed to the Jinja environment in
+ #: :meth:`create_jinja_environment`. Changing these options after
+ #: the environment is created (accessing :attr:`jinja_env`) will
+ #: have no effect.
+ #:
+ #: .. versionchanged:: 1.1.0
+ #: This is a ``dict`` instead of an ``ImmutableDict`` to allow
+ #: easier configuration.
+ #:
+ jinja_options: dict = {}
+
+ #: Default configuration parameters.
+ default_config = ImmutableDict(
+ {
+ "DEBUG": None,
+ "TESTING": False,
+ "PROPAGATE_EXCEPTIONS": None,
+ "SECRET_KEY": None,
+ "PERMANENT_SESSION_LIFETIME": timedelta(days=31),
+ "USE_X_SENDFILE": False,
+ "SERVER_NAME": None,
+ "APPLICATION_ROOT": "/",
+ "SESSION_COOKIE_NAME": "session",
+ "SESSION_COOKIE_DOMAIN": None,
+ "SESSION_COOKIE_PATH": None,
+ "SESSION_COOKIE_HTTPONLY": True,
+ "SESSION_COOKIE_SECURE": False,
+ "SESSION_COOKIE_SAMESITE": None,
+ "SESSION_REFRESH_EACH_REQUEST": True,
+ "MAX_CONTENT_LENGTH": None,
+ "SEND_FILE_MAX_AGE_DEFAULT": None,
+ "TRAP_BAD_REQUEST_ERRORS": None,
+ "TRAP_HTTP_EXCEPTIONS": False,
+ "EXPLAIN_TEMPLATE_LOADING": False,
+ "PREFERRED_URL_SCHEME": "http",
+ "TEMPLATES_AUTO_RELOAD": None,
+ "MAX_COOKIE_SIZE": 4093,
+ }
+ )
+
+ #: The rule object to use for URL rules created. This is used by
+ #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
+ #:
+ #: .. versionadded:: 0.7
+ url_rule_class = Rule
+
+ #: The map object to use for storing the URL rules and routing
+ #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
+ #:
+ #: .. versionadded:: 1.1.0
+ url_map_class = Map
+
+ #: The :meth:`test_client` method creates an instance of this test
+ #: client class. Defaults to :class:`~flask.testing.FlaskClient`.
+ #:
+ #: .. versionadded:: 0.7
+ test_client_class: type[FlaskClient] | None = None
+
+ #: The :class:`~click.testing.CliRunner` subclass, by default
+ #: :class:`~flask.testing.FlaskCliRunner` that is used by
+ #: :meth:`test_cli_runner`. Its ``__init__`` method should take a
+ #: Flask app object as the first argument.
+ #:
+ #: .. versionadded:: 1.0
+ test_cli_runner_class: type[FlaskCliRunner] | None = None
+
+ #: the session interface to use. By default an instance of
+ #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
+ #:
+ #: .. versionadded:: 0.8
+ session_interface: SessionInterface = SecureCookieSessionInterface()
+
+ def __init__(
+ self,
+ import_name: str,
+ static_url_path: str | None = None,
+ static_folder: str | os.PathLike | None = "static",
+ static_host: str | None = None,
+ host_matching: bool = False,
+ subdomain_matching: bool = False,
+ template_folder: str | os.PathLike | None = "templates",
+ instance_path: str | None = None,
+ instance_relative_config: bool = False,
+ root_path: str | None = None,
+ ):
+ super().__init__(
+ import_name=import_name,
+ static_folder=static_folder,
+ static_url_path=static_url_path,
+ template_folder=template_folder,
+ root_path=root_path,
+ )
+
+ if instance_path is None:
+ instance_path = self.auto_find_instance_path()
+ elif not os.path.isabs(instance_path):
+ raise ValueError(
+ "If an instance path is provided it must be absolute."
+ " A relative path was given instead."
+ )
+
+ #: Holds the path to the instance folder.
+ #:
+ #: .. versionadded:: 0.8
+ self.instance_path = instance_path
+
+ #: The configuration dictionary as :class:`Config`. This behaves
+ #: exactly like a regular dictionary but supports additional methods
+ #: to load a config from files.
+ self.config = self.make_config(instance_relative_config)
+
+ #: An instance of :attr:`aborter_class` created by
+ #: :meth:`make_aborter`. This is called by :func:`flask.abort`
+ #: to raise HTTP errors, and can be called directly as well.
+ #:
+ #: .. versionadded:: 2.2
+ #: Moved from ``flask.abort``, which calls this object.
+ self.aborter = self.make_aborter()
+
+ self.json: JSONProvider = self.json_provider_class(self)
+ """Provides access to JSON methods. Functions in ``flask.json``
+ will call methods on this provider when the application context
+ is active. Used for handling JSON requests and responses.
+
+ An instance of :attr:`json_provider_class`. Can be customized by
+ changing that attribute on a subclass, or by assigning to this
+ attribute afterwards.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`,
+ uses Python's built-in :mod:`json` library. A different provider
+ can use a different JSON library.
+
+ .. versionadded:: 2.2
+ """
+
+ #: A list of functions that are called by
+ #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
+ #: :exc:`~werkzeug.routing.BuildError`. Each function is called
+ #: with ``error``, ``endpoint`` and ``values``. If a function
+ #: returns ``None`` or raises a ``BuildError``, it is skipped.
+ #: Otherwise, its return value is returned by ``url_for``.
+ #:
+ #: .. versionadded:: 0.9
+ self.url_build_error_handlers: list[
+ t.Callable[[Exception, str, dict[str, t.Any]], str]
+ ] = []
+
+ #: A list of functions that are called when the application context
+ #: is destroyed. Since the application context is also torn down
+ #: if the request ends this is the place to store code that disconnects
+ #: from databases.
+ #:
+ #: .. versionadded:: 0.9
+ self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
+
+ #: A list of shell context processor functions that should be run
+ #: when a shell context is created.
+ #:
+ #: .. versionadded:: 0.11
+ self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
+
+ #: Maps registered blueprint names to blueprint objects. The
+ #: dict retains the order the blueprints were registered in.
+ #: Blueprints can be registered multiple times, this dict does
+ #: not track how often they were attached.
+ #:
+ #: .. versionadded:: 0.7
+ self.blueprints: dict[str, Blueprint] = {}
+
+ #: a place where extensions can store application specific state. For
+ #: example this is where an extension could store database engines and
+ #: similar things.
+ #:
+ #: The key must match the name of the extension module. For example in
+ #: case of a "Flask-Foo" extension in `flask_foo`, the key would be
+ #: ``'foo'``.
+ #:
+ #: .. versionadded:: 0.7
+ self.extensions: dict = {}
+
+ #: The :class:`~werkzeug.routing.Map` for this instance. You can use
+ #: this to change the routing converters after the class was created
+ #: but before any routes are connected. Example::
+ #:
+ #: from werkzeug.routing import BaseConverter
+ #:
+ #: class ListConverter(BaseConverter):
+ #: def to_python(self, value):
+ #: return value.split(',')
+ #: def to_url(self, values):
+ #: return ','.join(super(ListConverter, self).to_url(value)
+ #: for value in values)
+ #:
+ #: app = Flask(__name__)
+ #: app.url_map.converters['list'] = ListConverter
+ self.url_map = self.url_map_class()
+
+ self.url_map.host_matching = host_matching
+ self.subdomain_matching = subdomain_matching
+
+ # tracks internally if the application already handled at least one
+ # request.
+ self._got_first_request = False
+
+ # Add a static route using the provided static_url_path, static_host,
+ # and static_folder if there is a configured static_folder.
+ # Note we do this without checking if static_folder exists.
+ # For one, it might be created while the server is running (e.g. during
+ # development). Also, Google App Engine stores static files somewhere
+ if self.has_static_folder:
+ assert (
+ bool(static_host) == host_matching
+ ), "Invalid static_host/host_matching combination"
+ # Use a weakref to avoid creating a reference cycle between the app
+ # and the view function (see #3761).
+ self_ref = weakref.ref(self)
+ self.add_url_rule(
+ f"{self.static_url_path}/",
+ endpoint="static",
+ host=static_host,
+ view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
+ )
+
+ # Set the name of the Click group in case someone wants to add
+ # the app's commands to another CLI tool.
+ self.cli.name = self.name
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_first_request:
+ raise AssertionError(
+ f"The setup method '{f_name}' can no longer be called"
+ " on the application. It has already handled its first"
+ " request, any changes will not be applied"
+ " consistently.\n"
+ "Make sure all imports, decorators, functions, etc."
+ " needed to set up the application are done before"
+ " running it."
+ )
+
+ @cached_property
+ def name(self) -> str: # type: ignore
+ """The name of the application. This is usually the import name
+ with the difference that it's guessed from the run file if the
+ import name is main. This name is used as a display name when
+ Flask needs the name of the application. It can be set and overridden
+ to change the value.
+
+ .. versionadded:: 0.8
+ """
+ if self.import_name == "__main__":
+ fn = getattr(sys.modules["__main__"], "__file__", None)
+ if fn is None:
+ return "__main__"
+ return os.path.splitext(os.path.basename(fn))[0]
+ return self.import_name
+
+ @cached_property
+ def logger(self) -> logging.Logger:
+ """A standard Python :class:`~logging.Logger` for the app, with
+ the same name as :attr:`name`.
+
+ In debug mode, the logger's :attr:`~logging.Logger.level` will
+ be set to :data:`~logging.DEBUG`.
+
+ If there are no handlers configured, a default handler will be
+ added. See :doc:`/logging` for more information.
+
+ .. versionchanged:: 1.1.0
+ The logger takes the same name as :attr:`name` rather than
+ hard-coding ``"flask.app"``.
+
+ .. versionchanged:: 1.0.0
+ Behavior was simplified. The logger is always named
+ ``"flask.app"``. The level is only set during configuration,
+ it doesn't check ``app.debug`` each time. Only one format is
+ used, not different ones depending on ``app.debug``. No
+ handlers are removed, and a handler is only added if no
+ handlers are already configured.
+
+ .. versionadded:: 0.3
+ """
+ return create_logger(self)
+
+ @cached_property
+ def jinja_env(self) -> Environment:
+ """The Jinja environment used to load templates.
+
+ The environment is created the first time this property is
+ accessed. Changing :attr:`jinja_options` after that will have no
+ effect.
+ """
+ return self.create_jinja_environment()
+
+ @property
+ def got_first_request(self) -> bool:
+ """This attribute is set to ``True`` if the application started
+ handling the first request.
+
+ .. deprecated:: 2.3
+ Will be removed in Flask 2.4.
+
+ .. versionadded:: 0.8
+ """
+ import warnings
+
+ warnings.warn(
+ "'got_first_request' is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._got_first_request
+
+ def make_config(self, instance_relative: bool = False) -> Config:
+ """Used to create the config attribute by the Flask constructor.
+ The `instance_relative` parameter is passed in from the constructor
+ of Flask (there named `instance_relative_config`) and indicates if
+ the config should be relative to the instance path or the root path
+ of the application.
+
+ .. versionadded:: 0.8
+ """
+ root_path = self.root_path
+ if instance_relative:
+ root_path = self.instance_path
+ defaults = dict(self.default_config)
+ defaults["DEBUG"] = get_debug_flag()
+ return self.config_class(root_path, defaults)
+
+ def make_aborter(self) -> Aborter:
+ """Create the object to assign to :attr:`aborter`. That object
+ is called by :func:`flask.abort` to raise HTTP errors, and can
+ be called directly as well.
+
+ By default, this creates an instance of :attr:`aborter_class`,
+ which defaults to :class:`werkzeug.exceptions.Aborter`.
+
+ .. versionadded:: 2.2
+ """
+ return self.aborter_class()
+
+ def auto_find_instance_path(self) -> str:
+ """Tries to locate the instance path if it was not provided to the
+ constructor of the application class. It will basically calculate
+ the path to a folder named ``instance`` next to your main file or
+ the package.
+
+ .. versionadded:: 0.8
+ """
+ prefix, package_path = find_package(self.import_name)
+ if prefix is None:
+ return os.path.join(package_path, "instance")
+ return os.path.join(prefix, "var", f"{self.name}-instance")
+
+ def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Opens a resource from the application's instance folder
+ (:attr:`instance_path`). Otherwise works like
+ :meth:`open_resource`. Instance resources can also be opened for
+ writing.
+
+ :param resource: the name of the resource. To access resources within
+ subfolders use forward slashes as separator.
+ :param mode: resource file opening mode, default is 'rb'.
+ """
+ return open(os.path.join(self.instance_path, resource), mode)
+
+ def create_jinja_environment(self) -> Environment:
+ """Create the Jinja environment based on :attr:`jinja_options`
+ and the various Jinja-related methods of the app. Changing
+ :attr:`jinja_options` after this will have no effect. Also adds
+ Flask-related globals and filters to the environment.
+
+ .. versionchanged:: 0.11
+ ``Environment.auto_reload`` set in accordance with
+ ``TEMPLATES_AUTO_RELOAD`` configuration option.
+
+ .. versionadded:: 0.5
+ """
+ options = dict(self.jinja_options)
+
+ if "autoescape" not in options:
+ options["autoescape"] = self.select_jinja_autoescape
+
+ if "auto_reload" not in options:
+ auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
+
+ if auto_reload is None:
+ auto_reload = self.debug
+
+ options["auto_reload"] = auto_reload
+
+ rv = self.jinja_environment(self, **options)
+ rv.globals.update(
+ url_for=self.url_for,
+ get_flashed_messages=get_flashed_messages,
+ config=self.config,
+ # request, session and g are normally added with the
+ # context processor for efficiency reasons but for imported
+ # templates we also want the proxies in there.
+ request=request,
+ session=session,
+ g=g,
+ )
+ rv.policies["json.dumps_function"] = self.json.dumps
+ return rv
+
+ def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
+ """Creates the loader for the Jinja2 environment. Can be used to
+ override just the loader and keeping the rest unchanged. It's
+ discouraged to override this function. Instead one should override
+ the :meth:`jinja_loader` function instead.
+
+ The global loader dispatches between the loaders of the application
+ and the individual blueprints.
+
+ .. versionadded:: 0.7
+ """
+ return DispatchingJinjaLoader(self)
+
+ def select_jinja_autoescape(self, filename: str) -> bool:
+ """Returns ``True`` if autoescaping should be active for the given
+ template name. If no template name is given, returns `True`.
+
+ .. versionchanged:: 2.2
+ Autoescaping is now enabled by default for ``.svg`` files.
+
+ .. versionadded:: 0.5
+ """
+ if filename is None:
+ return True
+ return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
+
+ def update_template_context(self, context: dict) -> None:
+ """Update the template context with some commonly used variables.
+ This injects request, session, config and g into the template
+ context as well as everything template context processors want
+ to inject. Note that the as of Flask 0.6, the original values
+ in the context will not be overridden if a context processor
+ decides to return a value with the same key.
+
+ :param context: the context as a dictionary that is updated in place
+ to add extra variables.
+ """
+ names: t.Iterable[str | None] = (None,)
+
+ # A template may be rendered outside a request context.
+ if request:
+ names = chain(names, reversed(request.blueprints))
+
+ # The values passed to render_template take precedence. Keep a
+ # copy to re-apply after all context functions.
+ orig_ctx = context.copy()
+
+ for name in names:
+ if name in self.template_context_processors:
+ for func in self.template_context_processors[name]:
+ context.update(func())
+
+ context.update(orig_ctx)
+
+ def make_shell_context(self) -> dict:
+ """Returns the shell context for an interactive shell for this
+ application. This runs all the registered shell context
+ processors.
+
+ .. versionadded:: 0.11
+ """
+ rv = {"app": self, "g": g}
+ for processor in self.shell_context_processors:
+ rv.update(processor())
+ return rv
+
+ @property
+ def debug(self) -> bool:
+ """Whether debug mode is enabled. When using ``flask run`` to start the
+ development server, an interactive debugger will be shown for unhandled
+ exceptions, and the server will be reloaded when code changes. This maps to the
+ :data:`DEBUG` config key. It may not behave as expected if set late.
+
+ **Do not enable debug mode when deploying in production.**
+
+ Default: ``False``
+ """
+ return self.config["DEBUG"]
+
+ @debug.setter
+ def debug(self, value: bool) -> None:
+ self.config["DEBUG"] = value
+
+ if self.config["TEMPLATES_AUTO_RELOAD"] is None:
+ self.jinja_env.auto_reload = value
+
+ def run(
+ self,
+ host: str | None = None,
+ port: int | None = None,
+ debug: bool | None = None,
+ load_dotenv: bool = True,
+ **options: t.Any,
+ ) -> None:
+ """Runs the application on a local development server.
+
+ Do not use ``run()`` in a production setting. It is not intended to
+ meet security and performance requirements for a production server.
+ Instead, see :doc:`/deploying/index` for WSGI server recommendations.
+
+ If the :attr:`debug` flag is set the server will automatically reload
+ for code changes and show a debugger in case an exception happened.
+
+ If you want to run the application in debug mode, but disable the
+ code execution on the interactive debugger, you can pass
+ ``use_evalex=False`` as parameter. This will keep the debugger's
+ traceback screen active, but disable code execution.
+
+ It is not recommended to use this function for development with
+ automatic reloading as this is badly supported. Instead you should
+ be using the :command:`flask` command line script's ``run`` support.
+
+ .. admonition:: Keep in Mind
+
+ Flask will suppress any server error with a generic error page
+ unless it is in debug mode. As such to enable just the
+ interactive debugger without the code reloading, you have to
+ invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
+ Setting ``use_debugger`` to ``True`` without being in debug mode
+ won't catch any exceptions because there won't be any to
+ catch.
+
+ :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
+ have the server available externally as well. Defaults to
+ ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable
+ if present.
+ :param port: the port of the webserver. Defaults to ``5000`` or the
+ port defined in the ``SERVER_NAME`` config variable if present.
+ :param debug: if given, enable or disable debug mode. See
+ :attr:`debug`.
+ :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
+ files to set environment variables. Will also change the working
+ directory to the directory containing the first file found.
+ :param options: the options to be forwarded to the underlying Werkzeug
+ server. See :func:`werkzeug.serving.run_simple` for more
+ information.
+
+ .. versionchanged:: 1.0
+ If installed, python-dotenv will be used to load environment
+ variables from :file:`.env` and :file:`.flaskenv` files.
+
+ The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`.
+
+ Threaded mode is enabled by default.
+
+ .. versionchanged:: 0.10
+ The default port is now picked from the ``SERVER_NAME``
+ variable.
+ """
+ # Ignore this call so that it doesn't start another server if
+ # the 'flask run' command is used.
+ if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
+ if not is_running_from_reloader():
+ click.secho(
+ " * Ignoring a call to 'app.run()' that would block"
+ " the current 'flask' CLI command.\n"
+ " Only call 'app.run()' in an 'if __name__ =="
+ ' "__main__"\' guard.',
+ fg="red",
+ )
+
+ return
+
+ if get_load_dotenv(load_dotenv):
+ cli.load_dotenv()
+
+ # if set, env var overrides existing value
+ if "FLASK_DEBUG" in os.environ:
+ self.debug = get_debug_flag()
+
+ # debug passed to method overrides all other sources
+ if debug is not None:
+ self.debug = bool(debug)
+
+ server_name = self.config.get("SERVER_NAME")
+ sn_host = sn_port = None
+
+ if server_name:
+ sn_host, _, sn_port = server_name.partition(":")
+
+ if not host:
+ if sn_host:
+ host = sn_host
+ else:
+ host = "127.0.0.1"
+
+ if port or port == 0:
+ port = int(port)
+ elif sn_port:
+ port = int(sn_port)
+ else:
+ port = 5000
+
+ options.setdefault("use_reloader", self.debug)
+ options.setdefault("use_debugger", self.debug)
+ options.setdefault("threaded", True)
+
+ cli.show_server_banner(self.debug, self.name)
+
+ from werkzeug.serving import run_simple
+
+ try:
+ run_simple(t.cast(str, host), port, self, **options)
+ finally:
+ # reset the first request information if the development server
+ # reset normally. This makes it possible to restart the server
+ # without reloader and that stuff from an interactive shell.
+ self._got_first_request = False
+
+ def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient:
+ """Creates a test client for this application. For information
+ about unit testing head over to :doc:`/testing`.
+
+ Note that if you are testing for assertions or exceptions in your
+ application code, you must set ``app.testing = True`` in order for the
+ exceptions to propagate to the test client. Otherwise, the exception
+ will be handled by the application (not visible to the test client) and
+ the only indication of an AssertionError or other exception will be a
+ 500 status code response to the test client. See the :attr:`testing`
+ attribute. For example::
+
+ app.testing = True
+ client = app.test_client()
+
+ The test client can be used in a ``with`` block to defer the closing down
+ of the context until the end of the ``with`` block. This is useful if
+ you want to access the context locals for testing::
+
+ with app.test_client() as c:
+ rv = c.get('/?vodka=42')
+ assert request.args['vodka'] == '42'
+
+ Additionally, you may pass optional keyword arguments that will then
+ be passed to the application's :attr:`test_client_class` constructor.
+ For example::
+
+ from flask.testing import FlaskClient
+
+ class CustomClient(FlaskClient):
+ def __init__(self, *args, **kwargs):
+ self._authentication = kwargs.pop("authentication")
+ super(CustomClient,self).__init__( *args, **kwargs)
+
+ app.test_client_class = CustomClient
+ client = app.test_client(authentication='Basic ....')
+
+ See :class:`~flask.testing.FlaskClient` for more information.
+
+ .. versionchanged:: 0.4
+ added support for ``with`` block usage for the client.
+
+ .. versionadded:: 0.7
+ The `use_cookies` parameter was added as well as the ability
+ to override the client to be used by setting the
+ :attr:`test_client_class` attribute.
+
+ .. versionchanged:: 0.11
+ Added `**kwargs` to support passing additional keyword arguments to
+ the constructor of :attr:`test_client_class`.
+ """
+ cls = self.test_client_class
+ if cls is None:
+ from .testing import FlaskClient as cls
+ return cls( # type: ignore
+ self, self.response_class, use_cookies=use_cookies, **kwargs
+ )
+
+ def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:
+ """Create a CLI runner for testing CLI commands.
+ See :ref:`testing-cli`.
+
+ Returns an instance of :attr:`test_cli_runner_class`, by default
+ :class:`~flask.testing.FlaskCliRunner`. The Flask app object is
+ passed as the first argument.
+
+ .. versionadded:: 1.0
+ """
+ cls = self.test_cli_runner_class
+
+ if cls is None:
+ from .testing import FlaskCliRunner as cls
+
+ return cls(self, **kwargs) # type: ignore
+
+ @setupmethod
+ def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
+ """Register a :class:`~flask.Blueprint` on the application. Keyword
+ arguments passed to this method will override the defaults set on the
+ blueprint.
+
+ Calls the blueprint's :meth:`~flask.Blueprint.register` method after
+ recording the blueprint in the application's :attr:`blueprints`.
+
+ :param blueprint: The blueprint to register.
+ :param url_prefix: Blueprint routes will be prefixed with this.
+ :param subdomain: Blueprint routes will match on this subdomain.
+ :param url_defaults: Blueprint routes will use these default values for
+ view arguments.
+ :param options: Additional keyword arguments are passed to
+ :class:`~flask.blueprints.BlueprintSetupState`. They can be
+ accessed in :meth:`~flask.Blueprint.record` callbacks.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+
+ .. versionadded:: 0.7
+ """
+ blueprint.register(self, options)
+
+ def iter_blueprints(self) -> t.ValuesView[Blueprint]:
+ """Iterates over all blueprints by the order they were registered.
+
+ .. versionadded:: 0.11
+ """
+ return self.blueprints.values()
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func) # type: ignore
+ options["endpoint"] = endpoint
+ methods = options.pop("methods", None)
+
+ # if the methods are not given and the view_func object knows its
+ # methods we can use that instead. If neither exists, we go with
+ # a tuple of only ``GET`` as default.
+ if methods is None:
+ methods = getattr(view_func, "methods", None) or ("GET",)
+ if isinstance(methods, str):
+ raise TypeError(
+ "Allowed methods must be a list of strings, for"
+ ' example: @app.route(..., methods=["POST"])'
+ )
+ methods = {item.upper() for item in methods}
+
+ # Methods that should always be added
+ required_methods = set(getattr(view_func, "required_methods", ()))
+
+ # starting with Flask 0.8 the view_func object can disable and
+ # force-enable the automatic options handling.
+ if provide_automatic_options is None:
+ provide_automatic_options = getattr(
+ view_func, "provide_automatic_options", None
+ )
+
+ if provide_automatic_options is None:
+ if "OPTIONS" not in methods:
+ provide_automatic_options = True
+ required_methods.add("OPTIONS")
+ else:
+ provide_automatic_options = False
+
+ # Add the required methods now.
+ methods |= required_methods
+
+ rule = self.url_rule_class(rule, methods=methods, **options)
+ rule.provide_automatic_options = provide_automatic_options # type: ignore
+
+ self.url_map.add(rule)
+ if view_func is not None:
+ old_func = self.view_functions.get(endpoint)
+ if old_func is not None and old_func != view_func:
+ raise AssertionError(
+ "View function mapping is overwriting an existing"
+ f" endpoint function: {endpoint}"
+ )
+ self.view_functions[endpoint] = view_func
+
+ @setupmethod
+ def template_filter(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
+ """A decorator that is used to register custom template filter.
+ You can specify a name for the filter, otherwise the function
+ name will be used. Example::
+
+ @app.template_filter()
+ def reverse(s):
+ return s[::-1]
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_filter) -> T_template_filter:
+ self.add_template_filter(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_filter(
+ self, f: ft.TemplateFilterCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template filter. Works exactly like the
+ :meth:`template_filter` decorator.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.filters[name or f.__name__] = f
+
+ @setupmethod
+ def template_test(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_test], T_template_test]:
+ """A decorator that is used to register custom template test.
+ You can specify a name for the test, otherwise the function
+ name will be used. Example::
+
+ @app.template_test()
+ def is_prime(n):
+ if n == 2:
+ return True
+ for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
+ if n % i == 0:
+ return False
+ return True
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_test) -> T_template_test:
+ self.add_template_test(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_test(
+ self, f: ft.TemplateTestCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template test. Works exactly like the
+ :meth:`template_test` decorator.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.tests[name or f.__name__] = f
+
+ @setupmethod
+ def template_global(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_global], T_template_global]:
+ """A decorator that is used to register a custom template global function.
+ You can specify a name for the global function, otherwise the function
+ name will be used. Example::
+
+ @app.template_global()
+ def double(n):
+ return 2 * n
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global function, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_global) -> T_template_global:
+ self.add_template_global(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_global(
+ self, f: ft.TemplateGlobalCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template global function. Works exactly like the
+ :meth:`template_global` decorator.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global function, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.globals[name or f.__name__] = f
+
+ @setupmethod
+ def teardown_appcontext(self, f: T_teardown) -> T_teardown:
+ """Registers a function to be called when the application
+ context is popped. The application context is typically popped
+ after the request context for each request, at the end of CLI
+ commands, or after a manually pushed context ends.
+
+ .. code-block:: python
+
+ with app.app_context():
+ ...
+
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the app context is
+ made inactive. Since a request context typically also manages an
+ application context it would also be called when you pop a
+ request context.
+
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
+
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
+
+ The return values of teardown functions are ignored.
+
+ .. versionadded:: 0.9
+ """
+ self.teardown_appcontext_funcs.append(f)
+ return f
+
+ @setupmethod
+ def shell_context_processor(
+ self, f: T_shell_context_processor
+ ) -> T_shell_context_processor:
+ """Registers a shell context processor function.
+
+ .. versionadded:: 0.11
+ """
+ self.shell_context_processors.append(f)
+ return f
+
+ def _find_error_handler(self, e: Exception) -> ft.ErrorHandlerCallable | None:
+ """Return a registered error handler for an exception in this order:
+ blueprint handler for a specific code, app handler for a specific code,
+ blueprint handler for an exception class, app handler for an exception
+ class, or ``None`` if a suitable handler is not found.
+ """
+ exc_class, code = self._get_exc_class_and_code(type(e))
+ names = (*request.blueprints, None)
+
+ for c in (code, None) if code is not None else (None,):
+ for name in names:
+ handler_map = self.error_handler_spec[name][c]
+
+ if not handler_map:
+ continue
+
+ for cls in exc_class.__mro__:
+ handler = handler_map.get(cls)
+
+ if handler is not None:
+ return handler
+ return None
+
+ def handle_http_exception(
+ self, e: HTTPException
+ ) -> HTTPException | ft.ResponseReturnValue:
+ """Handles an HTTP exception. By default this will invoke the
+ registered error handlers and fall back to returning the
+ exception as response.
+
+ .. versionchanged:: 1.0.3
+ ``RoutingException``, used internally for actions such as
+ slash redirects during routing, is not passed to error
+ handlers.
+
+ .. versionchanged:: 1.0
+ Exceptions are looked up by code *and* by MRO, so
+ ``HTTPException`` subclasses can be handled with a catch-all
+ handler for the base ``HTTPException``.
+
+ .. versionadded:: 0.3
+ """
+ # Proxy exceptions don't have error codes. We want to always return
+ # those unchanged as errors
+ if e.code is None:
+ return e
+
+ # RoutingExceptions are used internally to trigger routing
+ # actions, such as slash redirects raising RequestRedirect. They
+ # are not raised or handled in user code.
+ if isinstance(e, RoutingException):
+ return e
+
+ handler = self._find_error_handler(e)
+ if handler is None:
+ return e
+ return self.ensure_sync(handler)(e)
+
+ def trap_http_exception(self, e: Exception) -> bool:
+ """Checks if an HTTP exception should be trapped or not. By default
+ this will return ``False`` for all exceptions except for a bad request
+ key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It
+ also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``.
+
+ This is called for all HTTP exceptions raised by a view function.
+ If it returns ``True`` for any exception the error handler for this
+ exception is not called and it shows up as regular exception in the
+ traceback. This is helpful for debugging implicitly raised HTTP
+ exceptions.
+
+ .. versionchanged:: 1.0
+ Bad request errors are not trapped by default in debug mode.
+
+ .. versionadded:: 0.8
+ """
+ if self.config["TRAP_HTTP_EXCEPTIONS"]:
+ return True
+
+ trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"]
+
+ # if unset, trap key errors in debug mode
+ if (
+ trap_bad_request is None
+ and self.debug
+ and isinstance(e, BadRequestKeyError)
+ ):
+ return True
+
+ if trap_bad_request:
+ return isinstance(e, BadRequest)
+
+ return False
+
+ def handle_user_exception(
+ self, e: Exception
+ ) -> HTTPException | ft.ResponseReturnValue:
+ """This method is called whenever an exception occurs that
+ should be handled. A special case is :class:`~werkzeug
+ .exceptions.HTTPException` which is forwarded to the
+ :meth:`handle_http_exception` method. This function will either
+ return a response value or reraise the exception with the same
+ traceback.
+
+ .. versionchanged:: 1.0
+ Key errors raised from request data like ``form`` show the
+ bad key in debug mode rather than a generic bad request
+ message.
+
+ .. versionadded:: 0.7
+ """
+ if isinstance(e, BadRequestKeyError) and (
+ self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]
+ ):
+ e.show_exception = True
+
+ if isinstance(e, HTTPException) and not self.trap_http_exception(e):
+ return self.handle_http_exception(e)
+
+ handler = self._find_error_handler(e)
+
+ if handler is None:
+ raise
+
+ return self.ensure_sync(handler)(e)
+
+ def handle_exception(self, e: Exception) -> Response:
+ """Handle an exception that did not have an error handler
+ associated with it, or that was raised from an error handler.
+ This always causes a 500 ``InternalServerError``.
+
+ Always sends the :data:`got_request_exception` signal.
+
+ If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug
+ mode, the error will be re-raised so that the debugger can
+ display it. Otherwise, the original exception is logged, and
+ an :exc:`~werkzeug.exceptions.InternalServerError` is returned.
+
+ If an error handler is registered for ``InternalServerError`` or
+ ``500``, it will be used. For consistency, the handler will
+ always receive the ``InternalServerError``. The original
+ unhandled exception is available as ``e.original_exception``.
+
+ .. versionchanged:: 1.1.0
+ Always passes the ``InternalServerError`` instance to the
+ handler, setting ``original_exception`` to the unhandled
+ error.
+
+ .. versionchanged:: 1.1.0
+ ``after_request`` functions and other finalization is done
+ even for the default 500 response when there is no handler.
+
+ .. versionadded:: 0.3
+ """
+ exc_info = sys.exc_info()
+ got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e)
+ propagate = self.config["PROPAGATE_EXCEPTIONS"]
+
+ if propagate is None:
+ propagate = self.testing or self.debug
+
+ if propagate:
+ # Re-raise if called with an active exception, otherwise
+ # raise the passed in exception.
+ if exc_info[1] is e:
+ raise
+
+ raise e
+
+ self.log_exception(exc_info)
+ server_error: InternalServerError | ft.ResponseReturnValue
+ server_error = InternalServerError(original_exception=e)
+ handler = self._find_error_handler(server_error)
+
+ if handler is not None:
+ server_error = self.ensure_sync(handler)(server_error)
+
+ return self.finalize_request(server_error, from_error_handler=True)
+
+ def log_exception(
+ self,
+ exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
+ ) -> None:
+ """Logs an exception. This is called by :meth:`handle_exception`
+ if debugging is disabled and right before the handler is called.
+ The default implementation logs the exception as error on the
+ :attr:`logger`.
+
+ .. versionadded:: 0.8
+ """
+ self.logger.error(
+ f"Exception on {request.path} [{request.method}]", exc_info=exc_info
+ )
+
+ def raise_routing_exception(self, request: Request) -> t.NoReturn:
+ """Intercept routing exceptions and possibly do something else.
+
+ In debug mode, intercept a routing redirect and replace it with
+ an error if the body will be discarded.
+
+ With modern Werkzeug this shouldn't occur, since it now uses a
+ 308 status which tells the browser to resend the method and
+ body.
+
+ .. versionchanged:: 2.1
+ Don't intercept 307 and 308 redirects.
+
+ :meta private:
+ :internal:
+ """
+ if (
+ not self.debug
+ or not isinstance(request.routing_exception, RequestRedirect)
+ or request.routing_exception.code in {307, 308}
+ or request.method in {"GET", "HEAD", "OPTIONS"}
+ ):
+ raise request.routing_exception # type: ignore
+
+ from .debughelpers import FormDataRoutingRedirect
+
+ raise FormDataRoutingRedirect(request)
+
+ def dispatch_request(self) -> ft.ResponseReturnValue:
+ """Does the request dispatching. Matches the URL and returns the
+ return value of the view or error handler. This does not have to
+ be a response object. In order to convert the return value to a
+ proper response object, call :func:`make_response`.
+
+ .. versionchanged:: 0.7
+ This no longer does the exception handling, this code was
+ moved to the new :meth:`full_dispatch_request`.
+ """
+ req = request_ctx.request
+ if req.routing_exception is not None:
+ self.raise_routing_exception(req)
+ rule: Rule = req.url_rule # type: ignore[assignment]
+ # if we provide automatic options for this URL and the
+ # request came with the OPTIONS method, reply automatically
+ if (
+ getattr(rule, "provide_automatic_options", False)
+ and req.method == "OPTIONS"
+ ):
+ return self.make_default_options_response()
+ # otherwise dispatch to the handler for that endpoint
+ view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
+ return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
+
+ def full_dispatch_request(self) -> Response:
+ """Dispatches the request and on top of that performs request
+ pre and postprocessing as well as HTTP exception catching and
+ error handling.
+
+ .. versionadded:: 0.7
+ """
+ self._got_first_request = True
+
+ try:
+ request_started.send(self, _async_wrapper=self.ensure_sync)
+ rv = self.preprocess_request()
+ if rv is None:
+ rv = self.dispatch_request()
+ except Exception as e:
+ rv = self.handle_user_exception(e)
+ return self.finalize_request(rv)
+
+ def finalize_request(
+ self,
+ rv: ft.ResponseReturnValue | HTTPException,
+ from_error_handler: bool = False,
+ ) -> Response:
+ """Given the return value from a view function this finalizes
+ the request by converting it into a response and invoking the
+ postprocessing functions. This is invoked for both normal
+ request dispatching as well as error handlers.
+
+ Because this means that it might be called as a result of a
+ failure a special safe mode is available which can be enabled
+ with the `from_error_handler` flag. If enabled, failures in
+ response processing will be logged and otherwise ignored.
+
+ :internal:
+ """
+ response = self.make_response(rv)
+ try:
+ response = self.process_response(response)
+ request_finished.send(
+ self, _async_wrapper=self.ensure_sync, response=response
+ )
+ except Exception:
+ if not from_error_handler:
+ raise
+ self.logger.exception(
+ "Request finalizing failed with an error while handling an error"
+ )
+ return response
+
+ def make_default_options_response(self) -> Response:
+ """This method is called to create the default ``OPTIONS`` response.
+ This can be changed through subclassing to change the default
+ behavior of ``OPTIONS`` responses.
+
+ .. versionadded:: 0.7
+ """
+ adapter = request_ctx.url_adapter
+ methods = adapter.allowed_methods() # type: ignore[union-attr]
+ rv = self.response_class()
+ rv.allow.update(methods)
+ return rv
+
+ def should_ignore_error(self, error: BaseException | None) -> bool:
+ """This is called to figure out if an error should be ignored
+ or not as far as the teardown system is concerned. If this
+ function returns ``True`` then the teardown handlers will not be
+ passed the error.
+
+ .. versionadded:: 0.10
+ """
+ return False
+
+ def ensure_sync(self, func: t.Callable) -> t.Callable:
+ """Ensure that the function is synchronous for WSGI workers.
+ Plain ``def`` functions are returned as-is. ``async def``
+ functions are wrapped to run and wait for the response.
+
+ Override this method to change how the app runs async views.
+
+ .. versionadded:: 2.0
+ """
+ if iscoroutinefunction(func):
+ return self.async_to_sync(func)
+
+ return func
+
+ def async_to_sync(
+ self, func: t.Callable[..., t.Coroutine]
+ ) -> t.Callable[..., t.Any]:
+ """Return a sync function that will run the coroutine function.
+
+ .. code-block:: python
+
+ result = app.async_to_sync(func)(*args, **kwargs)
+
+ Override this method to change how the app converts async code
+ to be synchronously callable.
+
+ .. versionadded:: 2.0
+ """
+ try:
+ from asgiref.sync import async_to_sync as asgiref_async_to_sync
+ except ImportError:
+ raise RuntimeError(
+ "Install Flask with the 'async' extra in order to use async views."
+ ) from None
+
+ return asgiref_async_to_sync(func)
+
+ def url_for(
+ self,
+ endpoint: str,
+ *,
+ _anchor: str | None = None,
+ _method: str | None = None,
+ _scheme: str | None = None,
+ _external: bool | None = None,
+ **values: t.Any,
+ ) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This is called by :func:`flask.url_for`, and can be called
+ directly as well.
+
+ An *endpoint* is the name of a URL rule, usually added with
+ :meth:`@app.route() `, and usually the same name as the
+ view function. A route defined in a :class:`~flask.Blueprint`
+ will prepend the blueprint's name separated by a ``.`` to the
+ endpoint.
+
+ In some cases, such as email messages, you want URLs to include
+ the scheme and domain, like ``https://example.com/hello``. When
+ not in an active request, URLs will be external by default, but
+ this requires setting :data:`SERVER_NAME` so Flask knows what
+ domain to use. :data:`APPLICATION_ROOT` and
+ :data:`PREFERRED_URL_SCHEME` should also be configured as
+ needed. This config is only used when not in an active request.
+
+ Functions can be decorated with :meth:`url_defaults` to modify
+ keyword arguments before the URL is built.
+
+ If building fails for some reason, such as an unknown endpoint
+ or incorrect values, the app's :meth:`handle_url_build_error`
+ method is called. If that returns a string, that is returned,
+ otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it
+ is external.
+ :param _external: If given, prefer the URL to be internal
+ (False) or require it to be external (True). External URLs
+ include the scheme and domain. When not in an active
+ request, URLs are external by default.
+ :param values: Values to use for the variable parts of the URL
+ rule. Unknown keys are appended as query string arguments,
+ like ``?a=b&c=d``.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.url_for``, which calls this method.
+ """
+ req_ctx = _cv_request.get(None)
+
+ if req_ctx is not None:
+ url_adapter = req_ctx.url_adapter
+ blueprint_name = req_ctx.request.blueprint
+
+ # If the endpoint starts with "." and the request matches a
+ # blueprint, the endpoint is relative to the blueprint.
+ if endpoint[:1] == ".":
+ if blueprint_name is not None:
+ endpoint = f"{blueprint_name}{endpoint}"
+ else:
+ endpoint = endpoint[1:]
+
+ # When in a request, generate a URL without scheme and
+ # domain by default, unless a scheme is given.
+ if _external is None:
+ _external = _scheme is not None
+ else:
+ app_ctx = _cv_app.get(None)
+
+ # If called by helpers.url_for, an app context is active,
+ # use its url_adapter. Otherwise, app.url_for was called
+ # directly, build an adapter.
+ if app_ctx is not None:
+ url_adapter = app_ctx.url_adapter
+ else:
+ url_adapter = self.create_url_adapter(None)
+
+ if url_adapter is None:
+ raise RuntimeError(
+ "Unable to build URLs outside an active request"
+ " without 'SERVER_NAME' configured. Also configure"
+ " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
+ " needed."
+ )
+
+ # When outside a request, generate a URL with scheme and
+ # domain by default.
+ if _external is None:
+ _external = True
+
+ # It is an error to set _scheme when _external=False, in order
+ # to avoid accidental insecure URLs.
+ if _scheme is not None and not _external:
+ raise ValueError("When specifying '_scheme', '_external' must be True.")
+
+ self.inject_url_defaults(endpoint, values)
+
+ try:
+ rv = url_adapter.build( # type: ignore[union-attr]
+ endpoint,
+ values,
+ method=_method,
+ url_scheme=_scheme,
+ force_external=_external,
+ )
+ except BuildError as error:
+ values.update(
+ _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
+ )
+ return self.handle_url_build_error(error, endpoint, values)
+
+ if _anchor is not None:
+ _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
+ rv = f"{rv}#{_anchor}"
+
+ return rv
+
+ def redirect(self, location: str, code: int = 302) -> BaseResponse:
+ """Create a redirect response object.
+
+ This is called by :func:`flask.redirect`, and can be called
+ directly as well.
+
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.redirect``, which calls this method.
+ """
+ return _wz_redirect(location, code=code, Response=self.response_class)
+
+ def make_response(self, rv: ft.ResponseReturnValue) -> Response:
+ """Convert the return value from a view function to an instance of
+ :attr:`response_class`.
+
+ :param rv: the return value from the view function. The view function
+ must return a response. Returning ``None``, or the view ending
+ without returning, is not allowed. The following types are allowed
+ for ``view_rv``:
+
+ ``str``
+ A response object is created with the string encoded to UTF-8
+ as the body.
+
+ ``bytes``
+ A response object is created with the bytes as the body.
+
+ ``dict``
+ A dictionary that will be jsonify'd before being returned.
+
+ ``list``
+ A list that will be jsonify'd before being returned.
+
+ ``generator`` or ``iterator``
+ A generator that returns ``str`` or ``bytes`` to be
+ streamed as the response.
+
+ ``tuple``
+ Either ``(body, status, headers)``, ``(body, status)``, or
+ ``(body, headers)``, where ``body`` is any of the other types
+ allowed here, ``status`` is a string or an integer, and
+ ``headers`` is a dictionary or a list of ``(key, value)``
+ tuples. If ``body`` is a :attr:`response_class` instance,
+ ``status`` overwrites the exiting value and ``headers`` are
+ extended.
+
+ :attr:`response_class`
+ The object is returned unchanged.
+
+ other :class:`~werkzeug.wrappers.Response` class
+ The object is coerced to :attr:`response_class`.
+
+ :func:`callable`
+ The function is called as a WSGI application. The result is
+ used to create a response object.
+
+ .. versionchanged:: 2.2
+ A generator will be converted to a streaming response.
+ A list will be converted to a JSON response.
+
+ .. versionchanged:: 1.1
+ A dict will be converted to a JSON response.
+
+ .. versionchanged:: 0.9
+ Previously a tuple was interpreted as the arguments for the
+ response object.
+ """
+
+ status = headers = None
+
+ # unpack tuple returns
+ if isinstance(rv, tuple):
+ len_rv = len(rv)
+
+ # a 3-tuple is unpacked directly
+ if len_rv == 3:
+ rv, status, headers = rv # type: ignore[misc]
+ # decide if a 2-tuple has status or headers
+ elif len_rv == 2:
+ if isinstance(rv[1], (Headers, dict, tuple, list)):
+ rv, headers = rv
+ else:
+ rv, status = rv # type: ignore[assignment,misc]
+ # other sized tuples are not allowed
+ else:
+ raise TypeError(
+ "The view function did not return a valid response tuple."
+ " The tuple must have the form (body, status, headers),"
+ " (body, status), or (body, headers)."
+ )
+
+ # the body must not be None
+ if rv is None:
+ raise TypeError(
+ f"The view function for {request.endpoint!r} did not"
+ " return a valid response. The function either returned"
+ " None or ended without a return statement."
+ )
+
+ # make sure the body is an instance of the response class
+ if not isinstance(rv, self.response_class):
+ if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator):
+ # let the response class set the status and headers instead of
+ # waiting to do it manually, so that the class can handle any
+ # special logic
+ rv = self.response_class(
+ rv,
+ status=status,
+ headers=headers, # type: ignore[arg-type]
+ )
+ status = headers = None
+ elif isinstance(rv, (dict, list)):
+ rv = self.json.response(rv)
+ elif isinstance(rv, BaseResponse) or callable(rv):
+ # evaluate a WSGI callable, or coerce a different response
+ # class to the correct type
+ try:
+ rv = self.response_class.force_type(
+ rv, request.environ # type: ignore[arg-type]
+ )
+ except TypeError as e:
+ raise TypeError(
+ f"{e}\nThe view function did not return a valid"
+ " response. The return type must be a string,"
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it"
+ f" was a {type(rv).__name__}."
+ ).with_traceback(sys.exc_info()[2]) from None
+ else:
+ raise TypeError(
+ "The view function did not return a valid"
+ " response. The return type must be a string,"
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it was a"
+ f" {type(rv).__name__}."
+ )
+
+ rv = t.cast(Response, rv)
+ # prefer the status if it was provided
+ if status is not None:
+ if isinstance(status, (str, bytes, bytearray)):
+ rv.status = status
+ else:
+ rv.status_code = status
+
+ # extend existing headers with provided headers
+ if headers:
+ rv.headers.update(headers) # type: ignore[arg-type]
+
+ return rv
+
+ def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
+ """Creates a URL adapter for the given request. The URL adapter
+ is created at a point where the request context is not yet set
+ up so the request is passed explicitly.
+
+ .. versionadded:: 0.6
+
+ .. versionchanged:: 0.9
+ This can now also be called without a request object when the
+ URL adapter is created for the application context.
+
+ .. versionchanged:: 1.0
+ :data:`SERVER_NAME` no longer implicitly enables subdomain
+ matching. Use :attr:`subdomain_matching` instead.
+ """
+ if request is not None:
+ # If subdomain matching is disabled (the default), use the
+ # default subdomain in all cases. This should be the default
+ # in Werkzeug but it currently does not have that feature.
+ if not self.subdomain_matching:
+ subdomain = self.url_map.default_subdomain or None
+ else:
+ subdomain = None
+
+ return self.url_map.bind_to_environ(
+ request.environ,
+ server_name=self.config["SERVER_NAME"],
+ subdomain=subdomain,
+ )
+ # We need at the very least the server name to be set for this
+ # to work.
+ if self.config["SERVER_NAME"] is not None:
+ return self.url_map.bind(
+ self.config["SERVER_NAME"],
+ script_name=self.config["APPLICATION_ROOT"],
+ url_scheme=self.config["PREFERRED_URL_SCHEME"],
+ )
+
+ return None
+
+ def inject_url_defaults(self, endpoint: str, values: dict) -> None:
+ """Injects the URL defaults for the given endpoint directly into
+ the values dictionary passed. This is used internally and
+ automatically called on URL building.
+
+ .. versionadded:: 0.7
+ """
+ names: t.Iterable[str | None] = (None,)
+
+ # url_for may be called outside a request context, parse the
+ # passed endpoint instead of using request.blueprints.
+ if "." in endpoint:
+ names = chain(
+ names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
+ )
+
+ for name in names:
+ if name in self.url_default_functions:
+ for func in self.url_default_functions[name]:
+ func(endpoint, values)
+
+ def handle_url_build_error(
+ self, error: BuildError, endpoint: str, values: dict[str, t.Any]
+ ) -> str:
+ """Called by :meth:`.url_for` if a
+ :exc:`~werkzeug.routing.BuildError` was raised. If this returns
+ a value, it will be returned by ``url_for``, otherwise the error
+ will be re-raised.
+
+ Each function in :attr:`url_build_error_handlers` is called with
+ ``error``, ``endpoint`` and ``values``. If a function returns
+ ``None`` or raises a ``BuildError``, it is skipped. Otherwise,
+ its return value is returned by ``url_for``.
+
+ :param error: The active ``BuildError`` being handled.
+ :param endpoint: The endpoint being built.
+ :param values: The keyword arguments passed to ``url_for``.
+ """
+ for handler in self.url_build_error_handlers:
+ try:
+ rv = handler(error, endpoint, values)
+ except BuildError as e:
+ # make error available outside except block
+ error = e
+ else:
+ if rv is not None:
+ return rv
+
+ # Re-raise if called with an active exception, otherwise raise
+ # the passed in exception.
+ if error is sys.exc_info()[1]:
+ raise
+
+ raise error
+
+ def preprocess_request(self) -> ft.ResponseReturnValue | None:
+ """Called before the request is dispatched. Calls
+ :attr:`url_value_preprocessors` registered with the app and the
+ current blueprint (if any). Then calls :attr:`before_request_funcs`
+ registered with the app and the blueprint.
+
+ If any :meth:`before_request` handler returns a non-None value, the
+ value is handled as if it was the return value from the view, and
+ further request handling is stopped.
+ """
+ names = (None, *reversed(request.blueprints))
+
+ for name in names:
+ if name in self.url_value_preprocessors:
+ for url_func in self.url_value_preprocessors[name]:
+ url_func(request.endpoint, request.view_args)
+
+ for name in names:
+ if name in self.before_request_funcs:
+ for before_func in self.before_request_funcs[name]:
+ rv = self.ensure_sync(before_func)()
+
+ if rv is not None:
+ return rv
+
+ return None
+
+ def process_response(self, response: Response) -> Response:
+ """Can be overridden in order to modify the response object
+ before it's sent to the WSGI server. By default this will
+ call all the :meth:`after_request` decorated functions.
+
+ .. versionchanged:: 0.5
+ As of Flask 0.5 the functions registered for after request
+ execution are called in reverse order of registration.
+
+ :param response: a :attr:`response_class` object.
+ :return: a new response object or the same, has to be an
+ instance of :attr:`response_class`.
+ """
+ ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
+
+ for func in ctx._after_request_functions:
+ response = self.ensure_sync(func)(response)
+
+ for name in chain(request.blueprints, (None,)):
+ if name in self.after_request_funcs:
+ for func in reversed(self.after_request_funcs[name]):
+ response = self.ensure_sync(func)(response)
+
+ if not self.session_interface.is_null_session(ctx.session):
+ self.session_interface.save_session(self, ctx.session, response)
+
+ return response
+
+ def do_teardown_request(
+ self, exc: BaseException | None = _sentinel # type: ignore
+ ) -> None:
+ """Called after the request is dispatched and the response is
+ returned, right before the request context is popped.
+
+ This calls all functions decorated with
+ :meth:`teardown_request`, and :meth:`Blueprint.teardown_request`
+ if a blueprint handled the request. Finally, the
+ :data:`request_tearing_down` signal is sent.
+
+ This is called by
+ :meth:`RequestContext.pop() `,
+ which may be delayed during testing to maintain access to
+ resources.
+
+ :param exc: An unhandled exception raised while dispatching the
+ request. Detected from the current exception information if
+ not passed. Passed to each teardown function.
+
+ .. versionchanged:: 0.9
+ Added the ``exc`` argument.
+ """
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+
+ for name in chain(request.blueprints, (None,)):
+ if name in self.teardown_request_funcs:
+ for func in reversed(self.teardown_request_funcs[name]):
+ self.ensure_sync(func)(exc)
+
+ request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
+
+ def do_teardown_appcontext(
+ self, exc: BaseException | None = _sentinel # type: ignore
+ ) -> None:
+ """Called right before the application context is popped.
+
+ When handling a request, the application context is popped
+ after the request context. See :meth:`do_teardown_request`.
+
+ This calls all functions decorated with
+ :meth:`teardown_appcontext`. Then the
+ :data:`appcontext_tearing_down` signal is sent.
+
+ This is called by
+ :meth:`AppContext.pop() `.
+
+ .. versionadded:: 0.9
+ """
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+
+ for func in reversed(self.teardown_appcontext_funcs):
+ self.ensure_sync(func)(exc)
+
+ appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
+
+ def app_context(self) -> AppContext:
+ """Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
+ block to push the context, which will make :data:`current_app`
+ point at this application.
+
+ An application context is automatically pushed by
+ :meth:`RequestContext.push() `
+ when handling a request, and when running a CLI command. Use
+ this to manually create a context outside of these situations.
+
+ ::
+
+ with app.app_context():
+ init_db()
+
+ See :doc:`/appcontext`.
+
+ .. versionadded:: 0.9
+ """
+ return AppContext(self)
+
+ def request_context(self, environ: dict) -> RequestContext:
+ """Create a :class:`~flask.ctx.RequestContext` representing a
+ WSGI environment. Use a ``with`` block to push the context,
+ which will make :data:`request` point at this request.
+
+ See :doc:`/reqcontext`.
+
+ Typically you should not call this from your own code. A request
+ context is automatically pushed by the :meth:`wsgi_app` when
+ handling a request. Use :meth:`test_request_context` to create
+ an environment and context instead of this method.
+
+ :param environ: a WSGI environment
+ """
+ return RequestContext(self, environ)
+
+ def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext:
+ """Create a :class:`~flask.ctx.RequestContext` for a WSGI
+ environment created from the given values. This is mostly useful
+ during testing, where you may want to run a function that uses
+ request data without dispatching a full request.
+
+ See :doc:`/reqcontext`.
+
+ Use a ``with`` block to push the context, which will make
+ :data:`request` point at the request for the created
+ environment. ::
+
+ with app.test_request_context(...):
+ generate_report()
+
+ When using the shell, it may be easier to push and pop the
+ context manually to avoid indentation. ::
+
+ ctx = app.test_request_context(...)
+ ctx.push()
+ ...
+ ctx.pop()
+
+ Takes the same arguments as Werkzeug's
+ :class:`~werkzeug.test.EnvironBuilder`, with some defaults from
+ the application. See the linked Werkzeug docs for most of the
+ available arguments. Flask-specific behavior is listed here.
+
+ :param path: URL path being requested.
+ :param base_url: Base URL where the app is being served, which
+ ``path`` is relative to. If not given, built from
+ :data:`PREFERRED_URL_SCHEME`, ``subdomain``,
+ :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
+ :param subdomain: Subdomain name to append to
+ :data:`SERVER_NAME`.
+ :param url_scheme: Scheme to use instead of
+ :data:`PREFERRED_URL_SCHEME`.
+ :param data: The request body, either as a string or a dict of
+ form keys and values.
+ :param json: If given, this is serialized as JSON and passed as
+ ``data``. Also defaults ``content_type`` to
+ ``application/json``.
+ :param args: other positional arguments passed to
+ :class:`~werkzeug.test.EnvironBuilder`.
+ :param kwargs: other keyword arguments passed to
+ :class:`~werkzeug.test.EnvironBuilder`.
+ """
+ from .testing import EnvironBuilder
+
+ builder = EnvironBuilder(self, *args, **kwargs)
+
+ try:
+ return self.request_context(builder.get_environ())
+ finally:
+ builder.close()
+
+ def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
+ """The actual WSGI application. This is not implemented in
+ :meth:`__call__` so that middlewares can be applied without
+ losing a reference to the app object. Instead of doing this::
+
+ app = MyMiddleware(app)
+
+ It's a better idea to do this instead::
+
+ app.wsgi_app = MyMiddleware(app.wsgi_app)
+
+ Then you still have the original application object around and
+ can continue to call methods on it.
+
+ .. versionchanged:: 0.7
+ Teardown events for the request and app contexts are called
+ even if an unhandled error occurs. Other events may not be
+ called depending on when an error occurs during dispatch.
+ See :ref:`callbacks-and-errors`.
+
+ :param environ: A WSGI environment.
+ :param start_response: A callable accepting a status code,
+ a list of headers, and an optional exception context to
+ start the response.
+ """
+ ctx = self.request_context(environ)
+ error: BaseException | None = None
+ try:
+ try:
+ ctx.push()
+ response = self.full_dispatch_request()
+ except Exception as e:
+ error = e
+ response = self.handle_exception(e)
+ except: # noqa: B001
+ error = sys.exc_info()[1]
+ raise
+ return response(environ, start_response)
+ finally:
+ if "werkzeug.debug.preserve_context" in environ:
+ environ["werkzeug.debug.preserve_context"](_cv_app.get())
+ environ["werkzeug.debug.preserve_context"](_cv_request.get())
+
+ if error is not None and self.should_ignore_error(error):
+ error = None
+
+ ctx.pop(error)
+
+ def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
+ """The WSGI server calls the Flask application object as the
+ WSGI application. This calls :meth:`wsgi_app`, which can be
+ wrapped to apply middleware.
+ """
+ return self.wsgi_app(environ, start_response)
diff --git a/lib/python3.12/site-packages/flask/blueprints.py b/lib/python3.12/site-packages/flask/blueprints.py
new file mode 100644
index 0000000000000000000000000000000000000000..0407f86fefdca40ce4487ad006a4e65bee0d1ee6
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/blueprints.py
@@ -0,0 +1,626 @@
+from __future__ import annotations
+
+import os
+import typing as t
+from collections import defaultdict
+from functools import update_wrapper
+
+from . import typing as ft
+from .scaffold import _endpoint_from_view_func
+from .scaffold import _sentinel
+from .scaffold import Scaffold
+from .scaffold import setupmethod
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+
+DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
+T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
+T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
+
+
+class BlueprintSetupState:
+ """Temporary holder object for registering a blueprint with the
+ application. An instance of this class is created by the
+ :meth:`~flask.Blueprint.make_setup_state` method and later passed
+ to all register callback functions.
+ """
+
+ def __init__(
+ self,
+ blueprint: Blueprint,
+ app: Flask,
+ options: t.Any,
+ first_registration: bool,
+ ) -> None:
+ #: a reference to the current application
+ self.app = app
+
+ #: a reference to the blueprint that created this setup state.
+ self.blueprint = blueprint
+
+ #: a dictionary with all options that were passed to the
+ #: :meth:`~flask.Flask.register_blueprint` method.
+ self.options = options
+
+ #: as blueprints can be registered multiple times with the
+ #: application and not everything wants to be registered
+ #: multiple times on it, this attribute can be used to figure
+ #: out if the blueprint was registered in the past already.
+ self.first_registration = first_registration
+
+ subdomain = self.options.get("subdomain")
+ if subdomain is None:
+ subdomain = self.blueprint.subdomain
+
+ #: The subdomain that the blueprint should be active for, ``None``
+ #: otherwise.
+ self.subdomain = subdomain
+
+ url_prefix = self.options.get("url_prefix")
+ if url_prefix is None:
+ url_prefix = self.blueprint.url_prefix
+ #: The prefix that should be used for all URLs defined on the
+ #: blueprint.
+ self.url_prefix = url_prefix
+
+ self.name = self.options.get("name", blueprint.name)
+ self.name_prefix = self.options.get("name_prefix", "")
+
+ #: A dictionary with URL defaults that is added to each and every
+ #: URL that was defined with the blueprint.
+ self.url_defaults = dict(self.blueprint.url_values_defaults)
+ self.url_defaults.update(self.options.get("url_defaults", ()))
+
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: t.Callable | None = None,
+ **options: t.Any,
+ ) -> None:
+ """A helper method to register a rule (and optionally a view function)
+ to the application. The endpoint is automatically prefixed with the
+ blueprint's name.
+ """
+ if self.url_prefix is not None:
+ if rule:
+ rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
+ else:
+ rule = self.url_prefix
+ options.setdefault("subdomain", self.subdomain)
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func) # type: ignore
+ defaults = self.url_defaults
+ if "defaults" in options:
+ defaults = dict(defaults, **options.pop("defaults"))
+
+ self.app.add_url_rule(
+ rule,
+ f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
+ view_func,
+ defaults=defaults,
+ **options,
+ )
+
+
+class Blueprint(Scaffold):
+ """Represents a blueprint, a collection of routes and other
+ app-related functions that can be registered on a real application
+ later.
+
+ A blueprint is an object that allows defining application functions
+ without requiring an application object ahead of time. It uses the
+ same decorators as :class:`~flask.Flask`, but defers the need for an
+ application by recording them for later registration.
+
+ Decorating a function with a blueprint creates a deferred function
+ that is called with :class:`~flask.blueprints.BlueprintSetupState`
+ when the blueprint is registered on an application.
+
+ See :doc:`/blueprints` for more information.
+
+ :param name: The name of the blueprint. Will be prepended to each
+ endpoint name.
+ :param import_name: The name of the blueprint package, usually
+ ``__name__``. This helps locate the ``root_path`` for the
+ blueprint.
+ :param static_folder: A folder with static files that should be
+ served by the blueprint's static route. The path is relative to
+ the blueprint's root path. Blueprint static files are disabled
+ by default.
+ :param static_url_path: The url to serve static files from.
+ Defaults to ``static_folder``. If the blueprint does not have
+ a ``url_prefix``, the app's static route will take precedence,
+ and the blueprint's static files won't be accessible.
+ :param template_folder: A folder with templates that should be added
+ to the app's template search path. The path is relative to the
+ blueprint's root path. Blueprint templates are disabled by
+ default. Blueprint templates have a lower precedence than those
+ in the app's templates folder.
+ :param url_prefix: A path to prepend to all of the blueprint's URLs,
+ to make them distinct from the rest of the app's routes.
+ :param subdomain: A subdomain that blueprint routes will match on by
+ default.
+ :param url_defaults: A dict of default values that blueprint routes
+ will receive by default.
+ :param root_path: By default, the blueprint will automatically set
+ this based on ``import_name``. In certain situations this
+ automatic detection can fail, so the path can be specified
+ manually instead.
+
+ .. versionchanged:: 1.1.0
+ Blueprints have a ``cli`` group to register nested CLI commands.
+ The ``cli_group`` parameter controls the name of the group under
+ the ``flask`` command.
+
+ .. versionadded:: 0.7
+ """
+
+ _got_registered_once = False
+
+ def __init__(
+ self,
+ name: str,
+ import_name: str,
+ static_folder: str | os.PathLike | None = None,
+ static_url_path: str | None = None,
+ template_folder: str | os.PathLike | None = None,
+ url_prefix: str | None = None,
+ subdomain: str | None = None,
+ url_defaults: dict | None = None,
+ root_path: str | None = None,
+ cli_group: str | None = _sentinel, # type: ignore
+ ):
+ super().__init__(
+ import_name=import_name,
+ static_folder=static_folder,
+ static_url_path=static_url_path,
+ template_folder=template_folder,
+ root_path=root_path,
+ )
+
+ if not name:
+ raise ValueError("'name' may not be empty.")
+
+ if "." in name:
+ raise ValueError("'name' may not contain a dot '.' character.")
+
+ self.name = name
+ self.url_prefix = url_prefix
+ self.subdomain = subdomain
+ self.deferred_functions: list[DeferredSetupFunction] = []
+
+ if url_defaults is None:
+ url_defaults = {}
+
+ self.url_values_defaults = url_defaults
+ self.cli_group = cli_group
+ self._blueprints: list[tuple[Blueprint, dict]] = []
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_registered_once:
+ raise AssertionError(
+ f"The setup method '{f_name}' can no longer be called on the blueprint"
+ f" '{self.name}'. It has already been registered at least once, any"
+ " changes will not be applied consistently.\n"
+ "Make sure all imports, decorators, functions, etc. needed to set up"
+ " the blueprint are done before registering it."
+ )
+
+ @setupmethod
+ def record(self, func: t.Callable) -> None:
+ """Registers a function that is called when the blueprint is
+ registered on the application. This function is called with the
+ state as argument as returned by the :meth:`make_setup_state`
+ method.
+ """
+ self.deferred_functions.append(func)
+
+ @setupmethod
+ def record_once(self, func: t.Callable) -> None:
+ """Works like :meth:`record` but wraps the function in another
+ function that will ensure the function is only called once. If the
+ blueprint is registered a second time on the application, the
+ function passed is not called.
+ """
+
+ def wrapper(state: BlueprintSetupState) -> None:
+ if state.first_registration:
+ func(state)
+
+ self.record(update_wrapper(wrapper, func))
+
+ def make_setup_state(
+ self, app: Flask, options: dict, first_registration: bool = False
+ ) -> BlueprintSetupState:
+ """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
+ object that is later passed to the register callback functions.
+ Subclasses can override this to return a subclass of the setup state.
+ """
+ return BlueprintSetupState(self, app, options, first_registration)
+
+ @setupmethod
+ def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
+ """Register a :class:`~flask.Blueprint` on this blueprint. Keyword
+ arguments passed to this method will override the defaults set
+ on the blueprint.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+
+ .. versionadded:: 2.0
+ """
+ if blueprint is self:
+ raise ValueError("Cannot register a blueprint on itself")
+ self._blueprints.append((blueprint, options))
+
+ def register(self, app: Flask, options: dict) -> None:
+ """Called by :meth:`Flask.register_blueprint` to register all
+ views and callbacks registered on the blueprint with the
+ application. Creates a :class:`.BlueprintSetupState` and calls
+ each :meth:`record` callback with it.
+
+ :param app: The application this blueprint is being registered
+ with.
+ :param options: Keyword arguments forwarded from
+ :meth:`~Flask.register_blueprint`.
+
+ .. versionchanged:: 2.3
+ Nested blueprints now correctly apply subdomains.
+
+ .. versionchanged:: 2.1
+ Registering the same blueprint with the same name multiple
+ times is an error.
+
+ .. versionchanged:: 2.0.1
+ Nested blueprints are registered with their dotted name.
+ This allows different blueprints with the same name to be
+ nested at different locations.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+ """
+ name_prefix = options.get("name_prefix", "")
+ self_name = options.get("name", self.name)
+ name = f"{name_prefix}.{self_name}".lstrip(".")
+
+ if name in app.blueprints:
+ bp_desc = "this" if app.blueprints[name] is self else "a different"
+ existing_at = f" '{name}'" if self_name != name else ""
+
+ raise ValueError(
+ f"The name '{self_name}' is already registered for"
+ f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
+ f" provide a unique name."
+ )
+
+ first_bp_registration = not any(bp is self for bp in app.blueprints.values())
+ first_name_registration = name not in app.blueprints
+
+ app.blueprints[name] = self
+ self._got_registered_once = True
+ state = self.make_setup_state(app, options, first_bp_registration)
+
+ if self.has_static_folder:
+ state.add_url_rule(
+ f"{self.static_url_path}/",
+ view_func=self.send_static_file,
+ endpoint="static",
+ )
+
+ # Merge blueprint data into parent.
+ if first_bp_registration or first_name_registration:
+
+ def extend(bp_dict, parent_dict):
+ for key, values in bp_dict.items():
+ key = name if key is None else f"{name}.{key}"
+ parent_dict[key].extend(values)
+
+ for key, value in self.error_handler_spec.items():
+ key = name if key is None else f"{name}.{key}"
+ value = defaultdict(
+ dict,
+ {
+ code: {
+ exc_class: func for exc_class, func in code_values.items()
+ }
+ for code, code_values in value.items()
+ },
+ )
+ app.error_handler_spec[key] = value
+
+ for endpoint, func in self.view_functions.items():
+ app.view_functions[endpoint] = func
+
+ extend(self.before_request_funcs, app.before_request_funcs)
+ extend(self.after_request_funcs, app.after_request_funcs)
+ extend(
+ self.teardown_request_funcs,
+ app.teardown_request_funcs,
+ )
+ extend(self.url_default_functions, app.url_default_functions)
+ extend(self.url_value_preprocessors, app.url_value_preprocessors)
+ extend(self.template_context_processors, app.template_context_processors)
+
+ for deferred in self.deferred_functions:
+ deferred(state)
+
+ cli_resolved_group = options.get("cli_group", self.cli_group)
+
+ if self.cli.commands:
+ if cli_resolved_group is None:
+ app.cli.commands.update(self.cli.commands)
+ elif cli_resolved_group is _sentinel:
+ self.cli.name = name
+ app.cli.add_command(self.cli)
+ else:
+ self.cli.name = cli_resolved_group
+ app.cli.add_command(self.cli)
+
+ for blueprint, bp_options in self._blueprints:
+ bp_options = bp_options.copy()
+ bp_url_prefix = bp_options.get("url_prefix")
+ bp_subdomain = bp_options.get("subdomain")
+
+ if bp_subdomain is None:
+ bp_subdomain = blueprint.subdomain
+
+ if state.subdomain is not None and bp_subdomain is not None:
+ bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
+ elif bp_subdomain is not None:
+ bp_options["subdomain"] = bp_subdomain
+ elif state.subdomain is not None:
+ bp_options["subdomain"] = state.subdomain
+
+ if bp_url_prefix is None:
+ bp_url_prefix = blueprint.url_prefix
+
+ if state.url_prefix is not None and bp_url_prefix is not None:
+ bp_options["url_prefix"] = (
+ state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
+ )
+ elif bp_url_prefix is not None:
+ bp_options["url_prefix"] = bp_url_prefix
+ elif state.url_prefix is not None:
+ bp_options["url_prefix"] = state.url_prefix
+
+ bp_options["name_prefix"] = name
+ blueprint.register(app, bp_options)
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
+ full documentation.
+
+ The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
+ used with :func:`url_for`, is prefixed with the blueprint's name.
+ """
+ if endpoint and "." in endpoint:
+ raise ValueError("'endpoint' may not contain a dot '.' character.")
+
+ if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
+ raise ValueError("'view_func' name may not contain a dot '.' character.")
+
+ self.record(
+ lambda s: s.add_url_rule(
+ rule,
+ endpoint,
+ view_func,
+ provide_automatic_options=provide_automatic_options,
+ **options,
+ )
+ )
+
+ @setupmethod
+ def app_template_filter(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
+ """Register a template filter, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_filter`.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_filter) -> T_template_filter:
+ self.add_app_template_filter(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_filter(
+ self, f: ft.TemplateFilterCallable, name: str | None = None
+ ) -> None:
+ """Register a template filter, available in any template rendered by the
+ application. Works like the :meth:`app_template_filter` decorator. Equivalent to
+ :meth:`.Flask.add_template_filter`.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.filters[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def app_template_test(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_test], T_template_test]:
+ """Register a template test, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_test`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_test) -> T_template_test:
+ self.add_app_template_test(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_test(
+ self, f: ft.TemplateTestCallable, name: str | None = None
+ ) -> None:
+ """Register a template test, available in any template rendered by the
+ application. Works like the :meth:`app_template_test` decorator. Equivalent to
+ :meth:`.Flask.add_template_test`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.tests[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def app_template_global(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_global], T_template_global]:
+ """Register a template global, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_global`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_global) -> T_template_global:
+ self.add_app_template_global(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_global(
+ self, f: ft.TemplateGlobalCallable, name: str | None = None
+ ) -> None:
+ """Register a template global, available in any template rendered by the
+ application. Works like the :meth:`app_template_global` decorator. Equivalent to
+ :meth:`.Flask.add_template_global`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.globals[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def before_app_request(self, f: T_before_request) -> T_before_request:
+ """Like :meth:`before_request`, but before every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.before_request`.
+ """
+ self.record_once(
+ lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def after_app_request(self, f: T_after_request) -> T_after_request:
+ """Like :meth:`after_request`, but after every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.after_request`.
+ """
+ self.record_once(
+ lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def teardown_app_request(self, f: T_teardown) -> T_teardown:
+ """Like :meth:`teardown_request`, but after every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
+ """
+ self.record_once(
+ lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_context_processor(
+ self, f: T_template_context_processor
+ ) -> T_template_context_processor:
+ """Like :meth:`context_processor`, but for templates rendered by every view, not
+ only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
+ """
+ self.record_once(
+ lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_errorhandler(
+ self, code: type[Exception] | int
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
+ """Like :meth:`errorhandler`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
+ """
+
+ def decorator(f: T_error_handler) -> T_error_handler:
+ self.record_once(lambda s: s.app.errorhandler(code)(f))
+ return f
+
+ return decorator
+
+ @setupmethod
+ def app_url_value_preprocessor(
+ self, f: T_url_value_preprocessor
+ ) -> T_url_value_preprocessor:
+ """Like :meth:`url_value_preprocessor`, but for every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
+ """
+ self.record_once(
+ lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+ """Like :meth:`url_defaults`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
+ """
+ self.record_once(
+ lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
+ )
+ return f
diff --git a/lib/python3.12/site-packages/flask/cli.py b/lib/python3.12/site-packages/flask/cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..dda266b30ca810c1db50e429e9357845b9a9962d
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/cli.py
@@ -0,0 +1,1068 @@
+from __future__ import annotations
+
+import ast
+import importlib.metadata
+import inspect
+import os
+import platform
+import re
+import sys
+import traceback
+import typing as t
+from functools import update_wrapper
+from operator import itemgetter
+
+import click
+from click.core import ParameterSource
+from werkzeug import run_simple
+from werkzeug.serving import is_running_from_reloader
+from werkzeug.utils import import_string
+
+from .globals import current_app
+from .helpers import get_debug_flag
+from .helpers import get_load_dotenv
+
+if t.TYPE_CHECKING:
+ from .app import Flask
+
+
+class NoAppException(click.UsageError):
+ """Raised if an application cannot be found or loaded."""
+
+
+def find_best_app(module):
+ """Given a module instance this tries to find the best possible
+ application in the module or raises an exception.
+ """
+ from . import Flask
+
+ # Search for the most common names first.
+ for attr_name in ("app", "application"):
+ app = getattr(module, attr_name, None)
+
+ if isinstance(app, Flask):
+ return app
+
+ # Otherwise find the only object that is a Flask instance.
+ matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
+
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ raise NoAppException(
+ "Detected multiple Flask applications in module"
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
+ " to specify the correct one."
+ )
+
+ # Search for app factory functions.
+ for attr_name in ("create_app", "make_app"):
+ app_factory = getattr(module, attr_name, None)
+
+ if inspect.isfunction(app_factory):
+ try:
+ app = app_factory()
+
+ if isinstance(app, Flask):
+ return app
+ except TypeError as e:
+ if not _called_with_wrong_args(app_factory):
+ raise
+
+ raise NoAppException(
+ f"Detected factory '{attr_name}' in module '{module.__name__}',"
+ " but could not call it without arguments. Use"
+ f" '{module.__name__}:{attr_name}(args)'"
+ " to specify arguments."
+ ) from e
+
+ raise NoAppException(
+ "Failed to find Flask application or factory in module"
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
+ " to specify one."
+ )
+
+
+def _called_with_wrong_args(f):
+ """Check whether calling a function raised a ``TypeError`` because
+ the call failed or because something in the factory raised the
+ error.
+
+ :param f: The function that was called.
+ :return: ``True`` if the call failed.
+ """
+ tb = sys.exc_info()[2]
+
+ try:
+ while tb is not None:
+ if tb.tb_frame.f_code is f.__code__:
+ # In the function, it was called successfully.
+ return False
+
+ tb = tb.tb_next
+
+ # Didn't reach the function.
+ return True
+ finally:
+ # Delete tb to break a circular reference.
+ # https://docs.python.org/2/library/sys.html#sys.exc_info
+ del tb
+
+
+def find_app_by_string(module, app_name):
+ """Check if the given string is a variable name or a function. Call
+ a function to get the app instance, or return the variable directly.
+ """
+ from . import Flask
+
+ # Parse app_name as a single expression to determine if it's a valid
+ # attribute name or function call.
+ try:
+ expr = ast.parse(app_name.strip(), mode="eval").body
+ except SyntaxError:
+ raise NoAppException(
+ f"Failed to parse {app_name!r} as an attribute name or function call."
+ ) from None
+
+ if isinstance(expr, ast.Name):
+ name = expr.id
+ args = []
+ kwargs = {}
+ elif isinstance(expr, ast.Call):
+ # Ensure the function name is an attribute name only.
+ if not isinstance(expr.func, ast.Name):
+ raise NoAppException(
+ f"Function reference must be a simple name: {app_name!r}."
+ )
+
+ name = expr.func.id
+
+ # Parse the positional and keyword arguments as literals.
+ try:
+ args = [ast.literal_eval(arg) for arg in expr.args]
+ kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
+ except ValueError:
+ # literal_eval gives cryptic error messages, show a generic
+ # message with the full expression instead.
+ raise NoAppException(
+ f"Failed to parse arguments as literal values: {app_name!r}."
+ ) from None
+ else:
+ raise NoAppException(
+ f"Failed to parse {app_name!r} as an attribute name or function call."
+ )
+
+ try:
+ attr = getattr(module, name)
+ except AttributeError as e:
+ raise NoAppException(
+ f"Failed to find attribute {name!r} in {module.__name__!r}."
+ ) from e
+
+ # If the attribute is a function, call it with any args and kwargs
+ # to get the real application.
+ if inspect.isfunction(attr):
+ try:
+ app = attr(*args, **kwargs)
+ except TypeError as e:
+ if not _called_with_wrong_args(attr):
+ raise
+
+ raise NoAppException(
+ f"The factory {app_name!r} in module"
+ f" {module.__name__!r} could not be called with the"
+ " specified arguments."
+ ) from e
+ else:
+ app = attr
+
+ if isinstance(app, Flask):
+ return app
+
+ raise NoAppException(
+ "A valid Flask application was not obtained from"
+ f" '{module.__name__}:{app_name}'."
+ )
+
+
+def prepare_import(path):
+ """Given a filename this will try to calculate the python path, add it
+ to the search path and return the actual module name that is expected.
+ """
+ path = os.path.realpath(path)
+
+ fname, ext = os.path.splitext(path)
+ if ext == ".py":
+ path = fname
+
+ if os.path.basename(path) == "__init__":
+ path = os.path.dirname(path)
+
+ module_name = []
+
+ # move up until outside package structure (no __init__.py)
+ while True:
+ path, name = os.path.split(path)
+ module_name.append(name)
+
+ if not os.path.exists(os.path.join(path, "__init__.py")):
+ break
+
+ if sys.path[0] != path:
+ sys.path.insert(0, path)
+
+ return ".".join(module_name[::-1])
+
+
+def locate_app(module_name, app_name, raise_if_not_found=True):
+ try:
+ __import__(module_name)
+ except ImportError:
+ # Reraise the ImportError if it occurred within the imported module.
+ # Determine this by checking whether the trace has a depth > 1.
+ if sys.exc_info()[2].tb_next:
+ raise NoAppException(
+ f"While importing {module_name!r}, an ImportError was"
+ f" raised:\n\n{traceback.format_exc()}"
+ ) from None
+ elif raise_if_not_found:
+ raise NoAppException(f"Could not import {module_name!r}.") from None
+ else:
+ return
+
+ module = sys.modules[module_name]
+
+ if app_name is None:
+ return find_best_app(module)
+ else:
+ return find_app_by_string(module, app_name)
+
+
+def get_version(ctx, param, value):
+ if not value or ctx.resilient_parsing:
+ return
+
+ flask_version = importlib.metadata.version("flask")
+ werkzeug_version = importlib.metadata.version("werkzeug")
+
+ click.echo(
+ f"Python {platform.python_version()}\n"
+ f"Flask {flask_version}\n"
+ f"Werkzeug {werkzeug_version}",
+ color=ctx.color,
+ )
+ ctx.exit()
+
+
+version_option = click.Option(
+ ["--version"],
+ help="Show the Flask version.",
+ expose_value=False,
+ callback=get_version,
+ is_flag=True,
+ is_eager=True,
+)
+
+
+class ScriptInfo:
+ """Helper object to deal with Flask applications. This is usually not
+ necessary to interface with as it's used internally in the dispatching
+ to click. In future versions of Flask this object will most likely play
+ a bigger role. Typically it's created automatically by the
+ :class:`FlaskGroup` but you can also manually create it and pass it
+ onwards as click object.
+ """
+
+ def __init__(
+ self,
+ app_import_path: str | None = None,
+ create_app: t.Callable[..., Flask] | None = None,
+ set_debug_flag: bool = True,
+ ) -> None:
+ #: Optionally the import path for the Flask application.
+ self.app_import_path = app_import_path
+ #: Optionally a function that is passed the script info to create
+ #: the instance of the application.
+ self.create_app = create_app
+ #: A dictionary with arbitrary data that can be associated with
+ #: this script info.
+ self.data: dict[t.Any, t.Any] = {}
+ self.set_debug_flag = set_debug_flag
+ self._loaded_app: Flask | None = None
+
+ def load_app(self) -> Flask:
+ """Loads the Flask app (if not yet loaded) and returns it. Calling
+ this multiple times will just result in the already loaded app to
+ be returned.
+ """
+ if self._loaded_app is not None:
+ return self._loaded_app
+
+ if self.create_app is not None:
+ app = self.create_app()
+ else:
+ if self.app_import_path:
+ path, name = (
+ re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None]
+ )[:2]
+ import_name = prepare_import(path)
+ app = locate_app(import_name, name)
+ else:
+ for path in ("wsgi.py", "app.py"):
+ import_name = prepare_import(path)
+ app = locate_app(import_name, None, raise_if_not_found=False)
+
+ if app:
+ break
+
+ if not app:
+ raise NoAppException(
+ "Could not locate a Flask application. Use the"
+ " 'flask --app' option, 'FLASK_APP' environment"
+ " variable, or a 'wsgi.py' or 'app.py' file in the"
+ " current directory."
+ )
+
+ if self.set_debug_flag:
+ # Update the app's debug flag through the descriptor so that
+ # other values repopulate as well.
+ app.debug = get_debug_flag()
+
+ self._loaded_app = app
+ return app
+
+
+pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
+
+
+def with_appcontext(f):
+ """Wraps a callback so that it's guaranteed to be executed with the
+ script's application context.
+
+ Custom commands (and their options) registered under ``app.cli`` or
+ ``blueprint.cli`` will always have an app context available, this
+ decorator is not required in that case.
+
+ .. versionchanged:: 2.2
+ The app context is active for subcommands as well as the
+ decorated callback. The app context is always available to
+ ``app.cli`` command and parameter callbacks.
+ """
+
+ @click.pass_context
+ def decorator(__ctx, *args, **kwargs):
+ if not current_app:
+ app = __ctx.ensure_object(ScriptInfo).load_app()
+ __ctx.with_resource(app.app_context())
+
+ return __ctx.invoke(f, *args, **kwargs)
+
+ return update_wrapper(decorator, f)
+
+
+class AppGroup(click.Group):
+ """This works similar to a regular click :class:`~click.Group` but it
+ changes the behavior of the :meth:`command` decorator so that it
+ automatically wraps the functions in :func:`with_appcontext`.
+
+ Not to be confused with :class:`FlaskGroup`.
+ """
+
+ def command(self, *args, **kwargs):
+ """This works exactly like the method of the same name on a regular
+ :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
+ unless it's disabled by passing ``with_appcontext=False``.
+ """
+ wrap_for_ctx = kwargs.pop("with_appcontext", True)
+
+ def decorator(f):
+ if wrap_for_ctx:
+ f = with_appcontext(f)
+ return click.Group.command(self, *args, **kwargs)(f)
+
+ return decorator
+
+ def group(self, *args, **kwargs):
+ """This works exactly like the method of the same name on a regular
+ :class:`click.Group` but it defaults the group class to
+ :class:`AppGroup`.
+ """
+ kwargs.setdefault("cls", AppGroup)
+ return click.Group.group(self, *args, **kwargs)
+
+
+def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
+ if value is None:
+ return None
+
+ info = ctx.ensure_object(ScriptInfo)
+ info.app_import_path = value
+ return value
+
+
+# This option is eager so the app will be available if --help is given.
+# --help is also eager, so --app must be before it in the param list.
+# no_args_is_help bypasses eager processing, so this option must be
+# processed manually in that case to ensure FLASK_APP gets picked up.
+_app_option = click.Option(
+ ["-A", "--app"],
+ metavar="IMPORT",
+ help=(
+ "The Flask application or factory function to load, in the form 'module:name'."
+ " Module can be a dotted import or file path. Name is not required if it is"
+ " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
+ " pass arguments."
+ ),
+ is_eager=True,
+ expose_value=False,
+ callback=_set_app,
+)
+
+
+def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
+ # If the flag isn't provided, it will default to False. Don't use
+ # that, let debug be set by env in that case.
+ source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
+
+ if source is not None and source in (
+ ParameterSource.DEFAULT,
+ ParameterSource.DEFAULT_MAP,
+ ):
+ return None
+
+ # Set with env var instead of ScriptInfo.load so that it can be
+ # accessed early during a factory function.
+ os.environ["FLASK_DEBUG"] = "1" if value else "0"
+ return value
+
+
+_debug_option = click.Option(
+ ["--debug/--no-debug"],
+ help="Set debug mode.",
+ expose_value=False,
+ callback=_set_debug,
+)
+
+
+def _env_file_callback(
+ ctx: click.Context, param: click.Option, value: str | None
+) -> str | None:
+ if value is None:
+ return None
+
+ import importlib
+
+ try:
+ importlib.import_module("dotenv")
+ except ImportError:
+ raise click.BadParameter(
+ "python-dotenv must be installed to load an env file.",
+ ctx=ctx,
+ param=param,
+ ) from None
+
+ # Don't check FLASK_SKIP_DOTENV, that only disables automatically
+ # loading .env and .flaskenv files.
+ load_dotenv(value)
+ return value
+
+
+# This option is eager so env vars are loaded as early as possible to be
+# used by other options.
+_env_file_option = click.Option(
+ ["-e", "--env-file"],
+ type=click.Path(exists=True, dir_okay=False),
+ help="Load environment variables from this file. python-dotenv must be installed.",
+ is_eager=True,
+ expose_value=False,
+ callback=_env_file_callback,
+)
+
+
+class FlaskGroup(AppGroup):
+ """Special subclass of the :class:`AppGroup` group that supports
+ loading more commands from the configured Flask app. Normally a
+ developer does not have to interface with this class but there are
+ some very advanced use cases for which it makes sense to create an
+ instance of this. see :ref:`custom-scripts`.
+
+ :param add_default_commands: if this is True then the default run and
+ shell commands will be added.
+ :param add_version_option: adds the ``--version`` option.
+ :param create_app: an optional callback that is passed the script info and
+ returns the loaded app.
+ :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
+ files to set environment variables. Will also change the working
+ directory to the directory containing the first file found.
+ :param set_debug_flag: Set the app's debug flag.
+
+ .. versionchanged:: 2.2
+ Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
+
+ .. versionchanged:: 2.2
+ An app context is pushed when running ``app.cli`` commands, so
+ ``@with_appcontext`` is no longer required for those commands.
+
+ .. versionchanged:: 1.0
+ If installed, python-dotenv will be used to load environment variables
+ from :file:`.env` and :file:`.flaskenv` files.
+ """
+
+ def __init__(
+ self,
+ add_default_commands: bool = True,
+ create_app: t.Callable[..., Flask] | None = None,
+ add_version_option: bool = True,
+ load_dotenv: bool = True,
+ set_debug_flag: bool = True,
+ **extra: t.Any,
+ ) -> None:
+ params = list(extra.pop("params", None) or ())
+ # Processing is done with option callbacks instead of a group
+ # callback. This allows users to make a custom group callback
+ # without losing the behavior. --env-file must come first so
+ # that it is eagerly evaluated before --app.
+ params.extend((_env_file_option, _app_option, _debug_option))
+
+ if add_version_option:
+ params.append(version_option)
+
+ if "context_settings" not in extra:
+ extra["context_settings"] = {}
+
+ extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
+
+ super().__init__(params=params, **extra)
+
+ self.create_app = create_app
+ self.load_dotenv = load_dotenv
+ self.set_debug_flag = set_debug_flag
+
+ if add_default_commands:
+ self.add_command(run_command)
+ self.add_command(shell_command)
+ self.add_command(routes_command)
+
+ self._loaded_plugin_commands = False
+
+ def _load_plugin_commands(self):
+ if self._loaded_plugin_commands:
+ return
+
+ if sys.version_info >= (3, 10):
+ from importlib import metadata
+ else:
+ # Use a backport on Python < 3.10. We technically have
+ # importlib.metadata on 3.8+, but the API changed in 3.10,
+ # so use the backport for consistency.
+ import importlib_metadata as metadata
+
+ for ep in metadata.entry_points(group="flask.commands"):
+ self.add_command(ep.load(), ep.name)
+
+ self._loaded_plugin_commands = True
+
+ def get_command(self, ctx, name):
+ self._load_plugin_commands()
+ # Look up built-in and plugin commands, which should be
+ # available even if the app fails to load.
+ rv = super().get_command(ctx, name)
+
+ if rv is not None:
+ return rv
+
+ info = ctx.ensure_object(ScriptInfo)
+
+ # Look up commands provided by the app, showing an error and
+ # continuing if the app couldn't be loaded.
+ try:
+ app = info.load_app()
+ except NoAppException as e:
+ click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
+ return None
+
+ # Push an app context for the loaded app unless it is already
+ # active somehow. This makes the context available to parameter
+ # and command callbacks without needing @with_appcontext.
+ if not current_app or current_app._get_current_object() is not app:
+ ctx.with_resource(app.app_context())
+
+ return app.cli.get_command(ctx, name)
+
+ def list_commands(self, ctx):
+ self._load_plugin_commands()
+ # Start with the built-in and plugin commands.
+ rv = set(super().list_commands(ctx))
+ info = ctx.ensure_object(ScriptInfo)
+
+ # Add commands provided by the app, showing an error and
+ # continuing if the app couldn't be loaded.
+ try:
+ rv.update(info.load_app().cli.list_commands(ctx))
+ except NoAppException as e:
+ # When an app couldn't be loaded, show the error message
+ # without the traceback.
+ click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
+ except Exception:
+ # When any other errors occurred during loading, show the
+ # full traceback.
+ click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
+
+ return sorted(rv)
+
+ def make_context(
+ self,
+ info_name: str | None,
+ args: list[str],
+ parent: click.Context | None = None,
+ **extra: t.Any,
+ ) -> click.Context:
+ # Set a flag to tell app.run to become a no-op. If app.run was
+ # not in a __name__ == __main__ guard, it would start the server
+ # when importing, blocking whatever command is being called.
+ os.environ["FLASK_RUN_FROM_CLI"] = "true"
+
+ # Attempt to load .env and .flask env files. The --env-file
+ # option can cause another file to be loaded.
+ if get_load_dotenv(self.load_dotenv):
+ load_dotenv()
+
+ if "obj" not in extra and "obj" not in self.context_settings:
+ extra["obj"] = ScriptInfo(
+ create_app=self.create_app, set_debug_flag=self.set_debug_flag
+ )
+
+ return super().make_context(info_name, args, parent=parent, **extra)
+
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
+ if not args and self.no_args_is_help:
+ # Attempt to load --env-file and --app early in case they
+ # were given as env vars. Otherwise no_args_is_help will not
+ # see commands from app.cli.
+ _env_file_option.handle_parse_result(ctx, {}, [])
+ _app_option.handle_parse_result(ctx, {}, [])
+
+ return super().parse_args(ctx, args)
+
+
+def _path_is_ancestor(path, other):
+ """Take ``other`` and remove the length of ``path`` from it. Then join it
+ to ``path``. If it is the original value, ``path`` is an ancestor of
+ ``other``."""
+ return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
+
+
+def load_dotenv(path: str | os.PathLike | None = None) -> bool:
+ """Load "dotenv" files in order of precedence to set environment variables.
+
+ If an env var is already set it is not overwritten, so earlier files in the
+ list are preferred over later files.
+
+ This is a no-op if `python-dotenv`_ is not installed.
+
+ .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
+
+ :param path: Load the file at this location instead of searching.
+ :return: ``True`` if a file was loaded.
+
+ .. versionchanged:: 2.0
+ The current directory is not changed to the location of the
+ loaded file.
+
+ .. versionchanged:: 2.0
+ When loading the env files, set the default encoding to UTF-8.
+
+ .. versionchanged:: 1.1.0
+ Returns ``False`` when python-dotenv is not installed, or when
+ the given path isn't a file.
+
+ .. versionadded:: 1.0
+ """
+ try:
+ import dotenv
+ except ImportError:
+ if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
+ click.secho(
+ " * Tip: There are .env or .flaskenv files present."
+ ' Do "pip install python-dotenv" to use them.',
+ fg="yellow",
+ err=True,
+ )
+
+ return False
+
+ # Always return after attempting to load a given path, don't load
+ # the default files.
+ if path is not None:
+ if os.path.isfile(path):
+ return dotenv.load_dotenv(path, encoding="utf-8")
+
+ return False
+
+ loaded = False
+
+ for name in (".env", ".flaskenv"):
+ path = dotenv.find_dotenv(name, usecwd=True)
+
+ if not path:
+ continue
+
+ dotenv.load_dotenv(path, encoding="utf-8")
+ loaded = True
+
+ return loaded # True if at least one file was located and loaded.
+
+
+def show_server_banner(debug, app_import_path):
+ """Show extra startup messages the first time the server is run,
+ ignoring the reloader.
+ """
+ if is_running_from_reloader():
+ return
+
+ if app_import_path is not None:
+ click.echo(f" * Serving Flask app '{app_import_path}'")
+
+ if debug is not None:
+ click.echo(f" * Debug mode: {'on' if debug else 'off'}")
+
+
+class CertParamType(click.ParamType):
+ """Click option type for the ``--cert`` option. Allows either an
+ existing file, the string ``'adhoc'``, or an import for a
+ :class:`~ssl.SSLContext` object.
+ """
+
+ name = "path"
+
+ def __init__(self):
+ self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
+
+ def convert(self, value, param, ctx):
+ try:
+ import ssl
+ except ImportError:
+ raise click.BadParameter(
+ 'Using "--cert" requires Python to be compiled with SSL support.',
+ ctx,
+ param,
+ ) from None
+
+ try:
+ return self.path_type(value, param, ctx)
+ except click.BadParameter:
+ value = click.STRING(value, param, ctx).lower()
+
+ if value == "adhoc":
+ try:
+ import cryptography # noqa: F401
+ except ImportError:
+ raise click.BadParameter(
+ "Using ad-hoc certificates requires the cryptography library.",
+ ctx,
+ param,
+ ) from None
+
+ return value
+
+ obj = import_string(value, silent=True)
+
+ if isinstance(obj, ssl.SSLContext):
+ return obj
+
+ raise
+
+
+def _validate_key(ctx, param, value):
+ """The ``--key`` option must be specified when ``--cert`` is a file.
+ Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
+ """
+ cert = ctx.params.get("cert")
+ is_adhoc = cert == "adhoc"
+
+ try:
+ import ssl
+ except ImportError:
+ is_context = False
+ else:
+ is_context = isinstance(cert, ssl.SSLContext)
+
+ if value is not None:
+ if is_adhoc:
+ raise click.BadParameter(
+ 'When "--cert" is "adhoc", "--key" is not used.', ctx, param
+ )
+
+ if is_context:
+ raise click.BadParameter(
+ 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
+ )
+
+ if not cert:
+ raise click.BadParameter('"--cert" must also be specified.', ctx, param)
+
+ ctx.params["cert"] = cert, value
+
+ else:
+ if cert and not (is_adhoc or is_context):
+ raise click.BadParameter('Required when using "--cert".', ctx, param)
+
+ return value
+
+
+class SeparatedPathType(click.Path):
+ """Click option type that accepts a list of values separated by the
+ OS's path separator (``:``, ``;`` on Windows). Each value is
+ validated as a :class:`click.Path` type.
+ """
+
+ def convert(self, value, param, ctx):
+ items = self.split_envvar_value(value)
+ super_convert = super().convert
+ return [super_convert(item, param, ctx) for item in items]
+
+
+@click.command("run", short_help="Run a development server.")
+@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
+@click.option("--port", "-p", default=5000, help="The port to bind to.")
+@click.option(
+ "--cert",
+ type=CertParamType(),
+ help="Specify a certificate file to use HTTPS.",
+ is_eager=True,
+)
+@click.option(
+ "--key",
+ type=click.Path(exists=True, dir_okay=False, resolve_path=True),
+ callback=_validate_key,
+ expose_value=False,
+ help="The key file to use when specifying a certificate.",
+)
+@click.option(
+ "--reload/--no-reload",
+ default=None,
+ help="Enable or disable the reloader. By default the reloader "
+ "is active if debug is enabled.",
+)
+@click.option(
+ "--debugger/--no-debugger",
+ default=None,
+ help="Enable or disable the debugger. By default the debugger "
+ "is active if debug is enabled.",
+)
+@click.option(
+ "--with-threads/--without-threads",
+ default=True,
+ help="Enable or disable multithreading.",
+)
+@click.option(
+ "--extra-files",
+ default=None,
+ type=SeparatedPathType(),
+ help=(
+ "Extra files that trigger a reload on change. Multiple paths"
+ f" are separated by {os.path.pathsep!r}."
+ ),
+)
+@click.option(
+ "--exclude-patterns",
+ default=None,
+ type=SeparatedPathType(),
+ help=(
+ "Files matching these fnmatch patterns will not trigger a reload"
+ " on change. Multiple patterns are separated by"
+ f" {os.path.pathsep!r}."
+ ),
+)
+@pass_script_info
+def run_command(
+ info,
+ host,
+ port,
+ reload,
+ debugger,
+ with_threads,
+ cert,
+ extra_files,
+ exclude_patterns,
+):
+ """Run a local development server.
+
+ This server is for development purposes only. It does not provide
+ the stability, security, or performance of production WSGI servers.
+
+ The reloader and debugger are enabled by default with the '--debug'
+ option.
+ """
+ try:
+ app = info.load_app()
+ except Exception as e:
+ if is_running_from_reloader():
+ # When reloading, print out the error immediately, but raise
+ # it later so the debugger or server can handle it.
+ traceback.print_exc()
+ err = e
+
+ def app(environ, start_response):
+ raise err from None
+
+ else:
+ # When not reloading, raise the error immediately so the
+ # command fails.
+ raise e from None
+
+ debug = get_debug_flag()
+
+ if reload is None:
+ reload = debug
+
+ if debugger is None:
+ debugger = debug
+
+ show_server_banner(debug, info.app_import_path)
+
+ run_simple(
+ host,
+ port,
+ app,
+ use_reloader=reload,
+ use_debugger=debugger,
+ threaded=with_threads,
+ ssl_context=cert,
+ extra_files=extra_files,
+ exclude_patterns=exclude_patterns,
+ )
+
+
+run_command.params.insert(0, _debug_option)
+
+
+@click.command("shell", short_help="Run a shell in the app context.")
+@with_appcontext
+def shell_command() -> None:
+ """Run an interactive Python shell in the context of a given
+ Flask application. The application will populate the default
+ namespace of this shell according to its configuration.
+
+ This is useful for executing small snippets of management code
+ without having to manually configure the application.
+ """
+ import code
+
+ banner = (
+ f"Python {sys.version} on {sys.platform}\n"
+ f"App: {current_app.import_name}\n"
+ f"Instance: {current_app.instance_path}"
+ )
+ ctx: dict = {}
+
+ # Support the regular Python interpreter startup script if someone
+ # is using it.
+ startup = os.environ.get("PYTHONSTARTUP")
+ if startup and os.path.isfile(startup):
+ with open(startup) as f:
+ eval(compile(f.read(), startup, "exec"), ctx)
+
+ ctx.update(current_app.make_shell_context())
+
+ # Site, customize, or startup script can set a hook to call when
+ # entering interactive mode. The default one sets up readline with
+ # tab and history completion.
+ interactive_hook = getattr(sys, "__interactivehook__", None)
+
+ if interactive_hook is not None:
+ try:
+ import readline
+ from rlcompleter import Completer
+ except ImportError:
+ pass
+ else:
+ # rlcompleter uses __main__.__dict__ by default, which is
+ # flask.__main__. Use the shell context instead.
+ readline.set_completer(Completer(ctx).complete)
+
+ interactive_hook()
+
+ code.interact(banner=banner, local=ctx)
+
+
+@click.command("routes", short_help="Show the routes for the app.")
+@click.option(
+ "--sort",
+ "-s",
+ type=click.Choice(("endpoint", "methods", "domain", "rule", "match")),
+ default="endpoint",
+ help=(
+ "Method to sort routes by. 'match' is the order that Flask will match routes"
+ " when dispatching a request."
+ ),
+)
+@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
+@with_appcontext
+def routes_command(sort: str, all_methods: bool) -> None:
+ """Show all registered routes with endpoints and methods."""
+ rules = list(current_app.url_map.iter_rules())
+
+ if not rules:
+ click.echo("No routes were registered.")
+ return
+
+ ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"}
+ host_matching = current_app.url_map.host_matching
+ has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules)
+ rows = []
+
+ for rule in rules:
+ row = [
+ rule.endpoint,
+ ", ".join(sorted((rule.methods or set()) - ignored_methods)),
+ ]
+
+ if has_domain:
+ row.append((rule.host if host_matching else rule.subdomain) or "")
+
+ row.append(rule.rule)
+ rows.append(row)
+
+ headers = ["Endpoint", "Methods"]
+ sorts = ["endpoint", "methods"]
+
+ if has_domain:
+ headers.append("Host" if host_matching else "Subdomain")
+ sorts.append("domain")
+
+ headers.append("Rule")
+ sorts.append("rule")
+
+ try:
+ rows.sort(key=itemgetter(sorts.index(sort)))
+ except ValueError:
+ pass
+
+ rows.insert(0, headers)
+ widths = [max(len(row[i]) for row in rows) for i in range(len(headers))]
+ rows.insert(1, ["-" * w for w in widths])
+ template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths))
+
+ for row in rows:
+ click.echo(template.format(*row))
+
+
+cli = FlaskGroup(
+ name="flask",
+ help="""\
+A general utility script for Flask applications.
+
+An application to load must be given with the '--app' option,
+'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
+in the current directory.
+""",
+)
+
+
+def main() -> None:
+ cli.main()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/python3.12/site-packages/flask/config.py b/lib/python3.12/site-packages/flask/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f921b4dff57067cef833689a835c21e3454f845
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/config.py
@@ -0,0 +1,347 @@
+from __future__ import annotations
+
+import errno
+import json
+import os
+import types
+import typing as t
+
+from werkzeug.utils import import_string
+
+
+class ConfigAttribute:
+ """Makes an attribute forward to the config"""
+
+ def __init__(self, name: str, get_converter: t.Callable | None = None) -> None:
+ self.__name__ = name
+ self.get_converter = get_converter
+
+ def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any:
+ if obj is None:
+ return self
+ rv = obj.config[self.__name__]
+ if self.get_converter is not None:
+ rv = self.get_converter(rv)
+ return rv
+
+ def __set__(self, obj: t.Any, value: t.Any) -> None:
+ obj.config[self.__name__] = value
+
+
+class Config(dict):
+ """Works exactly like a dict but provides ways to fill it from files
+ or special dictionaries. There are two common patterns to populate the
+ config.
+
+ Either you can fill the config from a config file::
+
+ app.config.from_pyfile('yourconfig.cfg')
+
+ Or alternatively you can define the configuration options in the
+ module that calls :meth:`from_object` or provide an import path to
+ a module that should be loaded. It is also possible to tell it to
+ use the same module and with that provide the configuration values
+ just before the call::
+
+ DEBUG = True
+ SECRET_KEY = 'development key'
+ app.config.from_object(__name__)
+
+ In both cases (loading from any Python file or loading from modules),
+ only uppercase keys are added to the config. This makes it possible to use
+ lowercase values in the config file for temporary values that are not added
+ to the config or to define the config keys in the same file that implements
+ the application.
+
+ Probably the most interesting way to load configurations is from an
+ environment variable pointing to a file::
+
+ app.config.from_envvar('YOURAPPLICATION_SETTINGS')
+
+ In this case before launching the application you have to set this
+ environment variable to the file you want to use. On Linux and OS X
+ use the export statement::
+
+ export YOURAPPLICATION_SETTINGS='/path/to/config/file'
+
+ On windows use `set` instead.
+
+ :param root_path: path to which files are read relative from. When the
+ config object is created by the application, this is
+ the application's :attr:`~flask.Flask.root_path`.
+ :param defaults: an optional dictionary of default values
+ """
+
+ def __init__(
+ self, root_path: str | os.PathLike, defaults: dict | None = None
+ ) -> None:
+ super().__init__(defaults or {})
+ self.root_path = root_path
+
+ def from_envvar(self, variable_name: str, silent: bool = False) -> bool:
+ """Loads a configuration from an environment variable pointing to
+ a configuration file. This is basically just a shortcut with nicer
+ error messages for this line of code::
+
+ app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
+
+ :param variable_name: name of the environment variable
+ :param silent: set to ``True`` if you want silent failure for missing
+ files.
+ :return: ``True`` if the file was loaded successfully.
+ """
+ rv = os.environ.get(variable_name)
+ if not rv:
+ if silent:
+ return False
+ raise RuntimeError(
+ f"The environment variable {variable_name!r} is not set"
+ " and as such configuration could not be loaded. Set"
+ " this variable and make it point to a configuration"
+ " file"
+ )
+ return self.from_pyfile(rv, silent=silent)
+
+ def from_prefixed_env(
+ self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads
+ ) -> bool:
+ """Load any environment variables that start with ``FLASK_``,
+ dropping the prefix from the env key for the config key. Values
+ are passed through a loading function to attempt to convert them
+ to more specific types than strings.
+
+ Keys are loaded in :func:`sorted` order.
+
+ The default loading function attempts to parse values as any
+ valid JSON type, including dicts and lists.
+
+ Specific items in nested dicts can be set by separating the
+ keys with double underscores (``__``). If an intermediate key
+ doesn't exist, it will be initialized to an empty dict.
+
+ :param prefix: Load env vars that start with this prefix,
+ separated with an underscore (``_``).
+ :param loads: Pass each string value to this function and use
+ the returned value as the config value. If any error is
+ raised it is ignored and the value remains a string. The
+ default is :func:`json.loads`.
+
+ .. versionadded:: 2.1
+ """
+ prefix = f"{prefix}_"
+ len_prefix = len(prefix)
+
+ for key in sorted(os.environ):
+ if not key.startswith(prefix):
+ continue
+
+ value = os.environ[key]
+
+ try:
+ value = loads(value)
+ except Exception:
+ # Keep the value as a string if loading failed.
+ pass
+
+ # Change to key.removeprefix(prefix) on Python >= 3.9.
+ key = key[len_prefix:]
+
+ if "__" not in key:
+ # A non-nested key, set directly.
+ self[key] = value
+ continue
+
+ # Traverse nested dictionaries with keys separated by "__".
+ current = self
+ *parts, tail = key.split("__")
+
+ for part in parts:
+ # If an intermediate dict does not exist, create it.
+ if part not in current:
+ current[part] = {}
+
+ current = current[part]
+
+ current[tail] = value
+
+ return True
+
+ def from_pyfile(self, filename: str | os.PathLike, silent: bool = False) -> bool:
+ """Updates the values in the config from a Python file. This function
+ behaves as if the file was imported as module with the
+ :meth:`from_object` function.
+
+ :param filename: the filename of the config. This can either be an
+ absolute filename or a filename relative to the
+ root path.
+ :param silent: set to ``True`` if you want silent failure for missing
+ files.
+ :return: ``True`` if the file was loaded successfully.
+
+ .. versionadded:: 0.7
+ `silent` parameter.
+ """
+ filename = os.path.join(self.root_path, filename)
+ d = types.ModuleType("config")
+ d.__file__ = filename
+ try:
+ with open(filename, mode="rb") as config_file:
+ exec(compile(config_file.read(), filename, "exec"), d.__dict__)
+ except OSError as e:
+ if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
+ return False
+ e.strerror = f"Unable to load configuration file ({e.strerror})"
+ raise
+ self.from_object(d)
+ return True
+
+ def from_object(self, obj: object | str) -> None:
+ """Updates the values from the given object. An object can be of one
+ of the following two types:
+
+ - a string: in this case the object with that name will be imported
+ - an actual object reference: that object is used directly
+
+ Objects are usually either modules or classes. :meth:`from_object`
+ loads only the uppercase attributes of the module/class. A ``dict``
+ object will not work with :meth:`from_object` because the keys of a
+ ``dict`` are not attributes of the ``dict`` class.
+
+ Example of module-based configuration::
+
+ app.config.from_object('yourapplication.default_config')
+ from yourapplication import default_config
+ app.config.from_object(default_config)
+
+ Nothing is done to the object before loading. If the object is a
+ class and has ``@property`` attributes, it needs to be
+ instantiated before being passed to this method.
+
+ You should not use this function to load the actual configuration but
+ rather configuration defaults. The actual config should be loaded
+ with :meth:`from_pyfile` and ideally from a location not within the
+ package because the package might be installed system wide.
+
+ See :ref:`config-dev-prod` for an example of class-based configuration
+ using :meth:`from_object`.
+
+ :param obj: an import name or object
+ """
+ if isinstance(obj, str):
+ obj = import_string(obj)
+ for key in dir(obj):
+ if key.isupper():
+ self[key] = getattr(obj, key)
+
+ def from_file(
+ self,
+ filename: str | os.PathLike,
+ load: t.Callable[[t.IO[t.Any]], t.Mapping],
+ silent: bool = False,
+ text: bool = True,
+ ) -> bool:
+ """Update the values in the config from a file that is loaded
+ using the ``load`` parameter. The loaded data is passed to the
+ :meth:`from_mapping` method.
+
+ .. code-block:: python
+
+ import json
+ app.config.from_file("config.json", load=json.load)
+
+ import tomllib
+ app.config.from_file("config.toml", load=tomllib.load, text=False)
+
+ :param filename: The path to the data file. This can be an
+ absolute path or relative to the config root path.
+ :param load: A callable that takes a file handle and returns a
+ mapping of loaded data from the file.
+ :type load: ``Callable[[Reader], Mapping]`` where ``Reader``
+ implements a ``read`` method.
+ :param silent: Ignore the file if it doesn't exist.
+ :param text: Open the file in text or binary mode.
+ :return: ``True`` if the file was loaded successfully.
+
+ .. versionchanged:: 2.3
+ The ``text`` parameter was added.
+
+ .. versionadded:: 2.0
+ """
+ filename = os.path.join(self.root_path, filename)
+
+ try:
+ with open(filename, "r" if text else "rb") as f:
+ obj = load(f)
+ except OSError as e:
+ if silent and e.errno in (errno.ENOENT, errno.EISDIR):
+ return False
+
+ e.strerror = f"Unable to load configuration file ({e.strerror})"
+ raise
+
+ return self.from_mapping(obj)
+
+ def from_mapping(
+ self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any
+ ) -> bool:
+ """Updates the config like :meth:`update` ignoring items with
+ non-upper keys.
+
+ :return: Always returns ``True``.
+
+ .. versionadded:: 0.11
+ """
+ mappings: dict[str, t.Any] = {}
+ if mapping is not None:
+ mappings.update(mapping)
+ mappings.update(kwargs)
+ for key, value in mappings.items():
+ if key.isupper():
+ self[key] = value
+ return True
+
+ def get_namespace(
+ self, namespace: str, lowercase: bool = True, trim_namespace: bool = True
+ ) -> dict[str, t.Any]:
+ """Returns a dictionary containing a subset of configuration options
+ that match the specified namespace/prefix. Example usage::
+
+ app.config['IMAGE_STORE_TYPE'] = 'fs'
+ app.config['IMAGE_STORE_PATH'] = '/var/app/images'
+ app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
+ image_store_config = app.config.get_namespace('IMAGE_STORE_')
+
+ The resulting dictionary `image_store_config` would look like::
+
+ {
+ 'type': 'fs',
+ 'path': '/var/app/images',
+ 'base_url': 'http://img.website.com'
+ }
+
+ This is often useful when configuration options map directly to
+ keyword arguments in functions or class constructors.
+
+ :param namespace: a configuration namespace
+ :param lowercase: a flag indicating if the keys of the resulting
+ dictionary should be lowercase
+ :param trim_namespace: a flag indicating if the keys of the resulting
+ dictionary should not include the namespace
+
+ .. versionadded:: 0.11
+ """
+ rv = {}
+ for k, v in self.items():
+ if not k.startswith(namespace):
+ continue
+ if trim_namespace:
+ key = k[len(namespace) :]
+ else:
+ key = k
+ if lowercase:
+ key = key.lower()
+ rv[key] = v
+ return rv
+
+ def __repr__(self) -> str:
+ return f"<{type(self).__name__} {dict.__repr__(self)}>"
diff --git a/lib/python3.12/site-packages/flask/ctx.py b/lib/python3.12/site-packages/flask/ctx.py
new file mode 100644
index 0000000000000000000000000000000000000000..b37e4e04a6c69254ba0f755813fccbeab9715ba4
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/ctx.py
@@ -0,0 +1,440 @@
+from __future__ import annotations
+
+import contextvars
+import sys
+import typing as t
+from functools import update_wrapper
+from types import TracebackType
+
+from werkzeug.exceptions import HTTPException
+
+from . import typing as ft
+from .globals import _cv_app
+from .globals import _cv_request
+from .signals import appcontext_popped
+from .signals import appcontext_pushed
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .sessions import SessionMixin
+ from .wrappers import Request
+
+
+# a singleton sentinel value for parameter defaults
+_sentinel = object()
+
+
+class _AppCtxGlobals:
+ """A plain object. Used as a namespace for storing data during an
+ application context.
+
+ Creating an app context automatically creates this object, which is
+ made available as the :data:`g` proxy.
+
+ .. describe:: 'key' in g
+
+ Check whether an attribute is present.
+
+ .. versionadded:: 0.10
+
+ .. describe:: iter(g)
+
+ Return an iterator over the attribute names.
+
+ .. versionadded:: 0.10
+ """
+
+ # Define attr methods to let mypy know this is a namespace object
+ # that has arbitrary attributes.
+
+ def __getattr__(self, name: str) -> t.Any:
+ try:
+ return self.__dict__[name]
+ except KeyError:
+ raise AttributeError(name) from None
+
+ def __setattr__(self, name: str, value: t.Any) -> None:
+ self.__dict__[name] = value
+
+ def __delattr__(self, name: str) -> None:
+ try:
+ del self.__dict__[name]
+ except KeyError:
+ raise AttributeError(name) from None
+
+ def get(self, name: str, default: t.Any | None = None) -> t.Any:
+ """Get an attribute by name, or a default value. Like
+ :meth:`dict.get`.
+
+ :param name: Name of attribute to get.
+ :param default: Value to return if the attribute is not present.
+
+ .. versionadded:: 0.10
+ """
+ return self.__dict__.get(name, default)
+
+ def pop(self, name: str, default: t.Any = _sentinel) -> t.Any:
+ """Get and remove an attribute by name. Like :meth:`dict.pop`.
+
+ :param name: Name of attribute to pop.
+ :param default: Value to return if the attribute is not present,
+ instead of raising a ``KeyError``.
+
+ .. versionadded:: 0.11
+ """
+ if default is _sentinel:
+ return self.__dict__.pop(name)
+ else:
+ return self.__dict__.pop(name, default)
+
+ def setdefault(self, name: str, default: t.Any = None) -> t.Any:
+ """Get the value of an attribute if it is present, otherwise
+ set and return a default value. Like :meth:`dict.setdefault`.
+
+ :param name: Name of attribute to get.
+ :param default: Value to set and return if the attribute is not
+ present.
+
+ .. versionadded:: 0.11
+ """
+ return self.__dict__.setdefault(name, default)
+
+ def __contains__(self, item: str) -> bool:
+ return item in self.__dict__
+
+ def __iter__(self) -> t.Iterator[str]:
+ return iter(self.__dict__)
+
+ def __repr__(self) -> str:
+ ctx = _cv_app.get(None)
+ if ctx is not None:
+ return f""
+ return object.__repr__(self)
+
+
+def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
+ """Executes a function after this request. This is useful to modify
+ response objects. The function is passed the response object and has
+ to return the same or a new one.
+
+ Example::
+
+ @app.route('/')
+ def index():
+ @after_this_request
+ def add_header(response):
+ response.headers['X-Foo'] = 'Parachute'
+ return response
+ return 'Hello World!'
+
+ This is more useful if a function other than the view function wants to
+ modify a response. For instance think of a decorator that wants to add
+ some headers without converting the return value into a response object.
+
+ .. versionadded:: 0.9
+ """
+ ctx = _cv_request.get(None)
+
+ if ctx is None:
+ raise RuntimeError(
+ "'after_this_request' can only be used when a request"
+ " context is active, such as in a view function."
+ )
+
+ ctx._after_request_functions.append(f)
+ return f
+
+
+def copy_current_request_context(f: t.Callable) -> t.Callable:
+ """A helper function that decorates a function to retain the current
+ request context. This is useful when working with greenlets. The moment
+ the function is decorated a copy of the request context is created and
+ then pushed when the function is called. The current session is also
+ included in the copied request context.
+
+ Example::
+
+ import gevent
+ from flask import copy_current_request_context
+
+ @app.route('/')
+ def index():
+ @copy_current_request_context
+ def do_some_work():
+ # do some work here, it can access flask.request or
+ # flask.session like you would otherwise in the view function.
+ ...
+ gevent.spawn(do_some_work)
+ return 'Regular response'
+
+ .. versionadded:: 0.10
+ """
+ ctx = _cv_request.get(None)
+
+ if ctx is None:
+ raise RuntimeError(
+ "'copy_current_request_context' can only be used when a"
+ " request context is active, such as in a view function."
+ )
+
+ ctx = ctx.copy()
+
+ def wrapper(*args, **kwargs):
+ with ctx:
+ return ctx.app.ensure_sync(f)(*args, **kwargs)
+
+ return update_wrapper(wrapper, f)
+
+
+def has_request_context() -> bool:
+ """If you have code that wants to test if a request context is there or
+ not this function can be used. For instance, you may want to take advantage
+ of request information if the request object is available, but fail
+ silently if it is unavailable.
+
+ ::
+
+ class User(db.Model):
+
+ def __init__(self, username, remote_addr=None):
+ self.username = username
+ if remote_addr is None and has_request_context():
+ remote_addr = request.remote_addr
+ self.remote_addr = remote_addr
+
+ Alternatively you can also just test any of the context bound objects
+ (such as :class:`request` or :class:`g`) for truthness::
+
+ class User(db.Model):
+
+ def __init__(self, username, remote_addr=None):
+ self.username = username
+ if remote_addr is None and request:
+ remote_addr = request.remote_addr
+ self.remote_addr = remote_addr
+
+ .. versionadded:: 0.7
+ """
+ return _cv_request.get(None) is not None
+
+
+def has_app_context() -> bool:
+ """Works like :func:`has_request_context` but for the application
+ context. You can also just do a boolean check on the
+ :data:`current_app` object instead.
+
+ .. versionadded:: 0.9
+ """
+ return _cv_app.get(None) is not None
+
+
+class AppContext:
+ """The app context contains application-specific information. An app
+ context is created and pushed at the beginning of each request if
+ one is not already active. An app context is also pushed when
+ running CLI commands.
+ """
+
+ def __init__(self, app: Flask) -> None:
+ self.app = app
+ self.url_adapter = app.create_url_adapter(None)
+ self.g: _AppCtxGlobals = app.app_ctx_globals_class()
+ self._cv_tokens: list[contextvars.Token] = []
+
+ def push(self) -> None:
+ """Binds the app context to the current context."""
+ self._cv_tokens.append(_cv_app.set(self))
+ appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
+
+ def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
+ """Pops the app context."""
+ try:
+ if len(self._cv_tokens) == 1:
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_appcontext(exc)
+ finally:
+ ctx = _cv_app.get()
+ _cv_app.reset(self._cv_tokens.pop())
+
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong app context. ({ctx!r} instead of {self!r})"
+ )
+
+ appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
+
+ def __enter__(self) -> AppContext:
+ self.push()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.pop(exc_value)
+
+
+class RequestContext:
+ """The request context contains per-request information. The Flask
+ app creates and pushes it at the beginning of the request, then pops
+ it at the end of the request. It will create the URL adapter and
+ request object for the WSGI environment provided.
+
+ Do not attempt to use this class directly, instead use
+ :meth:`~flask.Flask.test_request_context` and
+ :meth:`~flask.Flask.request_context` to create this object.
+
+ When the request context is popped, it will evaluate all the
+ functions registered on the application for teardown execution
+ (:meth:`~flask.Flask.teardown_request`).
+
+ The request context is automatically popped at the end of the
+ request. When using the interactive debugger, the context will be
+ restored so ``request`` is still accessible. Similarly, the test
+ client can preserve the context after the request ends. However,
+ teardown functions may already have closed some resources such as
+ database connections.
+ """
+
+ def __init__(
+ self,
+ app: Flask,
+ environ: dict,
+ request: Request | None = None,
+ session: SessionMixin | None = None,
+ ) -> None:
+ self.app = app
+ if request is None:
+ request = app.request_class(environ)
+ request.json_module = app.json
+ self.request: Request = request
+ self.url_adapter = None
+ try:
+ self.url_adapter = app.create_url_adapter(self.request)
+ except HTTPException as e:
+ self.request.routing_exception = e
+ self.flashes: list[tuple[str, str]] | None = None
+ self.session: SessionMixin | None = session
+ # Functions that should be executed after the request on the response
+ # object. These will be called before the regular "after_request"
+ # functions.
+ self._after_request_functions: list[ft.AfterRequestCallable] = []
+
+ self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = []
+
+ def copy(self) -> RequestContext:
+ """Creates a copy of this request context with the same request object.
+ This can be used to move a request context to a different greenlet.
+ Because the actual request object is the same this cannot be used to
+ move a request context to a different thread unless access to the
+ request object is locked.
+
+ .. versionadded:: 0.10
+
+ .. versionchanged:: 1.1
+ The current session object is used instead of reloading the original
+ data. This prevents `flask.session` pointing to an out-of-date object.
+ """
+ return self.__class__(
+ self.app,
+ environ=self.request.environ,
+ request=self.request,
+ session=self.session,
+ )
+
+ def match_request(self) -> None:
+ """Can be overridden by a subclass to hook into the matching
+ of the request.
+ """
+ try:
+ result = self.url_adapter.match(return_rule=True) # type: ignore
+ self.request.url_rule, self.request.view_args = result # type: ignore
+ except HTTPException as e:
+ self.request.routing_exception = e
+
+ def push(self) -> None:
+ # Before we push the request context we have to ensure that there
+ # is an application context.
+ app_ctx = _cv_app.get(None)
+
+ if app_ctx is None or app_ctx.app is not self.app:
+ app_ctx = self.app.app_context()
+ app_ctx.push()
+ else:
+ app_ctx = None
+
+ self._cv_tokens.append((_cv_request.set(self), app_ctx))
+
+ # Open the session at the moment that the request context is available.
+ # This allows a custom open_session method to use the request context.
+ # Only open a new session if this is the first time the request was
+ # pushed, otherwise stream_with_context loses the session.
+ if self.session is None:
+ session_interface = self.app.session_interface
+ self.session = session_interface.open_session(self.app, self.request)
+
+ if self.session is None:
+ self.session = session_interface.make_null_session(self.app)
+
+ # Match the request URL after loading the session, so that the
+ # session is available in custom URL converters.
+ if self.url_adapter is not None:
+ self.match_request()
+
+ def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
+ """Pops the request context and unbinds it by doing that. This will
+ also trigger the execution of functions registered by the
+ :meth:`~flask.Flask.teardown_request` decorator.
+
+ .. versionchanged:: 0.9
+ Added the `exc` argument.
+ """
+ clear_request = len(self._cv_tokens) == 1
+
+ try:
+ if clear_request:
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_request(exc)
+
+ request_close = getattr(self.request, "close", None)
+ if request_close is not None:
+ request_close()
+ finally:
+ ctx = _cv_request.get()
+ token, app_ctx = self._cv_tokens.pop()
+ _cv_request.reset(token)
+
+ # get rid of circular dependencies at the end of the request
+ # so that we don't require the GC to be active.
+ if clear_request:
+ ctx.request.environ["werkzeug.request"] = None
+
+ if app_ctx is not None:
+ app_ctx.pop(exc)
+
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong request context. ({ctx!r} instead of {self!r})"
+ )
+
+ def __enter__(self) -> RequestContext:
+ self.push()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.pop(exc_value)
+
+ def __repr__(self) -> str:
+ return (
+ f"<{type(self).__name__} {self.request.url!r}"
+ f" [{self.request.method}] of {self.app.name}>"
+ )
diff --git a/lib/python3.12/site-packages/flask/debughelpers.py b/lib/python3.12/site-packages/flask/debughelpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..6061441a89fbf984ff6414b0b597192e7bca5bbc
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/debughelpers.py
@@ -0,0 +1,160 @@
+from __future__ import annotations
+
+import typing as t
+
+from .app import Flask
+from .blueprints import Blueprint
+from .globals import request_ctx
+
+
+class UnexpectedUnicodeError(AssertionError, UnicodeError):
+ """Raised in places where we want some better error reporting for
+ unexpected unicode or binary data.
+ """
+
+
+class DebugFilesKeyError(KeyError, AssertionError):
+ """Raised from request.files during debugging. The idea is that it can
+ provide a better error message than just a generic KeyError/BadRequest.
+ """
+
+ def __init__(self, request, key):
+ form_matches = request.form.getlist(key)
+ buf = [
+ f"You tried to access the file {key!r} in the request.files"
+ " dictionary but it does not exist. The mimetype for the"
+ f" request is {request.mimetype!r} instead of"
+ " 'multipart/form-data' which means that no file contents"
+ " were transmitted. To fix this error you should provide"
+ ' enctype="multipart/form-data" in your form.'
+ ]
+ if form_matches:
+ names = ", ".join(repr(x) for x in form_matches)
+ buf.append(
+ "\n\nThe browser instead transmitted some file names. "
+ f"This was submitted: {names}"
+ )
+ self.msg = "".join(buf)
+
+ def __str__(self):
+ return self.msg
+
+
+class FormDataRoutingRedirect(AssertionError):
+ """This exception is raised in debug mode if a routing redirect
+ would cause the browser to drop the method or body. This happens
+ when method is not GET, HEAD or OPTIONS and the status code is not
+ 307 or 308.
+ """
+
+ def __init__(self, request):
+ exc = request.routing_exception
+ buf = [
+ f"A request was sent to '{request.url}', but routing issued"
+ f" a redirect to the canonical URL '{exc.new_url}'."
+ ]
+
+ if f"{request.base_url}/" == exc.new_url.partition("?")[0]:
+ buf.append(
+ " The URL was defined with a trailing slash. Flask"
+ " will redirect to the URL with a trailing slash if it"
+ " was accessed without one."
+ )
+
+ buf.append(
+ " Send requests to the canonical URL, or use 307 or 308 for"
+ " routing redirects. Otherwise, browsers will drop form"
+ " data.\n\n"
+ "This exception is only raised in debug mode."
+ )
+ super().__init__("".join(buf))
+
+
+def attach_enctype_error_multidict(request):
+ """Patch ``request.files.__getitem__`` to raise a descriptive error
+ about ``enctype=multipart/form-data``.
+
+ :param request: The request to patch.
+ :meta private:
+ """
+ oldcls = request.files.__class__
+
+ class newcls(oldcls):
+ def __getitem__(self, key):
+ try:
+ return super().__getitem__(key)
+ except KeyError as e:
+ if key not in request.form:
+ raise
+
+ raise DebugFilesKeyError(request, key).with_traceback(
+ e.__traceback__
+ ) from None
+
+ newcls.__name__ = oldcls.__name__
+ newcls.__module__ = oldcls.__module__
+ request.files.__class__ = newcls
+
+
+def _dump_loader_info(loader) -> t.Generator:
+ yield f"class: {type(loader).__module__}.{type(loader).__name__}"
+ for key, value in sorted(loader.__dict__.items()):
+ if key.startswith("_"):
+ continue
+ if isinstance(value, (tuple, list)):
+ if not all(isinstance(x, str) for x in value):
+ continue
+ yield f"{key}:"
+ for item in value:
+ yield f" - {item}"
+ continue
+ elif not isinstance(value, (str, int, float, bool)):
+ continue
+ yield f"{key}: {value!r}"
+
+
+def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
+ """This should help developers understand what failed"""
+ info = [f"Locating template {template!r}:"]
+ total_found = 0
+ blueprint = None
+ if request_ctx and request_ctx.request.blueprint is not None:
+ blueprint = request_ctx.request.blueprint
+
+ for idx, (loader, srcobj, triple) in enumerate(attempts):
+ if isinstance(srcobj, Flask):
+ src_info = f"application {srcobj.import_name!r}"
+ elif isinstance(srcobj, Blueprint):
+ src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
+ else:
+ src_info = repr(srcobj)
+
+ info.append(f"{idx + 1:5}: trying loader of {src_info}")
+
+ for line in _dump_loader_info(loader):
+ info.append(f" {line}")
+
+ if triple is None:
+ detail = "no match"
+ else:
+ detail = f"found ({triple[1] or ''!r})"
+ total_found += 1
+ info.append(f" -> {detail}")
+
+ seems_fishy = False
+ if total_found == 0:
+ info.append("Error: the template could not be found.")
+ seems_fishy = True
+ elif total_found > 1:
+ info.append("Warning: multiple loaders returned a match for the template.")
+ seems_fishy = True
+
+ if blueprint is not None and seems_fishy:
+ info.append(
+ " The template was looked up from an endpoint that belongs"
+ f" to the blueprint {blueprint!r}."
+ )
+ info.append(" Maybe you did not place a template in the right folder?")
+ info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
+
+ app.logger.info("\n".join(info))
diff --git a/lib/python3.12/site-packages/flask/globals.py b/lib/python3.12/site-packages/flask/globals.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9cd4acfcda8a51dda20c604678fd706709070b2
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/globals.py
@@ -0,0 +1,96 @@
+from __future__ import annotations
+
+import typing as t
+from contextvars import ContextVar
+
+from werkzeug.local import LocalProxy
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .ctx import _AppCtxGlobals
+ from .ctx import AppContext
+ from .ctx import RequestContext
+ from .sessions import SessionMixin
+ from .wrappers import Request
+
+
+class _FakeStack:
+ def __init__(self, name: str, cv: ContextVar[t.Any]) -> None:
+ self.name = name
+ self.cv = cv
+
+ @property
+ def top(self) -> t.Any | None:
+ import warnings
+
+ warnings.warn(
+ f"'_{self.name}_ctx_stack' is deprecated and will be removed in Flask 2.4."
+ f" Use 'g' to store data, or '{self.name}_ctx' to access the current"
+ " context.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.cv.get(None)
+
+
+_no_app_msg = """\
+Working outside of application context.
+
+This typically means that you attempted to use functionality that needed
+the current application. To solve this, set up an application context
+with app.app_context(). See the documentation for more information.\
+"""
+_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
+__app_ctx_stack = _FakeStack("app", _cv_app)
+app_ctx: AppContext = LocalProxy( # type: ignore[assignment]
+ _cv_app, unbound_message=_no_app_msg
+)
+current_app: Flask = LocalProxy( # type: ignore[assignment]
+ _cv_app, "app", unbound_message=_no_app_msg
+)
+g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment]
+ _cv_app, "g", unbound_message=_no_app_msg
+)
+
+_no_req_msg = """\
+Working outside of request context.
+
+This typically means that you attempted to use functionality that needed
+an active HTTP request. Consult the documentation on testing for
+information about how to avoid this problem.\
+"""
+_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
+__request_ctx_stack = _FakeStack("request", _cv_request)
+request_ctx: RequestContext = LocalProxy( # type: ignore[assignment]
+ _cv_request, unbound_message=_no_req_msg
+)
+request: Request = LocalProxy( # type: ignore[assignment]
+ _cv_request, "request", unbound_message=_no_req_msg
+)
+session: SessionMixin = LocalProxy( # type: ignore[assignment]
+ _cv_request, "session", unbound_message=_no_req_msg
+)
+
+
+def __getattr__(name: str) -> t.Any:
+ if name == "_app_ctx_stack":
+ import warnings
+
+ warnings.warn(
+ "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __app_ctx_stack
+
+ if name == "_request_ctx_stack":
+ import warnings
+
+ warnings.warn(
+ "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __request_ctx_stack
+
+ raise AttributeError(name)
diff --git a/lib/python3.12/site-packages/flask/helpers.py b/lib/python3.12/site-packages/flask/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..284c36965b527553d55dcf058c51110483061134
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/helpers.py
@@ -0,0 +1,701 @@
+from __future__ import annotations
+
+import importlib.util
+import os
+import socket
+import sys
+import typing as t
+import warnings
+from datetime import datetime
+from functools import lru_cache
+from functools import update_wrapper
+from threading import RLock
+
+import werkzeug.utils
+from werkzeug.exceptions import abort as _wz_abort
+from werkzeug.utils import redirect as _wz_redirect
+
+from .globals import _cv_request
+from .globals import current_app
+from .globals import request
+from .globals import request_ctx
+from .globals import session
+from .signals import message_flashed
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.wrappers import Response as BaseResponse
+ from .wrappers import Response
+
+
+def get_debug_flag() -> bool:
+ """Get whether debug mode should be enabled for the app, indicated by the
+ :envvar:`FLASK_DEBUG` environment variable. The default is ``False``.
+ """
+ val = os.environ.get("FLASK_DEBUG")
+ return bool(val and val.lower() not in {"0", "false", "no"})
+
+
+def get_load_dotenv(default: bool = True) -> bool:
+ """Get whether the user has disabled loading default dotenv files by
+ setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load
+ the files.
+
+ :param default: What to return if the env var isn't set.
+ """
+ val = os.environ.get("FLASK_SKIP_DOTENV")
+
+ if not val:
+ return default
+
+ return val.lower() in ("0", "false", "no")
+
+
+def stream_with_context(
+ generator_or_function: (
+ t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]]
+ )
+) -> t.Iterator[t.AnyStr]:
+ """Request contexts disappear when the response is started on the server.
+ This is done for efficiency reasons and to make it less likely to encounter
+ memory leaks with badly written WSGI middlewares. The downside is that if
+ you are using streamed responses, the generator cannot access request bound
+ information any more.
+
+ This function however can help you keep the context around for longer::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ @stream_with_context
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(generate())
+
+ Alternatively it can also be used around a specific generator::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(stream_with_context(generate()))
+
+ .. versionadded:: 0.9
+ """
+ try:
+ gen = iter(generator_or_function) # type: ignore
+ except TypeError:
+
+ def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
+ gen = generator_or_function(*args, **kwargs) # type: ignore
+ return stream_with_context(gen)
+
+ return update_wrapper(decorator, generator_or_function) # type: ignore
+
+ def generator() -> t.Generator:
+ ctx = _cv_request.get(None)
+ if ctx is None:
+ raise RuntimeError(
+ "'stream_with_context' can only be used when a request"
+ " context is active, such as in a view function."
+ )
+ with ctx:
+ # Dummy sentinel. Has to be inside the context block or we're
+ # not actually keeping the context around.
+ yield None
+
+ # The try/finally is here so that if someone passes a WSGI level
+ # iterator in we're still running the cleanup logic. Generators
+ # don't need that because they are closed on their destruction
+ # automatically.
+ try:
+ yield from gen
+ finally:
+ if hasattr(gen, "close"):
+ gen.close()
+
+ # The trick is to start the generator. Then the code execution runs until
+ # the first dummy None is yielded at which point the context was already
+ # pushed. This item is discarded. Then when the iteration continues the
+ # real generator is executed.
+ wrapped_g = generator()
+ next(wrapped_g)
+ return wrapped_g
+
+
+def make_response(*args: t.Any) -> Response:
+ """Sometimes it is necessary to set additional headers in a view. Because
+ views do not have to return response objects but can return a value that
+ is converted into a response object by Flask itself, it becomes tricky to
+ add headers to it. This function can be called instead of using a return
+ and you will get a response object which you can use to attach headers.
+
+ If view looked like this and you want to add a new header::
+
+ def index():
+ return render_template('index.html', foo=42)
+
+ You can now do something like this::
+
+ def index():
+ response = make_response(render_template('index.html', foo=42))
+ response.headers['X-Parachutes'] = 'parachutes are cool'
+ return response
+
+ This function accepts the very same arguments you can return from a
+ view function. This for example creates a response with a 404 error
+ code::
+
+ response = make_response(render_template('not_found.html'), 404)
+
+ The other use case of this function is to force the return value of a
+ view function into a response which is helpful with view
+ decorators::
+
+ response = make_response(view_function())
+ response.headers['X-Parachutes'] = 'parachutes are cool'
+
+ Internally this function does the following things:
+
+ - if no arguments are passed, it creates a new response argument
+ - if one argument is passed, :meth:`flask.Flask.make_response`
+ is invoked with it.
+ - if more than one argument is passed, the arguments are passed
+ to the :meth:`flask.Flask.make_response` function as tuple.
+
+ .. versionadded:: 0.6
+ """
+ if not args:
+ return current_app.response_class()
+ if len(args) == 1:
+ args = args[0]
+ return current_app.make_response(args) # type: ignore
+
+
+def url_for(
+ endpoint: str,
+ *,
+ _anchor: str | None = None,
+ _method: str | None = None,
+ _scheme: str | None = None,
+ _external: bool | None = None,
+ **values: t.Any,
+) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This requires an active request or application context, and calls
+ :meth:`current_app.url_for() `. See that method
+ for full documentation.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it is
+ external.
+ :param _external: If given, prefer the URL to be internal (False) or
+ require it to be external (True). External URLs include the
+ scheme and domain. When not in an active request, URLs are
+ external by default.
+ :param values: Values to use for the variable parts of the URL rule.
+ Unknown keys are appended as query string arguments, like
+ ``?a=b&c=d``.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.url_for``, allowing an app to override the
+ behavior.
+
+ .. versionchanged:: 0.10
+ The ``_scheme`` parameter was added.
+
+ .. versionchanged:: 0.9
+ The ``_anchor`` and ``_method`` parameters were added.
+
+ .. versionchanged:: 0.9
+ Calls ``app.handle_url_build_error`` on build errors.
+ """
+ return current_app.url_for(
+ endpoint,
+ _anchor=_anchor,
+ _method=_method,
+ _scheme=_scheme,
+ _external=_external,
+ **values,
+ )
+
+
+def redirect(
+ location: str, code: int = 302, Response: type[BaseResponse] | None = None
+) -> BaseResponse:
+ """Create a redirect response object.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`~flask.Flask.redirect` method, otherwise it will use
+ :func:`werkzeug.utils.redirect`.
+
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+ :param Response: The response class to use. Not used when
+ ``current_app`` is active, which uses ``app.response_class``.
+
+ .. versionadded:: 2.2
+ Calls ``current_app.redirect`` if available instead of always
+ using Werkzeug's default ``redirect``.
+ """
+ if current_app:
+ return current_app.redirect(location, code=code)
+
+ return _wz_redirect(location, code=code, Response=Response)
+
+
+def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
+ """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
+ status code.
+
+ If :data:`~flask.current_app` is available, it will call its
+ :attr:`~flask.Flask.aborter` object, otherwise it will use
+ :func:`werkzeug.exceptions.abort`.
+
+ :param code: The status code for the exception, which must be
+ registered in ``app.aborter``.
+ :param args: Passed to the exception.
+ :param kwargs: Passed to the exception.
+
+ .. versionadded:: 2.2
+ Calls ``current_app.aborter`` if available instead of always
+ using Werkzeug's default ``abort``.
+ """
+ if current_app:
+ current_app.aborter(code, *args, **kwargs)
+
+ _wz_abort(code, *args, **kwargs)
+
+
+def get_template_attribute(template_name: str, attribute: str) -> t.Any:
+ """Loads a macro (or variable) a template exports. This can be used to
+ invoke a macro from within Python code. If you for example have a
+ template named :file:`_cider.html` with the following contents:
+
+ .. sourcecode:: html+jinja
+
+ {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
+
+ You can access this from Python code like this::
+
+ hello = get_template_attribute('_cider.html', 'hello')
+ return hello('World')
+
+ .. versionadded:: 0.2
+
+ :param template_name: the name of the template
+ :param attribute: the name of the variable of macro to access
+ """
+ return getattr(current_app.jinja_env.get_template(template_name).module, attribute)
+
+
+def flash(message: str, category: str = "message") -> None:
+ """Flashes a message to the next request. In order to remove the
+ flashed message from the session and to display it to the user,
+ the template has to call :func:`get_flashed_messages`.
+
+ .. versionchanged:: 0.3
+ `category` parameter added.
+
+ :param message: the message to be flashed.
+ :param category: the category for the message. The following values
+ are recommended: ``'message'`` for any kind of message,
+ ``'error'`` for errors, ``'info'`` for information
+ messages and ``'warning'`` for warnings. However any
+ kind of string can be used as category.
+ """
+ # Original implementation:
+ #
+ # session.setdefault('_flashes', []).append((category, message))
+ #
+ # This assumed that changes made to mutable structures in the session are
+ # always in sync with the session object, which is not true for session
+ # implementations that use external storage for keeping their keys/values.
+ flashes = session.get("_flashes", [])
+ flashes.append((category, message))
+ session["_flashes"] = flashes
+ app = current_app._get_current_object() # type: ignore
+ message_flashed.send(
+ app,
+ _async_wrapper=app.ensure_sync,
+ message=message,
+ category=category,
+ )
+
+
+def get_flashed_messages(
+ with_categories: bool = False, category_filter: t.Iterable[str] = ()
+) -> list[str] | list[tuple[str, str]]:
+ """Pulls all flashed messages from the session and returns them.
+ Further calls in the same request to the function will return
+ the same messages. By default just the messages are returned,
+ but when `with_categories` is set to ``True``, the return value will
+ be a list of tuples in the form ``(category, message)`` instead.
+
+ Filter the flashed messages to one or more categories by providing those
+ categories in `category_filter`. This allows rendering categories in
+ separate html blocks. The `with_categories` and `category_filter`
+ arguments are distinct:
+
+ * `with_categories` controls whether categories are returned with message
+ text (``True`` gives a tuple, where ``False`` gives just the message text).
+ * `category_filter` filters the messages down to only those matching the
+ provided categories.
+
+ See :doc:`/patterns/flashing` for examples.
+
+ .. versionchanged:: 0.3
+ `with_categories` parameter added.
+
+ .. versionchanged:: 0.9
+ `category_filter` parameter added.
+
+ :param with_categories: set to ``True`` to also receive categories.
+ :param category_filter: filter of categories to limit return values. Only
+ categories in the list will be returned.
+ """
+ flashes = request_ctx.flashes
+ if flashes is None:
+ flashes = session.pop("_flashes") if "_flashes" in session else []
+ request_ctx.flashes = flashes
+ if category_filter:
+ flashes = list(filter(lambda f: f[0] in category_filter, flashes))
+ if not with_categories:
+ return [x[1] for x in flashes]
+ return flashes
+
+
+def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]:
+ if kwargs.get("max_age") is None:
+ kwargs["max_age"] = current_app.get_send_file_max_age
+
+ kwargs.update(
+ environ=request.environ,
+ use_x_sendfile=current_app.config["USE_X_SENDFILE"],
+ response_class=current_app.response_class,
+ _root_path=current_app.root_path, # type: ignore
+ )
+ return kwargs
+
+
+def send_file(
+ path_or_file: os.PathLike | str | t.BinaryIO,
+ mimetype: str | None = None,
+ as_attachment: bool = False,
+ download_name: str | None = None,
+ conditional: bool = True,
+ etag: bool | str = True,
+ last_modified: datetime | int | float | None = None,
+ max_age: None | (int | t.Callable[[str | None], int | None]) = None,
+) -> Response:
+ """Send the contents of a file to the client.
+
+ The first argument can be a file path or a file-like object. Paths
+ are preferred in most cases because Werkzeug can manage the file and
+ get extra information from the path. Passing a file-like object
+ requires that the file is opened in binary mode, and is mostly
+ useful when building a file in memory with :class:`io.BytesIO`.
+
+ Never pass file paths provided by a user. The path is assumed to be
+ trusted, so a user could craft a path to access a file you didn't
+ intend. Use :func:`send_from_directory` to safely serve
+ user-requested paths from within a directory.
+
+ If the WSGI server sets a ``file_wrapper`` in ``environ``, it is
+ used, otherwise Werkzeug's built-in wrapper is used. Alternatively,
+ if the HTTP server supports ``X-Sendfile``, configuring Flask with
+ ``USE_X_SENDFILE = True`` will tell the server to send the given
+ path, which is much more efficient than reading it in Python.
+
+ :param path_or_file: The path to the file to send, relative to the
+ current working directory if a relative path is given.
+ Alternatively, a file-like object opened in binary mode. Make
+ sure the file pointer is seeked to the start of the data.
+ :param mimetype: The MIME type to send for the file. If not
+ provided, it will try to detect it from the file name.
+ :param as_attachment: Indicate to a browser that it should offer to
+ save the file instead of displaying it.
+ :param download_name: The default name browsers will use when saving
+ the file. Defaults to the passed file name.
+ :param conditional: Enable conditional and range responses based on
+ request headers. Requires passing a file path and ``environ``.
+ :param etag: Calculate an ETag for the file, which requires passing
+ a file path. Can also be a string to use instead.
+ :param last_modified: The last modified time to send for the file,
+ in seconds. If not provided, it will try to detect it from the
+ file path.
+ :param max_age: How long the client should cache the file, in
+ seconds. If set, ``Cache-Control`` will be ``public``, otherwise
+ it will be ``no-cache`` to prefer conditional caching.
+
+ .. versionchanged:: 2.0
+ ``download_name`` replaces the ``attachment_filename``
+ parameter. If ``as_attachment=False``, it is passed with
+ ``Content-Disposition: inline`` instead.
+
+ .. versionchanged:: 2.0
+ ``max_age`` replaces the ``cache_timeout`` parameter.
+ ``conditional`` is enabled and ``max_age`` is not set by
+ default.
+
+ .. versionchanged:: 2.0
+ ``etag`` replaces the ``add_etags`` parameter. It can be a
+ string to use instead of generating one.
+
+ .. versionchanged:: 2.0
+ Passing a file-like object that inherits from
+ :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather
+ than sending an empty file.
+
+ .. versionadded:: 2.0
+ Moved the implementation to Werkzeug. This is now a wrapper to
+ pass some Flask-specific arguments.
+
+ .. versionchanged:: 1.1
+ ``filename`` may be a :class:`~os.PathLike` object.
+
+ .. versionchanged:: 1.1
+ Passing a :class:`~io.BytesIO` object supports range requests.
+
+ .. versionchanged:: 1.0.3
+ Filenames are encoded with ASCII instead of Latin-1 for broader
+ compatibility with WSGI servers.
+
+ .. versionchanged:: 1.0
+ UTF-8 filenames as specified in :rfc:`2231` are supported.
+
+ .. versionchanged:: 0.12
+ The filename is no longer automatically inferred from file
+ objects. If you want to use automatic MIME and etag support,
+ pass a filename via ``filename_or_fp`` or
+ ``attachment_filename``.
+
+ .. versionchanged:: 0.12
+ ``attachment_filename`` is preferred over ``filename`` for MIME
+ detection.
+
+ .. versionchanged:: 0.9
+ ``cache_timeout`` defaults to
+ :meth:`Flask.get_send_file_max_age`.
+
+ .. versionchanged:: 0.7
+ MIME guessing and etag support for file-like objects was
+ deprecated because it was unreliable. Pass a filename if you are
+ able to, otherwise attach an etag yourself.
+
+ .. versionchanged:: 0.5
+ The ``add_etags``, ``cache_timeout`` and ``conditional``
+ parameters were added. The default behavior is to add etags.
+
+ .. versionadded:: 0.2
+ """
+ return werkzeug.utils.send_file( # type: ignore[return-value]
+ **_prepare_send_file_kwargs(
+ path_or_file=path_or_file,
+ environ=request.environ,
+ mimetype=mimetype,
+ as_attachment=as_attachment,
+ download_name=download_name,
+ conditional=conditional,
+ etag=etag,
+ last_modified=last_modified,
+ max_age=max_age,
+ )
+ )
+
+
+def send_from_directory(
+ directory: os.PathLike | str,
+ path: os.PathLike | str,
+ **kwargs: t.Any,
+) -> Response:
+ """Send a file from within a directory using :func:`send_file`.
+
+ .. code-block:: python
+
+ @app.route("/uploads/")
+ def download_file(name):
+ return send_from_directory(
+ app.config['UPLOAD_FOLDER'], name, as_attachment=True
+ )
+
+ This is a secure way to serve files from a folder, such as static
+ files or uploads. Uses :func:`~werkzeug.security.safe_join` to
+ ensure the path coming from the client is not maliciously crafted to
+ point outside the specified directory.
+
+ If the final path does not point to an existing regular file,
+ raises a 404 :exc:`~werkzeug.exceptions.NotFound` error.
+
+ :param directory: The directory that ``path`` must be located under,
+ relative to the current application's root path.
+ :param path: The path to the file to send, relative to
+ ``directory``.
+ :param kwargs: Arguments to pass to :func:`send_file`.
+
+ .. versionchanged:: 2.0
+ ``path`` replaces the ``filename`` parameter.
+
+ .. versionadded:: 2.0
+ Moved the implementation to Werkzeug. This is now a wrapper to
+ pass some Flask-specific arguments.
+
+ .. versionadded:: 0.5
+ """
+ return werkzeug.utils.send_from_directory( # type: ignore[return-value]
+ directory, path, **_prepare_send_file_kwargs(**kwargs)
+ )
+
+
+def get_root_path(import_name: str) -> str:
+ """Find the root path of a package, or the path that contains a
+ module. If it cannot be found, returns the current working
+ directory.
+
+ Not to be confused with the value returned by :func:`find_package`.
+
+ :meta private:
+ """
+ # Module already imported and has a file attribute. Use that first.
+ mod = sys.modules.get(import_name)
+
+ if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None:
+ return os.path.dirname(os.path.abspath(mod.__file__))
+
+ # Next attempt: check the loader.
+ try:
+ spec = importlib.util.find_spec(import_name)
+
+ if spec is None:
+ raise ValueError
+ except (ImportError, ValueError):
+ loader = None
+ else:
+ loader = spec.loader
+
+ # Loader does not exist or we're referring to an unloaded main
+ # module or a main module without path (interactive sessions), go
+ # with the current working directory.
+ if loader is None:
+ return os.getcwd()
+
+ if hasattr(loader, "get_filename"):
+ filepath = loader.get_filename(import_name)
+ else:
+ # Fall back to imports.
+ __import__(import_name)
+ mod = sys.modules[import_name]
+ filepath = getattr(mod, "__file__", None)
+
+ # If we don't have a file path it might be because it is a
+ # namespace package. In this case pick the root path from the
+ # first module that is contained in the package.
+ if filepath is None:
+ raise RuntimeError(
+ "No root path can be found for the provided module"
+ f" {import_name!r}. This can happen because the module"
+ " came from an import hook that does not provide file"
+ " name information or because it's a namespace package."
+ " In this case the root path needs to be explicitly"
+ " provided."
+ )
+
+ # filepath is import_name.py for a module, or __init__.py for a package.
+ return os.path.dirname(os.path.abspath(filepath))
+
+
+class locked_cached_property(werkzeug.utils.cached_property):
+ """A :func:`property` that is only evaluated once. Like
+ :class:`werkzeug.utils.cached_property` except access uses a lock
+ for thread safety.
+
+ .. deprecated:: 2.3
+ Will be removed in Flask 2.4. Use a lock inside the decorated function if
+ locking is needed.
+
+ .. versionchanged:: 2.0
+ Inherits from Werkzeug's ``cached_property`` (and ``property``).
+ """
+
+ def __init__(
+ self,
+ fget: t.Callable[[t.Any], t.Any],
+ name: str | None = None,
+ doc: str | None = None,
+ ) -> None:
+ import warnings
+
+ warnings.warn(
+ "'locked_cached_property' is deprecated and will be removed in Flask 2.4."
+ " Use a lock inside the decorated function if locking is needed.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ super().__init__(fget, name=name, doc=doc)
+ self.lock = RLock()
+
+ def __get__(self, obj: object, type: type = None) -> t.Any: # type: ignore
+ if obj is None:
+ return self
+
+ with self.lock:
+ return super().__get__(obj, type=type)
+
+ def __set__(self, obj: object, value: t.Any) -> None:
+ with self.lock:
+ super().__set__(obj, value)
+
+ def __delete__(self, obj: object) -> None:
+ with self.lock:
+ super().__delete__(obj)
+
+
+def is_ip(value: str) -> bool:
+ """Determine if the given string is an IP address.
+
+ :param value: value to check
+ :type value: str
+
+ :return: True if string is an IP address
+ :rtype: bool
+
+ .. deprecated:: 2.3
+ Will be removed in Flask 2.4.
+ """
+ warnings.warn(
+ "The 'is_ip' function is deprecated and will be removed in Flask 2.4.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ for family in (socket.AF_INET, socket.AF_INET6):
+ try:
+ socket.inet_pton(family, value)
+ except OSError:
+ pass
+ else:
+ return True
+
+ return False
+
+
+@lru_cache(maxsize=None)
+def _split_blueprint_path(name: str) -> list[str]:
+ out: list[str] = [name]
+
+ if "." in name:
+ out.extend(_split_blueprint_path(name.rpartition(".")[0]))
+
+ return out
diff --git a/lib/python3.12/site-packages/flask/json/__init__.py b/lib/python3.12/site-packages/flask/json/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f15296fed0fbf3aa4157429aed9266b803370e3d
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/json/__init__.py
@@ -0,0 +1,170 @@
+from __future__ import annotations
+
+import json as _json
+import typing as t
+
+from ..globals import current_app
+from .provider import _default
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from ..wrappers import Response
+
+
+def dumps(obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dumps() `
+ method, otherwise it will use :func:`json.dumps`.
+
+ :param obj: The data to serialize.
+ :param kwargs: Arguments passed to the ``dumps`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dumps``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0.2
+ :class:`decimal.Decimal` is supported by converting to a string.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1.
+
+ .. versionchanged:: 1.0.3
+ ``app`` can be passed directly, rather than requiring an app
+ context for configuration.
+ """
+ if current_app:
+ return current_app.json.dumps(obj, **kwargs)
+
+ kwargs.setdefault("default", _default)
+ return _json.dumps(obj, **kwargs)
+
+
+def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize data as JSON and write to a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dump() `
+ method, otherwise it will use :func:`json.dump`.
+
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: Arguments passed to the ``dump`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dump``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0
+ Writing to a binary file, and the ``encoding`` argument, will be
+ removed in Flask 2.1.
+ """
+ if current_app:
+ current_app.json.dump(obj, fp, **kwargs)
+ else:
+ kwargs.setdefault("default", _default)
+ _json.dump(obj, fp, **kwargs)
+
+
+def loads(s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.loads() `
+ method, otherwise it will use :func:`json.loads`.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``loads`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.loads``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1. The data must be a
+ string or UTF-8 bytes.
+
+ .. versionchanged:: 1.0.3
+ ``app`` can be passed directly, rather than requiring an app
+ context for configuration.
+ """
+ if current_app:
+ return current_app.json.loads(s, **kwargs)
+
+ return _json.loads(s, **kwargs)
+
+
+def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.load() `
+ method, otherwise it will use :func:`json.load`.
+
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``load`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.load``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1. The file must be text
+ mode, or binary mode with UTF-8 bytes.
+ """
+ if current_app:
+ return current_app.json.load(fp, **kwargs)
+
+ return _json.load(fp, **kwargs)
+
+
+def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype. A dict or list returned from a view will be converted to a
+ JSON response automatically without needing to call this.
+
+ This requires an active request or application context, and calls
+ :meth:`app.json.response() `.
+
+ In debug mode, the output is formatted with indentation to make it
+ easier to read. This may also be controlled by the provider.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.response``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0.2
+ :class:`decimal.Decimal` is supported by converting to a string.
+
+ .. versionchanged:: 0.11
+ Added support for serializing top-level arrays. This was a
+ security risk in ancient browsers. See :ref:`security-json`.
+
+ .. versionadded:: 0.2
+ """
+ return current_app.json.response(*args, **kwargs)
diff --git a/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..674da725ed7faa33d17d649349050f71ee6f4556
Binary files /dev/null and b/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc b/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c5863420195b1178d4983fc27287cc4b44947330
Binary files /dev/null and b/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..91ccf04a9c039dc32833491bd2731d4a69fad55f
Binary files /dev/null and b/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/flask/json/provider.py b/lib/python3.12/site-packages/flask/json/provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..0edd3d58d7d9ca85e5fc216b24da0eacc16d47ec
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/json/provider.py
@@ -0,0 +1,216 @@
+from __future__ import annotations
+
+import dataclasses
+import decimal
+import json
+import typing as t
+import uuid
+import weakref
+from datetime import date
+
+from werkzeug.http import http_date
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from ..app import Flask
+ from ..wrappers import Response
+
+
+class JSONProvider:
+ """A standard set of JSON operations for an application. Subclasses
+ of this can be used to customize JSON behavior or use different
+ JSON libraries.
+
+ To implement a provider for a specific library, subclass this base
+ class and implement at least :meth:`dumps` and :meth:`loads`. All
+ other methods have default implementations.
+
+ To use a different provider, either subclass ``Flask`` and set
+ :attr:`~flask.Flask.json_provider_class` to a provider class, or set
+ :attr:`app.json ` to an instance of the class.
+
+ :param app: An application instance. This will be stored as a
+ :class:`weakref.proxy` on the :attr:`_app` attribute.
+
+ .. versionadded:: 2.2
+ """
+
+ def __init__(self, app: Flask) -> None:
+ self._app = weakref.proxy(app)
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+
+ :param obj: The data to serialize.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize data as JSON and write to a file.
+
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ fp.write(self.dumps(obj, **kwargs))
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ return self.loads(fp.read(), **kwargs)
+
+ def _prepare_response_obj(
+ self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
+ ) -> t.Any:
+ if args and kwargs:
+ raise TypeError("app.json.response() takes either args or kwargs, not both")
+
+ if not args and not kwargs:
+ return None
+
+ if len(args) == 1:
+ return args[0]
+
+ return args or kwargs
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype.
+
+ The :func:`~flask.json.jsonify` function calls this method for
+ the current application.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ return self._app.response_class(self.dumps(obj), mimetype="application/json")
+
+
+def _default(o: t.Any) -> t.Any:
+ if isinstance(o, date):
+ return http_date(o)
+
+ if isinstance(o, (decimal.Decimal, uuid.UUID)):
+ return str(o)
+
+ if dataclasses and dataclasses.is_dataclass(o):
+ return dataclasses.asdict(o)
+
+ if hasattr(o, "__html__"):
+ return str(o.__html__())
+
+ raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
+
+
+class DefaultJSONProvider(JSONProvider):
+ """Provide JSON operations using Python's built-in :mod:`json`
+ library. Serializes the following additional data types:
+
+ - :class:`datetime.datetime` and :class:`datetime.date` are
+ serialized to :rfc:`822` strings. This is the same as the HTTP
+ date format.
+ - :class:`uuid.UUID` is serialized to a string.
+ - :class:`dataclasses.dataclass` is passed to
+ :func:`dataclasses.asdict`.
+ - :class:`~markupsafe.Markup` (or any object with a ``__html__``
+ method) will call the ``__html__`` method to get a string.
+ """
+
+ default: t.Callable[[t.Any], t.Any] = staticmethod(
+ _default
+ ) # type: ignore[assignment]
+ """Apply this function to any object that :meth:`json.dumps` does
+ not know how to serialize. It should return a valid JSON type or
+ raise a ``TypeError``.
+ """
+
+ ensure_ascii = True
+ """Replace non-ASCII characters with escape sequences. This may be
+ more compatible with some clients, but can be disabled for better
+ performance and size.
+ """
+
+ sort_keys = True
+ """Sort the keys in any serialized dicts. This may be useful for
+ some caching situations, but can be disabled for better performance.
+ When enabled, keys must all be strings, they are not converted
+ before sorting.
+ """
+
+ compact: bool | None = None
+ """If ``True``, or ``None`` out of debug mode, the :meth:`response`
+ output will not add indentation, newlines, or spaces. If ``False``,
+ or ``None`` in debug mode, it will use a non-compact representation.
+ """
+
+ mimetype = "application/json"
+ """The mimetype set in :meth:`response`."""
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON to a string.
+
+ Keyword arguments are passed to :func:`json.dumps`. Sets some
+ parameter defaults from the :attr:`default`,
+ :attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
+
+ :param obj: The data to serialize.
+ :param kwargs: Passed to :func:`json.dumps`.
+ """
+ kwargs.setdefault("default", self.default)
+ kwargs.setdefault("ensure_ascii", self.ensure_ascii)
+ kwargs.setdefault("sort_keys", self.sort_keys)
+ return json.dumps(obj, **kwargs)
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON from a string or bytes.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: Passed to :func:`json.loads`.
+ """
+ return json.loads(s, **kwargs)
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with it. The response mimetype
+ will be "application/json" and can be changed with
+ :attr:`mimetype`.
+
+ If :attr:`compact` is ``False`` or debug mode is enabled, the
+ output will be formatted to be easier to read.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ dump_args: dict[str, t.Any] = {}
+
+ if (self.compact is None and self._app.debug) or self.compact is False:
+ dump_args.setdefault("indent", 2)
+ else:
+ dump_args.setdefault("separators", (",", ":"))
+
+ return self._app.response_class(
+ f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
+ )
diff --git a/lib/python3.12/site-packages/flask/json/tag.py b/lib/python3.12/site-packages/flask/json/tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..91cc4412d69b22bedc260d3cf598d135d706ed8f
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/json/tag.py
@@ -0,0 +1,314 @@
+"""
+Tagged JSON
+~~~~~~~~~~~
+
+A compact representation for lossless serialization of non-standard JSON
+types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this
+to serialize the session data, but it may be useful in other places. It
+can be extended to support other types.
+
+.. autoclass:: TaggedJSONSerializer
+ :members:
+
+.. autoclass:: JSONTag
+ :members:
+
+Let's see an example that adds support for
+:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so
+to handle this we will dump the items as a list of ``[key, value]``
+pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to
+identify the type. The session serializer processes dicts first, so
+insert the new tag at the front of the order since ``OrderedDict`` must
+be processed before ``dict``.
+
+.. code-block:: python
+
+ from flask.json.tag import JSONTag
+
+ class TagOrderedDict(JSONTag):
+ __slots__ = ('serializer',)
+ key = ' od'
+
+ def check(self, value):
+ return isinstance(value, OrderedDict)
+
+ def to_json(self, value):
+ return [[k, self.serializer.tag(v)] for k, v in iteritems(value)]
+
+ def to_python(self, value):
+ return OrderedDict(value)
+
+ app.session_interface.serializer.register(TagOrderedDict, index=0)
+"""
+from __future__ import annotations
+
+import typing as t
+from base64 import b64decode
+from base64 import b64encode
+from datetime import datetime
+from uuid import UUID
+
+from markupsafe import Markup
+from werkzeug.http import http_date
+from werkzeug.http import parse_date
+
+from ..json import dumps
+from ..json import loads
+
+
+class JSONTag:
+ """Base class for defining type tags for :class:`TaggedJSONSerializer`."""
+
+ __slots__ = ("serializer",)
+
+ #: The tag to mark the serialized object with. If ``None``, this tag is
+ #: only used as an intermediate step during tagging.
+ key: str | None = None
+
+ def __init__(self, serializer: TaggedJSONSerializer) -> None:
+ """Create a tagger for the given serializer."""
+ self.serializer = serializer
+
+ def check(self, value: t.Any) -> bool:
+ """Check if the given value should be tagged by this tag."""
+ raise NotImplementedError
+
+ def to_json(self, value: t.Any) -> t.Any:
+ """Convert the Python object to an object that is a valid JSON type.
+ The tag will be added later."""
+ raise NotImplementedError
+
+ def to_python(self, value: t.Any) -> t.Any:
+ """Convert the JSON representation back to the correct type. The tag
+ will already be removed."""
+ raise NotImplementedError
+
+ def tag(self, value: t.Any) -> t.Any:
+ """Convert the value to a valid JSON type and add the tag structure
+ around it."""
+ return {self.key: self.to_json(value)}
+
+
+class TagDict(JSONTag):
+ """Tag for 1-item dicts whose only key matches a registered tag.
+
+ Internally, the dict key is suffixed with `__`, and the suffix is removed
+ when deserializing.
+ """
+
+ __slots__ = ()
+ key = " di"
+
+ def check(self, value: t.Any) -> bool:
+ return (
+ isinstance(value, dict)
+ and len(value) == 1
+ and next(iter(value)) in self.serializer.tags
+ )
+
+ def to_json(self, value: t.Any) -> t.Any:
+ key = next(iter(value))
+ return {f"{key}__": self.serializer.tag(value[key])}
+
+ def to_python(self, value: t.Any) -> t.Any:
+ key = next(iter(value))
+ return {key[:-2]: value[key]}
+
+
+class PassDict(JSONTag):
+ __slots__ = ()
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, dict)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ # JSON objects may only have string keys, so don't bother tagging the
+ # key here.
+ return {k: self.serializer.tag(v) for k, v in value.items()}
+
+ tag = to_json
+
+
+class TagTuple(JSONTag):
+ __slots__ = ()
+ key = " t"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, tuple)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return [self.serializer.tag(item) for item in value]
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return tuple(value)
+
+
+class PassList(JSONTag):
+ __slots__ = ()
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, list)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return [self.serializer.tag(item) for item in value]
+
+ tag = to_json
+
+
+class TagBytes(JSONTag):
+ __slots__ = ()
+ key = " b"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, bytes)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return b64encode(value).decode("ascii")
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return b64decode(value)
+
+
+class TagMarkup(JSONTag):
+ """Serialize anything matching the :class:`~markupsafe.Markup` API by
+ having a ``__html__`` method to the result of that method. Always
+ deserializes to an instance of :class:`~markupsafe.Markup`."""
+
+ __slots__ = ()
+ key = " m"
+
+ def check(self, value: t.Any) -> bool:
+ return callable(getattr(value, "__html__", None))
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return str(value.__html__())
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return Markup(value)
+
+
+class TagUUID(JSONTag):
+ __slots__ = ()
+ key = " u"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, UUID)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return value.hex
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return UUID(value)
+
+
+class TagDateTime(JSONTag):
+ __slots__ = ()
+ key = " d"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, datetime)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return http_date(value)
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return parse_date(value)
+
+
+class TaggedJSONSerializer:
+ """Serializer that uses a tag system to compactly represent objects that
+ are not JSON types. Passed as the intermediate serializer to
+ :class:`itsdangerous.Serializer`.
+
+ The following extra types are supported:
+
+ * :class:`dict`
+ * :class:`tuple`
+ * :class:`bytes`
+ * :class:`~markupsafe.Markup`
+ * :class:`~uuid.UUID`
+ * :class:`~datetime.datetime`
+ """
+
+ __slots__ = ("tags", "order")
+
+ #: Tag classes to bind when creating the serializer. Other tags can be
+ #: added later using :meth:`~register`.
+ default_tags = [
+ TagDict,
+ PassDict,
+ TagTuple,
+ PassList,
+ TagBytes,
+ TagMarkup,
+ TagUUID,
+ TagDateTime,
+ ]
+
+ def __init__(self) -> None:
+ self.tags: dict[str, JSONTag] = {}
+ self.order: list[JSONTag] = []
+
+ for cls in self.default_tags:
+ self.register(cls)
+
+ def register(
+ self,
+ tag_class: type[JSONTag],
+ force: bool = False,
+ index: int | None = None,
+ ) -> None:
+ """Register a new tag with this serializer.
+
+ :param tag_class: tag class to register. Will be instantiated with this
+ serializer instance.
+ :param force: overwrite an existing tag. If false (default), a
+ :exc:`KeyError` is raised.
+ :param index: index to insert the new tag in the tag order. Useful when
+ the new tag is a special case of an existing tag. If ``None``
+ (default), the tag is appended to the end of the order.
+
+ :raise KeyError: if the tag key is already registered and ``force`` is
+ not true.
+ """
+ tag = tag_class(self)
+ key = tag.key
+
+ if key is not None:
+ if not force and key in self.tags:
+ raise KeyError(f"Tag '{key}' is already registered.")
+
+ self.tags[key] = tag
+
+ if index is None:
+ self.order.append(tag)
+ else:
+ self.order.insert(index, tag)
+
+ def tag(self, value: t.Any) -> dict[str, t.Any]:
+ """Convert a value to a tagged representation if necessary."""
+ for tag in self.order:
+ if tag.check(value):
+ return tag.tag(value)
+
+ return value
+
+ def untag(self, value: dict[str, t.Any]) -> t.Any:
+ """Convert a tagged representation back to the original type."""
+ if len(value) != 1:
+ return value
+
+ key = next(iter(value))
+
+ if key not in self.tags:
+ return value
+
+ return self.tags[key].to_python(value[key])
+
+ def dumps(self, value: t.Any) -> str:
+ """Tag the value and dump it to a compact JSON string."""
+ return dumps(self.tag(value), separators=(",", ":"))
+
+ def loads(self, value: str) -> t.Any:
+ """Load data from a JSON string and deserialized any tagged objects."""
+ return loads(value, object_hook=self.untag)
diff --git a/lib/python3.12/site-packages/flask/logging.py b/lib/python3.12/site-packages/flask/logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..99f6be85be8cf576729086d47e79d44aec53d6f5
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/logging.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+import logging
+import sys
+import typing as t
+
+from werkzeug.local import LocalProxy
+
+from .globals import request
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+
+
+@LocalProxy
+def wsgi_errors_stream() -> t.TextIO:
+ """Find the most appropriate error stream for the application. If a request
+ is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``.
+
+ If you configure your own :class:`logging.StreamHandler`, you may want to
+ use this for the stream. If you are using file or dict configuration and
+ can't import this directly, you can refer to it as
+ ``ext://flask.logging.wsgi_errors_stream``.
+ """
+ return request.environ["wsgi.errors"] if request else sys.stderr
+
+
+def has_level_handler(logger: logging.Logger) -> bool:
+ """Check if there is a handler in the logging chain that will handle the
+ given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`.
+ """
+ level = logger.getEffectiveLevel()
+ current = logger
+
+ while current:
+ if any(handler.level <= level for handler in current.handlers):
+ return True
+
+ if not current.propagate:
+ break
+
+ current = current.parent # type: ignore
+
+ return False
+
+
+#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format
+#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``.
+default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore
+default_handler.setFormatter(
+ logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s")
+)
+
+
+def create_logger(app: Flask) -> logging.Logger:
+ """Get the Flask app's logger and configure it if needed.
+
+ The logger name will be the same as
+ :attr:`app.import_name `.
+
+ When :attr:`~flask.Flask.debug` is enabled, set the logger level to
+ :data:`logging.DEBUG` if it is not set.
+
+ If there is no handler for the logger's effective level, add a
+ :class:`~logging.StreamHandler` for
+ :func:`~flask.logging.wsgi_errors_stream` with a basic format.
+ """
+ logger = logging.getLogger(app.name)
+
+ if app.debug and not logger.level:
+ logger.setLevel(logging.DEBUG)
+
+ if not has_level_handler(logger):
+ logger.addHandler(default_handler)
+
+ return logger
diff --git a/lib/python3.12/site-packages/flask/py.typed b/lib/python3.12/site-packages/flask/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/flask/scaffold.py b/lib/python3.12/site-packages/flask/scaffold.py
new file mode 100644
index 0000000000000000000000000000000000000000..d15b87338d7da90229dc6b9bdc5b684d71b5fb1b
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/scaffold.py
@@ -0,0 +1,873 @@
+from __future__ import annotations
+
+import importlib.util
+import os
+import pathlib
+import sys
+import typing as t
+from collections import defaultdict
+from datetime import timedelta
+from functools import update_wrapper
+
+from jinja2 import FileSystemLoader
+from werkzeug.exceptions import default_exceptions
+from werkzeug.exceptions import HTTPException
+from werkzeug.utils import cached_property
+
+from . import typing as ft
+from .cli import AppGroup
+from .globals import current_app
+from .helpers import get_root_path
+from .helpers import send_from_directory
+from .templating import _default_template_ctx_processor
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .wrappers import Response
+
+# a singleton sentinel value for parameter defaults
+_sentinel = object()
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
+T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
+T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
+T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
+
+
+def setupmethod(f: F) -> F:
+ f_name = f.__name__
+
+ def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
+ self._check_setup_finished(f_name)
+ return f(self, *args, **kwargs)
+
+ return t.cast(F, update_wrapper(wrapper_func, f))
+
+
+class Scaffold:
+ """Common behavior shared between :class:`~flask.Flask` and
+ :class:`~flask.blueprints.Blueprint`.
+
+ :param import_name: The import name of the module where this object
+ is defined. Usually :attr:`__name__` should be used.
+ :param static_folder: Path to a folder of static files to serve.
+ If this is set, a static route will be added.
+ :param static_url_path: URL prefix for the static route.
+ :param template_folder: Path to a folder containing template files.
+ for rendering. If this is set, a Jinja loader will be added.
+ :param root_path: The path that static, template, and resource files
+ are relative to. Typically not set, it is discovered based on
+ the ``import_name``.
+
+ .. versionadded:: 2.0
+ """
+
+ name: str
+ _static_folder: str | None = None
+ _static_url_path: str | None = None
+
+ def __init__(
+ self,
+ import_name: str,
+ static_folder: str | os.PathLike | None = None,
+ static_url_path: str | None = None,
+ template_folder: str | os.PathLike | None = None,
+ root_path: str | None = None,
+ ):
+ #: The name of the package or module that this object belongs
+ #: to. Do not change this once it is set by the constructor.
+ self.import_name = import_name
+
+ self.static_folder = static_folder # type: ignore
+ self.static_url_path = static_url_path
+
+ #: The path to the templates folder, relative to
+ #: :attr:`root_path`, to add to the template loader. ``None`` if
+ #: templates should not be added.
+ self.template_folder = template_folder
+
+ if root_path is None:
+ root_path = get_root_path(self.import_name)
+
+ #: Absolute path to the package on the filesystem. Used to look
+ #: up resources contained in the package.
+ self.root_path = root_path
+
+ #: The Click command group for registering CLI commands for this
+ #: object. The commands are available from the ``flask`` command
+ #: once the application has been discovered and blueprints have
+ #: been registered.
+ self.cli = AppGroup()
+
+ #: A dictionary mapping endpoint names to view functions.
+ #:
+ #: To register a view function, use the :meth:`route` decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.view_functions: dict[str, t.Callable] = {}
+
+ #: A data structure of registered error handlers, in the format
+ #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
+ #: the name of a blueprint the handlers are active for, or
+ #: ``None`` for all requests. The ``code`` key is the HTTP
+ #: status code for ``HTTPException``, or ``None`` for
+ #: other exceptions. The innermost dictionary maps exception
+ #: classes to handler functions.
+ #:
+ #: To register an error handler, use the :meth:`errorhandler`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.error_handler_spec: dict[
+ ft.AppOrBlueprintKey,
+ dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
+ ] = defaultdict(lambda: defaultdict(dict))
+
+ #: A data structure of functions to call at the beginning of
+ #: each request, in the format ``{scope: [functions]}``. The
+ #: ``scope`` key is the name of a blueprint the functions are
+ #: active for, or ``None`` for all requests.
+ #:
+ #: To register a function, use the :meth:`before_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.before_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call at the end of each
+ #: request, in the format ``{scope: [functions]}``. The
+ #: ``scope`` key is the name of a blueprint the functions are
+ #: active for, or ``None`` for all requests.
+ #:
+ #: To register a function, use the :meth:`after_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.after_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.AfterRequestCallable]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call at the end of each
+ #: request even if an exception is raised, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`teardown_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.teardown_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.TeardownCallable]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call to pass extra context
+ #: values when rendering templates, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`context_processor`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.template_context_processors: dict[
+ ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]
+ ] = defaultdict(list, {None: [_default_template_ctx_processor]})
+
+ #: A data structure of functions to call to modify the keyword
+ #: arguments passed to the view function, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the
+ #: :meth:`url_value_preprocessor` decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.url_value_preprocessors: dict[
+ ft.AppOrBlueprintKey,
+ list[ft.URLValuePreprocessorCallable],
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call to modify the keyword
+ #: arguments when generating URLs, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`url_defaults`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.url_default_functions: dict[
+ ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
+ ] = defaultdict(list)
+
+ def __repr__(self) -> str:
+ return f"<{type(self).__name__} {self.name!r}>"
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ raise NotImplementedError
+
+ @property
+ def static_folder(self) -> str | None:
+ """The absolute path to the configured static folder. ``None``
+ if no static folder is set.
+ """
+ if self._static_folder is not None:
+ return os.path.join(self.root_path, self._static_folder)
+ else:
+ return None
+
+ @static_folder.setter
+ def static_folder(self, value: str | os.PathLike | None) -> None:
+ if value is not None:
+ value = os.fspath(value).rstrip(r"\/")
+
+ self._static_folder = value
+
+ @property
+ def has_static_folder(self) -> bool:
+ """``True`` if :attr:`static_folder` is set.
+
+ .. versionadded:: 0.5
+ """
+ return self.static_folder is not None
+
+ @property
+ def static_url_path(self) -> str | None:
+ """The URL prefix that the static route will be accessible from.
+
+ If it was not configured during init, it is derived from
+ :attr:`static_folder`.
+ """
+ if self._static_url_path is not None:
+ return self._static_url_path
+
+ if self.static_folder is not None:
+ basename = os.path.basename(self.static_folder)
+ return f"/{basename}".rstrip("/")
+
+ return None
+
+ @static_url_path.setter
+ def static_url_path(self, value: str | None) -> None:
+ if value is not None:
+ value = value.rstrip("/")
+
+ self._static_url_path = value
+
+ def get_send_file_max_age(self, filename: str | None) -> int | None:
+ """Used by :func:`send_file` to determine the ``max_age`` cache
+ value for a given file path if it wasn't passed.
+
+ By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
+ the configuration of :data:`~flask.current_app`. This defaults
+ to ``None``, which tells the browser to use conditional requests
+ instead of a timed cache, which is usually preferable.
+
+ .. versionchanged:: 2.0
+ The default configuration is ``None`` instead of 12 hours.
+
+ .. versionadded:: 0.9
+ """
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
+
+ if value is None:
+ return None
+
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
+
+ return value
+
+ def send_static_file(self, filename: str) -> Response:
+ """The view function used to serve files from
+ :attr:`static_folder`. A route is automatically registered for
+ this view at :attr:`static_url_path` if :attr:`static_folder` is
+ set.
+
+ .. versionadded:: 0.5
+ """
+ if not self.has_static_folder:
+ raise RuntimeError("'static_folder' must be set to serve static_files.")
+
+ # send_file only knows to call get_send_file_max_age on the app,
+ # call it here so it works for blueprints too.
+ max_age = self.get_send_file_max_age(filename)
+ return send_from_directory(
+ t.cast(str, self.static_folder), filename, max_age=max_age
+ )
+
+ @cached_property
+ def jinja_loader(self) -> FileSystemLoader | None:
+ """The Jinja loader for this object's templates. By default this
+ is a class :class:`jinja2.loaders.FileSystemLoader` to
+ :attr:`template_folder` if it is set.
+
+ .. versionadded:: 0.5
+ """
+ if self.template_folder is not None:
+ return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
+ else:
+ return None
+
+ def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Open a resource file relative to :attr:`root_path` for
+ reading.
+
+ For example, if the file ``schema.sql`` is next to the file
+ ``app.py`` where the ``Flask`` app is defined, it can be opened
+ with:
+
+ .. code-block:: python
+
+ with app.open_resource("schema.sql") as f:
+ conn.executescript(f.read())
+
+ :param resource: Path to the resource relative to
+ :attr:`root_path`.
+ :param mode: Open the file in this mode. Only reading is
+ supported, valid values are "r" (or "rt") and "rb".
+ """
+ if mode not in {"r", "rt", "rb"}:
+ raise ValueError("Resources can only be opened for reading.")
+
+ return open(os.path.join(self.root_path, resource), mode)
+
+ def _method_route(
+ self,
+ method: str,
+ rule: str,
+ options: dict,
+ ) -> t.Callable[[T_route], T_route]:
+ if "methods" in options:
+ raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
+
+ return self.route(rule, methods=[method], **options)
+
+ @setupmethod
+ def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["GET"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("GET", rule, options)
+
+ @setupmethod
+ def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["POST"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("POST", rule, options)
+
+ @setupmethod
+ def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["PUT"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("PUT", rule, options)
+
+ @setupmethod
+ def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("DELETE", rule, options)
+
+ @setupmethod
+ def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("PATCH", rule, options)
+
+ @setupmethod
+ def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Decorate a view function to register it with the given URL
+ rule and options. Calls :meth:`add_url_rule`, which has more
+ details about the implementation.
+
+ .. code-block:: python
+
+ @app.route("/")
+ def index():
+ return "Hello, World!"
+
+ See :ref:`url-route-registrations`.
+
+ The endpoint name for the route defaults to the name of the view
+ function if the ``endpoint`` parameter isn't passed.
+
+ The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
+ ``OPTIONS`` are added automatically.
+
+ :param rule: The URL rule string.
+ :param options: Extra options passed to the
+ :class:`~werkzeug.routing.Rule` object.
+ """
+
+ def decorator(f: T_route) -> T_route:
+ endpoint = options.pop("endpoint", None)
+ self.add_url_rule(rule, endpoint, f, **options)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ """Register a rule for routing incoming requests and building
+ URLs. The :meth:`route` decorator is a shortcut to call this
+ with the ``view_func`` argument. These are equivalent:
+
+ .. code-block:: python
+
+ @app.route("/")
+ def index():
+ ...
+
+ .. code-block:: python
+
+ def index():
+ ...
+
+ app.add_url_rule("/", view_func=index)
+
+ See :ref:`url-route-registrations`.
+
+ The endpoint name for the route defaults to the name of the view
+ function if the ``endpoint`` parameter isn't passed. An error
+ will be raised if a function has already been registered for the
+ endpoint.
+
+ The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
+ always added automatically, and ``OPTIONS`` is added
+ automatically by default.
+
+ ``view_func`` does not necessarily need to be passed, but if the
+ rule should participate in routing an endpoint name must be
+ associated with a view function at some point with the
+ :meth:`endpoint` decorator.
+
+ .. code-block:: python
+
+ app.add_url_rule("/", endpoint="index")
+
+ @app.endpoint("index")
+ def index():
+ ...
+
+ If ``view_func`` has a ``required_methods`` attribute, those
+ methods are added to the passed and automatic methods. If it
+ has a ``provide_automatic_methods`` attribute, it is used as the
+ default if the parameter is not passed.
+
+ :param rule: The URL rule string.
+ :param endpoint: The endpoint name to associate with the rule
+ and view function. Used when routing and building URLs.
+ Defaults to ``view_func.__name__``.
+ :param view_func: The view function to associate with the
+ endpoint name.
+ :param provide_automatic_options: Add the ``OPTIONS`` method and
+ respond to ``OPTIONS`` requests automatically.
+ :param options: Extra options passed to the
+ :class:`~werkzeug.routing.Rule` object.
+ """
+ raise NotImplementedError
+
+ @setupmethod
+ def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
+ """Decorate a view function to register it for the given
+ endpoint. Used if a rule is added without a ``view_func`` with
+ :meth:`add_url_rule`.
+
+ .. code-block:: python
+
+ app.add_url_rule("/ex", endpoint="example")
+
+ @app.endpoint("example")
+ def example():
+ ...
+
+ :param endpoint: The endpoint name to associate with the view
+ function.
+ """
+
+ def decorator(f: F) -> F:
+ self.view_functions[endpoint] = f
+ return f
+
+ return decorator
+
+ @setupmethod
+ def before_request(self, f: T_before_request) -> T_before_request:
+ """Register a function to run before each request.
+
+ For example, this can be used to open a database connection, or
+ to load the logged in user from the session.
+
+ .. code-block:: python
+
+ @app.before_request
+ def load_user():
+ if "user_id" in session:
+ g.user = db.session.get(session["user_id"])
+
+ The function will be called without any arguments. If it returns
+ a non-``None`` value, the value is handled as if it was the
+ return value from the view, and further request handling is
+ stopped.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes before every request. When used on a blueprint, this executes before
+ every request that the blueprint handles. To register with a blueprint and
+ execute before every request, use :meth:`.Blueprint.before_app_request`.
+ """
+ self.before_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def after_request(self, f: T_after_request) -> T_after_request:
+ """Register a function to run after each request to this object.
+
+ The function is called with the response object, and must return
+ a response object. This allows the functions to modify or
+ replace the response before it is sent.
+
+ If a function raises an exception, any remaining
+ ``after_request`` functions will not be called. Therefore, this
+ should not be used for actions that must execute, such as to
+ close resources. Use :meth:`teardown_request` for that.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.after_app_request`.
+ """
+ self.after_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def teardown_request(self, f: T_teardown) -> T_teardown:
+ """Register a function to be called when the request context is
+ popped. Typically this happens at the end of each request, but
+ contexts may be pushed manually as well during testing.
+
+ .. code-block:: python
+
+ with app.test_request_context():
+ ...
+
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the request context is
+ made inactive.
+
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
+
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
+
+ The return values of teardown functions are ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.teardown_app_request`.
+ """
+ self.teardown_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def context_processor(
+ self,
+ f: T_template_context_processor,
+ ) -> T_template_context_processor:
+ """Registers a template context processor function. These functions run before
+ rendering a template. The keys of the returned dict are added as variables
+ available in the template.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every rendered template. When used on a blueprint, this is called
+ for templates rendered from the blueprint's views. To register with a blueprint
+ and affect every template, use :meth:`.Blueprint.app_context_processor`.
+ """
+ self.template_context_processors[None].append(f)
+ return f
+
+ @setupmethod
+ def url_value_preprocessor(
+ self,
+ f: T_url_value_preprocessor,
+ ) -> T_url_value_preprocessor:
+ """Register a URL value preprocessor function for all view
+ functions in the application. These functions will be called before the
+ :meth:`before_request` functions.
+
+ The function can modify the values captured from the matched url before
+ they are passed to the view. For example, this can be used to pop a
+ common language code value and place it in ``g`` rather than pass it to
+ every view.
+
+ The function is passed the endpoint name and values dict. The return
+ value is ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
+ """
+ self.url_value_preprocessors[None].append(f)
+ return f
+
+ @setupmethod
+ def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+ """Callback function for URL defaults for all view functions of the
+ application. It's called with the endpoint and values and should
+ update the values passed in place.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_defaults`.
+ """
+ self.url_default_functions[None].append(f)
+ return f
+
+ @setupmethod
+ def errorhandler(
+ self, code_or_exception: type[Exception] | int
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
+ """Register a function to handle errors by code or exception class.
+
+ A decorator that is used to register a function given an
+ error code. Example::
+
+ @app.errorhandler(404)
+ def page_not_found(error):
+ return 'This page does not exist', 404
+
+ You can also register handlers for arbitrary exceptions::
+
+ @app.errorhandler(DatabaseError)
+ def special_exception_handler(error):
+ return 'Database connection failed', 500
+
+ This is available on both app and blueprint objects. When used on an app, this
+ can handle errors from every request. When used on a blueprint, this can handle
+ errors from requests that the blueprint handles. To register with a blueprint
+ and affect every request, use :meth:`.Blueprint.app_errorhandler`.
+
+ .. versionadded:: 0.7
+ Use :meth:`register_error_handler` instead of modifying
+ :attr:`error_handler_spec` directly, for application wide error
+ handlers.
+
+ .. versionadded:: 0.7
+ One can now additionally also register custom exception types
+ that do not necessarily have to be a subclass of the
+ :class:`~werkzeug.exceptions.HTTPException` class.
+
+ :param code_or_exception: the code as integer for the handler, or
+ an arbitrary exception
+ """
+
+ def decorator(f: T_error_handler) -> T_error_handler:
+ self.register_error_handler(code_or_exception, f)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def register_error_handler(
+ self,
+ code_or_exception: type[Exception] | int,
+ f: ft.ErrorHandlerCallable,
+ ) -> None:
+ """Alternative error attach function to the :meth:`errorhandler`
+ decorator that is more straightforward to use for non decorator
+ usage.
+
+ .. versionadded:: 0.7
+ """
+ exc_class, code = self._get_exc_class_and_code(code_or_exception)
+ self.error_handler_spec[None][code][exc_class] = f
+
+ @staticmethod
+ def _get_exc_class_and_code(
+ exc_class_or_code: type[Exception] | int,
+ ) -> tuple[type[Exception], int | None]:
+ """Get the exception class being handled. For HTTP status codes
+ or ``HTTPException`` subclasses, return both the exception and
+ status code.
+
+ :param exc_class_or_code: Any exception class, or an HTTP status
+ code as an integer.
+ """
+ exc_class: type[Exception]
+
+ if isinstance(exc_class_or_code, int):
+ try:
+ exc_class = default_exceptions[exc_class_or_code]
+ except KeyError:
+ raise ValueError(
+ f"'{exc_class_or_code}' is not a recognized HTTP"
+ " error code. Use a subclass of HTTPException with"
+ " that code instead."
+ ) from None
+ else:
+ exc_class = exc_class_or_code
+
+ if isinstance(exc_class, Exception):
+ raise TypeError(
+ f"{exc_class!r} is an instance, not a class. Handlers"
+ " can only be registered for Exception classes or HTTP"
+ " error codes."
+ )
+
+ if not issubclass(exc_class, Exception):
+ raise ValueError(
+ f"'{exc_class.__name__}' is not a subclass of Exception."
+ " Handlers can only be registered for Exception classes"
+ " or HTTP error codes."
+ )
+
+ if issubclass(exc_class, HTTPException):
+ return exc_class, exc_class.code
+ else:
+ return exc_class, None
+
+
+def _endpoint_from_view_func(view_func: t.Callable) -> str:
+ """Internal helper that returns the default endpoint for a given
+ function. This always is the function name.
+ """
+ assert view_func is not None, "expected view func if endpoint is not provided."
+ return view_func.__name__
+
+
+def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
+ # Path.is_relative_to doesn't exist until Python 3.9
+ try:
+ path.relative_to(base)
+ return True
+ except ValueError:
+ return False
+
+
+def _find_package_path(import_name):
+ """Find the path that contains the package or module."""
+ root_mod_name, _, _ = import_name.partition(".")
+
+ try:
+ root_spec = importlib.util.find_spec(root_mod_name)
+
+ if root_spec is None:
+ raise ValueError("not found")
+ except (ImportError, ValueError):
+ # ImportError: the machinery told us it does not exist
+ # ValueError:
+ # - the module name was invalid
+ # - the module name is __main__
+ # - we raised `ValueError` due to `root_spec` being `None`
+ return os.getcwd()
+
+ if root_spec.origin in {"namespace", None}:
+ # namespace package
+ package_spec = importlib.util.find_spec(import_name)
+
+ if package_spec is not None and package_spec.submodule_search_locations:
+ # Pick the path in the namespace that contains the submodule.
+ package_path = pathlib.Path(
+ os.path.commonpath(package_spec.submodule_search_locations)
+ )
+ search_location = next(
+ location
+ for location in root_spec.submodule_search_locations
+ if _path_is_relative_to(package_path, location)
+ )
+ else:
+ # Pick the first path.
+ search_location = root_spec.submodule_search_locations[0]
+
+ return os.path.dirname(search_location)
+ elif root_spec.submodule_search_locations:
+ # package with __init__.py
+ return os.path.dirname(os.path.dirname(root_spec.origin))
+ else:
+ # module
+ return os.path.dirname(root_spec.origin)
+
+
+def find_package(import_name: str):
+ """Find the prefix that a package is installed under, and the path
+ that it would be imported from.
+
+ The prefix is the directory containing the standard directory
+ hierarchy (lib, bin, etc.). If the package is not installed to the
+ system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
+ ``None`` is returned.
+
+ The path is the entry in :attr:`sys.path` that contains the package
+ for import. If the package is not installed, it's assumed that the
+ package was imported from the current working directory.
+ """
+ package_path = _find_package_path(import_name)
+ py_prefix = os.path.abspath(sys.prefix)
+
+ # installed to the system
+ if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
+ return py_prefix, package_path
+
+ site_parent, site_folder = os.path.split(package_path)
+
+ # installed to a virtualenv
+ if site_folder.lower() == "site-packages":
+ parent, folder = os.path.split(site_parent)
+
+ # Windows (prefix/lib/site-packages)
+ if folder.lower() == "lib":
+ return parent, package_path
+
+ # Unix (prefix/lib/pythonX.Y/site-packages)
+ if os.path.basename(parent).lower() == "lib":
+ return os.path.dirname(parent), package_path
+
+ # something else (prefix/site-packages)
+ return site_parent, package_path
+
+ # not installed
+ return None, package_path
diff --git a/lib/python3.12/site-packages/flask/sessions.py b/lib/python3.12/site-packages/flask/sessions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5650d6862ba235cda3f935e21679cc9d866958c
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/sessions.py
@@ -0,0 +1,367 @@
+from __future__ import annotations
+
+import hashlib
+import typing as t
+from collections.abc import MutableMapping
+from datetime import datetime
+from datetime import timezone
+
+from itsdangerous import BadSignature
+from itsdangerous import URLSafeTimedSerializer
+from werkzeug.datastructures import CallbackDict
+
+from .json.tag import TaggedJSONSerializer
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .wrappers import Request, Response
+
+
+class SessionMixin(MutableMapping):
+ """Expands a basic dictionary with session attributes."""
+
+ @property
+ def permanent(self) -> bool:
+ """This reflects the ``'_permanent'`` key in the dict."""
+ return self.get("_permanent", False)
+
+ @permanent.setter
+ def permanent(self, value: bool) -> None:
+ self["_permanent"] = bool(value)
+
+ #: Some implementations can detect whether a session is newly
+ #: created, but that is not guaranteed. Use with caution. The mixin
+ # default is hard-coded ``False``.
+ new = False
+
+ #: Some implementations can detect changes to the session and set
+ #: this when that happens. The mixin default is hard coded to
+ #: ``True``.
+ modified = True
+
+ #: Some implementations can detect when session data is read or
+ #: written and set this when that happens. The mixin default is hard
+ #: coded to ``True``.
+ accessed = True
+
+
+class SecureCookieSession(CallbackDict, SessionMixin):
+ """Base class for sessions based on signed cookies.
+
+ This session backend will set the :attr:`modified` and
+ :attr:`accessed` attributes. It cannot reliably track whether a
+ session is new (vs. empty), so :attr:`new` remains hard coded to
+ ``False``.
+ """
+
+ #: When data is changed, this is set to ``True``. Only the session
+ #: dictionary itself is tracked; if the session contains mutable
+ #: data (for example a nested dict) then this must be set to
+ #: ``True`` manually when modifying that data. The session cookie
+ #: will only be written to the response if this is ``True``.
+ modified = False
+
+ #: When data is read or written, this is set to ``True``. Used by
+ # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
+ #: header, which allows caching proxies to cache different pages for
+ #: different users.
+ accessed = False
+
+ def __init__(self, initial: t.Any = None) -> None:
+ def on_update(self) -> None:
+ self.modified = True
+ self.accessed = True
+
+ super().__init__(initial, on_update)
+
+ def __getitem__(self, key: str) -> t.Any:
+ self.accessed = True
+ return super().__getitem__(key)
+
+ def get(self, key: str, default: t.Any = None) -> t.Any:
+ self.accessed = True
+ return super().get(key, default)
+
+ def setdefault(self, key: str, default: t.Any = None) -> t.Any:
+ self.accessed = True
+ return super().setdefault(key, default)
+
+
+class NullSession(SecureCookieSession):
+ """Class used to generate nicer error messages if sessions are not
+ available. Will still allow read-only access to the empty session
+ but fail on setting.
+ """
+
+ def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
+ raise RuntimeError(
+ "The session is unavailable because no secret "
+ "key was set. Set the secret_key on the "
+ "application to something unique and secret."
+ )
+
+ __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950
+ del _fail
+
+
+class SessionInterface:
+ """The basic interface you have to implement in order to replace the
+ default session interface which uses werkzeug's securecookie
+ implementation. The only methods you have to implement are
+ :meth:`open_session` and :meth:`save_session`, the others have
+ useful defaults which you don't need to change.
+
+ The session object returned by the :meth:`open_session` method has to
+ provide a dictionary like interface plus the properties and methods
+ from the :class:`SessionMixin`. We recommend just subclassing a dict
+ and adding that mixin::
+
+ class Session(dict, SessionMixin):
+ pass
+
+ If :meth:`open_session` returns ``None`` Flask will call into
+ :meth:`make_null_session` to create a session that acts as replacement
+ if the session support cannot work because some requirement is not
+ fulfilled. The default :class:`NullSession` class that is created
+ will complain that the secret key was not set.
+
+ To replace the session interface on an application all you have to do
+ is to assign :attr:`flask.Flask.session_interface`::
+
+ app = Flask(__name__)
+ app.session_interface = MySessionInterface()
+
+ Multiple requests with the same session may be sent and handled
+ concurrently. When implementing a new session interface, consider
+ whether reads or writes to the backing store must be synchronized.
+ There is no guarantee on the order in which the session for each
+ request is opened or saved, it will occur in the order that requests
+ begin and end processing.
+
+ .. versionadded:: 0.8
+ """
+
+ #: :meth:`make_null_session` will look here for the class that should
+ #: be created when a null session is requested. Likewise the
+ #: :meth:`is_null_session` method will perform a typecheck against
+ #: this type.
+ null_session_class = NullSession
+
+ #: A flag that indicates if the session interface is pickle based.
+ #: This can be used by Flask extensions to make a decision in regards
+ #: to how to deal with the session object.
+ #:
+ #: .. versionadded:: 0.10
+ pickle_based = False
+
+ def make_null_session(self, app: Flask) -> NullSession:
+ """Creates a null session which acts as a replacement object if the
+ real session support could not be loaded due to a configuration
+ error. This mainly aids the user experience because the job of the
+ null session is to still support lookup without complaining but
+ modifications are answered with a helpful error message of what
+ failed.
+
+ This creates an instance of :attr:`null_session_class` by default.
+ """
+ return self.null_session_class()
+
+ def is_null_session(self, obj: object) -> bool:
+ """Checks if a given object is a null session. Null sessions are
+ not asked to be saved.
+
+ This checks if the object is an instance of :attr:`null_session_class`
+ by default.
+ """
+ return isinstance(obj, self.null_session_class)
+
+ def get_cookie_name(self, app: Flask) -> str:
+ """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
+ return app.config["SESSION_COOKIE_NAME"]
+
+ def get_cookie_domain(self, app: Flask) -> str | None:
+ """The value of the ``Domain`` parameter on the session cookie. If not set,
+ browsers will only send the cookie to the exact domain it was set from.
+ Otherwise, they will send it to any subdomain of the given value as well.
+
+ Uses the :data:`SESSION_COOKIE_DOMAIN` config.
+
+ .. versionchanged:: 2.3
+ Not set by default, does not fall back to ``SERVER_NAME``.
+ """
+ rv = app.config["SESSION_COOKIE_DOMAIN"]
+ return rv if rv else None
+
+ def get_cookie_path(self, app: Flask) -> str:
+ """Returns the path for which the cookie should be valid. The
+ default implementation uses the value from the ``SESSION_COOKIE_PATH``
+ config var if it's set, and falls back to ``APPLICATION_ROOT`` or
+ uses ``/`` if it's ``None``.
+ """
+ return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"]
+
+ def get_cookie_httponly(self, app: Flask) -> bool:
+ """Returns True if the session cookie should be httponly. This
+ currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
+ config var.
+ """
+ return app.config["SESSION_COOKIE_HTTPONLY"]
+
+ def get_cookie_secure(self, app: Flask) -> bool:
+ """Returns True if the cookie should be secure. This currently
+ just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
+ """
+ return app.config["SESSION_COOKIE_SECURE"]
+
+ def get_cookie_samesite(self, app: Flask) -> str:
+ """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
+ ``SameSite`` attribute. This currently just returns the value of
+ the :data:`SESSION_COOKIE_SAMESITE` setting.
+ """
+ return app.config["SESSION_COOKIE_SAMESITE"]
+
+ def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None:
+ """A helper method that returns an expiration date for the session
+ or ``None`` if the session is linked to the browser session. The
+ default implementation returns now + the permanent session
+ lifetime configured on the application.
+ """
+ if session.permanent:
+ return datetime.now(timezone.utc) + app.permanent_session_lifetime
+ return None
+
+ def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool:
+ """Used by session backends to determine if a ``Set-Cookie`` header
+ should be set for this session cookie for this response. If the session
+ has been modified, the cookie is set. If the session is permanent and
+ the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
+ always set.
+
+ This check is usually skipped if the session was deleted.
+
+ .. versionadded:: 0.11
+ """
+
+ return session.modified or (
+ session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"]
+ )
+
+ def open_session(self, app: Flask, request: Request) -> SessionMixin | None:
+ """This is called at the beginning of each request, after
+ pushing the request context, before matching the URL.
+
+ This must return an object which implements a dictionary-like
+ interface as well as the :class:`SessionMixin` interface.
+
+ This will return ``None`` to indicate that loading failed in
+ some way that is not immediately an error. The request
+ context will fall back to using :meth:`make_null_session`
+ in this case.
+ """
+ raise NotImplementedError()
+
+ def save_session(
+ self, app: Flask, session: SessionMixin, response: Response
+ ) -> None:
+ """This is called at the end of each request, after generating
+ a response, before removing the request context. It is skipped
+ if :meth:`is_null_session` returns ``True``.
+ """
+ raise NotImplementedError()
+
+
+session_json_serializer = TaggedJSONSerializer()
+
+
+class SecureCookieSessionInterface(SessionInterface):
+ """The default session interface that stores sessions in signed cookies
+ through the :mod:`itsdangerous` module.
+ """
+
+ #: the salt that should be applied on top of the secret key for the
+ #: signing of cookie based sessions.
+ salt = "cookie-session"
+ #: the hash function to use for the signature. The default is sha1
+ digest_method = staticmethod(hashlib.sha1)
+ #: the name of the itsdangerous supported key derivation. The default
+ #: is hmac.
+ key_derivation = "hmac"
+ #: A python serializer for the payload. The default is a compact
+ #: JSON derived serializer with support for some extra Python types
+ #: such as datetime objects or tuples.
+ serializer = session_json_serializer
+ session_class = SecureCookieSession
+
+ def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
+ if not app.secret_key:
+ return None
+ signer_kwargs = dict(
+ key_derivation=self.key_derivation, digest_method=self.digest_method
+ )
+ return URLSafeTimedSerializer(
+ app.secret_key,
+ salt=self.salt,
+ serializer=self.serializer,
+ signer_kwargs=signer_kwargs,
+ )
+
+ def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None:
+ s = self.get_signing_serializer(app)
+ if s is None:
+ return None
+ val = request.cookies.get(self.get_cookie_name(app))
+ if not val:
+ return self.session_class()
+ max_age = int(app.permanent_session_lifetime.total_seconds())
+ try:
+ data = s.loads(val, max_age=max_age)
+ return self.session_class(data)
+ except BadSignature:
+ return self.session_class()
+
+ def save_session(
+ self, app: Flask, session: SessionMixin, response: Response
+ ) -> None:
+ name = self.get_cookie_name(app)
+ domain = self.get_cookie_domain(app)
+ path = self.get_cookie_path(app)
+ secure = self.get_cookie_secure(app)
+ samesite = self.get_cookie_samesite(app)
+ httponly = self.get_cookie_httponly(app)
+
+ # Add a "Vary: Cookie" header if the session was accessed at all.
+ if session.accessed:
+ response.vary.add("Cookie")
+
+ # If the session is modified to be empty, remove the cookie.
+ # If the session is empty, return without setting the cookie.
+ if not session:
+ if session.modified:
+ response.delete_cookie(
+ name,
+ domain=domain,
+ path=path,
+ secure=secure,
+ samesite=samesite,
+ httponly=httponly,
+ )
+ response.vary.add("Cookie")
+
+ return
+
+ if not self.should_set_cookie(app, session):
+ return
+
+ expires = self.get_expiration_time(app, session)
+ val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
+ response.set_cookie(
+ name,
+ val, # type: ignore
+ expires=expires,
+ httponly=httponly,
+ domain=domain,
+ path=path,
+ secure=secure,
+ samesite=samesite,
+ )
+ response.vary.add("Cookie")
diff --git a/lib/python3.12/site-packages/flask/signals.py b/lib/python3.12/site-packages/flask/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..d79f21f9396026c18428b149edd67d1debbeb07b
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/signals.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+import typing as t
+import warnings
+
+from blinker import Namespace
+
+# This namespace is only for signals provided by Flask itself.
+_signals = Namespace()
+
+template_rendered = _signals.signal("template-rendered")
+before_render_template = _signals.signal("before-render-template")
+request_started = _signals.signal("request-started")
+request_finished = _signals.signal("request-finished")
+request_tearing_down = _signals.signal("request-tearing-down")
+got_request_exception = _signals.signal("got-request-exception")
+appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
+appcontext_pushed = _signals.signal("appcontext-pushed")
+appcontext_popped = _signals.signal("appcontext-popped")
+message_flashed = _signals.signal("message-flashed")
+
+
+def __getattr__(name: str) -> t.Any:
+ if name == "signals_available":
+ warnings.warn(
+ "The 'signals_available' attribute is deprecated and will be removed in"
+ " Flask 2.4. Signals are always available.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return True
+
+ raise AttributeError(name)
diff --git a/lib/python3.12/site-packages/flask/templating.py b/lib/python3.12/site-packages/flask/templating.py
new file mode 100644
index 0000000000000000000000000000000000000000..769108f755229f6e5785718e2e48836f9b58223c
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/templating.py
@@ -0,0 +1,220 @@
+from __future__ import annotations
+
+import typing as t
+
+from jinja2 import BaseLoader
+from jinja2 import Environment as BaseEnvironment
+from jinja2 import Template
+from jinja2 import TemplateNotFound
+
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import current_app
+from .globals import request
+from .helpers import stream_with_context
+from .signals import before_render_template
+from .signals import template_rendered
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .scaffold import Scaffold
+
+
+def _default_template_ctx_processor() -> dict[str, t.Any]:
+ """Default template context processor. Injects `request`,
+ `session` and `g`.
+ """
+ appctx = _cv_app.get(None)
+ reqctx = _cv_request.get(None)
+ rv: dict[str, t.Any] = {}
+ if appctx is not None:
+ rv["g"] = appctx.g
+ if reqctx is not None:
+ rv["request"] = reqctx.request
+ rv["session"] = reqctx.session
+ return rv
+
+
+class Environment(BaseEnvironment):
+ """Works like a regular Jinja2 environment but has some additional
+ knowledge of how Flask's blueprint works so that it can prepend the
+ name of the blueprint to referenced templates if necessary.
+ """
+
+ def __init__(self, app: Flask, **options: t.Any) -> None:
+ if "loader" not in options:
+ options["loader"] = app.create_global_jinja_loader()
+ BaseEnvironment.__init__(self, **options)
+ self.app = app
+
+
+class DispatchingJinjaLoader(BaseLoader):
+ """A loader that looks for templates in the application and all
+ the blueprint folders.
+ """
+
+ def __init__(self, app: Flask) -> None:
+ self.app = app
+
+ def get_source( # type: ignore
+ self, environment: Environment, template: str
+ ) -> tuple[str, str | None, t.Callable | None]:
+ if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
+ return self._get_source_explained(environment, template)
+ return self._get_source_fast(environment, template)
+
+ def _get_source_explained(
+ self, environment: Environment, template: str
+ ) -> tuple[str, str | None, t.Callable | None]:
+ attempts = []
+ rv: tuple[str, str | None, t.Callable[[], bool] | None] | None
+ trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None
+
+ for srcobj, loader in self._iter_loaders(template):
+ try:
+ rv = loader.get_source(environment, template)
+ if trv is None:
+ trv = rv
+ except TemplateNotFound:
+ rv = None
+ attempts.append((loader, srcobj, rv))
+
+ from .debughelpers import explain_template_loading_attempts
+
+ explain_template_loading_attempts(self.app, template, attempts)
+
+ if trv is not None:
+ return trv
+ raise TemplateNotFound(template)
+
+ def _get_source_fast(
+ self, environment: Environment, template: str
+ ) -> tuple[str, str | None, t.Callable | None]:
+ for _srcobj, loader in self._iter_loaders(template):
+ try:
+ return loader.get_source(environment, template)
+ except TemplateNotFound:
+ continue
+ raise TemplateNotFound(template)
+
+ def _iter_loaders(
+ self, template: str
+ ) -> t.Generator[tuple[Scaffold, BaseLoader], None, None]:
+ loader = self.app.jinja_loader
+ if loader is not None:
+ yield self.app, loader
+
+ for blueprint in self.app.iter_blueprints():
+ loader = blueprint.jinja_loader
+ if loader is not None:
+ yield blueprint, loader
+
+ def list_templates(self) -> list[str]:
+ result = set()
+ loader = self.app.jinja_loader
+ if loader is not None:
+ result.update(loader.list_templates())
+
+ for blueprint in self.app.iter_blueprints():
+ loader = blueprint.jinja_loader
+ if loader is not None:
+ for template in loader.list_templates():
+ result.add(template)
+
+ return list(result)
+
+
+def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str:
+ app.update_template_context(context)
+ before_render_template.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+ rv = template.render(context)
+ template_rendered.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+ return rv
+
+
+def render_template(
+ template_name_or_list: str | Template | list[str | Template],
+ **context: t.Any,
+) -> str:
+ """Render a template by name with the given context.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _render(app, template, context)
+
+
+def render_template_string(source: str, **context: t.Any) -> str:
+ """Render a template from the given source string with the given
+ context.
+
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _render(app, template, context)
+
+
+def _stream(
+ app: Flask, template: Template, context: dict[str, t.Any]
+) -> t.Iterator[str]:
+ app.update_template_context(context)
+ before_render_template.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+
+ def generate() -> t.Iterator[str]:
+ yield from template.generate(context)
+ template_rendered.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+
+ rv = generate()
+
+ # If a request context is active, keep it while generating.
+ if request:
+ rv = stream_with_context(rv)
+
+ return rv
+
+
+def stream_template(
+ template_name_or_list: str | Template | list[str | Template],
+ **context: t.Any,
+) -> t.Iterator[str]:
+ """Render a template by name with the given context as a stream.
+ This returns an iterator of strings, which can be used as a
+ streaming response from a view.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _stream(app, template, context)
+
+
+def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
+ """Render a template from the given source string with the given
+ context as a stream. This returns an iterator of strings, which can
+ be used as a streaming response from a view.
+
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _stream(app, template, context)
diff --git a/lib/python3.12/site-packages/flask/testing.py b/lib/python3.12/site-packages/flask/testing.py
new file mode 100644
index 0000000000000000000000000000000000000000..69aa7851889fe02cbb5ee3649c32ed8a950a5280
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/testing.py
@@ -0,0 +1,295 @@
+from __future__ import annotations
+
+import importlib.metadata
+import typing as t
+from contextlib import contextmanager
+from contextlib import ExitStack
+from copy import copy
+from types import TracebackType
+from urllib.parse import urlsplit
+
+import werkzeug.test
+from click.testing import CliRunner
+from werkzeug.test import Client
+from werkzeug.wrappers import Request as BaseRequest
+
+from .cli import ScriptInfo
+from .sessions import SessionMixin
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.test import TestResponse
+
+ from .app import Flask
+
+
+class EnvironBuilder(werkzeug.test.EnvironBuilder):
+ """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the
+ application.
+
+ :param app: The Flask application to configure the environment from.
+ :param path: URL path being requested.
+ :param base_url: Base URL where the app is being served, which
+ ``path`` is relative to. If not given, built from
+ :data:`PREFERRED_URL_SCHEME`, ``subdomain``,
+ :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
+ :param subdomain: Subdomain name to append to :data:`SERVER_NAME`.
+ :param url_scheme: Scheme to use instead of
+ :data:`PREFERRED_URL_SCHEME`.
+ :param json: If given, this is serialized as JSON and passed as
+ ``data``. Also defaults ``content_type`` to
+ ``application/json``.
+ :param args: other positional arguments passed to
+ :class:`~werkzeug.test.EnvironBuilder`.
+ :param kwargs: other keyword arguments passed to
+ :class:`~werkzeug.test.EnvironBuilder`.
+ """
+
+ def __init__(
+ self,
+ app: Flask,
+ path: str = "/",
+ base_url: str | None = None,
+ subdomain: str | None = None,
+ url_scheme: str | None = None,
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> None:
+ assert not (base_url or subdomain or url_scheme) or (
+ base_url is not None
+ ) != bool(
+ subdomain or url_scheme
+ ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
+
+ if base_url is None:
+ http_host = app.config.get("SERVER_NAME") or "localhost"
+ app_root = app.config["APPLICATION_ROOT"]
+
+ if subdomain:
+ http_host = f"{subdomain}.{http_host}"
+
+ if url_scheme is None:
+ url_scheme = app.config["PREFERRED_URL_SCHEME"]
+
+ url = urlsplit(path)
+ base_url = (
+ f"{url.scheme or url_scheme}://{url.netloc or http_host}"
+ f"/{app_root.lstrip('/')}"
+ )
+ path = url.path
+
+ if url.query:
+ sep = b"?" if isinstance(url.query, bytes) else "?"
+ path += sep + url.query
+
+ self.app = app
+ super().__init__(path, base_url, *args, **kwargs)
+
+ def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore
+ """Serialize ``obj`` to a JSON-formatted string.
+
+ The serialization will be configured according to the config associated
+ with this EnvironBuilder's ``app``.
+ """
+ return self.app.json.dumps(obj, **kwargs)
+
+
+_werkzeug_version = ""
+
+
+def _get_werkzeug_version() -> str:
+ global _werkzeug_version
+
+ if not _werkzeug_version:
+ _werkzeug_version = importlib.metadata.version("werkzeug")
+
+ return _werkzeug_version
+
+
+class FlaskClient(Client):
+ """Works like a regular Werkzeug test client but has knowledge about
+ Flask's contexts to defer the cleanup of the request context until
+ the end of a ``with`` block. For general information about how to
+ use this class refer to :class:`werkzeug.test.Client`.
+
+ .. versionchanged:: 0.12
+ `app.test_client()` includes preset default environment, which can be
+ set after instantiation of the `app.test_client()` object in
+ `client.environ_base`.
+
+ Basic usage is outlined in the :doc:`/testing` chapter.
+ """
+
+ application: Flask
+
+ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
+ super().__init__(*args, **kwargs)
+ self.preserve_context = False
+ self._new_contexts: list[t.ContextManager[t.Any]] = []
+ self._context_stack = ExitStack()
+ self.environ_base = {
+ "REMOTE_ADDR": "127.0.0.1",
+ "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}",
+ }
+
+ @contextmanager
+ def session_transaction(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Generator[SessionMixin, None, None]:
+ """When used in combination with a ``with`` statement this opens a
+ session transaction. This can be used to modify the session that
+ the test client uses. Once the ``with`` block is left the session is
+ stored back.
+
+ ::
+
+ with client.session_transaction() as session:
+ session['value'] = 42
+
+ Internally this is implemented by going through a temporary test
+ request context and since session handling could depend on
+ request variables this function accepts the same arguments as
+ :meth:`~flask.Flask.test_request_context` which are directly
+ passed through.
+ """
+ if self._cookies is None:
+ raise TypeError(
+ "Cookies are disabled. Create a client with 'use_cookies=True'."
+ )
+
+ app = self.application
+ ctx = app.test_request_context(*args, **kwargs)
+ self._add_cookies_to_wsgi(ctx.request.environ)
+
+ with ctx:
+ sess = app.session_interface.open_session(app, ctx.request)
+
+ if sess is None:
+ raise RuntimeError("Session backend did not open a session.")
+
+ yield sess
+ resp = app.response_class()
+
+ if app.session_interface.is_null_session(sess):
+ return
+
+ with ctx:
+ app.session_interface.save_session(app, sess, resp)
+
+ self._update_cookies_from_response(
+ ctx.request.host.partition(":")[0],
+ ctx.request.path,
+ resp.headers.getlist("Set-Cookie"),
+ )
+
+ def _copy_environ(self, other):
+ out = {**self.environ_base, **other}
+
+ if self.preserve_context:
+ out["werkzeug.debug.preserve_context"] = self._new_contexts.append
+
+ return out
+
+ def _request_from_builder_args(self, args, kwargs):
+ kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))
+ builder = EnvironBuilder(self.application, *args, **kwargs)
+
+ try:
+ return builder.get_request()
+ finally:
+ builder.close()
+
+ def open(
+ self,
+ *args: t.Any,
+ buffered: bool = False,
+ follow_redirects: bool = False,
+ **kwargs: t.Any,
+ ) -> TestResponse:
+ if args and isinstance(
+ args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest)
+ ):
+ if isinstance(args[0], werkzeug.test.EnvironBuilder):
+ builder = copy(args[0])
+ builder.environ_base = self._copy_environ(builder.environ_base or {})
+ request = builder.get_request()
+ elif isinstance(args[0], dict):
+ request = EnvironBuilder.from_environ(
+ args[0], app=self.application, environ_base=self._copy_environ({})
+ ).get_request()
+ else:
+ # isinstance(args[0], BaseRequest)
+ request = copy(args[0])
+ request.environ = self._copy_environ(request.environ)
+ else:
+ # request is None
+ request = self._request_from_builder_args(args, kwargs)
+
+ # Pop any previously preserved contexts. This prevents contexts
+ # from being preserved across redirects or multiple requests
+ # within a single block.
+ self._context_stack.close()
+
+ response = super().open(
+ request,
+ buffered=buffered,
+ follow_redirects=follow_redirects,
+ )
+ response.json_module = self.application.json # type: ignore[assignment]
+
+ # Re-push contexts that were preserved during the request.
+ while self._new_contexts:
+ cm = self._new_contexts.pop()
+ self._context_stack.enter_context(cm)
+
+ return response
+
+ def __enter__(self) -> FlaskClient:
+ if self.preserve_context:
+ raise RuntimeError("Cannot nest client invocations")
+ self.preserve_context = True
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.preserve_context = False
+ self._context_stack.close()
+
+
+class FlaskCliRunner(CliRunner):
+ """A :class:`~click.testing.CliRunner` for testing a Flask app's
+ CLI commands. Typically created using
+ :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
+ """
+
+ def __init__(self, app: Flask, **kwargs: t.Any) -> None:
+ self.app = app
+ super().__init__(**kwargs)
+
+ def invoke( # type: ignore
+ self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any
+ ) -> t.Any:
+ """Invokes a CLI command in an isolated environment. See
+ :meth:`CliRunner.invoke ` for
+ full method documentation. See :ref:`testing-cli` for examples.
+
+ If the ``obj`` argument is not given, passes an instance of
+ :class:`~flask.cli.ScriptInfo` that knows how to load the Flask
+ app being tested.
+
+ :param cli: Command object to invoke. Default is the app's
+ :attr:`~flask.app.Flask.cli` group.
+ :param args: List of strings to invoke the command with.
+
+ :return: a :class:`~click.testing.Result` object.
+ """
+ if cli is None:
+ cli = self.app.cli # type: ignore
+
+ if "obj" not in kwargs:
+ kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
+
+ return super().invoke(cli, args, **kwargs)
diff --git a/lib/python3.12/site-packages/flask/typing.py b/lib/python3.12/site-packages/flask/typing.py
new file mode 100644
index 0000000000000000000000000000000000000000..50aef7f463361f2f2d10096e5472ef9768cab743
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/typing.py
@@ -0,0 +1,82 @@
+from __future__ import annotations
+
+import typing as t
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIApplication # noqa: F401
+ from werkzeug.datastructures import Headers # noqa: F401
+ from werkzeug.wrappers import Response # noqa: F401
+
+# The possible types that are directly convertible or are a Response object.
+ResponseValue = t.Union[
+ "Response",
+ str,
+ bytes,
+ t.List[t.Any],
+ # Only dict is actually accepted, but Mapping allows for TypedDict.
+ t.Mapping[str, t.Any],
+ t.Iterator[str],
+ t.Iterator[bytes],
+]
+
+# the possible types for an individual HTTP header
+# This should be a Union, but mypy doesn't pass unless it's a TypeVar.
+HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]]
+
+# the possible types for HTTP headers
+HeadersValue = t.Union[
+ "Headers",
+ t.Mapping[str, HeaderValue],
+ t.Sequence[t.Tuple[str, HeaderValue]],
+]
+
+# The possible types returned by a route function.
+ResponseReturnValue = t.Union[
+ ResponseValue,
+ t.Tuple[ResponseValue, HeadersValue],
+ t.Tuple[ResponseValue, int],
+ t.Tuple[ResponseValue, int, HeadersValue],
+ "WSGIApplication",
+]
+
+# Allow any subclass of werkzeug.Response, such as the one from Flask,
+# as a callback argument. Using werkzeug.Response directly makes a
+# callback annotated with flask.Response fail type checking.
+ResponseClass = t.TypeVar("ResponseClass", bound="Response")
+
+AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
+AfterRequestCallable = t.Union[
+ t.Callable[[ResponseClass], ResponseClass],
+ t.Callable[[ResponseClass], t.Awaitable[ResponseClass]],
+]
+BeforeFirstRequestCallable = t.Union[
+ t.Callable[[], None], t.Callable[[], t.Awaitable[None]]
+]
+BeforeRequestCallable = t.Union[
+ t.Callable[[], t.Optional[ResponseReturnValue]],
+ t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
+]
+ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
+TeardownCallable = t.Union[
+ t.Callable[[t.Optional[BaseException]], None],
+ t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
+]
+TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
+TemplateFilterCallable = t.Callable[..., t.Any]
+TemplateGlobalCallable = t.Callable[..., t.Any]
+TemplateTestCallable = t.Callable[..., bool]
+URLDefaultCallable = t.Callable[[str, dict], None]
+URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
+
+# This should take Exception, but that either breaks typing the argument
+# with a specific exception, or decorating multiple times with different
+# exceptions (and using a union type on the argument).
+# https://github.com/pallets/flask/issues/4095
+# https://github.com/pallets/flask/issues/4295
+# https://github.com/pallets/flask/issues/4297
+ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue]
+
+RouteCallable = t.Union[
+ t.Callable[..., ResponseReturnValue],
+ t.Callable[..., t.Awaitable[ResponseReturnValue]],
+]
diff --git a/lib/python3.12/site-packages/flask/views.py b/lib/python3.12/site-packages/flask/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7a2b621c89604cdd2f1278404c2d01ff1932083
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/views.py
@@ -0,0 +1,190 @@
+from __future__ import annotations
+
+import typing as t
+
+from . import typing as ft
+from .globals import current_app
+from .globals import request
+
+
+http_method_funcs = frozenset(
+ ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
+)
+
+
+class View:
+ """Subclass this class and override :meth:`dispatch_request` to
+ create a generic class-based view. Call :meth:`as_view` to create a
+ view function that creates an instance of the class with the given
+ arguments and calls its ``dispatch_request`` method with any URL
+ variables.
+
+ See :doc:`views` for a detailed guide.
+
+ .. code-block:: python
+
+ class Hello(View):
+ init_every_request = False
+
+ def dispatch_request(self, name):
+ return f"Hello, {name}!"
+
+ app.add_url_rule(
+ "/hello/", view_func=Hello.as_view("hello")
+ )
+
+ Set :attr:`methods` on the class to change what methods the view
+ accepts.
+
+ Set :attr:`decorators` on the class to apply a list of decorators to
+ the generated view function. Decorators applied to the class itself
+ will not be applied to the generated view function!
+
+ Set :attr:`init_every_request` to ``False`` for efficiency, unless
+ you need to store request-global data on ``self``.
+ """
+
+ #: The methods this view is registered for. Uses the same default
+ #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ methods: t.ClassVar[t.Collection[str] | None] = None
+
+ #: Control whether the ``OPTIONS`` method is handled automatically.
+ #: Uses the same default (``True``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ provide_automatic_options: t.ClassVar[bool | None] = None
+
+ #: A list of decorators to apply, in order, to the generated view
+ #: function. Remember that ``@decorator`` syntax is applied bottom
+ #: to top, so the first decorator in the list would be the bottom
+ #: decorator.
+ #:
+ #: .. versionadded:: 0.8
+ decorators: t.ClassVar[list[t.Callable]] = []
+
+ #: Create a new instance of this view class for every request by
+ #: default. If a view subclass sets this to ``False``, the same
+ #: instance is used for every request.
+ #:
+ #: A single instance is more efficient, especially if complex setup
+ #: is done during init. However, storing data on ``self`` is no
+ #: longer safe across requests, and :data:`~flask.g` should be used
+ #: instead.
+ #:
+ #: .. versionadded:: 2.2
+ init_every_request: t.ClassVar[bool] = True
+
+ def dispatch_request(self) -> ft.ResponseReturnValue:
+ """The actual view function behavior. Subclasses must override
+ this and return a valid response. Any variables from the URL
+ rule are passed as keyword arguments.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def as_view(
+ cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
+ ) -> ft.RouteCallable:
+ """Convert the class into a view function that can be registered
+ for a route.
+
+ By default, the generated view will create a new instance of the
+ view class for every request and call its
+ :meth:`dispatch_request` method. If the view class sets
+ :attr:`init_every_request` to ``False``, the same instance will
+ be used for every request.
+
+ Except for ``name``, all other arguments passed to this method
+ are forwarded to the view class ``__init__`` method.
+
+ .. versionchanged:: 2.2
+ Added the ``init_every_request`` class attribute.
+ """
+ if cls.init_every_request:
+
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ self = view.view_class( # type: ignore[attr-defined]
+ *class_args, **class_kwargs
+ )
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+
+ else:
+ self = cls(*class_args, **class_kwargs)
+
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+
+ if cls.decorators:
+ view.__name__ = name
+ view.__module__ = cls.__module__
+ for decorator in cls.decorators:
+ view = decorator(view)
+
+ # We attach the view class to the view function for two reasons:
+ # first of all it allows us to easily figure out what class-based
+ # view this thing came from, secondly it's also used for instantiating
+ # the view class so you can actually replace it with something else
+ # for testing purposes and debugging.
+ view.view_class = cls # type: ignore
+ view.__name__ = name
+ view.__doc__ = cls.__doc__
+ view.__module__ = cls.__module__
+ view.methods = cls.methods # type: ignore
+ view.provide_automatic_options = cls.provide_automatic_options # type: ignore
+ return view
+
+
+class MethodView(View):
+ """Dispatches request methods to the corresponding instance methods.
+ For example, if you implement a ``get`` method, it will be used to
+ handle ``GET`` requests.
+
+ This can be useful for defining a REST API.
+
+ :attr:`methods` is automatically set based on the methods defined on
+ the class.
+
+ See :doc:`views` for a detailed guide.
+
+ .. code-block:: python
+
+ class CounterAPI(MethodView):
+ def get(self):
+ return str(session.get("counter", 0))
+
+ def post(self):
+ session["counter"] = session.get("counter", 0) + 1
+ return redirect(url_for("counter"))
+
+ app.add_url_rule(
+ "/counter", view_func=CounterAPI.as_view("counter")
+ )
+ """
+
+ def __init_subclass__(cls, **kwargs: t.Any) -> None:
+ super().__init_subclass__(**kwargs)
+
+ if "methods" not in cls.__dict__:
+ methods = set()
+
+ for base in cls.__bases__:
+ if getattr(base, "methods", None):
+ methods.update(base.methods) # type: ignore[attr-defined]
+
+ for key in http_method_funcs:
+ if hasattr(cls, key):
+ methods.add(key.upper())
+
+ if methods:
+ cls.methods = methods
+
+ def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
+ meth = getattr(self, request.method.lower(), None)
+
+ # If the request method is HEAD and we don't have a handler for it
+ # retry with GET.
+ if meth is None and request.method == "HEAD":
+ meth = getattr(self, "get", None)
+
+ assert meth is not None, f"Unimplemented method {request.method!r}"
+ return current_app.ensure_sync(meth)(**kwargs)
diff --git a/lib/python3.12/site-packages/flask/wrappers.py b/lib/python3.12/site-packages/flask/wrappers.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef7aa38c0ebe8617d8015ec6b9ee7f4b30596424
--- /dev/null
+++ b/lib/python3.12/site-packages/flask/wrappers.py
@@ -0,0 +1,173 @@
+from __future__ import annotations
+
+import typing as t
+
+from werkzeug.exceptions import BadRequest
+from werkzeug.wrappers import Request as RequestBase
+from werkzeug.wrappers import Response as ResponseBase
+
+from . import json
+from .globals import current_app
+from .helpers import _split_blueprint_path
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.routing import Rule
+
+
+class Request(RequestBase):
+ """The request object used by default in Flask. Remembers the
+ matched endpoint and view arguments.
+
+ It is what ends up as :class:`~flask.request`. If you want to replace
+ the request object used you can subclass this and set
+ :attr:`~flask.Flask.request_class` to your subclass.
+
+ The request object is a :class:`~werkzeug.wrappers.Request` subclass and
+ provides all of the attributes Werkzeug defines plus a few Flask
+ specific ones.
+ """
+
+ json_module: t.Any = json
+
+ #: The internal URL rule that matched the request. This can be
+ #: useful to inspect which methods are allowed for the URL from
+ #: a before/after handler (``request.url_rule.methods``) etc.
+ #: Though if the request's method was invalid for the URL rule,
+ #: the valid list is available in ``routing_exception.valid_methods``
+ #: instead (an attribute of the Werkzeug exception
+ #: :exc:`~werkzeug.exceptions.MethodNotAllowed`)
+ #: because the request was never internally bound.
+ #:
+ #: .. versionadded:: 0.6
+ url_rule: Rule | None = None
+
+ #: A dict of view arguments that matched the request. If an exception
+ #: happened when matching, this will be ``None``.
+ view_args: dict[str, t.Any] | None = None
+
+ #: If matching the URL failed, this is the exception that will be
+ #: raised / was raised as part of the request handling. This is
+ #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
+ #: something similar.
+ routing_exception: Exception | None = None
+
+ @property
+ def max_content_length(self) -> int | None: # type: ignore
+ """Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
+ if current_app:
+ return current_app.config["MAX_CONTENT_LENGTH"]
+ else:
+ return None
+
+ @property
+ def endpoint(self) -> str | None:
+ """The endpoint that matched the request URL.
+
+ This will be ``None`` if matching failed or has not been
+ performed yet.
+
+ This in combination with :attr:`view_args` can be used to
+ reconstruct the same URL or a modified URL.
+ """
+ if self.url_rule is not None:
+ return self.url_rule.endpoint
+
+ return None
+
+ @property
+ def blueprint(self) -> str | None:
+ """The registered name of the current blueprint.
+
+ This will be ``None`` if the endpoint is not part of a
+ blueprint, or if URL matching failed or has not been performed
+ yet.
+
+ This does not necessarily match the name the blueprint was
+ created with. It may have been nested, or registered with a
+ different name.
+ """
+ endpoint = self.endpoint
+
+ if endpoint is not None and "." in endpoint:
+ return endpoint.rpartition(".")[0]
+
+ return None
+
+ @property
+ def blueprints(self) -> list[str]:
+ """The registered names of the current blueprint upwards through
+ parent blueprints.
+
+ This will be an empty list if there is no current blueprint, or
+ if URL matching failed.
+
+ .. versionadded:: 2.0.1
+ """
+ name = self.blueprint
+
+ if name is None:
+ return []
+
+ return _split_blueprint_path(name)
+
+ def _load_form_data(self) -> None:
+ super()._load_form_data()
+
+ # In debug mode we're replacing the files multidict with an ad-hoc
+ # subclass that raises a different error for key errors.
+ if (
+ current_app
+ and current_app.debug
+ and self.mimetype != "multipart/form-data"
+ and not self.files
+ ):
+ from .debughelpers import attach_enctype_error_multidict
+
+ attach_enctype_error_multidict(self)
+
+ def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
+ try:
+ return super().on_json_loading_failed(e)
+ except BadRequest as e:
+ if current_app and current_app.debug:
+ raise
+
+ raise BadRequest() from e
+
+
+class Response(ResponseBase):
+ """The response object that is used by default in Flask. Works like the
+ response object from Werkzeug but is set to have an HTML mimetype by
+ default. Quite often you don't have to create this object yourself because
+ :meth:`~flask.Flask.make_response` will take care of that for you.
+
+ If you want to replace the response object used you can subclass this and
+ set :attr:`~flask.Flask.response_class` to your subclass.
+
+ .. versionchanged:: 1.0
+ JSON support is added to the response, like the request. This is useful
+ when testing to get the test client response data as JSON.
+
+ .. versionchanged:: 1.0
+
+ Added :attr:`max_cookie_size`.
+ """
+
+ default_mimetype: str | None = "text/html"
+
+ json_module = json
+
+ autocorrect_location_header = False
+
+ @property
+ def max_cookie_size(self) -> int: # type: ignore
+ """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
+
+ See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
+ Werkzeug's docs.
+ """
+ if current_app:
+ return current_app.config["MAX_COOKIE_SIZE"]
+
+ # return Werkzeug's default when not in an app context
+ return super().max_cookie_size
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/INSTALLER b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/METADATA b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..30d66530bc93801fc3385006ce179e72b42644df
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/METADATA
@@ -0,0 +1,672 @@
+Metadata-Version: 2.4
+Name: frozenlist
+Version: 1.8.0
+Summary: A list-like structure which implements collections.abc.MutableSequence
+Home-page: https://github.com/aio-libs/frozenlist
+Maintainer: aiohttp team
+Maintainer-email: team@aiohttp.org
+License: Apache-2.0
+Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
+Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
+Project-URL: CI: Github Actions, https://github.com/aio-libs/frozenlist/actions
+Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md
+Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/frozenlist
+Project-URL: Docs: Changelog, https://github.com/aio-libs/frozenlist/blob/master/CHANGES.rst#changelog
+Project-URL: Docs: RTD, https://frozenlist.aio-libs.org
+Project-URL: GitHub: issues, https://github.com/aio-libs/frozenlist/issues
+Project-URL: GitHub: repo, https://github.com/aio-libs/frozenlist
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: Cython
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+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: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.9
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Dynamic: license-file
+
+frozenlist
+==========
+
+.. image:: https://github.com/aio-libs/frozenlist/workflows/CI/badge.svg
+ :target: https://github.com/aio-libs/frozenlist/actions
+ :alt: GitHub status for master branch
+
+.. image:: https://codecov.io/gh/aio-libs/frozenlist/branch/master/graph/badge.svg?flag=pytest
+ :target: https://codecov.io/gh/aio-libs/frozenlist?flags[]=pytest
+ :alt: codecov.io status for master branch
+
+.. image:: https://img.shields.io/pypi/v/frozenlist.svg?logo=Python&logoColor=white
+ :target: https://pypi.org/project/frozenlist
+ :alt: frozenlist @ PyPI
+
+.. image:: https://readthedocs.org/projects/frozenlist/badge/?version=latest
+ :target: https://frozenlist.aio-libs.org
+ :alt: Read The Docs build status badge
+
+.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs:matrix.org
+ :alt: Matrix Room — #aio-libs:matrix.org
+
+.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs-space:matrix.org
+ :alt: Matrix Space — #aio-libs-space:matrix.org
+
+Introduction
+------------
+
+``frozenlist.FrozenList`` is a list-like structure which implements
+``collections.abc.MutableSequence``. The list is *mutable* until ``FrozenList.freeze``
+is called, after which list modifications raise ``RuntimeError``:
+
+
+>>> from frozenlist import FrozenList
+>>> fl = FrozenList([17, 42])
+>>> fl.append('spam')
+>>> fl.append('Vikings')
+>>> fl
+
+>>> fl.freeze()
+>>> fl
+
+>>> fl.frozen
+True
+>>> fl.append("Monty")
+Traceback (most recent call last):
+ File "", line 1, in
+ File "frozenlist/_frozenlist.pyx", line 97, in frozenlist._frozenlist.FrozenList.append
+ self._check_frozen()
+ File "frozenlist/_frozenlist.pyx", line 19, in frozenlist._frozenlist.FrozenList._check_frozen
+ raise RuntimeError("Cannot modify frozen list.")
+RuntimeError: Cannot modify frozen list.
+
+
+FrozenList is also hashable, but only when frozen. Otherwise it also throws a RuntimeError:
+
+
+>>> fl = FrozenList([17, 42, 'spam'])
+>>> hash(fl)
+Traceback (most recent call last):
+ File "", line 1, in
+ File "frozenlist/_frozenlist.pyx", line 111, in frozenlist._frozenlist.FrozenList.__hash__
+ raise RuntimeError("Cannot hash unfrozen list.")
+RuntimeError: Cannot hash unfrozen list.
+>>> fl.freeze()
+>>> hash(fl)
+3713081631934410656
+>>> dictionary = {fl: 'Vikings'} # frozen fl can be a dict key
+>>> dictionary
+{: 'Vikings'}
+
+
+Installation
+------------
+
+::
+
+ $ pip install frozenlist
+
+
+Documentation
+-------------
+
+https://frozenlist.aio-libs.org
+
+Communication channels
+----------------------
+
+We have a *Matrix Space* `#aio-libs-space:matrix.org
+`_ which is
+also accessible via Gitter.
+
+License
+-------
+
+``frozenlist`` is offered under the Apache 2 license.
+
+Source code
+-----------
+
+The project is hosted on GitHub_
+
+Please file an issue in the `bug tracker
+`_ if you have found a bug
+or have some suggestions to improve the library.
+
+.. _GitHub: https://github.com/aio-libs/frozenlist
+
+=========
+Changelog
+=========
+
+..
+ You should *NOT* be adding new change log entries to this file, this
+ file is managed by towncrier. You *may* edit previous change logs to
+ fix problems like typo corrections or such.
+ To add a new change log entry, please see
+ https://pip.pypa.io/en/latest/development/contributing/#news-entries
+ we named the news folder "changes".
+
+ WARNING: Don't drop the next directive!
+
+.. towncrier release notes start
+
+v1.8.0
+======
+
+*(2025-10-05)*
+
+
+Contributor-facing changes
+--------------------------
+
+- The ``reusable-cibuildwheel.yml`` workflow has been refactored to
+ be more generic and ``ci-cd.yml`` now holds all the configuration
+ toggles -- by `@webknjaz `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#668 `__.
+
+- When building wheels, the source distribution is now passed directly
+ to the ``cibuildwheel`` invocation -- by `@webknjaz `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#669 `__.
+
+- Builds and tests have been added to
+ ``ci-cd.yml`` for arm64 Windows wheels -- by `@finnagin `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#677 `__.
+
+- Started building wheels for CPython 3.14 -- by `@kumaraditya303 `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#681 `__, `#682 `__.
+
+- Removed ``--config-settings=pure-python=false`` from ``requirements/dev.txt``.
+ Developers on CPython still get accelerated builds by default. To explicitly build
+ a pure Python wheel, use ``pip install -e . --config-settings=pure-python=true``
+ -- by `@bdraco `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#687 `__.
+
+
+----
+
+
+v1.7.0
+======
+
+*(2025-06-09)*
+
+
+Features
+--------
+
+- Added deepcopy support to FrozenList -- by `@bdraco `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#659 `__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Fixed an issue where ``frozenlist`` binary wheels would be built with debugging symbols and line tracing enabled, which significantly impacted performance. Line tracing is now disabled by default and can only be enabled explicitly -- by `@bdraco `__.
+
+ This change ensures that production builds are optimized for performance. Developers who need line tracing for debugging purposes can still enable it by:
+
+ 1. Setting the ``FROZENLIST_CYTHON_TRACING`` environment variable
+ 2. Using the ``--config-setting=with-cython-tracing=true`` option with pip
+
+ *Related issues and pull requests on GitHub:*
+ `#660 `__.
+
+- Enabled ``PIP_CONSTRAINT`` environment variable in the build configuration to ensure the pinned Cython version from ``requirements/cython.txt`` is used during wheel builds.
+
+ *Related issues and pull requests on GitHub:*
+ `#661 `__.
+
+
+----
+
+
+v1.6.2
+======
+
+*(2025-06-03)*
+
+
+No significant changes.
+
+
+----
+
+
+v1.6.1
+======
+
+*(2025-06-02)*
+
+
+Bug fixes
+---------
+
+- Correctly use ``cimport`` for including ``PyBool_FromLong`` -- by `@lysnikolaou `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#653 `__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Exclude ``_frozenlist.cpp`` from bdists/wheels -- by `@musicinmybrain `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#649 `__.
+
+- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#654 `__.
+
+
+----
+
+
+v1.6.0
+======
+
+*(2025-04-17)*
+
+
+Bug fixes
+---------
+
+- Stopped implicitly allowing the use of Cython pre-release versions when
+ building the distribution package -- by `@ajsanchezsanz `__ and
+ `@markgreene74 `__.
+
+ *Related commits on GitHub:*
+ `41591f2 `__.
+
+
+Features
+--------
+
+- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#618 `__.
+
+- Started building armv7l wheels -- by `@bdraco `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#642 `__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Stopped implicitly allowing the use of Cython pre-release versions when
+ building the distribution package -- by `@ajsanchezsanz `__ and
+ `@markgreene74 `__.
+
+ *Related commits on GitHub:*
+ `41591f2 `__.
+
+- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#618 `__.
+
+- The packaging metadata switched to including an SPDX license identifier introduced in `PEP 639 `__ -- by `@cdce8p `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#639 `__.
+
+
+Contributor-facing changes
+--------------------------
+
+- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem
+ dependencies using `re-actors/cache-python-deps`_ -- an action by
+ `@webknjaz `__ that takes into account ABI stability and the exact
+ version of Python runtime.
+
+ .. _`re-actors/cache-python-deps`:
+ https://github.com/marketplace/actions/cache-python-deps
+
+ *Related issues and pull requests on GitHub:*
+ `#633 `__.
+
+- Organized dependencies into test and lint dependencies so that no
+ unnecessary ones are installed during CI runs -- by `@lysnikolaou `__.
+
+ *Related issues and pull requests on GitHub:*
+ `#636 `__.
+
+
+----
+
+
+1.5.0 (2024-10-22)
+==================
+
+Bug fixes
+---------
+
+- An incorrect signature of the ``__class_getitem__`` class method
+ has been fixed, adding a missing ``class_item`` argument under
+ Python 3.8 and older.
+
+ This change also improves the code coverage of this method that
+ was previously missing -- by `@webknjaz `__.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#567 `__, `#571 `__.
+
+
+Improved documentation
+----------------------
+
+- Rendered issue, PR, and commit links now lead to
+ ``frozenlist``'s repo instead of ``yarl``'s repo.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#573 `__.
+
+- On the ``Contributing docs`` page,
+ a link to the ``Towncrier philosophy`` has been fixed.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#574 `__.
+
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- A name of a temporary building directory now reflects
+ that it's related to ``frozenlist``, not ``yarl``.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#573 `__.
+
+- Declared Python 3.13 supported officially in the distribution package metadata.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#595 `__.
+
+
+----
+
+
+1.4.1 (2023-12-15)
+==================
+
+Packaging updates and notes for downstreams
+-------------------------------------------
+
+- Declared Python 3.12 and PyPy 3.8-3.10 supported officially
+ in the distribution package metadata.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#553 `__.
+
+- Replaced the packaging is replaced from an old-fashioned ``setup.py`` to an
+ in-tree `PEP 517 `__ build backend -- by `@webknjaz `__.
+
+ Whenever the end-users or downstream packagers need to build ``frozenlist``
+ from source (a Git checkout or an sdist), they may pass a ``config_settings``
+ flag ``pure-python``. If this flag is not set, a C-extension will be built
+ and included into the distribution.
+
+ Here is how this can be done with ``pip``:
+
+ .. code-block:: console
+
+ $ python3 -m pip install . --config-settings=pure-python=
+
+ This will also work with ``-e | --editable``.
+
+ The same can be achieved via ``pypa/build``:
+
+ .. code-block:: console
+
+ $ python3 -m build --config-setting=pure-python=
+
+ Adding ``-w | --wheel`` can force ``pypa/build`` produce a wheel from source
+ directly, as opposed to building an ``sdist`` and then building from it.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#560 `__.
+
+
+Contributor-facing changes
+--------------------------
+
+- It is now possible to request line tracing in Cython builds using the
+ ``with-cython-tracing`` `PEP 517 `__ config setting
+ -- `@webknjaz `__.
+
+ This can be used in CI and development environment to measure coverage
+ on Cython modules, but is not normally useful to the end-users or
+ downstream packagers.
+
+ Here's a usage example:
+
+ .. code-block:: console
+
+ $ python3 -Im pip install . --config-settings=with-cython-tracing=true
+
+ For editable installs, this setting is on by default. Otherwise, it's
+ off unless requested explicitly.
+
+ The following produces C-files required for the Cython coverage
+ plugin to map the measurements back to the PYX-files:
+
+ .. code-block:: console
+
+ $ python -Im pip install -e .
+
+ Alternatively, the ``FROZENLIST_CYTHON_TRACING=1`` environment variable
+ can be set to do the same as the `PEP 517 `__ config setting.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#560 `__.
+
+- Coverage collection has been implemented for the Cython modules
+ -- by `@webknjaz `__.
+
+ It will also be reported to Codecov from any non-release CI jobs.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#561 `__.
+
+- A step-by-step ``Release Guide`` guide has
+ been added, describing how to release *frozenlist* -- by `@webknjaz `__.
+
+ This is primarily targeting the maintainers.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#563 `__.
+
+- Detailed ``Contributing Guidelines`` on
+ authoring the changelog fragments have been published in the
+ documentation -- by `@webknjaz `__.
+
+
+ *Related issues and pull requests on GitHub:*
+ `#564 `__.
+
+
+----
+
+
+1.4.0 (2023-07-12)
+==================
+
+The published source distribution package became buildable
+under Python 3.12.
+
+
+----
+
+
+Bugfixes
+--------
+
+- Removed an unused ``typing.Tuple`` import
+ `#411 `_
+
+
+Deprecations and Removals
+-------------------------
+
+- Dropped Python 3.7 support.
+ `#413 `_
+
+
+Misc
+----
+
+- `#410 `_, `#433 `_
+
+
+----
+
+
+1.3.3 (2022-11-08)
+==================
+
+- Fixed CI runs when creating a new release, where new towncrier versions
+ fail when the current version section is already present.
+
+
+----
+
+
+1.3.2 (2022-11-08)
+==================
+
+Misc
+----
+
+- Updated the CI runs to better check for test results and to avoid deprecated syntax. `#327 `_
+
+
+----
+
+
+1.3.1 (2022-08-02)
+==================
+
+The published source distribution package became buildable
+under Python 3.11.
+
+
+----
+
+
+1.3.0 (2022-01-18)
+==================
+
+Bugfixes
+--------
+
+- Do not install C sources with binary distributions.
+ `#250 `_
+
+
+Deprecations and Removals
+-------------------------
+
+- Dropped Python 3.6 support
+ `#274 `_
+
+
+----
+
+
+1.2.0 (2021-10-16)
+==================
+
+Features
+--------
+
+- ``FrozenList`` now supports being used as a generic type as per PEP 585, e.g. ``frozen_int_list: FrozenList[int]`` (requires Python 3.9 or newer).
+ `#172 `_
+- Added support for Python 3.10.
+ `#227 `_
+- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes.
+ `#227 `_
+- Started shipping platform-specific arm64 wheels for Apple Silicon.
+ `#227 `_
+
+
+----
+
+
+1.1.1 (2020-11-14)
+==================
+
+Bugfixes
+--------
+
+- Provide x86 Windows wheels.
+ `#169 `_
+
+
+----
+
+
+1.1.0 (2020-10-13)
+==================
+
+Features
+--------
+
+- Add support for hashing of a frozen list.
+ `#136 `_
+
+- Support Python 3.8 and 3.9.
+
+- Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on
+ Linux as well as ``x86_64``.
+
+
+----
+
+
+1.0.0 (2019-11-09)
+==================
+
+Deprecations and Removals
+-------------------------
+
+- Dropped support for Python 3.5; only 3.6, 3.7 and 3.8 are supported going forward.
+ `#24 `_
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/RECORD b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..cdf56a00940ec29d8d6ac0f7d728bbf9fa5880c9
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/RECORD
@@ -0,0 +1,12 @@
+frozenlist-1.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+frozenlist-1.8.0.dist-info/METADATA,sha256=lGwi3J9-LHby1pjwCkMG2-Sagg4rPxoOIv5ShsvsNfE,20333
+frozenlist-1.8.0.dist-info/RECORD,,
+frozenlist-1.8.0.dist-info/WHEEL,sha256=mX4U4odf6w47aVjwZUmTYd1MF9BbrhVLKlaWSvZwHEk,186
+frozenlist-1.8.0.dist-info/licenses/LICENSE,sha256=b9UkPpLdf5jsacesN3co50kFcJ_1J6W_mNbQJjwE9bY,11332
+frozenlist-1.8.0.dist-info/top_level.txt,sha256=jivtxsPXA3nK3WBWW2LW5Mtu_GHt8UZA13NeCs2cKuA,11
+frozenlist/__init__.py,sha256=xAIE2u9ncAbjATGIPfno_OJfe8AQ-1h7z_uc73dYsEA,2108
+frozenlist/__init__.pyi,sha256=vMEoES1xGegPtVXoCi9XydEeHsyuIq-KdeXwP5PdsaA,1470
+frozenlist/__pycache__/__init__.cpython-312.pyc,,
+frozenlist/_frozenlist.cpython-312-x86_64-linux-gnu.so,sha256=UzFjN2uN24Tz86SdErp07aJh4LOp8A0w9EhxkDP4i3E,786872
+frozenlist/_frozenlist.pyx,sha256=t-aGjuEiVt_MZPBJ0RnraavVmPBK6arz3i48ZvXuYsU,3708
+frozenlist/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/WHEEL b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..9921a02fb6f1f01a39d88a07edea51e0d980e0dd
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/WHEEL
@@ -0,0 +1,7 @@
+Wheel-Version: 1.0
+Generator: setuptools (80.9.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-manylinux_2_5_x86_64
+Tag: cp312-cp312-manylinux1_x86_64
+Tag: cp312-cp312-manylinux_2_28_x86_64
+
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..7082a2d5b9047bfc09589f387053e24ea490bc54
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/licenses/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2013-2019 Nikolay Kim and Andrew Svetlov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/top_level.txt b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..52f13fc459edf8f3def6f792c432f0b64f313176
--- /dev/null
+++ b/lib/python3.12/site-packages/frozenlist-1.8.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+frozenlist
diff --git a/lib/python3.12/site-packages/httpx/__init__.py b/lib/python3.12/site-packages/httpx/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9addde071f81758baf350c4ab6bde2556340131
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/__init__.py
@@ -0,0 +1,105 @@
+from .__version__ import __description__, __title__, __version__
+from ._api import *
+from ._auth import *
+from ._client import *
+from ._config import *
+from ._content import *
+from ._exceptions import *
+from ._models import *
+from ._status_codes import *
+from ._transports import *
+from ._types import *
+from ._urls import *
+
+try:
+ from ._main import main
+except ImportError: # pragma: no cover
+
+ def main() -> None: # type: ignore
+ import sys
+
+ print(
+ "The httpx command line client could not run because the required "
+ "dependencies were not installed.\nMake sure you've installed "
+ "everything with: pip install 'httpx[cli]'"
+ )
+ sys.exit(1)
+
+
+__all__ = [
+ "__description__",
+ "__title__",
+ "__version__",
+ "ASGITransport",
+ "AsyncBaseTransport",
+ "AsyncByteStream",
+ "AsyncClient",
+ "AsyncHTTPTransport",
+ "Auth",
+ "BaseTransport",
+ "BasicAuth",
+ "ByteStream",
+ "Client",
+ "CloseError",
+ "codes",
+ "ConnectError",
+ "ConnectTimeout",
+ "CookieConflict",
+ "Cookies",
+ "create_ssl_context",
+ "DecodingError",
+ "delete",
+ "DigestAuth",
+ "get",
+ "head",
+ "Headers",
+ "HTTPError",
+ "HTTPStatusError",
+ "HTTPTransport",
+ "InvalidURL",
+ "Limits",
+ "LocalProtocolError",
+ "main",
+ "MockTransport",
+ "NetRCAuth",
+ "NetworkError",
+ "options",
+ "patch",
+ "PoolTimeout",
+ "post",
+ "ProtocolError",
+ "Proxy",
+ "ProxyError",
+ "put",
+ "QueryParams",
+ "ReadError",
+ "ReadTimeout",
+ "RemoteProtocolError",
+ "request",
+ "Request",
+ "RequestError",
+ "RequestNotRead",
+ "Response",
+ "ResponseNotRead",
+ "stream",
+ "StreamClosed",
+ "StreamConsumed",
+ "StreamError",
+ "SyncByteStream",
+ "Timeout",
+ "TimeoutException",
+ "TooManyRedirects",
+ "TransportError",
+ "UnsupportedProtocol",
+ "URL",
+ "USE_CLIENT_DEFAULT",
+ "WriteError",
+ "WriteTimeout",
+ "WSGITransport",
+]
+
+
+__locals = locals()
+for __name in __all__:
+ if not __name.startswith("__"):
+ setattr(__locals[__name], "__module__", "httpx") # noqa
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..58eae17e28d8f4b74c851c7e8c5d6268dd4a344e
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..eeb3131613a5ab819d43d68c7def10030a9a0a94
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b45741603ea6413377b787c3d9bfd81f230c1600
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d0848529c661bad7887870a9cb25a340e086e70c
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5d962aa83e689d061a2c50d0bc5636a579c98f29
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7409c8e805e690c2c843e8c02b113406db0c01b4
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..17ccbce617c8a8a39a91b00a00191874e199e78b
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d8353710709fe5076d475959d7537ed6cba3d76d
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3b28842dbdf905ed911cfc95210625ad14f31e81
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..793c1cd5f41e74ac0bc56bbf9581c72e4a209995
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d24eb1274806cfa93fe90dff846374a3038b3969
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fd489527c45e1f9707757fd2d0f067ec584fb380
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7a4bae98478ee2bfcc05e690a557440459805e90
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a175bbf26d56729e81dc1b3c48f15e354daa0aa5
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..66fcfc46ec4039a2b33ea69e891b65a73fc48881
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c95dacc8c279f2cc84bf40df7ae248ea97864d32
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc b/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a03127453ad1de9d48640bbca282f188cd5f28b6
Binary files /dev/null and b/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/httpx/__version__.py b/lib/python3.12/site-packages/httpx/__version__.py
new file mode 100644
index 0000000000000000000000000000000000000000..801bfacf671017cfbebf1ac26ec385daa02ed260
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/__version__.py
@@ -0,0 +1,3 @@
+__title__ = "httpx"
+__description__ = "A next generation HTTP client, for Python 3."
+__version__ = "0.28.1"
diff --git a/lib/python3.12/site-packages/httpx/_api.py b/lib/python3.12/site-packages/httpx/_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3cda1ecda8629edbdca2e3bc04bc51dba5e1430
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/_api.py
@@ -0,0 +1,438 @@
+from __future__ import annotations
+
+import typing
+from contextlib import contextmanager
+
+from ._client import Client
+from ._config import DEFAULT_TIMEOUT_CONFIG
+from ._models import Response
+from ._types import (
+ AuthTypes,
+ CookieTypes,
+ HeaderTypes,
+ ProxyTypes,
+ QueryParamTypes,
+ RequestContent,
+ RequestData,
+ RequestFiles,
+ TimeoutTypes,
+)
+from ._urls import URL
+
+if typing.TYPE_CHECKING:
+ import ssl # pragma: no cover
+
+
+__all__ = [
+ "delete",
+ "get",
+ "head",
+ "options",
+ "patch",
+ "post",
+ "put",
+ "request",
+ "stream",
+]
+
+
+def request(
+ method: str,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends an HTTP request.
+
+ **Parameters:**
+
+ * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`,
+ `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`.
+ * **url** - URL for the new `Request` object.
+ * **params** - *(optional)* Query parameters to include in the URL, as a
+ string, dictionary, or sequence of two-tuples.
+ * **content** - *(optional)* Binary content to include in the body of the
+ request, as bytes or a byte iterator.
+ * **data** - *(optional)* Form data to include in the body of the request,
+ as a dictionary.
+ * **files** - *(optional)* A dictionary of upload files to include in the
+ body of the request.
+ * **json** - *(optional)* A JSON serializable object to include in the body
+ of the request.
+ * **headers** - *(optional)* Dictionary of HTTP headers to include in the
+ request.
+ * **cookies** - *(optional)* Dictionary of Cookie items to include in the
+ request.
+ * **auth** - *(optional)* An authentication class to use when sending the
+ request.
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
+ * **timeout** - *(optional)* The timeout configuration to use when sending
+ the request.
+ * **follow_redirects** - *(optional)* Enables or disables HTTP redirects.
+ * **verify** - *(optional)* Either `True` to use an SSL context with the
+ default CA bundle, `False` to disable verification, or an instance of
+ `ssl.SSLContext` to use a custom context.
+ * **trust_env** - *(optional)* Enables or disables usage of environment
+ variables for configuration.
+
+ **Returns:** `Response`
+
+ Usage:
+
+ ```
+ >>> import httpx
+ >>> response = httpx.request('GET', 'https://httpbin.org/get')
+ >>> response
+
+ ```
+ """
+ with Client(
+ cookies=cookies,
+ proxy=proxy,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ ) as client:
+ return client.request(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ )
+
+
+@contextmanager
+def stream(
+ method: str,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ trust_env: bool = True,
+) -> typing.Iterator[Response]:
+ """
+ Alternative to `httpx.request()` that streams the response body
+ instead of loading it into memory at once.
+
+ **Parameters**: See `httpx.request`.
+
+ See also: [Streaming Responses][0]
+
+ [0]: /quickstart#streaming-responses
+ """
+ with Client(
+ cookies=cookies,
+ proxy=proxy,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ ) as client:
+ with client.stream(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ ) as response:
+ yield response
+
+
+def get(
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `GET` request.
+
+ **Parameters**: See `httpx.request`.
+
+ Note that the `data`, `files`, `json` and `content` parameters are not available
+ on this function, as `GET` requests should not include a request body.
+ """
+ return request(
+ "GET",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def options(
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends an `OPTIONS` request.
+
+ **Parameters**: See `httpx.request`.
+
+ Note that the `data`, `files`, `json` and `content` parameters are not available
+ on this function, as `OPTIONS` requests should not include a request body.
+ """
+ return request(
+ "OPTIONS",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def head(
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `HEAD` request.
+
+ **Parameters**: See `httpx.request`.
+
+ Note that the `data`, `files`, `json` and `content` parameters are not available
+ on this function, as `HEAD` requests should not include a request body.
+ """
+ return request(
+ "HEAD",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def post(
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `POST` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return request(
+ "POST",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def put(
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `PUT` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return request(
+ "PUT",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def patch(
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ verify: ssl.SSLContext | str | bool = True,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `PATCH` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return request(
+ "PATCH",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
+
+
+def delete(
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | None = None,
+ proxy: ProxyTypes | None = None,
+ follow_redirects: bool = False,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ verify: ssl.SSLContext | str | bool = True,
+ trust_env: bool = True,
+) -> Response:
+ """
+ Sends a `DELETE` request.
+
+ **Parameters**: See `httpx.request`.
+
+ Note that the `data`, `files`, `json` and `content` parameters are not available
+ on this function, as `DELETE` requests should not include a request body.
+ """
+ return request(
+ "DELETE",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ proxy=proxy,
+ follow_redirects=follow_redirects,
+ verify=verify,
+ timeout=timeout,
+ trust_env=trust_env,
+ )
diff --git a/lib/python3.12/site-packages/httpx/_auth.py b/lib/python3.12/site-packages/httpx/_auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..b03971ab4b311d60790dc22ca24d9966426ec0a4
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/_auth.py
@@ -0,0 +1,348 @@
+from __future__ import annotations
+
+import hashlib
+import os
+import re
+import time
+import typing
+from base64 import b64encode
+from urllib.request import parse_http_list
+
+from ._exceptions import ProtocolError
+from ._models import Cookies, Request, Response
+from ._utils import to_bytes, to_str, unquote
+
+if typing.TYPE_CHECKING: # pragma: no cover
+ from hashlib import _Hash
+
+
+__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]
+
+
+class Auth:
+ """
+ Base class for all authentication schemes.
+
+ To implement a custom authentication scheme, subclass `Auth` and override
+ the `.auth_flow()` method.
+
+ If the authentication scheme does I/O such as disk access or network calls, or uses
+ synchronization primitives such as locks, you should override `.sync_auth_flow()`
+ and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized
+ implementations that will be used by `Client` and `AsyncClient` respectively.
+ """
+
+ requires_request_body = False
+ requires_response_body = False
+
+ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
+ """
+ Execute the authentication flow.
+
+ To dispatch a request, `yield` it:
+
+ ```
+ yield request
+ ```
+
+ The client will `.send()` the response back into the flow generator. You can
+ access it like so:
+
+ ```
+ response = yield request
+ ```
+
+ A `return` (or reaching the end of the generator) will result in the
+ client returning the last response obtained from the server.
+
+ You can dispatch as many requests as is necessary.
+ """
+ yield request
+
+ def sync_auth_flow(
+ self, request: Request
+ ) -> typing.Generator[Request, Response, None]:
+ """
+ Execute the authentication flow synchronously.
+
+ By default, this defers to `.auth_flow()`. You should override this method
+ when the authentication scheme does I/O and/or uses concurrency primitives.
+ """
+ if self.requires_request_body:
+ request.read()
+
+ flow = self.auth_flow(request)
+ request = next(flow)
+
+ while True:
+ response = yield request
+ if self.requires_response_body:
+ response.read()
+
+ try:
+ request = flow.send(response)
+ except StopIteration:
+ break
+
+ async def async_auth_flow(
+ self, request: Request
+ ) -> typing.AsyncGenerator[Request, Response]:
+ """
+ Execute the authentication flow asynchronously.
+
+ By default, this defers to `.auth_flow()`. You should override this method
+ when the authentication scheme does I/O and/or uses concurrency primitives.
+ """
+ if self.requires_request_body:
+ await request.aread()
+
+ flow = self.auth_flow(request)
+ request = next(flow)
+
+ while True:
+ response = yield request
+ if self.requires_response_body:
+ await response.aread()
+
+ try:
+ request = flow.send(response)
+ except StopIteration:
+ break
+
+
+class FunctionAuth(Auth):
+ """
+ Allows the 'auth' argument to be passed as a simple callable function,
+ that takes the request, and returns a new, modified request.
+ """
+
+ def __init__(self, func: typing.Callable[[Request], Request]) -> None:
+ self._func = func
+
+ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
+ yield self._func(request)
+
+
+class BasicAuth(Auth):
+ """
+ Allows the 'auth' argument to be passed as a (username, password) pair,
+ and uses HTTP Basic authentication.
+ """
+
+ def __init__(self, username: str | bytes, password: str | bytes) -> None:
+ self._auth_header = self._build_auth_header(username, password)
+
+ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
+ request.headers["Authorization"] = self._auth_header
+ yield request
+
+ def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
+ userpass = b":".join((to_bytes(username), to_bytes(password)))
+ token = b64encode(userpass).decode()
+ return f"Basic {token}"
+
+
+class NetRCAuth(Auth):
+ """
+ Use a 'netrc' file to lookup basic auth credentials based on the url host.
+ """
+
+ def __init__(self, file: str | None = None) -> None:
+ # Lazily import 'netrc'.
+ # There's no need for us to load this module unless 'NetRCAuth' is being used.
+ import netrc
+
+ self._netrc_info = netrc.netrc(file)
+
+ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
+ auth_info = self._netrc_info.authenticators(request.url.host)
+ if auth_info is None or not auth_info[2]:
+ # The netrc file did not have authentication credentials for this host.
+ yield request
+ else:
+ # Build a basic auth header with credentials from the netrc file.
+ request.headers["Authorization"] = self._build_auth_header(
+ username=auth_info[0], password=auth_info[2]
+ )
+ yield request
+
+ def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
+ userpass = b":".join((to_bytes(username), to_bytes(password)))
+ token = b64encode(userpass).decode()
+ return f"Basic {token}"
+
+
+class DigestAuth(Auth):
+ _ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = {
+ "MD5": hashlib.md5,
+ "MD5-SESS": hashlib.md5,
+ "SHA": hashlib.sha1,
+ "SHA-SESS": hashlib.sha1,
+ "SHA-256": hashlib.sha256,
+ "SHA-256-SESS": hashlib.sha256,
+ "SHA-512": hashlib.sha512,
+ "SHA-512-SESS": hashlib.sha512,
+ }
+
+ def __init__(self, username: str | bytes, password: str | bytes) -> None:
+ self._username = to_bytes(username)
+ self._password = to_bytes(password)
+ self._last_challenge: _DigestAuthChallenge | None = None
+ self._nonce_count = 1
+
+ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
+ if self._last_challenge:
+ request.headers["Authorization"] = self._build_auth_header(
+ request, self._last_challenge
+ )
+
+ response = yield request
+
+ if response.status_code != 401 or "www-authenticate" not in response.headers:
+ # If the response is not a 401 then we don't
+ # need to build an authenticated request.
+ return
+
+ for auth_header in response.headers.get_list("www-authenticate"):
+ if auth_header.lower().startswith("digest "):
+ break
+ else:
+ # If the response does not include a 'WWW-Authenticate: Digest ...'
+ # header, then we don't need to build an authenticated request.
+ return
+
+ self._last_challenge = self._parse_challenge(request, response, auth_header)
+ self._nonce_count = 1
+
+ request.headers["Authorization"] = self._build_auth_header(
+ request, self._last_challenge
+ )
+ if response.cookies:
+ Cookies(response.cookies).set_cookie_header(request=request)
+ yield request
+
+ def _parse_challenge(
+ self, request: Request, response: Response, auth_header: str
+ ) -> _DigestAuthChallenge:
+ """
+ Returns a challenge from a Digest WWW-Authenticate header.
+ These take the form of:
+ `Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"`
+ """
+ scheme, _, fields = auth_header.partition(" ")
+
+ # This method should only ever have been called with a Digest auth header.
+ assert scheme.lower() == "digest"
+
+ header_dict: dict[str, str] = {}
+ for field in parse_http_list(fields):
+ key, value = field.strip().split("=", 1)
+ header_dict[key] = unquote(value)
+
+ try:
+ realm = header_dict["realm"].encode()
+ nonce = header_dict["nonce"].encode()
+ algorithm = header_dict.get("algorithm", "MD5")
+ opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None
+ qop = header_dict["qop"].encode() if "qop" in header_dict else None
+ return _DigestAuthChallenge(
+ realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop
+ )
+ except KeyError as exc:
+ message = "Malformed Digest WWW-Authenticate header"
+ raise ProtocolError(message, request=request) from exc
+
+ def _build_auth_header(
+ self, request: Request, challenge: _DigestAuthChallenge
+ ) -> str:
+ hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()]
+
+ def digest(data: bytes) -> bytes:
+ return hash_func(data).hexdigest().encode()
+
+ A1 = b":".join((self._username, challenge.realm, self._password))
+
+ path = request.url.raw_path
+ A2 = b":".join((request.method.encode(), path))
+ # TODO: implement auth-int
+ HA2 = digest(A2)
+
+ nc_value = b"%08x" % self._nonce_count
+ cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce)
+ self._nonce_count += 1
+
+ HA1 = digest(A1)
+ if challenge.algorithm.lower().endswith("-sess"):
+ HA1 = digest(b":".join((HA1, challenge.nonce, cnonce)))
+
+ qop = self._resolve_qop(challenge.qop, request=request)
+ if qop is None:
+ # Following RFC 2069
+ digest_data = [HA1, challenge.nonce, HA2]
+ else:
+ # Following RFC 2617/7616
+ digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2]
+
+ format_args = {
+ "username": self._username,
+ "realm": challenge.realm,
+ "nonce": challenge.nonce,
+ "uri": path,
+ "response": digest(b":".join(digest_data)),
+ "algorithm": challenge.algorithm.encode(),
+ }
+ if challenge.opaque:
+ format_args["opaque"] = challenge.opaque
+ if qop:
+ format_args["qop"] = b"auth"
+ format_args["nc"] = nc_value
+ format_args["cnonce"] = cnonce
+
+ return "Digest " + self._get_header_value(format_args)
+
+ def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes:
+ s = str(nonce_count).encode()
+ s += nonce
+ s += time.ctime().encode()
+ s += os.urandom(8)
+
+ return hashlib.sha1(s).hexdigest()[:16].encode()
+
+ def _get_header_value(self, header_fields: dict[str, bytes]) -> str:
+ NON_QUOTED_FIELDS = ("algorithm", "qop", "nc")
+ QUOTED_TEMPLATE = '{}="{}"'
+ NON_QUOTED_TEMPLATE = "{}={}"
+
+ header_value = ""
+ for i, (field, value) in enumerate(header_fields.items()):
+ if i > 0:
+ header_value += ", "
+ template = (
+ QUOTED_TEMPLATE
+ if field not in NON_QUOTED_FIELDS
+ else NON_QUOTED_TEMPLATE
+ )
+ header_value += template.format(field, to_str(value))
+
+ return header_value
+
+ def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None:
+ if qop is None:
+ return None
+ qops = re.split(b", ?", qop)
+ if b"auth" in qops:
+ return b"auth"
+
+ if qops == [b"auth-int"]:
+ raise NotImplementedError("Digest auth-int support is not yet implemented")
+
+ message = f'Unexpected qop value "{qop!r}" in digest auth'
+ raise ProtocolError(message, request=request)
+
+
+class _DigestAuthChallenge(typing.NamedTuple):
+ realm: bytes
+ nonce: bytes
+ algorithm: str
+ opaque: bytes | None
+ qop: bytes | None
diff --git a/lib/python3.12/site-packages/httpx/_client.py b/lib/python3.12/site-packages/httpx/_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..2249231f8c3b912c731ff160344d3672e2f11738
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/_client.py
@@ -0,0 +1,2019 @@
+from __future__ import annotations
+
+import datetime
+import enum
+import logging
+import time
+import typing
+import warnings
+from contextlib import asynccontextmanager, contextmanager
+from types import TracebackType
+
+from .__version__ import __version__
+from ._auth import Auth, BasicAuth, FunctionAuth
+from ._config import (
+ DEFAULT_LIMITS,
+ DEFAULT_MAX_REDIRECTS,
+ DEFAULT_TIMEOUT_CONFIG,
+ Limits,
+ Proxy,
+ Timeout,
+)
+from ._decoders import SUPPORTED_DECODERS
+from ._exceptions import (
+ InvalidURL,
+ RemoteProtocolError,
+ TooManyRedirects,
+ request_context,
+)
+from ._models import Cookies, Headers, Request, Response
+from ._status_codes import codes
+from ._transports.base import AsyncBaseTransport, BaseTransport
+from ._transports.default import AsyncHTTPTransport, HTTPTransport
+from ._types import (
+ AsyncByteStream,
+ AuthTypes,
+ CertTypes,
+ CookieTypes,
+ HeaderTypes,
+ ProxyTypes,
+ QueryParamTypes,
+ RequestContent,
+ RequestData,
+ RequestExtensions,
+ RequestFiles,
+ SyncByteStream,
+ TimeoutTypes,
+)
+from ._urls import URL, QueryParams
+from ._utils import URLPattern, get_environment_proxies
+
+if typing.TYPE_CHECKING:
+ import ssl # pragma: no cover
+
+__all__ = ["USE_CLIENT_DEFAULT", "AsyncClient", "Client"]
+
+# The type annotation for @classmethod and context managers here follows PEP 484
+# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods
+T = typing.TypeVar("T", bound="Client")
+U = typing.TypeVar("U", bound="AsyncClient")
+
+
+def _is_https_redirect(url: URL, location: URL) -> bool:
+ """
+ Return 'True' if 'location' is a HTTPS upgrade of 'url'
+ """
+ if url.host != location.host:
+ return False
+
+ return (
+ url.scheme == "http"
+ and _port_or_default(url) == 80
+ and location.scheme == "https"
+ and _port_or_default(location) == 443
+ )
+
+
+def _port_or_default(url: URL) -> int | None:
+ if url.port is not None:
+ return url.port
+ return {"http": 80, "https": 443}.get(url.scheme)
+
+
+def _same_origin(url: URL, other: URL) -> bool:
+ """
+ Return 'True' if the given URLs share the same origin.
+ """
+ return (
+ url.scheme == other.scheme
+ and url.host == other.host
+ and _port_or_default(url) == _port_or_default(other)
+ )
+
+
+class UseClientDefault:
+ """
+ For some parameters such as `auth=...` and `timeout=...` we need to be able
+ to indicate the default "unset" state, in a way that is distinctly different
+ to using `None`.
+
+ The default "unset" state indicates that whatever default is set on the
+ client should be used. This is different to setting `None`, which
+ explicitly disables the parameter, possibly overriding a client default.
+
+ For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature.
+ Omitting the `timeout` parameter will send a request using whatever default
+ timeout has been configured on the client. Including `timeout=None` will
+ ensure no timeout is used.
+
+ Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant,
+ but it is used internally when a parameter is not included.
+ """
+
+
+USE_CLIENT_DEFAULT = UseClientDefault()
+
+
+logger = logging.getLogger("httpx")
+
+USER_AGENT = f"python-httpx/{__version__}"
+ACCEPT_ENCODING = ", ".join(
+ [key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
+)
+
+
+class ClientState(enum.Enum):
+ # UNOPENED:
+ # The client has been instantiated, but has not been used to send a request,
+ # or been opened by entering the context of a `with` block.
+ UNOPENED = 1
+ # OPENED:
+ # The client has either sent a request, or is within a `with` block.
+ OPENED = 2
+ # CLOSED:
+ # The client has either exited the `with` block, or `close()` has
+ # been called explicitly.
+ CLOSED = 3
+
+
+class BoundSyncStream(SyncByteStream):
+ """
+ A byte stream that is bound to a given response instance, and that
+ ensures the `response.elapsed` is set once the response is closed.
+ """
+
+ def __init__(
+ self, stream: SyncByteStream, response: Response, start: float
+ ) -> None:
+ self._stream = stream
+ self._response = response
+ self._start = start
+
+ def __iter__(self) -> typing.Iterator[bytes]:
+ for chunk in self._stream:
+ yield chunk
+
+ def close(self) -> None:
+ elapsed = time.perf_counter() - self._start
+ self._response.elapsed = datetime.timedelta(seconds=elapsed)
+ self._stream.close()
+
+
+class BoundAsyncStream(AsyncByteStream):
+ """
+ An async byte stream that is bound to a given response instance, and that
+ ensures the `response.elapsed` is set once the response is closed.
+ """
+
+ def __init__(
+ self, stream: AsyncByteStream, response: Response, start: float
+ ) -> None:
+ self._stream = stream
+ self._response = response
+ self._start = start
+
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ async for chunk in self._stream:
+ yield chunk
+
+ async def aclose(self) -> None:
+ elapsed = time.perf_counter() - self._start
+ self._response.elapsed = datetime.timedelta(seconds=elapsed)
+ await self._stream.aclose()
+
+
+EventHook = typing.Callable[..., typing.Any]
+
+
+class BaseClient:
+ def __init__(
+ self,
+ *,
+ auth: AuthTypes | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ follow_redirects: bool = False,
+ max_redirects: int = DEFAULT_MAX_REDIRECTS,
+ event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
+ base_url: URL | str = "",
+ trust_env: bool = True,
+ default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
+ ) -> None:
+ event_hooks = {} if event_hooks is None else event_hooks
+
+ self._base_url = self._enforce_trailing_slash(URL(base_url))
+
+ self._auth = self._build_auth(auth)
+ self._params = QueryParams(params)
+ self.headers = Headers(headers)
+ self._cookies = Cookies(cookies)
+ self._timeout = Timeout(timeout)
+ self.follow_redirects = follow_redirects
+ self.max_redirects = max_redirects
+ self._event_hooks = {
+ "request": list(event_hooks.get("request", [])),
+ "response": list(event_hooks.get("response", [])),
+ }
+ self._trust_env = trust_env
+ self._default_encoding = default_encoding
+ self._state = ClientState.UNOPENED
+
+ @property
+ def is_closed(self) -> bool:
+ """
+ Check if the client being closed
+ """
+ return self._state == ClientState.CLOSED
+
+ @property
+ def trust_env(self) -> bool:
+ return self._trust_env
+
+ def _enforce_trailing_slash(self, url: URL) -> URL:
+ if url.raw_path.endswith(b"/"):
+ return url
+ return url.copy_with(raw_path=url.raw_path + b"/")
+
+ def _get_proxy_map(
+ self, proxy: ProxyTypes | None, allow_env_proxies: bool
+ ) -> dict[str, Proxy | None]:
+ if proxy is None:
+ if allow_env_proxies:
+ return {
+ key: None if url is None else Proxy(url=url)
+ for key, url in get_environment_proxies().items()
+ }
+ return {}
+ else:
+ proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
+ return {"all://": proxy}
+
+ @property
+ def timeout(self) -> Timeout:
+ return self._timeout
+
+ @timeout.setter
+ def timeout(self, timeout: TimeoutTypes) -> None:
+ self._timeout = Timeout(timeout)
+
+ @property
+ def event_hooks(self) -> dict[str, list[EventHook]]:
+ return self._event_hooks
+
+ @event_hooks.setter
+ def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None:
+ self._event_hooks = {
+ "request": list(event_hooks.get("request", [])),
+ "response": list(event_hooks.get("response", [])),
+ }
+
+ @property
+ def auth(self) -> Auth | None:
+ """
+ Authentication class used when none is passed at the request-level.
+
+ See also [Authentication][0].
+
+ [0]: /quickstart/#authentication
+ """
+ return self._auth
+
+ @auth.setter
+ def auth(self, auth: AuthTypes) -> None:
+ self._auth = self._build_auth(auth)
+
+ @property
+ def base_url(self) -> URL:
+ """
+ Base URL to use when sending requests with relative URLs.
+ """
+ return self._base_url
+
+ @base_url.setter
+ def base_url(self, url: URL | str) -> None:
+ self._base_url = self._enforce_trailing_slash(URL(url))
+
+ @property
+ def headers(self) -> Headers:
+ """
+ HTTP headers to include when sending requests.
+ """
+ return self._headers
+
+ @headers.setter
+ def headers(self, headers: HeaderTypes) -> None:
+ client_headers = Headers(
+ {
+ b"Accept": b"*/*",
+ b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"),
+ b"Connection": b"keep-alive",
+ b"User-Agent": USER_AGENT.encode("ascii"),
+ }
+ )
+ client_headers.update(headers)
+ self._headers = client_headers
+
+ @property
+ def cookies(self) -> Cookies:
+ """
+ Cookie values to include when sending requests.
+ """
+ return self._cookies
+
+ @cookies.setter
+ def cookies(self, cookies: CookieTypes) -> None:
+ self._cookies = Cookies(cookies)
+
+ @property
+ def params(self) -> QueryParams:
+ """
+ Query parameters to include in the URL when sending requests.
+ """
+ return self._params
+
+ @params.setter
+ def params(self, params: QueryParamTypes) -> None:
+ self._params = QueryParams(params)
+
+ def build_request(
+ self,
+ method: str,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Request:
+ """
+ Build and return a request instance.
+
+ * The `params`, `headers` and `cookies` arguments
+ are merged with any values set on the client.
+ * The `url` argument is merged with any `base_url` set on the client.
+
+ See also: [Request instances][0]
+
+ [0]: /advanced/clients/#request-instances
+ """
+ url = self._merge_url(url)
+ headers = self._merge_headers(headers)
+ cookies = self._merge_cookies(cookies)
+ params = self._merge_queryparams(params)
+ extensions = {} if extensions is None else extensions
+ if "timeout" not in extensions:
+ timeout = (
+ self.timeout
+ if isinstance(timeout, UseClientDefault)
+ else Timeout(timeout)
+ )
+ extensions = dict(**extensions, timeout=timeout.as_dict())
+ return Request(
+ method,
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ extensions=extensions,
+ )
+
+ def _merge_url(self, url: URL | str) -> URL:
+ """
+ Merge a URL argument together with any 'base_url' on the client,
+ to create the URL used for the outgoing request.
+ """
+ merge_url = URL(url)
+ if merge_url.is_relative_url:
+ # To merge URLs we always append to the base URL. To get this
+ # behaviour correct we always ensure the base URL ends in a '/'
+ # separator, and strip any leading '/' from the merge URL.
+ #
+ # So, eg...
+ #
+ # >>> client = Client(base_url="https://www.example.com/subpath")
+ # >>> client.base_url
+ # URL('https://www.example.com/subpath/')
+ # >>> client.build_request("GET", "/path").url
+ # URL('https://www.example.com/subpath/path')
+ merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
+ return self.base_url.copy_with(raw_path=merge_raw_path)
+ return merge_url
+
+ def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None:
+ """
+ Merge a cookies argument together with any cookies on the client,
+ to create the cookies used for the outgoing request.
+ """
+ if cookies or self.cookies:
+ merged_cookies = Cookies(self.cookies)
+ merged_cookies.update(cookies)
+ return merged_cookies
+ return cookies
+
+ def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None:
+ """
+ Merge a headers argument together with any headers on the client,
+ to create the headers used for the outgoing request.
+ """
+ merged_headers = Headers(self.headers)
+ merged_headers.update(headers)
+ return merged_headers
+
+ def _merge_queryparams(
+ self, params: QueryParamTypes | None = None
+ ) -> QueryParamTypes | None:
+ """
+ Merge a queryparams argument together with any queryparams on the client,
+ to create the queryparams used for the outgoing request.
+ """
+ if params or self.params:
+ merged_queryparams = QueryParams(self.params)
+ return merged_queryparams.merge(params)
+ return params
+
+ def _build_auth(self, auth: AuthTypes | None) -> Auth | None:
+ if auth is None:
+ return None
+ elif isinstance(auth, tuple):
+ return BasicAuth(username=auth[0], password=auth[1])
+ elif isinstance(auth, Auth):
+ return auth
+ elif callable(auth):
+ return FunctionAuth(func=auth)
+ else:
+ raise TypeError(f'Invalid "auth" argument: {auth!r}')
+
+ def _build_request_auth(
+ self,
+ request: Request,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ ) -> Auth:
+ auth = (
+ self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
+ )
+
+ if auth is not None:
+ return auth
+
+ username, password = request.url.username, request.url.password
+ if username or password:
+ return BasicAuth(username=username, password=password)
+
+ return Auth()
+
+ def _build_redirect_request(self, request: Request, response: Response) -> Request:
+ """
+ Given a request and a redirect response, return a new request that
+ should be used to effect the redirect.
+ """
+ method = self._redirect_method(request, response)
+ url = self._redirect_url(request, response)
+ headers = self._redirect_headers(request, url, method)
+ stream = self._redirect_stream(request, method)
+ cookies = Cookies(self.cookies)
+ return Request(
+ method=method,
+ url=url,
+ headers=headers,
+ cookies=cookies,
+ stream=stream,
+ extensions=request.extensions,
+ )
+
+ def _redirect_method(self, request: Request, response: Response) -> str:
+ """
+ When being redirected we may want to change the method of the request
+ based on certain specs or browser behavior.
+ """
+ method = request.method
+
+ # https://tools.ietf.org/html/rfc7231#section-6.4.4
+ if response.status_code == codes.SEE_OTHER and method != "HEAD":
+ method = "GET"
+
+ # Do what the browsers do, despite standards...
+ # Turn 302s into GETs.
+ if response.status_code == codes.FOUND and method != "HEAD":
+ method = "GET"
+
+ # If a POST is responded to with a 301, turn it into a GET.
+ # This bizarre behaviour is explained in 'requests' issue 1704.
+ if response.status_code == codes.MOVED_PERMANENTLY and method == "POST":
+ method = "GET"
+
+ return method
+
+ def _redirect_url(self, request: Request, response: Response) -> URL:
+ """
+ Return the URL for the redirect to follow.
+ """
+ location = response.headers["Location"]
+
+ try:
+ url = URL(location)
+ except InvalidURL as exc:
+ raise RemoteProtocolError(
+ f"Invalid URL in location header: {exc}.", request=request
+ ) from None
+
+ # Handle malformed 'Location' headers that are "absolute" form, have no host.
+ # See: https://github.com/encode/httpx/issues/771
+ if url.scheme and not url.host:
+ url = url.copy_with(host=request.url.host)
+
+ # Facilitate relative 'Location' headers, as allowed by RFC 7231.
+ # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
+ if url.is_relative_url:
+ url = request.url.join(url)
+
+ # Attach previous fragment if needed (RFC 7231 7.1.2)
+ if request.url.fragment and not url.fragment:
+ url = url.copy_with(fragment=request.url.fragment)
+
+ return url
+
+ def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers:
+ """
+ Return the headers that should be used for the redirect request.
+ """
+ headers = Headers(request.headers)
+
+ if not _same_origin(url, request.url):
+ if not _is_https_redirect(request.url, url):
+ # Strip Authorization headers when responses are redirected
+ # away from the origin. (Except for direct HTTP to HTTPS redirects.)
+ headers.pop("Authorization", None)
+
+ # Update the Host header.
+ headers["Host"] = url.netloc.decode("ascii")
+
+ if method != request.method and method == "GET":
+ # If we've switch to a 'GET' request, then strip any headers which
+ # are only relevant to the request body.
+ headers.pop("Content-Length", None)
+ headers.pop("Transfer-Encoding", None)
+
+ # We should use the client cookie store to determine any cookie header,
+ # rather than whatever was on the original outgoing request.
+ headers.pop("Cookie", None)
+
+ return headers
+
+ def _redirect_stream(
+ self, request: Request, method: str
+ ) -> SyncByteStream | AsyncByteStream | None:
+ """
+ Return the body that should be used for the redirect request.
+ """
+ if method != request.method and method == "GET":
+ return None
+
+ return request.stream
+
+ def _set_timeout(self, request: Request) -> None:
+ if "timeout" not in request.extensions:
+ timeout = (
+ self.timeout
+ if isinstance(self.timeout, UseClientDefault)
+ else Timeout(self.timeout)
+ )
+ request.extensions = dict(**request.extensions, timeout=timeout.as_dict())
+
+
+class Client(BaseClient):
+ """
+ An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.
+
+ It can be shared between threads.
+
+ Usage:
+
+ ```python
+ >>> client = httpx.Client()
+ >>> response = client.get('https://example.org')
+ ```
+
+ **Parameters:**
+
+ * **auth** - *(optional)* An authentication class to use when sending
+ requests.
+ * **params** - *(optional)* Query parameters to include in request URLs, as
+ a string, dictionary, or sequence of two-tuples.
+ * **headers** - *(optional)* Dictionary of HTTP headers to include when
+ sending requests.
+ * **cookies** - *(optional)* Dictionary of Cookie items to include when
+ sending requests.
+ * **verify** - *(optional)* Either `True` to use an SSL context with the
+ default CA bundle, `False` to disable verification, or an instance of
+ `ssl.SSLContext` to use a custom context.
+ * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
+ enabled. Defaults to `False`.
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
+ * **timeout** - *(optional)* The timeout configuration to use when sending
+ requests.
+ * **limits** - *(optional)* The limits configuration to use.
+ * **max_redirects** - *(optional)* The maximum number of redirect responses
+ that should be followed.
+ * **base_url** - *(optional)* A URL to use as the base when building
+ request URLs.
+ * **transport** - *(optional)* A transport class to use for sending requests
+ over the network.
+ * **trust_env** - *(optional)* Enables or disables usage of environment
+ variables for configuration.
+ * **default_encoding** - *(optional)* The default encoding to use for decoding
+ response text, if no charset information is included in a response Content-Type
+ header. Set to a callable for automatic character set detection. Default: "utf-8".
+ """
+
+ def __init__(
+ self,
+ *,
+ auth: AuthTypes | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+ http1: bool = True,
+ http2: bool = False,
+ proxy: ProxyTypes | None = None,
+ mounts: None | (typing.Mapping[str, BaseTransport | None]) = None,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ follow_redirects: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ max_redirects: int = DEFAULT_MAX_REDIRECTS,
+ event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
+ base_url: URL | str = "",
+ transport: BaseTransport | None = None,
+ default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
+ ) -> None:
+ super().__init__(
+ auth=auth,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ follow_redirects=follow_redirects,
+ max_redirects=max_redirects,
+ event_hooks=event_hooks,
+ base_url=base_url,
+ trust_env=trust_env,
+ default_encoding=default_encoding,
+ )
+
+ if http2:
+ try:
+ import h2 # noqa
+ except ImportError: # pragma: no cover
+ raise ImportError(
+ "Using http2=True, but the 'h2' package is not installed. "
+ "Make sure to install httpx using `pip install httpx[http2]`."
+ ) from None
+
+ allow_env_proxies = trust_env and transport is None
+ proxy_map = self._get_proxy_map(proxy, allow_env_proxies)
+
+ self._transport = self._init_transport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ transport=transport,
+ )
+ self._mounts: dict[URLPattern, BaseTransport | None] = {
+ URLPattern(key): None
+ if proxy is None
+ else self._init_proxy_transport(
+ proxy,
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ )
+ for key, proxy in proxy_map.items()
+ }
+ if mounts is not None:
+ self._mounts.update(
+ {URLPattern(key): transport for key, transport in mounts.items()}
+ )
+
+ self._mounts = dict(sorted(self._mounts.items()))
+
+ def _init_transport(
+ self,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+ http1: bool = True,
+ http2: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ transport: BaseTransport | None = None,
+ ) -> BaseTransport:
+ if transport is not None:
+ return transport
+
+ return HTTPTransport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ )
+
+ def _init_proxy_transport(
+ self,
+ proxy: Proxy,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+ http1: bool = True,
+ http2: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ ) -> BaseTransport:
+ return HTTPTransport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ proxy=proxy,
+ )
+
+ def _transport_for_url(self, url: URL) -> BaseTransport:
+ """
+ Returns the transport instance that should be used for a given URL.
+ This will either be the standard connection pool, or a proxy.
+ """
+ for pattern, transport in self._mounts.items():
+ if pattern.matches(url):
+ return self._transport if transport is None else transport
+
+ return self._transport
+
+ def request(
+ self,
+ method: str,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Build and send a request.
+
+ Equivalent to:
+
+ ```python
+ request = client.build_request(...)
+ response = client.send(request, ...)
+ ```
+
+ See `Client.build_request()`, `Client.send()` and
+ [Merging of configuration][0] for how the various parameters
+ are merged with client-level configuration.
+
+ [0]: /advanced/clients/#merging-of-configuration
+ """
+ if cookies is not None:
+ message = (
+ "Setting per-request cookies=<...> is being deprecated, because "
+ "the expected behaviour on cookie persistence is ambiguous. Set "
+ "cookies directly on the client instance instead."
+ )
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
+
+ request = self.build_request(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ extensions=extensions,
+ )
+ return self.send(request, auth=auth, follow_redirects=follow_redirects)
+
+ @contextmanager
+ def stream(
+ self,
+ method: str,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> typing.Iterator[Response]:
+ """
+ Alternative to `httpx.request()` that streams the response body
+ instead of loading it into memory at once.
+
+ **Parameters**: See `httpx.request`.
+
+ See also: [Streaming Responses][0]
+
+ [0]: /quickstart#streaming-responses
+ """
+ request = self.build_request(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ extensions=extensions,
+ )
+ response = self.send(
+ request=request,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ stream=True,
+ )
+ try:
+ yield response
+ finally:
+ response.close()
+
+ def send(
+ self,
+ request: Request,
+ *,
+ stream: bool = False,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ ) -> Response:
+ """
+ Send a request.
+
+ The request is sent as-is, unmodified.
+
+ Typically you'll want to build one with `Client.build_request()`
+ so that any client-level configuration is merged into the request,
+ but passing an explicit `httpx.Request()` is supported as well.
+
+ See also: [Request instances][0]
+
+ [0]: /advanced/clients/#request-instances
+ """
+ if self._state == ClientState.CLOSED:
+ raise RuntimeError("Cannot send a request, as the client has been closed.")
+
+ self._state = ClientState.OPENED
+ follow_redirects = (
+ self.follow_redirects
+ if isinstance(follow_redirects, UseClientDefault)
+ else follow_redirects
+ )
+
+ self._set_timeout(request)
+
+ auth = self._build_request_auth(request, auth)
+
+ response = self._send_handling_auth(
+ request,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ history=[],
+ )
+ try:
+ if not stream:
+ response.read()
+
+ return response
+
+ except BaseException as exc:
+ response.close()
+ raise exc
+
+ def _send_handling_auth(
+ self,
+ request: Request,
+ auth: Auth,
+ follow_redirects: bool,
+ history: list[Response],
+ ) -> Response:
+ auth_flow = auth.sync_auth_flow(request)
+ try:
+ request = next(auth_flow)
+
+ while True:
+ response = self._send_handling_redirects(
+ request,
+ follow_redirects=follow_redirects,
+ history=history,
+ )
+ try:
+ try:
+ next_request = auth_flow.send(response)
+ except StopIteration:
+ return response
+
+ response.history = list(history)
+ response.read()
+ request = next_request
+ history.append(response)
+
+ except BaseException as exc:
+ response.close()
+ raise exc
+ finally:
+ auth_flow.close()
+
+ def _send_handling_redirects(
+ self,
+ request: Request,
+ follow_redirects: bool,
+ history: list[Response],
+ ) -> Response:
+ while True:
+ if len(history) > self.max_redirects:
+ raise TooManyRedirects(
+ "Exceeded maximum allowed redirects.", request=request
+ )
+
+ for hook in self._event_hooks["request"]:
+ hook(request)
+
+ response = self._send_single_request(request)
+ try:
+ for hook in self._event_hooks["response"]:
+ hook(response)
+ response.history = list(history)
+
+ if not response.has_redirect_location:
+ return response
+
+ request = self._build_redirect_request(request, response)
+ history = history + [response]
+
+ if follow_redirects:
+ response.read()
+ else:
+ response.next_request = request
+ return response
+
+ except BaseException as exc:
+ response.close()
+ raise exc
+
+ def _send_single_request(self, request: Request) -> Response:
+ """
+ Sends a single request, without handling any redirections.
+ """
+ transport = self._transport_for_url(request.url)
+ start = time.perf_counter()
+
+ if not isinstance(request.stream, SyncByteStream):
+ raise RuntimeError(
+ "Attempted to send an async request with a sync Client instance."
+ )
+
+ with request_context(request=request):
+ response = transport.handle_request(request)
+
+ assert isinstance(response.stream, SyncByteStream)
+
+ response.request = request
+ response.stream = BoundSyncStream(
+ response.stream, response=response, start=start
+ )
+ self.cookies.extract_cookies(response)
+ response.default_encoding = self._default_encoding
+
+ logger.info(
+ 'HTTP Request: %s %s "%s %d %s"',
+ request.method,
+ request.url,
+ response.http_version,
+ response.status_code,
+ response.reason_phrase,
+ )
+
+ return response
+
+ def get(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `GET` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "GET",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def options(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send an `OPTIONS` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "OPTIONS",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def head(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `HEAD` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "HEAD",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def post(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `POST` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "POST",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def put(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `PUT` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "PUT",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def patch(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `PATCH` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "PATCH",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def delete(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `DELETE` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return self.request(
+ "DELETE",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ def close(self) -> None:
+ """
+ Close transport and proxies.
+ """
+ if self._state != ClientState.CLOSED:
+ self._state = ClientState.CLOSED
+
+ self._transport.close()
+ for transport in self._mounts.values():
+ if transport is not None:
+ transport.close()
+
+ def __enter__(self: T) -> T:
+ if self._state != ClientState.UNOPENED:
+ msg = {
+ ClientState.OPENED: "Cannot open a client instance more than once.",
+ ClientState.CLOSED: (
+ "Cannot reopen a client instance, once it has been closed."
+ ),
+ }[self._state]
+ raise RuntimeError(msg)
+
+ self._state = ClientState.OPENED
+
+ self._transport.__enter__()
+ for transport in self._mounts.values():
+ if transport is not None:
+ transport.__enter__()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: TracebackType | None = None,
+ ) -> None:
+ self._state = ClientState.CLOSED
+
+ self._transport.__exit__(exc_type, exc_value, traceback)
+ for transport in self._mounts.values():
+ if transport is not None:
+ transport.__exit__(exc_type, exc_value, traceback)
+
+
+class AsyncClient(BaseClient):
+ """
+ An asynchronous HTTP client, with connection pooling, HTTP/2, redirects,
+ cookie persistence, etc.
+
+ It can be shared between tasks.
+
+ Usage:
+
+ ```python
+ >>> async with httpx.AsyncClient() as client:
+ >>> response = await client.get('https://example.org')
+ ```
+
+ **Parameters:**
+
+ * **auth** - *(optional)* An authentication class to use when sending
+ requests.
+ * **params** - *(optional)* Query parameters to include in request URLs, as
+ a string, dictionary, or sequence of two-tuples.
+ * **headers** - *(optional)* Dictionary of HTTP headers to include when
+ sending requests.
+ * **cookies** - *(optional)* Dictionary of Cookie items to include when
+ sending requests.
+ * **verify** - *(optional)* Either `True` to use an SSL context with the
+ default CA bundle, `False` to disable verification, or an instance of
+ `ssl.SSLContext` to use a custom context.
+ * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
+ enabled. Defaults to `False`.
+ * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
+ * **timeout** - *(optional)* The timeout configuration to use when sending
+ requests.
+ * **limits** - *(optional)* The limits configuration to use.
+ * **max_redirects** - *(optional)* The maximum number of redirect responses
+ that should be followed.
+ * **base_url** - *(optional)* A URL to use as the base when building
+ request URLs.
+ * **transport** - *(optional)* A transport class to use for sending requests
+ over the network.
+ * **trust_env** - *(optional)* Enables or disables usage of environment
+ variables for configuration.
+ * **default_encoding** - *(optional)* The default encoding to use for decoding
+ response text, if no charset information is included in a response Content-Type
+ header. Set to a callable for automatic character set detection. Default: "utf-8".
+ """
+
+ def __init__(
+ self,
+ *,
+ auth: AuthTypes | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ http1: bool = True,
+ http2: bool = False,
+ proxy: ProxyTypes | None = None,
+ mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None,
+ timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+ follow_redirects: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ max_redirects: int = DEFAULT_MAX_REDIRECTS,
+ event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
+ base_url: URL | str = "",
+ transport: AsyncBaseTransport | None = None,
+ trust_env: bool = True,
+ default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
+ ) -> None:
+ super().__init__(
+ auth=auth,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ follow_redirects=follow_redirects,
+ max_redirects=max_redirects,
+ event_hooks=event_hooks,
+ base_url=base_url,
+ trust_env=trust_env,
+ default_encoding=default_encoding,
+ )
+
+ if http2:
+ try:
+ import h2 # noqa
+ except ImportError: # pragma: no cover
+ raise ImportError(
+ "Using http2=True, but the 'h2' package is not installed. "
+ "Make sure to install httpx using `pip install httpx[http2]`."
+ ) from None
+
+ allow_env_proxies = trust_env and transport is None
+ proxy_map = self._get_proxy_map(proxy, allow_env_proxies)
+
+ self._transport = self._init_transport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ transport=transport,
+ )
+
+ self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {
+ URLPattern(key): None
+ if proxy is None
+ else self._init_proxy_transport(
+ proxy,
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ )
+ for key, proxy in proxy_map.items()
+ }
+ if mounts is not None:
+ self._mounts.update(
+ {URLPattern(key): transport for key, transport in mounts.items()}
+ )
+ self._mounts = dict(sorted(self._mounts.items()))
+
+ def _init_transport(
+ self,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+ http1: bool = True,
+ http2: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ transport: AsyncBaseTransport | None = None,
+ ) -> AsyncBaseTransport:
+ if transport is not None:
+ return transport
+
+ return AsyncHTTPTransport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ )
+
+ def _init_proxy_transport(
+ self,
+ proxy: Proxy,
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+ http1: bool = True,
+ http2: bool = False,
+ limits: Limits = DEFAULT_LIMITS,
+ ) -> AsyncBaseTransport:
+ return AsyncHTTPTransport(
+ verify=verify,
+ cert=cert,
+ trust_env=trust_env,
+ http1=http1,
+ http2=http2,
+ limits=limits,
+ proxy=proxy,
+ )
+
+ def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
+ """
+ Returns the transport instance that should be used for a given URL.
+ This will either be the standard connection pool, or a proxy.
+ """
+ for pattern, transport in self._mounts.items():
+ if pattern.matches(url):
+ return self._transport if transport is None else transport
+
+ return self._transport
+
+ async def request(
+ self,
+ method: str,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Build and send a request.
+
+ Equivalent to:
+
+ ```python
+ request = client.build_request(...)
+ response = await client.send(request, ...)
+ ```
+
+ See `AsyncClient.build_request()`, `AsyncClient.send()`
+ and [Merging of configuration][0] for how the various parameters
+ are merged with client-level configuration.
+
+ [0]: /advanced/clients/#merging-of-configuration
+ """
+
+ if cookies is not None: # pragma: no cover
+ message = (
+ "Setting per-request cookies=<...> is being deprecated, because "
+ "the expected behaviour on cookie persistence is ambiguous. Set "
+ "cookies directly on the client instance instead."
+ )
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
+
+ request = self.build_request(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ extensions=extensions,
+ )
+ return await self.send(request, auth=auth, follow_redirects=follow_redirects)
+
+ @asynccontextmanager
+ async def stream(
+ self,
+ method: str,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> typing.AsyncIterator[Response]:
+ """
+ Alternative to `httpx.request()` that streams the response body
+ instead of loading it into memory at once.
+
+ **Parameters**: See `httpx.request`.
+
+ See also: [Streaming Responses][0]
+
+ [0]: /quickstart#streaming-responses
+ """
+ request = self.build_request(
+ method=method,
+ url=url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ extensions=extensions,
+ )
+ response = await self.send(
+ request=request,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ stream=True,
+ )
+ try:
+ yield response
+ finally:
+ await response.aclose()
+
+ async def send(
+ self,
+ request: Request,
+ *,
+ stream: bool = False,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ ) -> Response:
+ """
+ Send a request.
+
+ The request is sent as-is, unmodified.
+
+ Typically you'll want to build one with `AsyncClient.build_request()`
+ so that any client-level configuration is merged into the request,
+ but passing an explicit `httpx.Request()` is supported as well.
+
+ See also: [Request instances][0]
+
+ [0]: /advanced/clients/#request-instances
+ """
+ if self._state == ClientState.CLOSED:
+ raise RuntimeError("Cannot send a request, as the client has been closed.")
+
+ self._state = ClientState.OPENED
+ follow_redirects = (
+ self.follow_redirects
+ if isinstance(follow_redirects, UseClientDefault)
+ else follow_redirects
+ )
+
+ self._set_timeout(request)
+
+ auth = self._build_request_auth(request, auth)
+
+ response = await self._send_handling_auth(
+ request,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ history=[],
+ )
+ try:
+ if not stream:
+ await response.aread()
+
+ return response
+
+ except BaseException as exc:
+ await response.aclose()
+ raise exc
+
+ async def _send_handling_auth(
+ self,
+ request: Request,
+ auth: Auth,
+ follow_redirects: bool,
+ history: list[Response],
+ ) -> Response:
+ auth_flow = auth.async_auth_flow(request)
+ try:
+ request = await auth_flow.__anext__()
+
+ while True:
+ response = await self._send_handling_redirects(
+ request,
+ follow_redirects=follow_redirects,
+ history=history,
+ )
+ try:
+ try:
+ next_request = await auth_flow.asend(response)
+ except StopAsyncIteration:
+ return response
+
+ response.history = list(history)
+ await response.aread()
+ request = next_request
+ history.append(response)
+
+ except BaseException as exc:
+ await response.aclose()
+ raise exc
+ finally:
+ await auth_flow.aclose()
+
+ async def _send_handling_redirects(
+ self,
+ request: Request,
+ follow_redirects: bool,
+ history: list[Response],
+ ) -> Response:
+ while True:
+ if len(history) > self.max_redirects:
+ raise TooManyRedirects(
+ "Exceeded maximum allowed redirects.", request=request
+ )
+
+ for hook in self._event_hooks["request"]:
+ await hook(request)
+
+ response = await self._send_single_request(request)
+ try:
+ for hook in self._event_hooks["response"]:
+ await hook(response)
+
+ response.history = list(history)
+
+ if not response.has_redirect_location:
+ return response
+
+ request = self._build_redirect_request(request, response)
+ history = history + [response]
+
+ if follow_redirects:
+ await response.aread()
+ else:
+ response.next_request = request
+ return response
+
+ except BaseException as exc:
+ await response.aclose()
+ raise exc
+
+ async def _send_single_request(self, request: Request) -> Response:
+ """
+ Sends a single request, without handling any redirections.
+ """
+ transport = self._transport_for_url(request.url)
+ start = time.perf_counter()
+
+ if not isinstance(request.stream, AsyncByteStream):
+ raise RuntimeError(
+ "Attempted to send an sync request with an AsyncClient instance."
+ )
+
+ with request_context(request=request):
+ response = await transport.handle_async_request(request)
+
+ assert isinstance(response.stream, AsyncByteStream)
+ response.request = request
+ response.stream = BoundAsyncStream(
+ response.stream, response=response, start=start
+ )
+ self.cookies.extract_cookies(response)
+ response.default_encoding = self._default_encoding
+
+ logger.info(
+ 'HTTP Request: %s %s "%s %d %s"',
+ request.method,
+ request.url,
+ response.http_version,
+ response.status_code,
+ response.reason_phrase,
+ )
+
+ return response
+
+ async def get(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `GET` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "GET",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def options(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send an `OPTIONS` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "OPTIONS",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def head(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `HEAD` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "HEAD",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def post(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `POST` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "POST",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def put(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `PUT` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "PUT",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def patch(
+ self,
+ url: URL | str,
+ *,
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: typing.Any | None = None,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `PATCH` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "PATCH",
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def delete(
+ self,
+ url: URL | str,
+ *,
+ params: QueryParamTypes | None = None,
+ headers: HeaderTypes | None = None,
+ cookies: CookieTypes | None = None,
+ auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
+ extensions: RequestExtensions | None = None,
+ ) -> Response:
+ """
+ Send a `DELETE` request.
+
+ **Parameters**: See `httpx.request`.
+ """
+ return await self.request(
+ "DELETE",
+ url,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ auth=auth,
+ follow_redirects=follow_redirects,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+ async def aclose(self) -> None:
+ """
+ Close transport and proxies.
+ """
+ if self._state != ClientState.CLOSED:
+ self._state = ClientState.CLOSED
+
+ await self._transport.aclose()
+ for proxy in self._mounts.values():
+ if proxy is not None:
+ await proxy.aclose()
+
+ async def __aenter__(self: U) -> U:
+ if self._state != ClientState.UNOPENED:
+ msg = {
+ ClientState.OPENED: "Cannot open a client instance more than once.",
+ ClientState.CLOSED: (
+ "Cannot reopen a client instance, once it has been closed."
+ ),
+ }[self._state]
+ raise RuntimeError(msg)
+
+ self._state = ClientState.OPENED
+
+ await self._transport.__aenter__()
+ for proxy in self._mounts.values():
+ if proxy is not None:
+ await proxy.__aenter__()
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None = None,
+ exc_value: BaseException | None = None,
+ traceback: TracebackType | None = None,
+ ) -> None:
+ self._state = ClientState.CLOSED
+
+ await self._transport.__aexit__(exc_type, exc_value, traceback)
+ for proxy in self._mounts.values():
+ if proxy is not None:
+ await proxy.__aexit__(exc_type, exc_value, traceback)
diff --git a/lib/python3.12/site-packages/httpx/_config.py b/lib/python3.12/site-packages/httpx/_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..467a6c90ae269babe3af7963d9d7c78b9f012268
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/_config.py
@@ -0,0 +1,248 @@
+from __future__ import annotations
+
+import os
+import typing
+
+from ._models import Headers
+from ._types import CertTypes, HeaderTypes, TimeoutTypes
+from ._urls import URL
+
+if typing.TYPE_CHECKING:
+ import ssl # pragma: no cover
+
+__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"]
+
+
+class UnsetType:
+ pass # pragma: no cover
+
+
+UNSET = UnsetType()
+
+
+def create_ssl_context(
+ verify: ssl.SSLContext | str | bool = True,
+ cert: CertTypes | None = None,
+ trust_env: bool = True,
+) -> ssl.SSLContext:
+ import ssl
+ import warnings
+
+ import certifi
+
+ if verify is True:
+ if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover
+ ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"])
+ elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover
+ ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"])
+ else:
+ # Default case...
+ ctx = ssl.create_default_context(cafile=certifi.where())
+ elif verify is False:
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ ctx.check_hostname = False
+ ctx.verify_mode = ssl.CERT_NONE
+ elif isinstance(verify, str): # pragma: nocover
+ message = (
+ "`verify=` is deprecated. "
+ "Use `verify=ssl.create_default_context(cafile=...)` "
+ "or `verify=ssl.create_default_context(capath=...)` instead."
+ )
+ warnings.warn(message, DeprecationWarning)
+ if os.path.isdir(verify):
+ return ssl.create_default_context(capath=verify)
+ return ssl.create_default_context(cafile=verify)
+ else:
+ ctx = verify
+
+ if cert: # pragma: nocover
+ message = (
+ "`cert=...` is deprecated. Use `verify=` instead,"
+ "with `.load_cert_chain()` to configure the certificate chain."
+ )
+ warnings.warn(message, DeprecationWarning)
+ if isinstance(cert, str):
+ ctx.load_cert_chain(cert)
+ else:
+ ctx.load_cert_chain(*cert)
+
+ return ctx
+
+
+class Timeout:
+ """
+ Timeout configuration.
+
+ **Usage**:
+
+ Timeout(None) # No timeouts.
+ Timeout(5.0) # 5s timeout on all operations.
+ Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts.
+ Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere.
+ Timeout(5.0, pool=None) # No timeout on acquiring connection from pool.
+ # 5s timeout elsewhere.
+ """
+
+ def __init__(
+ self,
+ timeout: TimeoutTypes | UnsetType = UNSET,
+ *,
+ connect: None | float | UnsetType = UNSET,
+ read: None | float | UnsetType = UNSET,
+ write: None | float | UnsetType = UNSET,
+ pool: None | float | UnsetType = UNSET,
+ ) -> None:
+ if isinstance(timeout, Timeout):
+ # Passed as a single explicit Timeout.
+ assert connect is UNSET
+ assert read is UNSET
+ assert write is UNSET
+ assert pool is UNSET
+ self.connect = timeout.connect # type: typing.Optional[float]
+ self.read = timeout.read # type: typing.Optional[float]
+ self.write = timeout.write # type: typing.Optional[float]
+ self.pool = timeout.pool # type: typing.Optional[float]
+ elif isinstance(timeout, tuple):
+ # Passed as a tuple.
+ self.connect = timeout[0]
+ self.read = timeout[1]
+ self.write = None if len(timeout) < 3 else timeout[2]
+ self.pool = None if len(timeout) < 4 else timeout[3]
+ elif not (
+ isinstance(connect, UnsetType)
+ or isinstance(read, UnsetType)
+ or isinstance(write, UnsetType)
+ or isinstance(pool, UnsetType)
+ ):
+ self.connect = connect
+ self.read = read
+ self.write = write
+ self.pool = pool
+ else:
+ if isinstance(timeout, UnsetType):
+ raise ValueError(
+ "httpx.Timeout must either include a default, or set all "
+ "four parameters explicitly."
+ )
+ self.connect = timeout if isinstance(connect, UnsetType) else connect
+ self.read = timeout if isinstance(read, UnsetType) else read
+ self.write = timeout if isinstance(write, UnsetType) else write
+ self.pool = timeout if isinstance(pool, UnsetType) else pool
+
+ def as_dict(self) -> dict[str, float | None]:
+ return {
+ "connect": self.connect,
+ "read": self.read,
+ "write": self.write,
+ "pool": self.pool,
+ }
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return (
+ isinstance(other, self.__class__)
+ and self.connect == other.connect
+ and self.read == other.read
+ and self.write == other.write
+ and self.pool == other.pool
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ if len({self.connect, self.read, self.write, self.pool}) == 1:
+ return f"{class_name}(timeout={self.connect})"
+ return (
+ f"{class_name}(connect={self.connect}, "
+ f"read={self.read}, write={self.write}, pool={self.pool})"
+ )
+
+
+class Limits:
+ """
+ Configuration for limits to various client behaviors.
+
+ **Parameters:**
+
+ * **max_connections** - The maximum number of concurrent connections that may be
+ established.
+ * **max_keepalive_connections** - Allow the connection pool to maintain
+ keep-alive connections below this point. Should be less than or equal
+ to `max_connections`.
+ * **keepalive_expiry** - Time limit on idle keep-alive connections in seconds.
+ """
+
+ def __init__(
+ self,
+ *,
+ max_connections: int | None = None,
+ max_keepalive_connections: int | None = None,
+ keepalive_expiry: float | None = 5.0,
+ ) -> None:
+ self.max_connections = max_connections
+ self.max_keepalive_connections = max_keepalive_connections
+ self.keepalive_expiry = keepalive_expiry
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return (
+ isinstance(other, self.__class__)
+ and self.max_connections == other.max_connections
+ and self.max_keepalive_connections == other.max_keepalive_connections
+ and self.keepalive_expiry == other.keepalive_expiry
+ )
+
+ def __repr__(self) -> str:
+ class_name = self.__class__.__name__
+ return (
+ f"{class_name}(max_connections={self.max_connections}, "
+ f"max_keepalive_connections={self.max_keepalive_connections}, "
+ f"keepalive_expiry={self.keepalive_expiry})"
+ )
+
+
+class Proxy:
+ def __init__(
+ self,
+ url: URL | str,
+ *,
+ ssl_context: ssl.SSLContext | None = None,
+ auth: tuple[str, str] | None = None,
+ headers: HeaderTypes | None = None,
+ ) -> None:
+ url = URL(url)
+ headers = Headers(headers)
+
+ if url.scheme not in ("http", "https", "socks5", "socks5h"):
+ raise ValueError(f"Unknown scheme for proxy URL {url!r}")
+
+ if url.username or url.password:
+ # Remove any auth credentials from the URL.
+ auth = (url.username, url.password)
+ url = url.copy_with(username=None, password=None)
+
+ self.url = url
+ self.auth = auth
+ self.headers = headers
+ self.ssl_context = ssl_context
+
+ @property
+ def raw_auth(self) -> tuple[bytes, bytes] | None:
+ # The proxy authentication as raw bytes.
+ return (
+ None
+ if self.auth is None
+ else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8"))
+ )
+
+ def __repr__(self) -> str:
+ # The authentication is represented with the password component masked.
+ auth = (self.auth[0], "********") if self.auth else None
+
+ # Build a nice concise representation.
+ url_str = f"{str(self.url)!r}"
+ auth_str = f", auth={auth!r}" if auth else ""
+ headers_str = f", headers={dict(self.headers)!r}" if self.headers else ""
+ return f"Proxy({url_str}{auth_str}{headers_str})"
+
+
+DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
+DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
+DEFAULT_MAX_REDIRECTS = 20
diff --git a/lib/python3.12/site-packages/httpx/_content.py b/lib/python3.12/site-packages/httpx/_content.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f479a0885f723b7395843d41164a87041820776
--- /dev/null
+++ b/lib/python3.12/site-packages/httpx/_content.py
@@ -0,0 +1,240 @@
+from __future__ import annotations
+
+import inspect
+import warnings
+from json import dumps as json_dumps
+from typing import (
+ Any,
+ AsyncIterable,
+ AsyncIterator,
+ Iterable,
+ Iterator,
+ Mapping,
+)
+from urllib.parse import urlencode
+
+from ._exceptions import StreamClosed, StreamConsumed
+from ._multipart import MultipartStream
+from ._types import (
+ AsyncByteStream,
+ RequestContent,
+ RequestData,
+ RequestFiles,
+ ResponseContent,
+ SyncByteStream,
+)
+from ._utils import peek_filelike_length, primitive_value_to_str
+
+__all__ = ["ByteStream"]
+
+
+class ByteStream(AsyncByteStream, SyncByteStream):
+ def __init__(self, stream: bytes) -> None:
+ self._stream = stream
+
+ def __iter__(self) -> Iterator[bytes]:
+ yield self._stream
+
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ yield self._stream
+
+
+class IteratorByteStream(SyncByteStream):
+ CHUNK_SIZE = 65_536
+
+ def __init__(self, stream: Iterable[bytes]) -> None:
+ self._stream = stream
+ self._is_stream_consumed = False
+ self._is_generator = inspect.isgenerator(stream)
+
+ def __iter__(self) -> Iterator[bytes]:
+ if self._is_stream_consumed and self._is_generator:
+ raise StreamConsumed()
+
+ self._is_stream_consumed = True
+ if hasattr(self._stream, "read"):
+ # File-like interfaces should use 'read' directly.
+ chunk = self._stream.read(self.CHUNK_SIZE)
+ while chunk:
+ yield chunk
+ chunk = self._stream.read(self.CHUNK_SIZE)
+ else:
+ # Otherwise iterate.
+ for part in self._stream:
+ yield part
+
+
+class AsyncIteratorByteStream(AsyncByteStream):
+ CHUNK_SIZE = 65_536
+
+ def __init__(self, stream: AsyncIterable[bytes]) -> None:
+ self._stream = stream
+ self._is_stream_consumed = False
+ self._is_generator = inspect.isasyncgen(stream)
+
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ if self._is_stream_consumed and self._is_generator:
+ raise StreamConsumed()
+
+ self._is_stream_consumed = True
+ if hasattr(self._stream, "aread"):
+ # File-like interfaces should use 'aread' directly.
+ chunk = await self._stream.aread(self.CHUNK_SIZE)
+ while chunk:
+ yield chunk
+ chunk = await self._stream.aread(self.CHUNK_SIZE)
+ else:
+ # Otherwise iterate.
+ async for part in self._stream:
+ yield part
+
+
+class UnattachedStream(AsyncByteStream, SyncByteStream):
+ """
+ If a request or response is serialized using pickle, then it is no longer
+ attached to a stream for I/O purposes. Any stream operations should result
+ in `httpx.StreamClosed`.
+ """
+
+ def __iter__(self) -> Iterator[bytes]:
+ raise StreamClosed()
+
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ raise StreamClosed()
+ yield b"" # pragma: no cover
+
+
+def encode_content(
+ content: str | bytes | Iterable[bytes] | AsyncIterable[bytes],
+) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
+ if isinstance(content, (bytes, str)):
+ body = content.encode("utf-8") if isinstance(content, str) else content
+ content_length = len(body)
+ headers = {"Content-Length": str(content_length)} if body else {}
+ return headers, ByteStream(body)
+
+ elif isinstance(content, Iterable) and not isinstance(content, dict):
+ # `not isinstance(content, dict)` is a bit oddly specific, but it
+ # catches a case that's easy for users to make in error, and would
+ # otherwise pass through here, like any other bytes-iterable,
+ # because `dict` happens to be iterable. See issue #2491.
+ content_length_or_none = peek_filelike_length(content)
+
+ if content_length_or_none is None:
+ headers = {"Transfer-Encoding": "chunked"}
+ else:
+ headers = {"Content-Length": str(content_length_or_none)}
+ return headers, IteratorByteStream(content) # type: ignore
+
+ elif isinstance(content, AsyncIterable):
+ headers = {"Transfer-Encoding": "chunked"}
+ return headers, AsyncIteratorByteStream(content)
+
+ raise TypeError(f"Unexpected type for 'content', {type(content)!r}")
+
+
+def encode_urlencoded_data(
+ data: RequestData,
+) -> tuple[dict[str, str], ByteStream]:
+ plain_data = []
+ for key, value in data.items():
+ if isinstance(value, (list, tuple)):
+ plain_data.extend([(key, primitive_value_to_str(item)) for item in value])
+ else:
+ plain_data.append((key, primitive_value_to_str(value)))
+ body = urlencode(plain_data, doseq=True).encode("utf-8")
+ content_length = str(len(body))
+ content_type = "application/x-www-form-urlencoded"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, ByteStream(body)
+
+
+def encode_multipart_data(
+ data: RequestData, files: RequestFiles, boundary: bytes | None
+) -> tuple[dict[str, str], MultipartStream]:
+ multipart = MultipartStream(data=data, files=files, boundary=boundary)
+ headers = multipart.get_headers()
+ return headers, multipart
+
+
+def encode_text(text: str) -> tuple[dict[str, str], ByteStream]:
+ body = text.encode("utf-8")
+ content_length = str(len(body))
+ content_type = "text/plain; charset=utf-8"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, ByteStream(body)
+
+
+def encode_html(html: str) -> tuple[dict[str, str], ByteStream]:
+ body = html.encode("utf-8")
+ content_length = str(len(body))
+ content_type = "text/html; charset=utf-8"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, ByteStream(body)
+
+
+def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]:
+ body = json_dumps(
+ json, ensure_ascii=False, separators=(",", ":"), allow_nan=False
+ ).encode("utf-8")
+ content_length = str(len(body))
+ content_type = "application/json"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, ByteStream(body)
+
+
+def encode_request(
+ content: RequestContent | None = None,
+ data: RequestData | None = None,
+ files: RequestFiles | None = None,
+ json: Any | None = None,
+ boundary: bytes | None = None,
+) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
+ """
+ Handles encoding the given `content`, `data`, `files`, and `json`,
+ returning a two-tuple of (, ).
+ """
+ if data is not None and not isinstance(data, Mapping):
+ # We prefer to separate `content=`
+ # for raw request content, and `data=