diff --git a/.devcontainer/msteams-FAQPlus/devcontainer.json b/.devcontainer/msteams-FAQPlus/devcontainer.json new file mode 100644 index 0000000000..4d5bcc365d --- /dev/null +++ b/.devcontainer/msteams-FAQPlus/devcontainer.json @@ -0,0 +1,42 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye", + + // Default workspace folder + "workspaceFolder": "/workspaces/Microsoft-Teams-Samples/samples/msteams-FAQPlus", + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/devcontainers/features/rust:1": {}, + "ghcr.io/devcontainers-contrib/features/poetry:2": {}, + "ghcr.io/devcontainers/features/azure-cli:1": {} + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "onCreateCommand": "poetry install && poetry build", + + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "TeamsDevApp.ms-teams-vscode-extension" + ], + "settings": { + "python.defaultInterpreterPath": ".venv/bin/python" // Point to the Python interpreter in the .venv directory + } + } + } + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/samples/msteams-FAQPlus/.gitignore b/samples/msteams-FAQPlus/.gitignore new file mode 100644 index 0000000000..0723e2a716 --- /dev/null +++ b/samples/msteams-FAQPlus/.gitignore @@ -0,0 +1,16 @@ +# Teams Toolkit +.env +env/*local* +env/*user* +appPackage/build +.deployment + +src/requirements.txt + +# Python stuff +.venv +dist +src/__pycache__ +src/cards/__pycache__ +src/indexer/__pycache__ +__pycache__ diff --git a/samples/msteams-FAQPlus/.pylintrc b/samples/msteams-FAQPlus/.pylintrc new file mode 100644 index 0000000000..0e8543fa9c --- /dev/null +++ b/samples/msteams-FAQPlus/.pylintrc @@ -0,0 +1,640 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.8 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +# docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=ai, + ci, + sk, + i, + j, + e, + o, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + C0116, + C0115, + R0903, + W0212 + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/samples/msteams-FAQPlus/.vscode/launch.json b/samples/msteams-FAQPlus/.vscode/launch.json new file mode 100644 index 0000000000..da07064c2b --- /dev/null +++ b/samples/msteams-FAQPlus/.vscode/launch.json @@ -0,0 +1,102 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Start", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/src/app.py", + "console": "integratedTerminal", + } + ], + "compounds": [ + { + "name": "Debug (Edge)", + "configurations": [ + "Launch App (Edge)", + "Start" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "all", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Start" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "all", + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug", + "configurations": [ + "Start" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "all", + "order": 3 + }, + "stopAll": true + } + ] +} \ No newline at end of file diff --git a/samples/msteams-FAQPlus/.vscode/settings.json b/samples/msteams-FAQPlus/.vscode/settings.json new file mode 100644 index 0000000000..97c87102f7 --- /dev/null +++ b/samples/msteams-FAQPlus/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.terminal.activateEnvironment": true, + "python.terminal.activateEnvInCurrentTerminal": true, + "python.analysis.typeCheckingMode": "basic", +} diff --git a/samples/msteams-FAQPlus/.vscode/tasks.json b/samples/msteams-FAQPlus/.vscode/tasks.json new file mode 100644 index 0000000000..25fde9bd53 --- /dev/null +++ b/samples/msteams-FAQPlus/.vscode/tasks.json @@ -0,0 +1,80 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + } + ] +} \ No newline at end of file diff --git a/samples/msteams-FAQPlus/.webappignore b/samples/msteams-FAQPlus/.webappignore new file mode 100644 index 0000000000..4c49bcdf28 --- /dev/null +++ b/samples/msteams-FAQPlus/.webappignore @@ -0,0 +1,9 @@ +.venv/ +.vscode/ +.env +env/ +README.md +teamsapp.yml +teamsapp.local.yml +__pycache__ +.mypy_cache \ No newline at end of file diff --git a/samples/msteams-FAQPlus/README.md b/samples/msteams-FAQPlus/README.md new file mode 100644 index 0000000000..2ad5cb15b9 --- /dev/null +++ b/samples/msteams-FAQPlus/README.md @@ -0,0 +1,141 @@ +--- +page_type: sample +description: This sample showcases a Teams app template that answers queries based on a custom database using retrieval-augmented generation (RAG) with Azure OpenAI and Azure AI Search or connects you to an expert from a Teams Channel. +products: +- office-teams +languages: +- python +extensions: + contentType: samples + createdDate: "07/30/2024 12:54 PM" +urlFragment: officedev-microsoft-teams-samples-msteams-faqplus + +--- + +# FAQ+ Bot +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=322043759&devcontainer_path=.devcontainer%2Fmsteams-FAQPlus%2Fdevcontainer.json&resume=1) + +This sample implements a chatbot that answers queries based on a custom database using retrieval-augmented generation (RAG) with Azure OpenAI and Azure AI Search and escalates queries to a Teams channel when asked. +- The user can set up a datasource such as Azure AI Search, Azure Blob Storage, URL/web address, Azure Cosmos DB for MongoDB vCore, uploaded files, or Elasticsearch. +- The user can then interact with the data through the bot. +- The bot sends an adaptive card using proactive messaging to a Teams Channel if asked. +- Experts can then engage with users and update the knowledge base dynamically. + +## Included Features +* **Azure Open AI:** Azure OpenAI On Your Data leverages OpenAI's language models, like GPT-3.5 and GPT-4, augmented with RAG to provide a conversational interface for answering queries from your domain data using the chat completions API. +* **Azure AI Search:** Azure AI Search is utilized as a data source for Azure OpenAI's chat completions API. +* **Proactive Messaging:** Proactive messaging is used to send a message to the Teams channel by initiating a conversation. +* **Adaptive Cards:** Adaptive cards are used to send the conversation history to the Teams channel and allow for actions such as "chat with user" or "close ticket". + +## Interaction with the app + ![bot-in-action](images/app.gif) + +## Set up and run the sample app in Github Codespaces +1. Click **Open in GitHub Codespaces** badge above to create a codespace for the sample app. Wait for the codespace to be setup, it may take a couple of minutes. +2. Using the Teams Toolkit extension, sign in to your Microsoft 365 account and Azure account under ```ACCOUNTS```. +3. [Set up your knowledge base using Azure AI resources](#set-up-your-knowledge-base). +4. [Populate the environment files](#populate-the-environment-files). +5. Press **Ctrl+Shift+D** to open the ```Run and Debug``` menu. Select ```Debug``` and press ```F5``` or click on the play button. +6. Download the zip file ```appPackage/build/appPackage.local.zip``` and [sideload the app to Teams personal chat](#sideload-the-app-to-teams-personal-chat) and [sideload the app to Teams Channel](#sideload-the-app-to-teams-channel) +7. You can now [run the sample app](#run-the-sample-app). + +## Set up and run the sample app locally +### Prerequisites +- [Python 3.11](https://www.python.org/downloads/) +- [Node.js](https://nodejs.org/) +- [Rust](https://www.rust-lang.org/tools/install) +- [Poetry](https://python-poetry.org/docs/#installation) +- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) +- [Visual Studio Code](https://code.visualstudio.com/download) +- [Teams Toolkit Extension ](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) +- [Python Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) + +1. Clone the repository + ```git clone https://github.com/t-mbindra/chat-with-your-data.git``` +2. Open the project folder(Microsoft-Teams-Samples/samples/msteams-chat-with-your-data) in VS Code. +3. Run + ```poetry install``` +4. Run + ```poetry build``` +5. Using the Teams Toolkit extension, sign in to your Microsoft 365 account and Azure account under ```ACCOUNTS```. +6. [Set up your knowledge base using Azure AI resources](#set-up-your-knowledge-base). +7. [Populate the environment files](#populate-the-environment-files). +8. Press **Ctrl+Shift+D** to open the ```Run and Debug``` menu. Press ```F5``` or click on the play button. Also, [sideload the app to Teams Channel](#sideload-the-app-to-teams-channel) +9. You can now [run the sample app](#run-the-sample-app). + +>[!Note] +> Check the status of all your local(Debug/F5 flow) bots on [Microsoft Bot Framework](https://dev.botframework.com/bots). +> Check the status of all your Teams apps on [Teams Developer Portal](https://dev.teams.microsoft.com/apps). +> Teams toolkit will also generate an app registration along with a password which can be seen under **App Registrations** on the Azure portal. + +## Set up your knowledge base +1. Run```sh deploy.sh``` in the terminal to deploy Azure Saerch and Azure OpenAI. You will be prompted to login to Azure and select a subscription. +2. Add all your files to the ```src/data``` folder and URLs to ```src/indexer/URL.txt``` and run ```python ./src/indexer/setup.py```. +3. To delete the exisiting index, run ```python .src/indexer/delete.py```. + +## Populate the environment files +1. You need to populate the environment variables in ```env/.env.local.user``` if you are using the ```Debug``` or ```F5``` flow. Else, populate the environment variables in ```env/.env.dev.user``` if you are dpleoying the app on Azure. +2. Go to the the [Azure portal](https://ms.portal.azure.com/) and navigate to the resource group ```FaqBot```. +3. To populate ```SECRET_AZURE_OPENAI_KEY, SECRET_AZURE_OPENAI_ENDPOINT``` variables: + - Go to the ```faqbot-cognitive``` resource. Select the ```Keys and Endpoints``` tab under ```Resource Management```. Use ```Key 1``` and ```Endpoint```. +4. To populate ```SECRET_AZURE_SEARCH_ENDPOINT``` variable: + - Go to the ```faqbot-search``` resource. Use the ```Url``` given. +5. To populate ```SECRET_AZURE_SEARCH_KEY``` variable: + - Go to the ```faqbot-search``` resource. Select the ```Keys``` tab under ```Settings```. Use ```Primary admin key```. +6. Set the environment variable ```TEAMS_CHANNEL_ID``` from the link of the channel - https://teams.microsoft.com/l/channel//. Remember to first decode the URL-encoded string. + +## Sideload the app to Teams personal chat: +1. Go to your Teams app and click on the ```Apps``` icon. Select ```Manage your apps``` followed by ```Upload an app```. +2. Select ```Upload a custom app``` and open the relevant zip file. Click on ```Add``` when prompted. + +## Sideload the app to Teams channel: +1. Go to the channel where you want to raise requests for help. Click on the ```Apps``` icon. Select ```Got more apps``` followed by ```Manage your apps```. +2. Click on ```Upload an app``` and open the relevant zip file. Click on ```Add``` when prompted. Select the correct channel and click on ```Go``` + +> If you do not have permission to upload custom apps (sideloading), Teams Toolkit will recommend creating and using a Microsoft 365 Developer Program account - a free program to get your own dev environment sandbox that includes Teams. + +## Run the sample app +1. The bot sends a welcome message. + ![welcome-message](images/welcome.png) +2. User chats with the bot. + ![chat-with-the-bot](images/chat.png) +3. User wants to talk to an expert. + ![talk-to-an-expert](images/expert.png) +4. The query and brief conversation history is posted to the Teams channel. + ![post-query-on-channel](images/channel.png) +5. Anyone on the Teams channel can assign the ticket to themselves. + ![assign-ticket-to-me](images/assign.png) +6. Anyone on the Teams channel can chat with the user. + ![chat-with-user](images/chat_with_user.png) +7. Anyone on the Teams channel can close the ticket. + ![close-ticket](images/close.png) +8. User clears the chat history. + ![clear-conversation-history](images/clear.png) + +## How the knowledge base works +![FAQBot Setup](images/architecture.png) +1. **User Query**: + - The user asks the bot a question. +2. **Prompt Generation**: + - The bot generates a prompt based on the user's question. +3. **Azure AI Search**: + - The prompt is sent as a query to Azure AI Search to find relevant information. + - The top ranked search results are sent to the LLM. +4. **Azure OpenAI**: + - The promt is also sent to Azure OpenAI model to set the context and intent. + - The model provides a generative response which is sent back to the user. + +## Deploy/Host the app on Azure +Instead of the ```Debug``` or ```F5``` flow, you can deploy the app on Azure: +1. [Populate the environment files](#populate-the-environment-files). +2. Using the Teams Toolkit Extension tab, click on ```Provision``` followed by ```DEPLOY``` under ```LIFECYCLE```. You will be asked to select the subscription and resource group for provisioning. +3. Using the zip file ```appPackage/build/appPackage.dev.zip```, [sideload the app to Teams personal chat](#sideload-the-app-to-teams-personal-chat) and [sideload the app to Teams Channel](#sideload-the-app-to-teams-channel) + +>[!Note] +> Check the status of your dev(hosted on Azure) bots on [Azure Portal](https://portal.azure.com/#home) by navigating to the relevant resource group. +> Check the status of all your Teams apps on [Teams Developer Portal](https://dev.teams.microsoft.com/apps). +> Teams toolkit will also generate an app registration along with a password which can be seen under **App Registrations** on the Azure portal. + +## Further Reading +- [Azure OpenAI On Your Data](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data?tabs=ai-search%2Ccopilot) +- [Azure AI search](https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search) \ No newline at end of file diff --git a/samples/msteams-FAQPlus/appPackage/color.png b/samples/msteams-FAQPlus/appPackage/color.png new file mode 100644 index 0000000000..f27ccf2036 Binary files /dev/null and b/samples/msteams-FAQPlus/appPackage/color.png differ diff --git a/samples/msteams-FAQPlus/appPackage/manifest.json b/samples/msteams-FAQPlus/appPackage/manifest.json new file mode 100644 index 0000000000..c85bc56f21 --- /dev/null +++ b/samples/msteams-FAQPlus/appPackage/manifest.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "version": "1.0.0", + "manifestVersion": "1.16", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.package.name", + "name": { + "short": "FaqBot-${{TEAMSFX_ENV}}", + "full": "FAQ Bot" + }, + "developer": { + "name": "Microsoft", + "mpnId": "", + "websiteUrl": "https://teams.microsoft.com", + "privacyUrl": "https://privacy.microsoft.com/privacystatement", + "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" + }, + "description": { + "short": "Custom copilot to chat with your data or talk to an expert", + "full": "Custom copilot to chat with your data via RAG through Azure OpenAI On Your Data and Azure AI Search or talk to an expert from a Teams Channel" + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#FFFFFF", + "staticTabs": [ + { + "entityId": "conversations", + "scopes": ["personal"] + }, + { + "entityId": "about", + "scopes": ["personal"] + } + ], + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": ["personal", "team", "groupChat"], + "isNotificationOnly": false, + "supportsCalling": false, + "supportsVideo": false, + "supportsFiles": false + } + ], + "validDomains": [ + "${{BOT_DOMAIN}}" + ] +} diff --git a/samples/msteams-FAQPlus/appPackage/outline.png b/samples/msteams-FAQPlus/appPackage/outline.png new file mode 100644 index 0000000000..e8cb4b6ba4 Binary files /dev/null and b/samples/msteams-FAQPlus/appPackage/outline.png differ diff --git a/samples/msteams-FAQPlus/deploy.sh b/samples/msteams-FAQPlus/deploy.sh new file mode 100644 index 0000000000..24de725c86 --- /dev/null +++ b/samples/msteams-FAQPlus/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Azure CLI login +az login --use-device-code + +az group create -l westus -n FaqBot + +# Deploy ARM template using Azure CLI +az deployment group create \ + --resource-group FaqBot \ + --template-file ./infra/azure.ai.resources.json \ No newline at end of file diff --git a/samples/msteams-FAQPlus/env/.env.dev b/samples/msteams-FAQPlus/env/.env.dev new file mode 100644 index 0000000000..2f7601d422 --- /dev/null +++ b/samples/msteams-FAQPlus/env/.env.dev @@ -0,0 +1,12 @@ +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Populated by Teams Toolkit during provisioning +RESOURCE_SUFFIX= +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +BOT_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/msteams-FAQPlus/env/.env.dev.user b/samples/msteams-FAQPlus/env/.env.dev.user new file mode 100644 index 0000000000..99fff88d22 --- /dev/null +++ b/samples/msteams-FAQPlus/env/.env.dev.user @@ -0,0 +1,18 @@ +# This file includes environment variables that will not be committed to git by default. +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. + +# This is automatically updated. +SECRET_BOT_PASSWORD= +TEAMS_APP_UPDATE_TIME= + +# Provide the channel id +TEAMS_CHANNEL_ID= + +# Provide the following key/endpoints. +SECRET_AZURE_OPENAI_KEY= +SECRET_AZURE_OPENAI_ENDPOINT= +SECRET_AZURE_SEARCH_ENDPOINT= +SECRET_AZURE_SEARCH_KEY= + +AZURE_SEARCH_INDEX=faq-index +AZURE_OPENAI_MODEL=gpt-4o \ No newline at end of file diff --git a/samples/msteams-FAQPlus/env/.env.local b/samples/msteams-FAQPlus/env/.env.local new file mode 100644 index 0000000000..841befeb28 --- /dev/null +++ b/samples/msteams-FAQPlus/env/.env.local @@ -0,0 +1,9 @@ +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= +TEAMS_APP_TENANT_ID= \ No newline at end of file diff --git a/samples/msteams-FAQPlus/env/.env.local.user b/samples/msteams-FAQPlus/env/.env.local.user new file mode 100644 index 0000000000..3ce9a5a884 --- /dev/null +++ b/samples/msteams-FAQPlus/env/.env.local.user @@ -0,0 +1,18 @@ +# This file includes environment variables that will not be committed to git by default. +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. + +# This is automatically updated. +SECRET_BOT_PASSWORD= +TEAMS_APP_UPDATE_TIME= + +# Provide the channel id +TEAMS_CHANNEL_ID= + +# Provide the following key/endpoints. +SECRET_AZURE_OPENAI_KEY= +SECRET_AZURE_OPENAI_ENDPOINT= +SECRET_AZURE_SEARCH_ENDPOINT= +SECRET_AZURE_SEARCH_KEY= + +AZURE_SEARCH_INDEX=faq-index +AZURE_OPENAI_MODEL=gpt-4o \ No newline at end of file diff --git a/samples/msteams-FAQPlus/images/app.gif b/samples/msteams-FAQPlus/images/app.gif new file mode 100644 index 0000000000..04026d84e3 Binary files /dev/null and b/samples/msteams-FAQPlus/images/app.gif differ diff --git a/samples/msteams-FAQPlus/images/architecture.png b/samples/msteams-FAQPlus/images/architecture.png new file mode 100644 index 0000000000..b6e5b408d0 Binary files /dev/null and b/samples/msteams-FAQPlus/images/architecture.png differ diff --git a/samples/msteams-FAQPlus/images/assign.png b/samples/msteams-FAQPlus/images/assign.png new file mode 100644 index 0000000000..a01573ac4e Binary files /dev/null and b/samples/msteams-FAQPlus/images/assign.png differ diff --git a/samples/msteams-FAQPlus/images/channel.png b/samples/msteams-FAQPlus/images/channel.png new file mode 100644 index 0000000000..1f0278c4bb Binary files /dev/null and b/samples/msteams-FAQPlus/images/channel.png differ diff --git a/samples/msteams-FAQPlus/images/chat.png b/samples/msteams-FAQPlus/images/chat.png new file mode 100644 index 0000000000..1badd3aa45 Binary files /dev/null and b/samples/msteams-FAQPlus/images/chat.png differ diff --git a/samples/msteams-FAQPlus/images/chat_with_user.png b/samples/msteams-FAQPlus/images/chat_with_user.png new file mode 100644 index 0000000000..10cb8c50b9 Binary files /dev/null and b/samples/msteams-FAQPlus/images/chat_with_user.png differ diff --git a/samples/msteams-FAQPlus/images/clear.png b/samples/msteams-FAQPlus/images/clear.png new file mode 100644 index 0000000000..0b4414bed5 Binary files /dev/null and b/samples/msteams-FAQPlus/images/clear.png differ diff --git a/samples/msteams-FAQPlus/images/close.png b/samples/msteams-FAQPlus/images/close.png new file mode 100644 index 0000000000..f83ab85c68 Binary files /dev/null and b/samples/msteams-FAQPlus/images/close.png differ diff --git a/samples/msteams-FAQPlus/images/expert.png b/samples/msteams-FAQPlus/images/expert.png new file mode 100644 index 0000000000..62fb6217ae Binary files /dev/null and b/samples/msteams-FAQPlus/images/expert.png differ diff --git a/samples/msteams-FAQPlus/images/welcome.png b/samples/msteams-FAQPlus/images/welcome.png new file mode 100644 index 0000000000..c2b847990e Binary files /dev/null and b/samples/msteams-FAQPlus/images/welcome.png differ diff --git a/samples/msteams-FAQPlus/infra/azure.ai.resources.json b/samples/msteams-FAQPlus/infra/azure.ai.resources.json new file mode 100644 index 0000000000..b71a1bf9a3 --- /dev/null +++ b/samples/msteams-FAQPlus/infra/azure.ai.resources.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "searchServices_name": { + "defaultValue": "faqbot-search", + "type": "string" + }, + "accounts_azure_openai_name": { + "defaultValue": "faqbot-cognitive", + "type": "string" + }, + "location":{ + "defaultValue": "west us", + "type": "string" + }, + "capacity":{ + "defaultValue": 50, + "type": "int" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-04-01-preview", + "name": "[parameters('accounts_azure_openai_name')]", + "location": "[parameters('location')]", + "sku": { + "name": "S0" + }, + "kind": "OpenAI", + "identity": { + "type": "SystemAssigned" + }, + "properties": {} + }, + { + "type": "Microsoft.Search/searchServices", + "apiVersion": "2024-03-01-preview", + "name": "[parameters('searchServices_name')]", + "location": "[parameters('location')]", + "sku": { + "name": "standard" + }, + "properties": {} + }, + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-04-01-preview", + "name": "[concat(parameters('accounts_azure_openai_name'), '/gpt-4o')]", + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('accounts_azure_openai_name'))]" + ], + "sku": { + "name": "Standard", + "capacity": "[parameters('capacity')]" + }, + "properties": { + "model": { + "format": "OpenAI", + "name": "gpt-4o", + "version": "2024-05-13" + }, + "versionUpgradeOption": "OnceCurrentVersionExpired", + "currentCapacity": 150, + "raiPolicyName": "Microsoft.Default" + } + } + ] +} diff --git a/samples/msteams-FAQPlus/infra/azure.bicep b/samples/msteams-FAQPlus/infra/azure.bicep new file mode 100644 index 0000000000..7e2c3b4aef --- /dev/null +++ b/samples/msteams-FAQPlus/infra/azure.bicep @@ -0,0 +1,130 @@ +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +param azureOpenAIKey string = '' + +@secure() +param azureOpenAIEndpoint string = '' + +@secure() +param azureSearchKey string = '' + +@secure() +param azureSearchEndpoint string = '' + +param azureSearchIndex string = '' + +param azureOpenAIModel string = '' + +param teamsChannelId string = '' + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +param webAppSKU string +param linuxFxVersion string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location +param pythonVersion string = linuxFxVersion + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app,linux' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } + properties:{ + reserved: true + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app,linux' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appCommandLine: 'gunicorn --bind 0.0.0.0 --worker-class aiohttp.worker.GunicornWebWorker --timeout 600 api:api' + linuxFxVersion: pythonVersion + appSettings: [ + { + name: 'SCM_DO_BUILD_DURING_DEPLOYMENT' + value: 'true' + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + { + name: 'AZURE_OPENAI_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_SEARCH_KEY' + value: azureSearchKey + } + { + name: 'AZURE_SEARCH_ENDPOINT' + value: azureSearchEndpoint + } + { + name: 'AZURE_SEARCH_INDEX' + value: azureSearchIndex + } + { + name: 'AZURE_OPENAI_MODEL' + value: azureOpenAIModel + } + { + name: 'TEAMS_CHANNEL_ID' + value: teamsChannelId + } + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/samples/msteams-FAQPlus/infra/azure.parameters.json b/samples/msteams-FAQPlus/infra/azure.parameters.json new file mode 100644 index 0000000000..6bb3aa4161 --- /dev/null +++ b/samples/msteams-FAQPlus/infra/azure.parameters.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "FaqBot-${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "FaqBot" + }, + "linuxFxVersion": { + "value": "PYTHON|3.11" + }, + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{SECRET_AZURE_OPENAI_ENDPOINT}}" + }, + "azureSearchKey": { + "value": "${{SECRET_AZURE_SEARCH_KEY}}" + }, + "azureSearchEndpoint": { + "value": "${{SECRET_AZURE_SEARCH_ENDPOINT}}" + }, + "azureSearchIndex": { + "value": "${{AZURE_SEARCH_INDEX}}" + }, + "azureOpenAIModel": { + "value": "${{AZURE_OPENAI_MODEL}}" + }, + "teamsChannelId": { + "value": "${{TEAMS_CHANNEL_ID}}" + } + } +} diff --git a/samples/msteams-FAQPlus/infra/botRegistration/azurebot.bicep b/samples/msteams-FAQPlus/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..e9d8e49050 --- /dev/null +++ b/samples/msteams-FAQPlus/infra/botRegistration/azurebot.bicep @@ -0,0 +1,36 @@ +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/msteams-FAQPlus/infra/botRegistration/readme.md b/samples/msteams-FAQPlus/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/msteams-FAQPlus/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/msteams-FAQPlus/poetry.lock b/samples/msteams-FAQPlus/poetry.lock new file mode 100644 index 0000000000..5ef5de9218 --- /dev/null +++ b/samples/msteams-FAQPlus/poetry.lock @@ -0,0 +1,2965 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.9.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "aiounittest" +version = "1.3.0" +description = "Test asyncio code more easily." +optional = false +python-versions = "*" +files = [ + {file = "aiounittest-1.3.0.tar.gz", hash = "sha256:e847482b85c2b01fa8c99ffc9606811257448899707b7c7ec06e0f11c019603f"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "azure-ai-contentsafety" +version = "1.0.0" +description = "Microsoft Azure AI Content Safety Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-ai-contentsafety-1.0.0.tar.gz", hash = "sha256:052731bd1419a720fa00910f46bf3428c4e5bd05280da7393d0c8106d46cc6d7"}, + {file = "azure_ai_contentsafety-1.0.0-py3-none-any.whl", hash = "sha256:e1c5574a541f9290fdd071d23535e14b1f463af231a6f0ac0f917e125f0463cf"}, +] + +[package.dependencies] +azure-core = ">=1.28.0,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + +[[package]] +name = "azure-common" +version = "1.1.28" +description = "Microsoft Azure Client Library for Python (Common)" +optional = false +python-versions = "*" +files = [ + {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, + {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, +] + +[[package]] +name = "azure-core" +version = "1.30.2" +description = "Microsoft Azure Core Library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "azure-core-1.30.2.tar.gz", hash = "sha256:a14dc210efcd608821aa472d9fb8e8d035d29b68993819147bc290a8ac224472"}, + {file = "azure_core-1.30.2-py3-none-any.whl", hash = "sha256:cf019c1ca832e96274ae85abd3d9f752397194d9fea3b41487290562ac8abe4a"}, +] + +[package.dependencies] +requests = ">=2.21.0" +six = ">=1.11.0" +typing-extensions = ">=4.6.0" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + +[[package]] +name = "azure-identity" +version = "1.17.1" +description = "Microsoft Azure Identity Library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "azure-identity-1.17.1.tar.gz", hash = "sha256:32ecc67cc73f4bd0595e4f64b1ca65cd05186f4fe6f98ed2ae9f1aa32646efea"}, + {file = "azure_identity-1.17.1-py3-none-any.whl", hash = "sha256:db8d59c183b680e763722bfe8ebc45930e6c57df510620985939f7f3191e0382"}, +] + +[package.dependencies] +azure-core = ">=1.23.0" +cryptography = ">=2.5" +msal = ">=1.24.0" +msal-extensions = ">=0.3.0" +typing-extensions = ">=4.0.0" + +[[package]] +name = "azure-search-documents" +version = "11.5.0" +description = "Microsoft Azure Cognitive Search Client Library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "azure_search_documents-11.5.0-py3-none-any.whl", hash = "sha256:e973df13fc46c1c1bfa3760d74b45ca6ef928816e0f8c7df0c68171a428365ac"}, + {file = "azure_search_documents-11.5.0.tar.gz", hash = "sha256:13883d12c833b66546c188cce847de6fa67b83545cb21163d8956ac031e53cf8"}, +] + +[package.dependencies] +azure-common = ">=1.1" +azure-core = ">=1.28.0" +isodate = ">=0.6.0" +typing-extensions = ">=4.6.0" + +[[package]] +name = "babel" +version = "2.9.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, +] + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "botbuilder-core" +version = "4.16.1" +description = "Microsoft Bot Framework Bot Builder" +optional = false +python-versions = "*" +files = [ + {file = "botbuilder_core-4.16.1-py3-none-any.whl", hash = "sha256:61943b16f2c43adf29c2d3cd446c9dce02e9bad17b8e49f937e1a6728da5f455"}, +] + +[package.dependencies] +botbuilder-schema = "4.16.1" +botframework-connector = "4.16.1" +botframework-streaming = "4.16.1" +jsonpickle = ">=1.2,<1.5" + +[[package]] +name = "botbuilder-dialogs" +version = "4.16.1" +description = "Microsoft Bot Framework Bot Builder" +optional = false +python-versions = "*" +files = [ + {file = "botbuilder_dialogs-4.16.1-py3-none-any.whl", hash = "sha256:6469cc529ffc6ae07ea0697d97fdfa7805c9c1d95cd99d3426803ec2eb03eed8"}, +] + +[package.dependencies] +aiounittest = "1.3.0" +babel = "2.9.1" +botbuilder-core = "4.16.1" +botbuilder-schema = "4.16.1" +botframework-connector = "4.16.1" +emoji = "1.7.0" +recognizers-text = ">=1.0.2a1" +recognizers-text-choice = ">=1.0.2a1" +recognizers-text-date-time = ">=1.0.2a1" +recognizers-text-number = ">=1.0.2a1" +recognizers-text-number-with-unit = ">=1.0.2a1" +regex = ">=2022.1.18" + +[[package]] +name = "botbuilder-integration-aiohttp" +version = "4.16.1" +description = "Microsoft Bot Framework Bot Builder" +optional = false +python-versions = "*" +files = [ + {file = "botbuilder_integration_aiohttp-4.16.1-py3-none-any.whl", hash = "sha256:e7a14499762900eb25b94610586f831f9232302ffe528a6d8b658561bcd7979d"}, +] + +[package.dependencies] +aiohttp = "3.9.5" +botbuilder-core = "4.16.1" +botbuilder-schema = "4.16.1" +botframework-connector = "4.16.1" +yarl = ">=1.8.1" + +[[package]] +name = "botbuilder-schema" +version = "4.16.1" +description = "BotBuilder Schema" +optional = false +python-versions = "*" +files = [ + {file = "botbuilder_schema-4.16.1-py2.py3-none-any.whl", hash = "sha256:68288ab661f537046a14aaea9801798ee0e6e7ef93a981ec564908a3fb260b4c"}, +] + +[package.dependencies] +msrest = "==0.7.*" +urllib3 = "<2.0.0" + +[[package]] +name = "botframework-connector" +version = "4.16.1" +description = "Microsoft Bot Framework Bot Builder SDK for Python." +optional = false +python-versions = "*" +files = [ + {file = "botframework_connector-4.16.1-py2.py3-none-any.whl", hash = "sha256:4f49aa19773f277ef920cc676618a9757cf7df20f78ccbd519f2e78c3e726123"}, +] + +[package.dependencies] +botbuilder-schema = "4.16.1" +msal = ">=1.29.0" +msrest = "==0.7.*" +PyJWT = ">=2.4.0" + +[[package]] +name = "botframework-streaming" +version = "4.16.1" +description = "Microsoft Bot Framework Bot Builder" +optional = false +python-versions = "*" +files = [ + {file = "botframework_streaming-4.16.1-py3-none-any.whl", hash = "sha256:04e035bb50580a9ebddaca46066cd3bf34357ca7126b125136b51d69c934290a"}, +] + +[package.dependencies] +botbuilder-schema = "4.16.1" +botframework-connector = "4.16.1" + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "43.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, + {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, + {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, + {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, + {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, + {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, + {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, + {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + +[[package]] +name = "datedelta" +version = "1.4" +description = "Like datetime.timedelta, for date arithmetic." +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "datedelta-1.4-py3-none-any.whl", hash = "sha256:9f7a8a2bd80d29d6cfe1036990979ea404c8e1ddbc5a73cff74d283186d12b4d"}, + {file = "datedelta-1.4.tar.gz", hash = "sha256:3f1ef319ead642a76a3cab731917bf14a0ced0d91943f33ff57ae615837cab97"}, +] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "emoji" +version = "1.7.0" +description = "Emoji for Python" +optional = false +python-versions = "*" +files = [ + {file = "emoji-1.7.0.tar.gz", hash = "sha256:65c54533ea3c78f30d0729288998715f418d7467de89ec258a31c0ce8660a1d1"}, +] + +[package.extras] +dev = ["coverage", "coveralls", "pytest"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "grapheme" +version = "0.6.0" +description = "Unicode grapheme helpers" +optional = false +python-versions = "*" +files = [ + {file = "grapheme-0.6.0.tar.gz", hash = "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca"}, +] + +[package.extras] +test = ["pytest", "sphinx", "sphinx-autobuild", "twine", "wheel"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-resources" +version = "6.4.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jsonpickle" +version = "1.4.2" +description = "Python library for serializing any arbitrary object graph into JSON" +optional = false +python-versions = ">=2.7" +files = [ + {file = "jsonpickle-1.4.2-py2.py3-none-any.whl", hash = "sha256:2ac5863099864c63d7f0c367af5e512c94f3384977dd367f2eae5f2303f7b92c"}, + {file = "jsonpickle-1.4.2.tar.gz", hash = "sha256:c9b99b28a9e6a3043ec993552db79f4389da11afcb1d0246d93c79f4b5e64062"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["coverage (<5)", "ecdsa", "enum34", "feedparser", "jsonlib", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8", "sqlalchemy"] +testing-libs = ["demjson", "simplejson", "ujson", "yajl"] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +jsonschema-specifications = ">=2023.03.6" +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +referencing = ">=0.31.0" + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + +[[package]] +name = "lxml" +version = "5.2.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.10)"] + +[[package]] +name = "marshmallow" +version = "3.21.3" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "msal" +version = "1.30.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." +optional = false +python-versions = ">=3.7" +files = [ + {file = "msal-1.30.0-py3-none-any.whl", hash = "sha256:423872177410cb61683566dc3932db7a76f661a5d2f6f52f02a047f101e1c1de"}, + {file = "msal-1.30.0.tar.gz", hash = "sha256:b4bf00850092e465157d814efa24a18f788284c9a479491024d62903085ea2fb"}, +] + +[package.dependencies] +cryptography = ">=2.5,<45" +PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +requests = ">=2.0.0,<3" + +[package.extras] +broker = ["pymsalruntime (>=0.13.2,<0.17)"] + +[[package]] +name = "msal-extensions" +version = "1.2.0" +description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." +optional = false +python-versions = ">=3.7" +files = [ + {file = "msal_extensions-1.2.0-py3-none-any.whl", hash = "sha256:cf5ba83a2113fa6dc011a254a72f1c223c88d7dfad74cc30617c4679a417704d"}, + {file = "msal_extensions-1.2.0.tar.gz", hash = "sha256:6f41b320bfd2933d631a215c91ca0dd3e67d84bd1a2f50ce917d5874ec646bef"}, +] + +[package.dependencies] +msal = ">=1.29,<2" +portalocker = ">=1.4,<3" + +[[package]] +name = "msrest" +version = "0.7.1" +description = "AutoRest swagger generator Python client runtime." +optional = false +python-versions = ">=3.6" +files = [ + {file = "msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32"}, + {file = "msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9"}, +] + +[package.dependencies] +azure-core = ">=1.24.0" +certifi = ">=2017.4.17" +isodate = ">=0.6.0" +requests = ">=2.16,<3.0" +requests-oauthlib = ">=0.5.0" + +[package.extras] +async = ["aiodns", "aiohttp (>=3.0)"] + +[[package]] +name = "multidict" +version = "6.0.5" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, +] + +[[package]] +name = "multipledispatch" +version = "1.0.0" +description = "Multiple dispatch" +optional = false +python-versions = "*" +files = [ + {file = "multipledispatch-1.0.0-py3-none-any.whl", hash = "sha256:0c53cd8b077546da4e48869f49b13164bebafd0c2a5afceb6bb6a316e7fb46e4"}, + {file = "multipledispatch-1.0.0.tar.gz", hash = "sha256:5c839915465c68206c3e9c473357908216c28383b425361e5d144594bf85a7e0"}, +] + +[[package]] +name = "mypy" +version = "1.11.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"}, + {file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"}, + {file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"}, + {file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"}, + {file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"}, + {file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"}, + {file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"}, + {file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"}, + {file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"}, + {file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"}, + {file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"}, + {file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"}, + {file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"}, + {file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"}, + {file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"}, + {file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"}, + {file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"}, + {file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"}, + {file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "openai" +version = "1.37.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.37.0-py3-none-any.whl", hash = "sha256:a903245c0ecf622f2830024acdaa78683c70abb8e9d37a497b851670864c9f73"}, + {file = "openai-1.37.0.tar.gz", hash = "sha256:dc8197fc40ab9d431777b6620d962cc49f4544ffc3011f03ce0a805e6eb54adb"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "portalocker" +version = "2.10.1" +description = "Wraps the portalocker recipe for easy usage" +optional = false +python-versions = ">=3.8" +files = [ + {file = "portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf"}, + {file = "portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pylint" +version = "2.17.7" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, +] + +[package.dependencies] +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pypdf2" +version = "3.0.1" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440"}, + {file = "pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928"}, +] + +[package.dependencies] +typing_extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +crypto = ["PyCryptodome"] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow", "PyCryptodome"] +image = ["Pillow"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.21.2" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, + {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-docx" +version = "1.1.2" +description = "Create, read, and update Microsoft Word .docx files." +optional = false +python-versions = ">=3.7" +files = [ + {file = "python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe"}, + {file = "python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd"}, +] + +[package.dependencies] +lxml = ">=3.1.0" +typing-extensions = ">=4.9.0" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-pptx" +version = "0.6.23" +description = "Generate and manipulate Open XML PowerPoint (.pptx) files" +optional = false +python-versions = "*" +files = [ + {file = "python-pptx-0.6.23.tar.gz", hash = "sha256:587497ff28e779ab18dbb074f6d4052893c85dedc95ed75df319364f331fedee"}, + {file = "python_pptx-0.6.23-py3-none-any.whl", hash = "sha256:dd0527194627a2b7cc05f3ba23ecaa2d9a0d5ac9b6193a28ed1b7a716f4217d4"}, +] + +[package.dependencies] +lxml = ">=3.1.0" +Pillow = ">=3.3.2" +XlsxWriter = ">=0.5.7" + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "recognizers-text" +version = "1.0.2a2" +description = "recognizers-text README" +optional = false +python-versions = "*" +files = [ + {file = "recognizers-text-1.0.2a2.tar.gz", hash = "sha256:866cb469c9d5bcc1d230bd31cb684bd5577bdda3c732f5e17817e515c9d69d7c"}, + {file = "recognizers_text-1.0.2a2-py3-none-any.whl", hash = "sha256:0679d60e952fa3c55b1d459da8f7881425900b0aad29fdfd9291d053168f86af"}, +] + +[package.dependencies] +emoji = "*" +multipledispatch = "*" + +[[package]] +name = "recognizers-text-choice" +version = "1.0.2a2" +description = "recognizers-text-choice README" +optional = false +python-versions = "*" +files = [ + {file = "recognizers-text-choice-1.0.2a2.tar.gz", hash = "sha256:fbae3ccf5dc3689055c15e3633767bc9ed0478058bd09c71000550c5684af69d"}, + {file = "recognizers_text_choice-1.0.2a2-py3-none-any.whl", hash = "sha256:129f78009a81b66e90bd51099ba9af018e282c8dccdeec9999039aea636a8f11"}, +] + +[package.dependencies] +grapheme = "*" +recognizers-text = ">=1.0.2.a2" +regex = "*" + +[[package]] +name = "recognizers-text-date-time" +version = "1.0.2a2" +description = "recognizers-text-date-time README" +optional = false +python-versions = "*" +files = [ + {file = "recognizers-text-date-time-1.0.2a2.tar.gz", hash = "sha256:e27b98b4f426ffa17eccff5fa1bcda7c4e00c1fbbd079a693c9838925bf59501"}, + {file = "recognizers_text_date_time-1.0.2a2-py3-none-any.whl", hash = "sha256:701819711f4c23c33f5748fc2135c0e65dc2ad27356b20479cccb1d49da9f627"}, +] + +[package.dependencies] +datedelta = "*" +recognizers-text = ">=1.0.2.a2" +recognizers-text-number = ">=1.0.2.a2" +recognizers-text-number-with-unit = ">=1.0.2.a2" +regex = "*" + +[[package]] +name = "recognizers-text-number" +version = "1.0.2a2" +description = "recognizers-text-number README" +optional = false +python-versions = "*" +files = [ + {file = "recognizers-text-number-1.0.2a2.tar.gz", hash = "sha256:06d41a87989e874eda3926ca02f800773a7c522f237403241a0183baa0b426d6"}, + {file = "recognizers_text_number-1.0.2a2-py3-none-any.whl", hash = "sha256:b6b098dee2333362e4ba5a410550eef77019940a8f6dbd32aa7342c53359700e"}, +] + +[package.dependencies] +recognizers-text = ">=1.0.2.a2" +regex = "*" + +[[package]] +name = "recognizers-text-number-with-unit" +version = "1.0.2a2" +description = "recognizers-text-number-with-unit README" +optional = false +python-versions = "*" +files = [ + {file = "recognizers-text-number-with-unit-1.0.2a2.tar.gz", hash = "sha256:6b8aa9efcfa13f701932dfbc9f10cabcae0d7cff76ca8da1461878f53e2ec404"}, + {file = "recognizers_text_number_with_unit-1.0.2a2-py3-none-any.whl", hash = "sha256:c7b118cfcbc4435fefec0ecc17a0954fe37e7248648a50953912dac12240058a"}, +] + +[package.dependencies] +recognizers-text = ">=1.0.2.a2" +recognizers-text-number = ">=1.0.2.a2" +regex = "*" + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2024.5.15" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=3.4" +files = [ + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rpds-py" +version = "0.19.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.19.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:fb37bd599f031f1a6fb9e58ec62864ccf3ad549cf14bac527dbfa97123edcca4"}, + {file = "rpds_py-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3384d278df99ec2c6acf701d067147320b864ef6727405d6470838476e44d9e8"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54548e0be3ac117595408fd4ca0ac9278fde89829b0b518be92863b17ff67a2"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8eb488ef928cdbc05a27245e52de73c0d7c72a34240ef4d9893fdf65a8c1a955"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5da93debdfe27b2bfc69eefb592e1831d957b9535e0943a0ee8b97996de21b5"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79e205c70afddd41f6ee79a8656aec738492a550247a7af697d5bd1aee14f766"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:959179efb3e4a27610e8d54d667c02a9feaa86bbabaf63efa7faa4dfa780d4f1"}, + {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a6e605bb9edcf010f54f8b6a590dd23a4b40a8cb141255eec2a03db249bc915b"}, + {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9133d75dc119a61d1a0ded38fb9ba40a00ef41697cc07adb6ae098c875195a3f"}, + {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd36b712d35e757e28bf2f40a71e8f8a2d43c8b026d881aa0c617b450d6865c9"}, + {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354f3a91718489912f2e0fc331c24eaaf6a4565c080e00fbedb6015857c00582"}, + {file = "rpds_py-0.19.0-cp310-none-win32.whl", hash = "sha256:ebcbf356bf5c51afc3290e491d3722b26aaf5b6af3c1c7f6a1b757828a46e336"}, + {file = "rpds_py-0.19.0-cp310-none-win_amd64.whl", hash = "sha256:75a6076289b2df6c8ecb9d13ff79ae0cad1d5fb40af377a5021016d58cd691ec"}, + {file = "rpds_py-0.19.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6d45080095e585f8c5097897313def60caa2046da202cdb17a01f147fb263b81"}, + {file = "rpds_py-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5c9581019c96f865483d031691a5ff1cc455feb4d84fc6920a5ffc48a794d8a"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1540d807364c84516417115c38f0119dfec5ea5c0dd9a25332dea60b1d26fc4d"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e65489222b410f79711dc3d2d5003d2757e30874096b2008d50329ea4d0f88c"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da6f400eeb8c36f72ef6646ea530d6d175a4f77ff2ed8dfd6352842274c1d8b"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37f46bb11858717e0efa7893c0f7055c43b44c103e40e69442db5061cb26ed34"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:071d4adc734de562bd11d43bd134330fb6249769b2f66b9310dab7460f4bf714"}, + {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9625367c8955e4319049113ea4f8fee0c6c1145192d57946c6ffcd8fe8bf48dd"}, + {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e19509145275d46bc4d1e16af0b57a12d227c8253655a46bbd5ec317e941279d"}, + {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d438e4c020d8c39961deaf58f6913b1bf8832d9b6f62ec35bd93e97807e9cbc"}, + {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90bf55d9d139e5d127193170f38c584ed3c79e16638890d2e36f23aa1630b952"}, + {file = "rpds_py-0.19.0-cp311-none-win32.whl", hash = "sha256:8d6ad132b1bc13d05ffe5b85e7a01a3998bf3a6302ba594b28d61b8c2cf13aaf"}, + {file = "rpds_py-0.19.0-cp311-none-win_amd64.whl", hash = "sha256:7ec72df7354e6b7f6eb2a17fa6901350018c3a9ad78e48d7b2b54d0412539a67"}, + {file = "rpds_py-0.19.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5095a7c838a8647c32aa37c3a460d2c48debff7fc26e1136aee60100a8cd8f68"}, + {file = "rpds_py-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f2f78ef14077e08856e788fa482107aa602636c16c25bdf59c22ea525a785e9"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7cc6cb44f8636fbf4a934ca72f3e786ba3c9f9ba4f4d74611e7da80684e48d2"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf902878b4af334a09de7a45badbff0389e7cf8dc2e4dcf5f07125d0b7c2656d"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:688aa6b8aa724db1596514751ffb767766e02e5c4a87486ab36b8e1ebc1aedac"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57dbc9167d48e355e2569346b5aa4077f29bf86389c924df25c0a8b9124461fb"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4cf5a9497874822341c2ebe0d5850fed392034caadc0bad134ab6822c0925b"}, + {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a790d235b9d39c70a466200d506bb33a98e2ee374a9b4eec7a8ac64c2c261fa"}, + {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d16089dfa58719c98a1c06f2daceba6d8e3fb9b5d7931af4a990a3c486241cb"}, + {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bc9128e74fe94650367fe23f37074f121b9f796cabbd2f928f13e9661837296d"}, + {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c8f77e661ffd96ff104bebf7d0f3255b02aa5d5b28326f5408d6284c4a8b3248"}, + {file = "rpds_py-0.19.0-cp312-none-win32.whl", hash = "sha256:5f83689a38e76969327e9b682be5521d87a0c9e5a2e187d2bc6be4765f0d4600"}, + {file = "rpds_py-0.19.0-cp312-none-win_amd64.whl", hash = "sha256:06925c50f86da0596b9c3c64c3837b2481337b83ef3519e5db2701df695453a4"}, + {file = "rpds_py-0.19.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:52e466bea6f8f3a44b1234570244b1cff45150f59a4acae3fcc5fd700c2993ca"}, + {file = "rpds_py-0.19.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e21cc693045fda7f745c790cb687958161ce172ffe3c5719ca1764e752237d16"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b31f059878eb1f5da8b2fd82480cc18bed8dcd7fb8fe68370e2e6285fa86da6"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dd46f309e953927dd018567d6a9e2fb84783963650171f6c5fe7e5c41fd5666"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a01a4490e170376cd79258b7f755fa13b1a6c3667e872c8e35051ae857a92b"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcf426a8c38eb57f7bf28932e68425ba86def6e756a5b8cb4731d8e62e4e0223"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68eea5df6347d3f1378ce992d86b2af16ad7ff4dcb4a19ccdc23dea901b87fb"}, + {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dab8d921b55a28287733263c0e4c7db11b3ee22aee158a4de09f13c93283c62d"}, + {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6fe87efd7f47266dfc42fe76dae89060038f1d9cb911f89ae7e5084148d1cc08"}, + {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:535d4b52524a961d220875688159277f0e9eeeda0ac45e766092bfb54437543f"}, + {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8b1a94b8afc154fbe36978a511a1f155f9bd97664e4f1f7a374d72e180ceb0ae"}, + {file = "rpds_py-0.19.0-cp38-none-win32.whl", hash = "sha256:7c98298a15d6b90c8f6e3caa6457f4f022423caa5fa1a1ca7a5e9e512bdb77a4"}, + {file = "rpds_py-0.19.0-cp38-none-win_amd64.whl", hash = "sha256:b0da31853ab6e58a11db3205729133ce0df26e6804e93079dee095be3d681dc1"}, + {file = "rpds_py-0.19.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5039e3cef7b3e7a060de468a4a60a60a1f31786da94c6cb054e7a3c75906111c"}, + {file = "rpds_py-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab1932ca6cb8c7499a4d87cb21ccc0d3326f172cfb6a64021a889b591bb3045c"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2afd2164a1e85226fcb6a1da77a5c8896c18bfe08e82e8ceced5181c42d2179"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1c30841f5040de47a0046c243fc1b44ddc87d1b12435a43b8edff7e7cb1e0d0"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f757f359f30ec7dcebca662a6bd46d1098f8b9fb1fcd661a9e13f2e8ce343ba1"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15e65395a59d2e0e96caf8ee5389ffb4604e980479c32742936ddd7ade914b22"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb0f6eb3a320f24b94d177e62f4074ff438f2ad9d27e75a46221904ef21a7b05"}, + {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b228e693a2559888790936e20f5f88b6e9f8162c681830eda303bad7517b4d5a"}, + {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2575efaa5d949c9f4e2cdbe7d805d02122c16065bfb8d95c129372d65a291a0b"}, + {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5c872814b77a4e84afa293a1bee08c14daed1068b2bb1cc312edbf020bbbca2b"}, + {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850720e1b383df199b8433a20e02b25b72f0fded28bc03c5bd79e2ce7ef050be"}, + {file = "rpds_py-0.19.0-cp39-none-win32.whl", hash = "sha256:ce84a7efa5af9f54c0aa7692c45861c1667080814286cacb9958c07fc50294fb"}, + {file = "rpds_py-0.19.0-cp39-none-win_amd64.whl", hash = "sha256:1c26da90b8d06227d7769f34915913911222d24ce08c0ab2d60b354e2d9c7aff"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:75969cf900d7be665ccb1622a9aba225cf386bbc9c3bcfeeab9f62b5048f4a07"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8445f23f13339da640d1be8e44e5baf4af97e396882ebbf1692aecd67f67c479"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5a7c1062ef8aea3eda149f08120f10795835fc1c8bc6ad948fb9652a113ca55"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:462b0c18fbb48fdbf980914a02ee38c423a25fcc4cf40f66bacc95a2d2d73bc8"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3208f9aea18991ac7f2b39721e947bbd752a1abbe79ad90d9b6a84a74d44409b"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3444fe52b82f122d8a99bf66777aed6b858d392b12f4c317da19f8234db4533"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb4bac7185a9f0168d38c01d7a00addece9822a52870eee26b8d5b61409213"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b130bd4163c93798a6b9bb96be64a7c43e1cec81126ffa7ffaa106e1fc5cef5"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a707b158b4410aefb6b054715545bbb21aaa5d5d0080217290131c49c2124a6e"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dc9ac4659456bde7c567107556ab065801622396b435a3ff213daef27b495388"}, + {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:81ea573aa46d3b6b3d890cd3c0ad82105985e6058a4baed03cf92518081eec8c"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f148c3f47f7f29a79c38cc5d020edcb5ca780020fab94dbc21f9af95c463581"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0906357f90784a66e89ae3eadc2654f36c580a7d65cf63e6a616e4aec3a81be"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f629ecc2db6a4736b5ba95a8347b0089240d69ad14ac364f557d52ad68cf94b0"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6feacd1d178c30e5bc37184526e56740342fd2aa6371a28367bad7908d454fc"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b6068ee374fdfab63689be0963333aa83b0815ead5d8648389a8ded593378"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d57546bad81e0da13263e4c9ce30e96dcbe720dbff5ada08d2600a3502e526"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b6683a37338818646af718c9ca2a07f89787551057fae57c4ec0446dc6224b"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e8481b946792415adc07410420d6fc65a352b45d347b78fec45d8f8f0d7496f0"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bec35eb20792ea64c3c57891bc3ca0bedb2884fbac2c8249d9b731447ecde4fa"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:aa5476c3e3a402c37779e95f7b4048db2cb5b0ed0b9d006983965e93f40fe05a"}, + {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:19d02c45f2507b489fd4df7b827940f1420480b3e2e471e952af4d44a1ea8e34"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3e2fd14c5d49ee1da322672375963f19f32b3d5953f0615b175ff7b9d38daed"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:93a91c2640645303e874eada51f4f33351b84b351a689d470f8108d0e0694210"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b9fc03bf76a94065299d4a2ecd8dfbae4ae8e2e8098bbfa6ab6413ca267709"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a4b07cdf3f84310c08c1de2c12ddadbb7a77568bcb16e95489f9c81074322ed"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba0ed0dc6763d8bd6e5de5cf0d746d28e706a10b615ea382ac0ab17bb7388633"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:474bc83233abdcf2124ed3f66230a1c8435896046caa4b0b5ab6013c640803cc"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329c719d31362355a96b435f4653e3b4b061fcc9eba9f91dd40804ca637d914e"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef9101f3f7b59043a34f1dccbb385ca760467590951952d6701df0da9893ca0c"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0121803b0f424ee2109d6e1f27db45b166ebaa4b32ff47d6aa225642636cd834"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8344127403dea42f5970adccf6c5957a71a47f522171fafaf4c6ddb41b61703a"}, + {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:443cec402ddd650bb2b885113e1dcedb22b1175c6be223b14246a714b61cd521"}, + {file = "rpds_py-0.19.0.tar.gz", hash = "sha256:4fdc9afadbeb393b4bbbad75481e0ea78e4469f2e1d713a90811700830b553a9"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "teams-ai" +version = "1.2.2" +description = "SDK focused on building AI based applications for Microsoft Teams." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "teams_ai-1.2.2-py3-none-any.whl", hash = "sha256:1914b5dccf12385a8aa493e10ea02a81555afa174d73fd10ecfd501541753ab3"}, + {file = "teams_ai-1.2.2.tar.gz", hash = "sha256:0eba0002735f412a0d101a3b2044c227ceebe3641744b1f48ad07e236dd8a3b4"}, +] + +[package.dependencies] +aiohttp = ">=3.9.3,<4.0.0" +azure-ai-contentsafety = ">=1.0.0,<2.0.0" +botbuilder-core = ">=4.15.0,<5.0.0" +botbuilder-dialogs = ">=4.14.8,<5.0.0" +botbuilder-integration-aiohttp = ">=4.15.0,<5.0.0" +botframework-connector = ">=4.15.0,<5.0.0" +dataclasses-json = ">=0.6.4,<0.7.0" +jsonschema = ">=4.21.1,<5.0.0" +msal = ">=1.28.0,<2.0.0" +openai = ">=1.27.0,<2.0.0" +pyyaml = ">=6.0.1,<7.0.0" +tiktoken = ">=0.7.0,<0.8.0" +types-pyyaml = ">=6.0.12.12,<7.0.0.0" + +[[package]] +name = "tiktoken" +version = "0.7.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, + {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, + {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590"}, + {file = "tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c"}, + {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311"}, + {file = "tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5"}, + {file = "tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702"}, + {file = "tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f"}, + {file = "tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f"}, + {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b"}, + {file = "tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992"}, + {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1"}, + {file = "tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89"}, + {file = "tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb"}, + {file = "tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908"}, + {file = "tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410"}, + {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704"}, + {file = "tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350"}, + {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4"}, + {file = "tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97"}, + {file = "tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f"}, + {file = "tiktoken-0.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2398fecd38c921bcd68418675a6d155fad5f5e14c2e92fcf5fe566fa5485a858"}, + {file = "tiktoken-0.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f5f6afb52fb8a7ea1c811e435e4188f2bef81b5e0f7a8635cc79b0eef0193d6"}, + {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:861f9ee616766d736be4147abac500732b505bf7013cfaf019b85892637f235e"}, + {file = "tiktoken-0.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54031f95c6939f6b78122c0aa03a93273a96365103793a22e1793ee86da31685"}, + {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fffdcb319b614cf14f04d02a52e26b1d1ae14a570f90e9b55461a72672f7b13d"}, + {file = "tiktoken-0.7.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c72baaeaefa03ff9ba9688624143c858d1f6b755bb85d456d59e529e17234769"}, + {file = "tiktoken-0.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:131b8aeb043a8f112aad9f46011dced25d62629091e51d9dc1adbf4a1cc6aa98"}, + {file = "tiktoken-0.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cabc6dc77460df44ec5b879e68692c63551ae4fae7460dd4ff17181df75f1db7"}, + {file = "tiktoken-0.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d57f29171255f74c0aeacd0651e29aa47dff6f070cb9f35ebc14c82278f3b25"}, + {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee92776fdbb3efa02a83f968c19d4997a55c8e9ce7be821ceee04a1d1ee149c"}, + {file = "tiktoken-0.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e215292e99cb41fbc96988ef62ea63bb0ce1e15f2c147a61acc319f8b4cbe5bf"}, + {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a81bac94769cab437dd3ab0b8a4bc4e0f9cf6835bcaa88de71f39af1791727a"}, + {file = "tiktoken-0.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d6d73ea93e91d5ca771256dfc9d1d29f5a554b83821a1dc0891987636e0ae226"}, + {file = "tiktoken-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:2bcb28ddf79ffa424f171dfeef9a4daff61a94c631ca6813f43967cb263b83b9"}, + {file = "tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, +] + +[[package]] +name = "tqdm" +version = "4.66.4" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240311" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-PyYAML-6.0.12.20240311.tar.gz", hash = "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342"}, + {file = "types_PyYAML-6.0.12.20240311-py3-none-any.whl", hash = "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "urllib3" +version = "1.26.19" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, + {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.0" +description = "A Python module for creating Excel XLSX files." +optional = false +python-versions = ">=3.6" +files = [ + {file = "XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e"}, + {file = "XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c"}, +] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.19.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8,<4.0" +content-hash = "4eca5a43b8b4614a87fddc3b3c28a63bc95a309b044eb84113f185246ee0c9a1" diff --git a/samples/msteams-FAQPlus/poetry.toml b/samples/msteams-FAQPlus/poetry.toml new file mode 100644 index 0000000000..efa46ec0ec --- /dev/null +++ b/samples/msteams-FAQPlus/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true \ No newline at end of file diff --git a/samples/msteams-FAQPlus/pyproject.toml b/samples/msteams-FAQPlus/pyproject.toml new file mode 100644 index 0000000000..159fcd610f --- /dev/null +++ b/samples/msteams-FAQPlus/pyproject.toml @@ -0,0 +1,58 @@ +[tool.poetry] +name = "faqplusplus" +version = "0.0.0" +description = "Data Source - Azure OpenAI" +authors = ["Microsoft "] +readme = "README.md" +packages = [ + { include = "src" } +] + +[tool.poetry.dependencies] +python = ">=3.8,<4.0" +teams-ai = "^1.1.0" +python-dotenv = "^1.0.1" +azure-identity = "^1.17.0" +azure-search-documents = "^11.4.0" +beautifulsoup4 = "^4.9.3" +pypdf2 = "^3.0.1" +python-docx = "^1.1.2" +python-pptx = "^0.6.23" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.0" +pylint = "^2.17.4" +pytest-cov = "^4.1.0" +pytest-asyncio = "^0.21.1" +black = "^24.3.0" +isort = "^5.12.0" +mypy = "^1.5.0" + +[tool.poetry.scripts] +lint = "scripts:lint" +fmt = "scripts:fmt" +test = "scripts:test" +clean = "scripts:clean" +ci = "scripts:ci" +start = "scripts:start" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 100 +target-version = ['py38'] + +[tool.isort] +profile = "black" + +[tool.pytest.ini_options] +addopts = "--cov-report html:coverage --cov=src" + +[tool.mypy] +python_version = "3.8" +ignore_missing_imports = true +show_error_codes = true +no_implicit_optional = true +warn_unused_ignores = true \ No newline at end of file diff --git a/samples/msteams-FAQPlus/scripts/__init__.py b/samples/msteams-FAQPlus/scripts/__init__.py new file mode 100644 index 0000000000..7fcbc831ce --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/__init__.py @@ -0,0 +1,11 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from .ci import * +from .clean import * +from .fmt import * +from .lint import * +from .start import * +from .test import * diff --git a/samples/msteams-FAQPlus/scripts/ci.py b/samples/msteams-FAQPlus/scripts/ci.py new file mode 100644 index 0000000000..b8e2334ba0 --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/ci.py @@ -0,0 +1,13 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import subprocess + + +def ci(): + subprocess.run(["poetry", "check"], check=True) + subprocess.run(["poetry", "run", "lint"], check=True) + subprocess.run(["poetry", "run", "test"], check=True) + subprocess.run(["poetry", "build"], check=True) diff --git a/samples/msteams-FAQPlus/scripts/clean.py b/samples/msteams-FAQPlus/scripts/clean.py new file mode 100644 index 0000000000..b6ac8744f1 --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/clean.py @@ -0,0 +1,22 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import shutil +from pathlib import Path + + +def clean(): + base = Path("./") + + for e in base.rglob("**/*"): + if ( + e.match("dist") + or e.match("__pycache__") + or e.match(".pytest_cache") + or e.match("coverage") + ): + if e.is_dir(): + print(e) + shutil.rmtree(e) diff --git a/samples/msteams-FAQPlus/scripts/fmt.py b/samples/msteams-FAQPlus/scripts/fmt.py new file mode 100644 index 0000000000..b939a539db --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/fmt.py @@ -0,0 +1,11 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import subprocess + + +def fmt(): + subprocess.run(["poetry", "run", "black", "src", "scripts"], check=True) + subprocess.run(["poetry", "run", "isort", "src", "scripts"], check=True) diff --git a/samples/msteams-FAQPlus/scripts/lint.py b/samples/msteams-FAQPlus/scripts/lint.py new file mode 100644 index 0000000000..6138aa9729 --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/lint.py @@ -0,0 +1,11 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import subprocess + + +def lint(): + subprocess.run(["poetry", "run", "pylint", "src", "scripts", "tests"], check=True) + subprocess.run(["poetry", "run", "mypy", "-p", "src"], check=True) diff --git a/samples/msteams-FAQPlus/scripts/start.py b/samples/msteams-FAQPlus/scripts/start.py new file mode 100644 index 0000000000..308ac7e444 --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/start.py @@ -0,0 +1,10 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import subprocess + + +def start(): + subprocess.run(["poetry", "run", "python", "src/app.py"], check=False) diff --git a/samples/msteams-FAQPlus/scripts/test.py b/samples/msteams-FAQPlus/scripts/test.py new file mode 100644 index 0000000000..1ade3417b9 --- /dev/null +++ b/samples/msteams-FAQPlus/scripts/test.py @@ -0,0 +1,10 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import sys + + +def test(): + return sys.exit() diff --git a/samples/msteams-FAQPlus/src/api.py b/samples/msteams-FAQPlus/src/api.py new file mode 100644 index 0000000000..b08c464dd7 --- /dev/null +++ b/samples/msteams-FAQPlus/src/api.py @@ -0,0 +1,30 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. + +Description: initialize the api and route incoming messages +to our app +""" + +from http import HTTPStatus + +from aiohttp import web +from botbuilder.core.integration import aiohttp_error_middleware + +from bot import app + +routes = web.RouteTableDef() + + +@routes.post("/api/messages") +async def on_messages(req: web.Request) -> web.Response: + res = await app.process(req) + + if res is not None: + return res + + return web.Response(status=HTTPStatus.OK) + + +api = web.Application(middlewares=[aiohttp_error_middleware]) +api.add_routes(routes) diff --git a/samples/msteams-FAQPlus/src/app.py b/samples/msteams-FAQPlus/src/app.py new file mode 100644 index 0000000000..103a163a59 --- /dev/null +++ b/samples/msteams-FAQPlus/src/app.py @@ -0,0 +1,12 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from aiohttp import web + +from api import api +from config import Config + +if __name__ == "__main__": + web.run_app(api, host="localhost", port=Config.PORT) \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/bot.py b/samples/msteams-FAQPlus/src/bot.py new file mode 100644 index 0000000000..42b6100523 --- /dev/null +++ b/samples/msteams-FAQPlus/src/bot.py @@ -0,0 +1,263 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. + +Description: initialize the app and listen for `message` activitys +""" + +import os +import sys +import traceback +import re +from logging import Logger, StreamHandler, DEBUG +from pathlib import Path + +from botbuilder.core import MemoryStorage, TurnContext +from botbuilder.core.teams import TeamsInfo +from botbuilder.schema import Activity, ConversationParameters +from botbuilder.schema.teams import TeamsChannelData, ChannelInfo +from teams import Application, ApplicationOptions, TeamsAdapter +from teams.ai import AIOptions +from teams.ai.models import AzureOpenAIModelOptions, OpenAIModel +from teams.ai.planners import ActionPlanner, ActionPlannerOptions +from teams.ai.prompts import PromptManager, PromptManagerOptions, PromptTemplate +from teams.state import TurnState +from azure.identity import get_bearer_token_provider, DefaultAzureCredential + +from config import Config +from state import AppTurnState +from cards.talk_to_expert_card import create_talk_to_expert_card +from cards.close_request_card import close_request_card + +logger = Logger("teamsai:openai", DEBUG) +logger.addHandler(StreamHandler(sys.stdout)) + +config = Config() + +if config.AZURE_OPENAI_ENDPOINT is None: + raise RuntimeError("Missing environment variables - please check that AZURE_OPENAI_ENDPOINT is set.") + +# Create AI components +model: OpenAIModel +logger = Logger("teamsai:openai", DEBUG) +logger.addHandler(StreamHandler(sys.stdout)) + +if config.AZURE_OPENAI_KEY: + model = OpenAIModel( + AzureOpenAIModelOptions( + api_key=config.AZURE_OPENAI_KEY, + default_model=config.AZURE_OPENAI_MODEL, + api_version="2024-02-15-preview", + endpoint=config.AZURE_OPENAI_ENDPOINT, + logger=logger + ) + ) +else: + model = OpenAIModel( + AzureOpenAIModelOptions( + azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'), + default_model=config.AZURE_OPENAI_MODEL, + api_version="2024-02-15-preview", + endpoint=config.AZURE_OPENAI_ENDPOINT, + logger=logger + ) + ) + +prompts = PromptManager( + PromptManagerOptions(prompts_folder=f"{os.path.dirname(os.path.abspath(__file__))}/prompts"), +) + +# gets prompt template and adds data source config +async def get_default_prompt(context: TurnContext, state: TurnState, planner: ActionPlanner) -> PromptTemplate: + prompt = await prompts.get_prompt("chat") + + prompt.config.completion.model = config.AZURE_OPENAI_MODEL + + if config.AZURE_SEARCH_ENDPOINT: + if config.AZURE_SEARCH_KEY: + + prompt.config.completion.data_sources = [ + { + "type": 'azure_search', + "parameters": { + "endpoint": config.AZURE_SEARCH_ENDPOINT, + "index_name": config.AZURE_SEARCH_INDEX, + "key": config.AZURE_SEARCH_KEY, + "semantic_configuration": 'default', + "query_type": 'semantic', + "fields_mapping": { }, + "in_scope": True, + "strictness": 3, + "top_n_documents": 5, + "role_information": Path(__file__).resolve().parent.joinpath('./prompts/chat/skprompt.txt').read_text(encoding='utf-8'), + } + } + ] + + else: + prompt.config.completion.data_sources = [ + { + "type": 'azure_search', + "parameters": { + "endpoint": config.AZURE_SEARCH_ENDPOINT, + "index_name": config.AZURE_SEARCH_INDEX, + "semantic_configuration": 'default', + "query_type": 'semantic', + "fields_mapping": { }, + "in_scope": True, + "strictness": 3, + "top_n_documents": 5, + "role_information": Path(__file__).resolve().parent.joinpath('./prompts/chat/skprompt.txt').read_text(encoding='utf-8'), + "authentication": { + "type": 'system_assigned_managed_identity' + } + } + } + ] + + + return prompt + +storage = MemoryStorage() +app = Application[AppTurnState]( + ApplicationOptions( + bot_app_id=config.APP_ID, + storage=storage, + adapter=TeamsAdapter(config), + ai=AIOptions( + planner=ActionPlanner[AppTurnState]( + ActionPlannerOptions(model=model, prompts=prompts, default_prompt=get_default_prompt) + ) + ), + ), +) + + +@app.conversation_update("membersAdded") +async def on_member_added(context: TurnContext, state: AppTurnState): + + channel = context.activity.channel_data.get("settings", {}).get("selectedChannel", {}).get("id") # type: ignore + + if (context.activity.members_added[0].id == context.activity.recipient.id and # type: ignore + channel == config.TEAMS_CHANNEL_ID): + await context.send_activity("The FAQ Bot has been added to this channel.") + return True + + member_added_to_channel = context.activity.channel_data.get("team") # type: ignore + + if (member_added_to_channel is None): + await context.send_activity( + "Welcome to the FAQ Bot ! I'm here to answer your queries. To clear the conversation history, type `/clear` in the chat. To talk to an expert, type `/expert`." + ) + + return True + + +@app.message(re.compile(r"/clear", re.IGNORECASE)) +async def on_clear(context: TurnContext, state: AppTurnState): + + del state.conversation + await context.send_activity( + "New chat session started: Previous messages won't be used as context for new queries." + ) + return True + + +def get_chat_history(chat_history): + chat_items = [] + + if chat_history: + # Limit the chat history to the last 10 entries + limited_history = chat_history[-10:] if len(chat_history) > 10 else chat_history + + for entry in limited_history: + role = entry.role.capitalize() + content = entry.content + chat_items.append( + { + "type": "TextBlock", + "text": f"**{role}**: {content}", + "wrap": True, + "spacing": "small" + } + ) + + return chat_items + +def create_teams_channel_data(team_channel_id: str): + channel_data = TeamsChannelData() + channel_data.channel = ChannelInfo() + channel_data.channel.id = team_channel_id + return channel_data + +async def do_nothing(tc2: TurnContext): + return + +@app.message(re.compile(r"/expert", re.IGNORECASE)) +async def on_talk_to_an_expert(context: TurnContext, state: AppTurnState): + + member = await TeamsInfo.get_member(context, context.activity.from_property.id) # type: ignore + attachment = create_talk_to_expert_card( + member.user_principal_name, context.activity.from_property.name, # type: ignore + get_chat_history(state.conversation.get("chat_history")), "New" + ) + + params = ConversationParameters( + channel_data=create_teams_channel_data(config.TEAMS_CHANNEL_ID), + bot=context.activity.recipient, is_group=True, + activity=Activity(type="message",attachments=[attachment])) + + await context.adapter.create_conversation(bot_app_id=config.APP_ID, + callback=do_nothing, + conversation_parameters = params, + channel_id="msteams", + service_url=context.activity.service_url) + + # Send the message to the user + await context.send_activity("I'm connecting you to an expert. In the meantime, would you like to ask me anything else?") + return True + + +@app.adaptive_cards.action_submit("resolve_ticket") +async def on_assign_to_me(context: TurnContext, state: AppTurnState, data: dict): + + attachment = create_talk_to_expert_card(data.get("user_principal_name"), data.get("user_name"), + data.get("chat_items"), "In progress") + await context.update_activity(Activity(id=context.activity.reply_to_id, + type="message", attachments=[attachment])) + + if "messageid" not in context.activity.conversation.id: # type: ignore + custom_string = f"{context.activity.channel_data.get('channel').get('id')};messageid={context.activity.reply_to_id}" # type: ignore + context.activity.conversation.id = custom_string # type: ignore + + await context.send_activity(Activity(type="message", + text=f"{context.activity.from_property.name} is resolving the request.")) # type: ignore + + +@app.adaptive_cards.action_submit("close_ticket") +async def on_close_ticket(context: TurnContext, state: AppTurnState, data: dict): + + attachment = close_request_card(data.get("user_name"), data.get("chat_items")) # type: ignore + await context.update_activity(Activity(id=context.activity.reply_to_id, + type="message", attachments=[attachment])) + if "messageid" not in context.activity.conversation.id: # type: ignore + custom_string = f"{context.activity.channel_data.get('channel').get('id')};messageid={context.activity.reply_to_id}" # type: ignore + context.activity.conversation.id = custom_string # type: ignore + await context.send_activity(f"{context.activity.from_property.name} closed the request.") # type: ignore + + +@app.turn_state_factory +async def turn_state_factory(context: TurnContext): + return await AppTurnState.load(context, storage) + + +@app.error +async def on_error(context: TurnContext, error: Exception): + # This check writes out errors to console log .vs. app insights. + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + + # Send a message to the user + await context.send_activity("The bot encountered an error or bug.") diff --git a/samples/msteams-FAQPlus/src/cards/close_request_card.py b/samples/msteams-FAQPlus/src/cards/close_request_card.py new file mode 100644 index 0000000000..d0ab1a6313 --- /dev/null +++ b/samples/msteams-FAQPlus/src/cards/close_request_card.py @@ -0,0 +1,59 @@ +from botbuilder.core import CardFactory +from botbuilder.schema import Attachment + +def close_request_card(userName, chat_items) -> Attachment: + card_body = [] + + if chat_items: + card_body.append( + { + "type": "TextBlock", + "text": f"Conversation History with {userName}", + "weight": "Bolder", + "spacing": "small" + } + ) + card_body.append( + { + "type": "Container", + "id": "chat", + "items": [ + { + "type": "Container", + "items": chat_items, + "spacing": "small", + "padding": "None" + } + ], + "spacing": "small", + "padding": "None" + } + ) + else: + card_body.append( + { + "type": "TextBlock", + "text": f"{userName} is requesting help.", + "weight": "Bolder", + "spacing": "small" + } + ) + + card_body.append( + { + "type": "TextBlock", + "text": f"Status : Resolved", + "weight": "Bolder", + "spacing": "small" + } + ) + + return CardFactory.adaptive_card( + { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5", + "type": "AdaptiveCard", + "body": card_body, + "padding": "None" + } + ) \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/cards/talk_to_expert_card.py b/samples/msteams-FAQPlus/src/cards/talk_to_expert_card.py new file mode 100644 index 0000000000..94eccf50b5 --- /dev/null +++ b/samples/msteams-FAQPlus/src/cards/talk_to_expert_card.py @@ -0,0 +1,101 @@ +from botbuilder.core import CardFactory +from botbuilder.schema import Attachment + +def create_talk_to_expert_card(user_principal_name, user_name, chat_items, status) -> Attachment: + card_body = [] + + if chat_items: + card_body.append( + { + "type": "TextBlock", + "text": f"Conversation History with {user_name}", + "weight": "Bolder", + "spacing": "small" + } + ) + card_body.append( + { + "type": "Container", + "id": "chat", + "items": [ + { + "type": "Container", + "items": chat_items, + "spacing": "small", + "padding": "None" + } + ], + "spacing": "small", + "padding": "None" + } + ) + else: + card_body.append( + { + "type": "TextBlock", + "text": f"{user_name} is requesting help.", + "weight": "Bolder", + "spacing": "small" + } + ) + + card_body.append( + { + "type": "TextBlock", + "text": f"Status : {status}", + "weight": "Bolder", + "spacing": "small" + } + ) + + chat_link = f"https://teams.microsoft.com/l/chat/0/0?users={user_principal_name}" # Construct the chat link + + card_body.append( + { + "type": "ActionSet", + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Chat with User", + "url": chat_link, + "spacing": "small", + "padding": "None" + }, + { + "type": "Action.Submit", + "title": "Assign Ticket to Me", + "data": { + "verb": "resolve_ticket", + "chat_items": chat_items, + "user_principal_name": user_principal_name, + "user_name": user_name + }, + "spacing": "small", + "padding": "None" + }, + { + "type": "Action.Submit", + "title": "Close Ticket", + "data": { + "verb": "close_ticket", + "chat_items": chat_items, + "user_name": user_name + }, + "spacing": "small", + "padding": "None" + } + ], + "spacing": "small", + "padding": "None" + } + ) + + return CardFactory.adaptive_card( + { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5", + "type": "AdaptiveCard", + "body": card_body, + "padding": "None" + } + ) \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/config.py b/samples/msteams-FAQPlus/src/config.py new file mode 100644 index 0000000000..19657d52a4 --- /dev/null +++ b/samples/msteams-FAQPlus/src/config.py @@ -0,0 +1,25 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import os + +from dotenv import load_dotenv + +load_dotenv() + + +class Config: + """Bot Configuration""" + + PORT = 3978 + APP_ID = os.environ["BOT_ID"] + APP_PASSWORD = os.environ["BOT_PASSWORD"] + AZURE_OPENAI_KEY = os.environ.get("AZURE_OPENAI_KEY", "") + AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT", "") + AZURE_OPENAI_MODEL = os.environ.get("AZURE_OPENAI_MODEL", "") + AZURE_SEARCH_ENDPOINT = os.environ.get("AZURE_SEARCH_ENDPOINT", "") + AZURE_SEARCH_KEY = os.environ.get("AZURE_SEARCH_KEY", "") + AZURE_SEARCH_INDEX = os.environ.get("AZURE_SEARCH_INDEX", "") + TEAMS_CHANNEL_ID = os.environ.get("TEAMS_CHANNEL_ID", "") diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/design.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/design.txt new file mode 100644 index 0000000000..5b318452f9 --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/design.txt @@ -0,0 +1,810 @@ +Design and History FAQ +********************** + + +Why does Python use indentation for grouping of statements? +=========================================================== + +Guido van Rossum believes that using indentation for grouping is +extremely elegant and contributes a lot to the clarity of the average +Python program. Most people learn to love this feature after a while. + +Since there are no begin/end brackets there cannot be a disagreement +between grouping perceived by the parser and the human reader. +Occasionally C programmers will encounter a fragment of code like +this: + + if (x <= y) + x++; + y--; + z++; + +Only the "x++" statement is executed if the condition is true, but the +indentation leads many to believe otherwise. Even experienced C +programmers will sometimes stare at it a long time wondering as to why +"y" is being decremented even for "x > y". + +Because there are no begin/end brackets, Python is much less prone to +coding-style conflicts. In C there are many different ways to place +the braces. After becoming used to reading and writing code using a +particular style, it is normal to feel somewhat uneasy when reading +(or being required to write) in a different one. + +Many coding styles place begin/end brackets on a line by themselves. +This makes programs considerably longer and wastes valuable screen +space, making it harder to get a good overview of a program. Ideally, +a function should fit on one screen (say, 20--30 lines). 20 lines of +Python can do a lot more work than 20 lines of C. This is not solely +due to the lack of begin/end brackets -- the lack of declarations and +the high-level data types are also responsible -- but the indentation- +based syntax certainly helps. + + +Why am I getting strange results with simple arithmetic operations? +=================================================================== + +See the next question. + + +Why are floating-point calculations so inaccurate? +================================================== + +Users are often surprised by results like this: + + >>> 1.2 - 1.0 + 0.19999999999999996 + +and think it is a bug in Python. It's not. This has little to do +with Python, and much more to do with how the underlying platform +handles floating-point numbers. + +The "float" type in CPython uses a C "double" for storage. A "float" +object's value is stored in binary floating-point with a fixed +precision (typically 53 bits) and Python uses C operations, which in +turn rely on the hardware implementation in the processor, to perform +floating-point operations. This means that as far as floating-point +operations are concerned, Python behaves like many popular languages +including C and Java. + +Many numbers that can be written easily in decimal notation cannot be +expressed exactly in binary floating point. For example, after: + + >>> x = 1.2 + +the value stored for "x" is a (very good) approximation to the decimal +value "1.2", but is not exactly equal to it. On a typical machine, +the actual stored value is: + + 1.0011001100110011001100110011001100110011001100110011 (binary) + +which is exactly: + + 1.1999999999999999555910790149937383830547332763671875 (decimal) + +The typical precision of 53 bits provides Python floats with 15--16 +decimal digits of accuracy. + +For a fuller explanation, please see the floating-point arithmetic +chapter in the Python tutorial. + + +Why are Python strings immutable? +================================= + +There are several advantages. + +One is performance: knowing that a string is immutable means we can +allocate space for it at creation time, and the storage requirements +are fixed and unchanging. This is also one of the reasons for the +distinction between tuples and lists. + +Another advantage is that strings in Python are considered as +"elemental" as numbers. No amount of activity will change the value 8 +to anything else, and in Python, no amount of activity will change the +string "eight" to anything else. + + +Why must 'self' be used explicitly in method definitions and calls? +=================================================================== + +The idea was borrowed from Modula-3. It turns out to be very useful, +for a variety of reasons. + +First, it's more obvious that you are using a method or instance +attribute instead of a local variable. Reading "self.x" or +"self.meth()" makes it absolutely clear that an instance variable or +method is used even if you don't know the class definition by heart. +In C++, you can sort of tell by the lack of a local variable +declaration (assuming globals are rare or easily recognizable) -- but +in Python, there are no local variable declarations, so you'd have to +look up the class definition to be sure. Some C++ and Java coding +standards call for instance attributes to have an "m_" prefix, so this +explicitness is still useful in those languages, too. + +Second, it means that no special syntax is necessary if you want to +explicitly reference or call the method from a particular class. In +C++, if you want to use a method from a base class which is overridden +in a derived class, you have to use the "::" operator -- in Python you +can write "baseclass.methodname(self, )". This is +particularly useful for "__init__()" methods, and in general in cases +where a derived class method wants to extend the base class method of +the same name and thus has to call the base class method somehow. + +Finally, for instance variables it solves a syntactic problem with +assignment: since local variables in Python are (by definition!) those +variables to which a value is assigned in a function body (and that +aren't explicitly declared global), there has to be some way to tell +the interpreter that an assignment was meant to assign to an instance +variable instead of to a local variable, and it should preferably be +syntactic (for efficiency reasons). C++ does this through +declarations, but Python doesn't have declarations and it would be a +pity having to introduce them just for this purpose. Using the +explicit "self.var" solves this nicely. Similarly, for using instance +variables, having to write "self.var" means that references to +unqualified names inside a method don't have to search the instance's +directories. To put it another way, local variables and instance +variables live in two different namespaces, and you need to tell +Python which namespace to use. + + +Why can't I use an assignment in an expression? +=============================================== + +Starting in Python 3.8, you can! + +Assignment expressions using the walrus operator ":=" assign a +variable in an expression: + + while chunk := fp.read(200): + print(chunk) + +See **PEP 572** for more information. + + +Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))? +================================================================================================================ + +As Guido said: + + (a) For some operations, prefix notation just reads better than + postfix -- prefix (and infix!) operations have a long tradition in + mathematics which likes notations where the visuals help the + mathematician thinking about a problem. Compare the easy with which + we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness + of doing the same thing using a raw OO notation. + + (b) When I read code that says len(x) I *know* that it is asking + for the length of something. This tells me two things: the result + is an integer, and the argument is some kind of container. To the + contrary, when I read x.len(), I have to already know that x is + some kind of container implementing an interface or inheriting from + a class that has a standard len(). Witness the confusion we + occasionally have when a class that is not implementing a mapping + has a get() or keys() method, or something that isn't a file has a + write() method. + + -- https://mail.python.org/pipermail/python-3000/2006-November/004 + 643.html + + +Why is join() a string method instead of a list or tuple method? +================================================================ + +Strings became much more like other standard types starting in Python +1.6, when methods were added which give the same functionality that +has always been available using the functions of the string module. +Most of these new methods have been widely accepted, but the one which +appears to make some programmers feel uncomfortable is: + + ", ".join(['1', '2', '4', '8', '16']) + +which gives the result: + + "1, 2, 4, 8, 16" + +There are two common arguments against this usage. + +The first runs along the lines of: "It looks really ugly using a +method of a string literal (string constant)", to which the answer is +that it might, but a string literal is just a fixed value. If the +methods are to be allowed on names bound to strings there is no +logical reason to make them unavailable on literals. + +The second objection is typically cast as: "I am really telling a +sequence to join its members together with a string constant". Sadly, +you aren't. For some reason there seems to be much less difficulty +with having "split()" as a string method, since in that case it is +easy to see that + + "1, 2, 4, 8, 16".split(", ") + +is an instruction to a string literal to return the substrings +delimited by the given separator (or, by default, arbitrary runs of +white space). + +"join()" is a string method because in using it you are telling the +separator string to iterate over a sequence of strings and insert +itself between adjacent elements. This method can be used with any +argument which obeys the rules for sequence objects, including any new +classes you might define yourself. Similar methods exist for bytes and +bytearray objects. + + +How fast are exceptions? +======================== + +A "try"/"except" block is extremely efficient if no exceptions are +raised. Actually catching an exception is expensive. In versions of +Python prior to 2.0 it was common to use this idiom: + + try: + value = mydict[key] + except KeyError: + mydict[key] = getvalue(key) + value = mydict[key] + +This only made sense when you expected the dict to have the key almost +all the time. If that wasn't the case, you coded it like this: + + if key in mydict: + value = mydict[key] + else: + value = mydict[key] = getvalue(key) + +For this specific case, you could also use "value = +dict.setdefault(key, getvalue(key))", but only if the "getvalue()" +call is cheap enough because it is evaluated in all cases. + + +Why isn't there a switch or case statement in Python? +===================================================== + +In general, structured switch statements execute one block of code +when an expression has a particular value or set of values. Since +Python 3.10 one can easily match literal values, or constants within a +namespace, with a "match ... case" statement. An older alternative is +a sequence of "if... elif... elif... else". + +For cases where you need to choose from a very large number of +possibilities, you can create a dictionary mapping case values to +functions to call. For example: + + functions = {'a': function_1, + 'b': function_2, + 'c': self.method_1} + + func = functions[value] + func() + +For calling methods on objects, you can simplify yet further by using +the "getattr()" built-in to retrieve methods with a particular name: + + class MyVisitor: + def visit_a(self): + ... + + def dispatch(self, value): + method_name = 'visit_' + str(value) + method = getattr(self, method_name) + method() + +It's suggested that you use a prefix for the method names, such as +"visit_" in this example. Without such a prefix, if values are coming +from an untrusted source, an attacker would be able to call any method +on your object. + +Imitating switch with fallthrough, as with C's switch-case-default, is +possible, much harder, and less needed. + + +Can't you emulate threads in the interpreter instead of relying on an OS-specific thread implementation? +======================================================================================================== + +Answer 1: Unfortunately, the interpreter pushes at least one C stack +frame for each Python stack frame. Also, extensions can call back +into Python at almost random moments. Therefore, a complete threads +implementation requires thread support for C. + +Answer 2: Fortunately, there is Stackless Python, which has a +completely redesigned interpreter loop that avoids the C stack. + + +Why can't lambda expressions contain statements? +================================================ + +Python lambda expressions cannot contain statements because Python's +syntactic framework can't handle statements nested inside expressions. +However, in Python, this is not a serious problem. Unlike lambda +forms in other languages, where they add functionality, Python lambdas +are only a shorthand notation if you're too lazy to define a function. + +Functions are already first class objects in Python, and can be +declared in a local scope. Therefore the only advantage of using a +lambda instead of a locally defined function is that you don't need to +invent a name for the function -- but that's just a local variable to +which the function object (which is exactly the same type of object +that a lambda expression yields) is assigned! + + +Can Python be compiled to machine code, C or some other language? +================================================================= + +Cython compiles a modified version of Python with optional annotations +into C extensions. Nuitka is an up-and-coming compiler of Python into +C++ code, aiming to support the full Python language. + + +How does Python manage memory? +============================== + +The details of Python memory management depend on the implementation. +The standard implementation of Python, *CPython*, uses reference +counting to detect inaccessible objects, and another mechanism to +collect reference cycles, periodically executing a cycle detection +algorithm which looks for inaccessible cycles and deletes the objects +involved. The "gc" module provides functions to perform a garbage +collection, obtain debugging statistics, and tune the collector's +parameters. + +Other implementations (such as Jython or PyPy), however, can rely on a +different mechanism such as a full-blown garbage collector. This +difference can cause some subtle porting problems if your Python code +depends on the behavior of the reference counting implementation. + +In some Python implementations, the following code (which is fine in +CPython) will probably run out of file descriptors: + + for file in very_long_list_of_files: + f = open(file) + c = f.read(1) + +Indeed, using CPython's reference counting and destructor scheme, each +new assignment to "f" closes the previous file. With a traditional +GC, however, those file objects will only get collected (and closed) +at varying and possibly long intervals. + +If you want to write code that will work with any Python +implementation, you should explicitly close the file or use the "with" +statement; this will work regardless of memory management scheme: + + for file in very_long_list_of_files: + with open(file) as f: + c = f.read(1) + + +Why doesn't CPython use a more traditional garbage collection scheme? +===================================================================== + +For one thing, this is not a C standard feature and hence it's not +portable. (Yes, we know about the Boehm GC library. It has bits of +assembler code for *most* common platforms, not for all of them, and +although it is mostly transparent, it isn't completely transparent; +patches are required to get Python to work with it.) + +Traditional GC also becomes a problem when Python is embedded into +other applications. While in a standalone Python it's fine to replace +the standard "malloc()" and "free()" with versions provided by the GC +library, an application embedding Python may want to have its *own* +substitute for "malloc()" and "free()", and may not want Python's. +Right now, CPython works with anything that implements "malloc()" and +"free()" properly. + + +Why isn't all memory freed when CPython exits? +============================================== + +Objects referenced from the global namespaces of Python modules are +not always deallocated when Python exits. This may happen if there +are circular references. There are also certain bits of memory that +are allocated by the C library that are impossible to free (e.g. a +tool like Purify will complain about these). Python is, however, +aggressive about cleaning up memory on exit and does try to destroy +every single object. + +If you want to force Python to delete certain things on deallocation +use the "atexit" module to run a function that will force those +deletions. + + +Why are there separate tuple and list data types? +================================================= + +Lists and tuples, while similar in many respects, are generally used +in fundamentally different ways. Tuples can be thought of as being +similar to Pascal "records" or C "structs"; they're small collections +of related data which may be of different types which are operated on +as a group. For example, a Cartesian coordinate is appropriately +represented as a tuple of two or three numbers. + +Lists, on the other hand, are more like arrays in other languages. +They tend to hold a varying number of objects all of which have the +same type and which are operated on one-by-one. For example, +"os.listdir('.')" returns a list of strings representing the files in +the current directory. Functions which operate on this output would +generally not break if you added another file or two to the directory. + +Tuples are immutable, meaning that once a tuple has been created, you +can't replace any of its elements with a new value. Lists are +mutable, meaning that you can always change a list's elements. Only +immutable elements can be used as dictionary keys, and hence only +tuples and not lists can be used as keys. + + +How are lists implemented in CPython? +===================================== + +CPython's lists are really variable-length arrays, not Lisp-style +linked lists. The implementation uses a contiguous array of references +to other objects, and keeps a pointer to this array and the array's +length in a list head structure. + +This makes indexing a list "a[i]" an operation whose cost is +independent of the size of the list or the value of the index. + +When items are appended or inserted, the array of references is +resized. Some cleverness is applied to improve the performance of +appending items repeatedly; when the array must be grown, some extra +space is allocated so the next few times don't require an actual +resize. + + +How are dictionaries implemented in CPython? +============================================ + +CPython's dictionaries are implemented as resizable hash tables. +Compared to B-trees, this gives better performance for lookup (the +most common operation by far) under most circumstances, and the +implementation is simpler. + +Dictionaries work by computing a hash code for each key stored in the +dictionary using the "hash()" built-in function. The hash code varies +widely depending on the key and a per-process seed; for example, +"'Python'" could hash to "-539294296" while "'python'", a string that +differs by a single bit, could hash to "1142331976". The hash code is +then used to calculate a location in an internal array where the value +will be stored. Assuming that you're storing keys that all have +different hash values, this means that dictionaries take constant time +-- *O*(1), in Big-O notation -- to retrieve a key. + + +Why must dictionary keys be immutable? +====================================== + +The hash table implementation of dictionaries uses a hash value +calculated from the key value to find the key. If the key were a +mutable object, its value could change, and thus its hash could also +change. But since whoever changes the key object can't tell that it +was being used as a dictionary key, it can't move the entry around in +the dictionary. Then, when you try to look up the same object in the +dictionary it won't be found because its hash value is different. If +you tried to look up the old value it wouldn't be found either, +because the value of the object found in that hash bin would be +different. + +If you want a dictionary indexed with a list, simply convert the list +to a tuple first; the function "tuple(L)" creates a tuple with the +same entries as the list "L". Tuples are immutable and can therefore +be used as dictionary keys. + +Some unacceptable solutions that have been proposed: + +* Hash lists by their address (object ID). This doesn't work because + if you construct a new list with the same value it won't be found; + e.g.: + + mydict = {[1, 2]: '12'} + print(mydict[[1, 2]]) + + would raise a "KeyError" exception because the id of the "[1, 2]" + used in the second line differs from that in the first line. In + other words, dictionary keys should be compared using "==", not + using "is". + +* Make a copy when using a list as a key. This doesn't work because + the list, being a mutable object, could contain a reference to + itself, and then the copying code would run into an infinite loop. + +* Allow lists as keys but tell the user not to modify them. This + would allow a class of hard-to-track bugs in programs when you + forgot or modified a list by accident. It also invalidates an + important invariant of dictionaries: every value in "d.keys()" is + usable as a key of the dictionary. + +* Mark lists as read-only once they are used as a dictionary key. The + problem is that it's not just the top-level object that could change + its value; you could use a tuple containing a list as a key. + Entering anything as a key into a dictionary would require marking + all objects reachable from there as read-only -- and again, self- + referential objects could cause an infinite loop. + +There is a trick to get around this if you need to, but use it at your +own risk: You can wrap a mutable structure inside a class instance +which has both a "__eq__()" and a "__hash__()" method. You must then +make sure that the hash value for all such wrapper objects that reside +in a dictionary (or other hash based structure), remain fixed while +the object is in the dictionary (or other structure). + + class ListWrapper: + def __init__(self, the_list): + self.the_list = the_list + + def __eq__(self, other): + return self.the_list == other.the_list + + def __hash__(self): + l = self.the_list + result = 98767 - len(l)*555 + for i, el in enumerate(l): + try: + result = result + (hash(el) % 9999999) * 1001 + i + except Exception: + result = (result % 7777777) + i * 333 + return result + +Note that the hash computation is complicated by the possibility that +some members of the list may be unhashable and also by the possibility +of arithmetic overflow. + +Furthermore it must always be the case that if "o1 == o2" (ie +"o1.__eq__(o2) is True") then "hash(o1) == hash(o2)" (ie, +"o1.__hash__() == o2.__hash__()"), regardless of whether the object is +in a dictionary or not. If you fail to meet these restrictions +dictionaries and other hash based structures will misbehave. + +In the case of "ListWrapper", whenever the wrapper object is in a +dictionary the wrapped list must not change to avoid anomalies. Don't +do this unless you are prepared to think hard about the requirements +and the consequences of not meeting them correctly. Consider yourself +warned. + + +Why doesn't list.sort() return the sorted list? +=============================================== + +In situations where performance matters, making a copy of the list +just to sort it would be wasteful. Therefore, "list.sort()" sorts the +list in place. In order to remind you of that fact, it does not return +the sorted list. This way, you won't be fooled into accidentally +overwriting a list when you need a sorted copy but also need to keep +the unsorted version around. + +If you want to return a new list, use the built-in "sorted()" function +instead. This function creates a new list from a provided iterable, +sorts it and returns it. For example, here's how to iterate over the +keys of a dictionary in sorted order: + + for key in sorted(mydict): + ... # do whatever with mydict[key]... + + +How do you specify and enforce an interface spec in Python? +=========================================================== + +An interface specification for a module as provided by languages such +as C++ and Java describes the prototypes for the methods and functions +of the module. Many feel that compile-time enforcement of interface +specifications helps in the construction of large programs. + +Python 2.6 adds an "abc" module that lets you define Abstract Base +Classes (ABCs). You can then use "isinstance()" and "issubclass()" to +check whether an instance or a class implements a particular ABC. The +"collections.abc" module defines a set of useful ABCs such as +"Iterable", "Container", and "MutableMapping". + +For Python, many of the advantages of interface specifications can be +obtained by an appropriate test discipline for components. + +A good test suite for a module can both provide a regression test and +serve as a module interface specification and a set of examples. Many +Python modules can be run as a script to provide a simple "self test." +Even modules which use complex external interfaces can often be tested +in isolation using trivial "stub" emulations of the external +interface. The "doctest" and "unittest" modules or third-party test +frameworks can be used to construct exhaustive test suites that +exercise every line of code in a module. + +An appropriate testing discipline can help build large complex +applications in Python as well as having interface specifications +would. In fact, it can be better because an interface specification +cannot test certain properties of a program. For example, the +"list.append()" method is expected to add new elements to the end of +some internal list; an interface specification cannot test that your +"list.append()" implementation will actually do this correctly, but +it's trivial to check this property in a test suite. + +Writing test suites is very helpful, and you might want to design your +code to make it easily tested. One increasingly popular technique, +test-driven development, calls for writing parts of the test suite +first, before you write any of the actual code. Of course Python +allows you to be sloppy and not write test cases at all. + + +Why is there no goto? +===================== + +In the 1970s people realized that unrestricted goto could lead to +messy "spaghetti" code that was hard to understand and revise. In a +high-level language, it is also unneeded as long as there are ways to +branch (in Python, with "if" statements and "or", "and", and +"if"/"else" expressions) and loop (with "while" and "for" statements, +possibly containing "continue" and "break"). + +One can also use exceptions to provide a "structured goto" that works +even across function calls. Many feel that exceptions can +conveniently emulate all reasonable uses of the "go" or "goto" +constructs of C, Fortran, and other languages. For example: + + class label(Exception): pass # declare a label + + try: + ... + if condition: raise label() # goto label + ... + except label: # where to goto + pass + ... + +This doesn't allow you to jump into the middle of a loop, but that's +usually considered an abuse of "goto" anyway. Use sparingly. + + +Why can't raw strings (r-strings) end with a backslash? +======================================================= + +More precisely, they can't end with an odd number of backslashes: the +unpaired backslash at the end escapes the closing quote character, +leaving an unterminated string. + +Raw strings were designed to ease creating input for processors +(chiefly regular expression engines) that want to do their own +backslash escape processing. Such processors consider an unmatched +trailing backslash to be an error anyway, so raw strings disallow +that. In return, they allow you to pass on the string quote character +by escaping it with a backslash. These rules work well when r-strings +are used for their intended purpose. + +If you're trying to build Windows pathnames, note that all Windows +system calls accept forward slashes too: + + f = open("/mydir/file.txt") # works fine! + +If you're trying to build a pathname for a DOS command, try e.g. one +of + + dir = r"\this\is\my\dos\dir" "\\" + dir = r"\this\is\my\dos\dir\ "[:-1] + dir = "\\this\\is\\my\\dos\\dir\\" + + +Why doesn't Python have a "with" statement for attribute assignments? +===================================================================== + +Python has a "with" statement that wraps the execution of a block, +calling code on the entrance and exit from the block. Some languages +have a construct that looks like this: + + with obj: + a = 1 # equivalent to obj.a = 1 + total = total + 1 # obj.total = obj.total + 1 + +In Python, such a construct would be ambiguous. + +Other languages, such as Object Pascal, Delphi, and C++, use static +types, so it's possible to know, in an unambiguous way, what member is +being assigned to. This is the main point of static typing -- the +compiler *always* knows the scope of every variable at compile time. + +Python uses dynamic types. It is impossible to know in advance which +attribute will be referenced at runtime. Member attributes may be +added or removed from objects on the fly. This makes it impossible to +know, from a simple reading, what attribute is being referenced: a +local one, a global one, or a member attribute? + +For instance, take the following incomplete snippet: + + def foo(a): + with a: + print(x) + +The snippet assumes that "a" must have a member attribute called "x". +However, there is nothing in Python that tells the interpreter this. +What should happen if "a" is, let us say, an integer? If there is a +global variable named "x", will it be used inside the "with" block? +As you see, the dynamic nature of Python makes such choices much +harder. + +The primary benefit of "with" and similar language features (reduction +of code volume) can, however, easily be achieved in Python by +assignment. Instead of: + + function(args).mydict[index][index].a = 21 + function(args).mydict[index][index].b = 42 + function(args).mydict[index][index].c = 63 + +write this: + + ref = function(args).mydict[index][index] + ref.a = 21 + ref.b = 42 + ref.c = 63 + +This also has the side-effect of increasing execution speed because +name bindings are resolved at run-time in Python, and the second +version only needs to perform the resolution once. + +Similar proposals that would introduce syntax to further reduce code +volume, such as using a 'leading dot', have been rejected in favour of +explicitness (see https://mail.python.org/pipermail/python- +ideas/2016-May/040070.html). + + +Why don't generators support the with statement? +================================================ + +For technical reasons, a generator used directly as a context manager +would not work correctly. When, as is most common, a generator is +used as an iterator run to completion, no closing is needed. When it +is, wrap it as "contextlib.closing(generator)" in the "with" +statement. + + +Why are colons required for the if/while/def/class statements? +============================================================== + +The colon is required primarily to enhance readability (one of the +results of the experimental ABC language). Consider this: + + if a == b + print(a) + +versus + + if a == b: + print(a) + +Notice how the second one is slightly easier to read. Notice further +how a colon sets off the example in this FAQ answer; it's a standard +usage in English. + +Another minor reason is that the colon makes it easier for editors +with syntax highlighting; they can look for colons to decide when +indentation needs to be increased instead of having to do a more +elaborate parsing of the program text. + + +Why does Python allow commas at the end of lists and tuples? +============================================================ + +Python lets you add a trailing comma at the end of lists, tuples, and +dictionaries: + + [1, 2, 3,] + ('a', 'b', 'c',) + d = { + "A": [1, 5], + "B": [6, 7], # last trailing comma is optional but good style + } + +There are several reasons to allow this. + +When you have a literal value for a list, tuple, or dictionary spread +across multiple lines, it's easier to add more elements because you +don't have to remember to add a comma to the previous line. The lines +can also be reordered without creating a syntax error. + +Accidentally omitting the comma can lead to errors that are hard to +diagnose. For example: + + x = [ + "fee", + "fie" + "foo", + "fum" + ] + +This list looks like it has four elements, but it actually contains +three: "fee", "fiefoo" and "fum". Always adding the comma avoids this +source of error. + +Allowing the trailing comma may also make programmatic code generation +easier. diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/extending.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/extending.txt new file mode 100644 index 0000000000..eb52b24271 --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/extending.txt @@ -0,0 +1,274 @@ +Extending/Embedding FAQ +*********************** + + +Can I create my own functions in C? +=================================== + +Yes, you can create built-in modules containing functions, variables, +exceptions and even new types in C. This is explained in the document +Extending and Embedding the Python Interpreter. + +Most intermediate or advanced Python books will also cover this topic. + + +Can I create my own functions in C++? +===================================== + +Yes, using the C compatibility features found in C++. Place "extern +"C" { ... }" around the Python include files and put "extern "C"" +before each function that is going to be called by the Python +interpreter. Global or static C++ objects with constructors are +probably not a good idea. + + +Writing C is hard; are there any alternatives? +============================================== + +There are a number of alternatives to writing your own C extensions, +depending on what you're trying to do. + +Cython and its relative Pyrex are compilers that accept a slightly +modified form of Python and generate the corresponding C code. Cython +and Pyrex make it possible to write an extension without having to +learn Python's C API. + +If you need to interface to some C or C++ library for which no Python +extension currently exists, you can try wrapping the library's data +types and functions with a tool such as SWIG. SIP, CXX Boost, or +Weave are also alternatives for wrapping C++ libraries. + + +How can I execute arbitrary Python statements from C? +===================================================== + +The highest-level function to do this is "PyRun_SimpleString()" which +takes a single string argument to be executed in the context of the +module "__main__" and returns "0" for success and "-1" when an +exception occurred (including "SyntaxError"). If you want more +control, use "PyRun_String()"; see the source for +"PyRun_SimpleString()" in "Python/pythonrun.c". + + +How can I evaluate an arbitrary Python expression from C? +========================================================= + +Call the function "PyRun_String()" from the previous question with the +start symbol "Py_eval_input"; it parses an expression, evaluates it +and returns its value. + + +How do I extract C values from a Python object? +=============================================== + +That depends on the object's type. If it's a tuple, "PyTuple_Size()" +returns its length and "PyTuple_GetItem()" returns the item at a +specified index. Lists have similar functions, "PyList_Size()" and +"PyList_GetItem()". + +For bytes, "PyBytes_Size()" returns its length and +"PyBytes_AsStringAndSize()" provides a pointer to its value and its +length. Note that Python bytes objects may contain null bytes so C's +"strlen()" should not be used. + +To test the type of an object, first make sure it isn't "NULL", and +then use "PyBytes_Check()", "PyTuple_Check()", "PyList_Check()", etc. + +There is also a high-level API to Python objects which is provided by +the so-called 'abstract' interface -- read "Include/abstract.h" for +further details. It allows interfacing with any kind of Python +sequence using calls like "PySequence_Length()", +"PySequence_GetItem()", etc. as well as many other useful protocols +such as numbers ("PyNumber_Index()" et al.) and mappings in the +PyMapping APIs. + + +How do I use Py_BuildValue() to create a tuple of arbitrary length? +=================================================================== + +You can't. Use "PyTuple_Pack()" instead. + + +How do I call an object's method from C? +======================================== + +The "PyObject_CallMethod()" function can be used to call an arbitrary +method of an object. The parameters are the object, the name of the +method to call, a format string like that used with "Py_BuildValue()", +and the argument values: + + PyObject * + PyObject_CallMethod(PyObject *object, const char *method_name, + const char *arg_format, ...); + +This works for any object that has methods -- whether built-in or +user-defined. You are responsible for eventually "Py_DECREF()"'ing the +return value. + +To call, e.g., a file object's "seek" method with arguments 10, 0 +(assuming the file object pointer is "f"): + + res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0); + if (res == NULL) { + ... an exception occurred ... + } + else { + Py_DECREF(res); + } + +Note that since "PyObject_CallObject()" *always* wants a tuple for the +argument list, to call a function without arguments, pass "()" for the +format, and to call a function with one argument, surround the +argument in parentheses, e.g. "(i)". + + +How do I catch the output from PyErr_Print() (or anything that prints to stdout/stderr)? +======================================================================================== + +In Python code, define an object that supports the "write()" method. +Assign this object to "sys.stdout" and "sys.stderr". Call +print_error, or just allow the standard traceback mechanism to work. +Then, the output will go wherever your "write()" method sends it. + +The easiest way to do this is to use the "io.StringIO" class: + + >>> import io, sys + >>> sys.stdout = io.StringIO() + >>> print('foo') + >>> print('hello world!') + >>> sys.stderr.write(sys.stdout.getvalue()) + foo + hello world! + +A custom object to do the same would look like this: + + >>> import io, sys + >>> class StdoutCatcher(io.TextIOBase): + ... def __init__(self): + ... self.data = [] + ... def write(self, stuff): + ... self.data.append(stuff) + ... + >>> import sys + >>> sys.stdout = StdoutCatcher() + >>> print('foo') + >>> print('hello world!') + >>> sys.stderr.write(''.join(sys.stdout.data)) + foo + hello world! + + +How do I access a module written in Python from C? +================================================== + +You can get a pointer to the module object as follows: + + module = PyImport_ImportModule(""); + +If the module hasn't been imported yet (i.e. it is not yet present in +"sys.modules"), this initializes the module; otherwise it simply +returns the value of "sys.modules[""]". Note that it +doesn't enter the module into any namespace -- it only ensures it has +been initialized and is stored in "sys.modules". + +You can then access the module's attributes (i.e. any name defined in +the module) as follows: + + attr = PyObject_GetAttrString(module, ""); + +Calling "PyObject_SetAttrString()" to assign to variables in the +module also works. + + +How do I interface to C++ objects from Python? +============================================== + +Depending on your requirements, there are many approaches. To do this +manually, begin by reading the "Extending and Embedding" document. +Realize that for the Python run-time system, there isn't a whole lot +of difference between C and C++ -- so the strategy of building a new +Python type around a C structure (pointer) type will also work for C++ +objects. + +For C++ libraries, see Writing C is hard; are there any alternatives?. + + +I added a module using the Setup file and the make fails; why? +============================================================== + +Setup must end in a newline, if there is no newline there, the build +process fails. (Fixing this requires some ugly shell script hackery, +and this bug is so minor that it doesn't seem worth the effort.) + + +How do I debug an extension? +============================ + +When using GDB with dynamically loaded extensions, you can't set a +breakpoint in your extension until your extension is loaded. + +In your ".gdbinit" file (or interactively), add the command: + + br _PyImport_LoadDynamicModule + +Then, when you run GDB: + + $ gdb /local/bin/python + gdb) run myscript.py + gdb) continue # repeat until your extension is loaded + gdb) finish # so that your extension is loaded + gdb) br myfunction.c:50 + gdb) continue + + +I want to compile a Python module on my Linux system, but some files are missing. Why? +====================================================================================== + +Most packaged versions of Python don't include the +"/usr/lib/python2.*x*/config/" directory, which contains various files +required for compiling Python extensions. + +For Red Hat, install the python-devel RPM to get the necessary files. + +For Debian, run "apt-get install python-dev". + + +How do I tell "incomplete input" from "invalid input"? +====================================================== + +Sometimes you want to emulate the Python interactive interpreter's +behavior, where it gives you a continuation prompt when the input is +incomplete (e.g. you typed the start of an "if" statement or you +didn't close your parentheses or triple string quotes), but it gives +you a syntax error message immediately when the input is invalid. + +In Python you can use the "codeop" module, which approximates the +parser's behavior sufficiently. IDLE uses this, for example. + +The easiest way to do it in C is to call "PyRun_InteractiveLoop()" +(perhaps in a separate thread) and let the Python interpreter handle +the input for you. You can also set the +"PyOS_ReadlineFunctionPointer()" to point at your custom input +function. See "Modules/readline.c" and "Parser/myreadline.c" for more +hints. + + +How do I find undefined g++ symbols __builtin_new or __pure_virtual? +==================================================================== + +To dynamically load g++ extension modules, you must recompile Python, +relink it using g++ (change LINKCC in the Python Modules Makefile), +and link your extension module using g++ (e.g., "g++ -shared -o +mymodule.so mymodule.o"). + + +Can I create an object class with some methods implemented in C and others in Python (e.g. through inheritance)? +================================================================================================================ + +Yes, you can inherit from built-in classes such as "int", "list", +"dict", etc. + +The Boost Python Library (BPL, +https://www.boost.org/libs/python/doc/index.html) provides a way of +doing this from C++ (i.e. you can inherit from an extension class +written in C++ using the BPL). diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/general.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/general.txt new file mode 100644 index 0000000000..07a16f4e4e --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/general.txt @@ -0,0 +1,465 @@ +General Python FAQ +****************** + + +General Information +=================== + + +What is Python? +--------------- + +Python is an interpreted, interactive, object-oriented programming +language. It incorporates modules, exceptions, dynamic typing, very +high level dynamic data types, and classes. It supports multiple +programming paradigms beyond object-oriented programming, such as +procedural and functional programming. Python combines remarkable +power with very clear syntax. It has interfaces to many system calls +and libraries, as well as to various window systems, and is extensible +in C or C++. It is also usable as an extension language for +applications that need a programmable interface. Finally, Python is +portable: it runs on many Unix variants including Linux and macOS, and +on Windows. + +To find out more, start with The Python Tutorial. The Beginner's +Guide to Python links to other introductory tutorials and resources +for learning Python. + + +What is the Python Software Foundation? +--------------------------------------- + +The Python Software Foundation is an independent non-profit +organization that holds the copyright on Python versions 2.1 and +newer. The PSF's mission is to advance open source technology related +to the Python programming language and to publicize the use of Python. +The PSF's home page is at https://www.python.org/psf/. + +Donations to the PSF are tax-exempt in the US. If you use Python and +find it helpful, please contribute via the PSF donation page. + + +Are there copyright restrictions on the use of Python? +------------------------------------------------------ + +You can do anything you want with the source, as long as you leave the +copyrights in and display those copyrights in any documentation about +Python that you produce. If you honor the copyright rules, it's OK to +use Python for commercial use, to sell copies of Python in source or +binary form (modified or unmodified), or to sell products that +incorporate Python in some form. We would still like to know about +all commercial use of Python, of course. + +See the license page to find further explanations and the full text of +the PSF License. + +The Python logo is trademarked, and in certain cases permission is +required to use it. Consult the Trademark Usage Policy for more +information. + + +Why was Python created in the first place? +------------------------------------------ + +Here's a *very* brief summary of what started it all, written by Guido +van Rossum: + + I had extensive experience with implementing an interpreted + language in the ABC group at CWI, and from working with this group + I had learned a lot about language design. This is the origin of + many Python features, including the use of indentation for + statement grouping and the inclusion of very-high-level data types + (although the details are all different in Python). + + I had a number of gripes about the ABC language, but also liked + many of its features. It was impossible to extend the ABC language + (or its implementation) to remedy my complaints -- in fact its lack + of extensibility was one of its biggest problems. I had some + experience with using Modula-2+ and talked with the designers of + Modula-3 and read the Modula-3 report. Modula-3 is the origin of + the syntax and semantics used for exceptions, and some other Python + features. + + I was working in the Amoeba distributed operating system group at + CWI. We needed a better way to do system administration than by + writing either C programs or Bourne shell scripts, since Amoeba had + its own system call interface which wasn't easily accessible from + the Bourne shell. My experience with error handling in Amoeba made + me acutely aware of the importance of exceptions as a programming + language feature. + + It occurred to me that a scripting language with a syntax like ABC + but with access to the Amoeba system calls would fill the need. I + realized that it would be foolish to write an Amoeba-specific + language, so I decided that I needed a language that was generally + extensible. + + During the 1989 Christmas holidays, I had a lot of time on my hand, + so I decided to give it a try. During the next year, while still + mostly working on it in my own time, Python was used in the Amoeba + project with increasing success, and the feedback from colleagues + made me add many early improvements. + + In February 1991, after just over a year of development, I decided + to post to USENET. The rest is in the "Misc/HISTORY" file. + + +What is Python good for? +------------------------ + +Python is a high-level general-purpose programming language that can +be applied to many different classes of problems. + +The language comes with a large standard library that covers areas +such as string processing (regular expressions, Unicode, calculating +differences between files), internet protocols (HTTP, FTP, SMTP, XML- +RPC, POP, IMAP), software engineering (unit testing, logging, +profiling, parsing Python code), and operating system interfaces +(system calls, filesystems, TCP/IP sockets). Look at the table of +contents for The Python Standard Library to get an idea of what's +available. A wide variety of third-party extensions are also +available. Consult the Python Package Index to find packages of +interest to you. + + +How does the Python version numbering scheme work? +-------------------------------------------------- + +Python versions are numbered "A.B.C" or "A.B": + +* *A* is the major version number -- it is only incremented for really + major changes in the language. + +* *B* is the minor version number -- it is incremented for less earth- + shattering changes. + +* *C* is the micro version number -- it is incremented for each bugfix + release. + +Not all releases are bugfix releases. In the run-up to a new feature +release, a series of development releases are made, denoted as alpha, +beta, or release candidate. Alphas are early releases in which +interfaces aren't yet finalized; it's not unexpected to see an +interface change between two alpha releases. Betas are more stable, +preserving existing interfaces but possibly adding new modules, and +release candidates are frozen, making no changes except as needed to +fix critical bugs. + +Alpha, beta and release candidate versions have an additional suffix: + +* The suffix for an alpha version is "aN" for some small number *N*. + +* The suffix for a beta version is "bN" for some small number *N*. + +* The suffix for a release candidate version is "rcN" for some small + number *N*. + +In other words, all versions labeled *2.0aN* precede the versions +labeled *2.0bN*, which precede versions labeled *2.0rcN*, and *those* +precede 2.0. + +You may also find version numbers with a "+" suffix, e.g. "2.2+". +These are unreleased versions, built directly from the CPython +development repository. In practice, after a final minor release is +made, the version is incremented to the next minor version, which +becomes the "a0" version, e.g. "2.4a0". + +See the Developer's Guide for more information about the development +cycle, and **PEP 387** to learn more about Python's backward +compatibility policy. See also the documentation for "sys.version", +"sys.hexversion", and "sys.version_info". + + +How do I obtain a copy of the Python source? +-------------------------------------------- + +The latest Python source distribution is always available from +python.org, at https://www.python.org/downloads/. The latest +development sources can be obtained at +https://github.com/python/cpython/. + +The source distribution is a gzipped tar file containing the complete +C source, Sphinx-formatted documentation, Python library modules, +example programs, and several useful pieces of freely distributable +software. The source will compile and run out of the box on most UNIX +platforms. + +Consult the Getting Started section of the Python Developer's Guide +for more information on getting the source code and compiling it. + + +How do I get documentation on Python? +------------------------------------- + +The standard documentation for the current stable version of Python is +available at https://docs.python.org/3/. PDF, plain text, and +downloadable HTML versions are also available at +https://docs.python.org/3/download.html. + +The documentation is written in reStructuredText and processed by the +Sphinx documentation tool. The reStructuredText source for the +documentation is part of the Python source distribution. + + +I've never programmed before. Is there a Python tutorial? +--------------------------------------------------------- + +There are numerous tutorials and books available. The standard +documentation includes The Python Tutorial. + +Consult the Beginner's Guide to find information for beginning Python +programmers, including lists of tutorials. + + +Is there a newsgroup or mailing list devoted to Python? +------------------------------------------------------- + +There is a newsgroup, *comp.lang.python*, and a mailing list, python- +list. The newsgroup and mailing list are gatewayed into each other -- +if you can read news it's unnecessary to subscribe to the mailing +list. *comp.lang.python* is high-traffic, receiving hundreds of +postings every day, and Usenet readers are often more able to cope +with this volume. + +Announcements of new software releases and events can be found in +comp.lang.python.announce, a low-traffic moderated list that receives +about five postings per day. It's available as the python-announce +mailing list. + +More info about other mailing lists and newsgroups can be found at +https://www.python.org/community/lists/. + + +How do I get a beta test version of Python? +------------------------------------------- + +Alpha and beta releases are available from +https://www.python.org/downloads/. All releases are announced on the +comp.lang.python and comp.lang.python.announce newsgroups and on the +Python home page at https://www.python.org/; an RSS feed of news is +available. + +You can also access the development version of Python through Git. +See The Python Developer's Guide for details. + + +How do I submit bug reports and patches for Python? +--------------------------------------------------- + +To report a bug or submit a patch, use the issue tracker at +https://github.com/python/cpython/issues. + +For more information on how Python is developed, consult the Python +Developer's Guide. + + +Are there any published articles about Python that I can reference? +------------------------------------------------------------------- + +It's probably best to cite your favorite book about Python. + +The very first article about Python was written in 1991 and is now +quite outdated. + + Guido van Rossum and Jelke de Boer, "Interactively Testing Remote + Servers Using the Python Programming Language", CWI Quarterly, + Volume 4, Issue 4 (December 1991), Amsterdam, pp 283--303. + + +Are there any books on Python? +------------------------------ + +Yes, there are many, and more are being published. See the python.org +wiki at https://wiki.python.org/moin/PythonBooks for a list. + +You can also search online bookstores for "Python" and filter out the +Monty Python references; or perhaps search for "Python" and +"language". + + +Where in the world is www.python.org located? +--------------------------------------------- + +The Python project's infrastructure is located all over the world and +is managed by the Python Infrastructure Team. Details here. + + +Why is it called Python? +------------------------ + +When he began implementing Python, Guido van Rossum was also reading +the published scripts from "Monty Python's Flying Circus", a BBC +comedy series from the 1970s. Van Rossum thought he needed a name +that was short, unique, and slightly mysterious, so he decided to call +the language Python. + + +Do I have to like "Monty Python's Flying Circus"? +------------------------------------------------- + +No, but it helps. :) + + +Python in the real world +======================== + + +How stable is Python? +--------------------- + +Very stable. New, stable releases have been coming out roughly every +6 to 18 months since 1991, and this seems likely to continue. As of +version 3.9, Python will have a new feature release every 12 months +(**PEP 602**). + +The developers issue bugfix releases of older versions, so the +stability of existing releases gradually improves. Bugfix releases, +indicated by a third component of the version number (e.g. 3.5.3, +3.6.2), are managed for stability; only fixes for known problems are +included in a bugfix release, and it's guaranteed that interfaces will +remain the same throughout a series of bugfix releases. + +The latest stable releases can always be found on the Python download +page. There are two production-ready versions of Python: 2.x and 3.x. +The recommended version is 3.x, which is supported by most widely used +libraries. Although 2.x is still widely used, it is not maintained +anymore. + + +How many people are using Python? +--------------------------------- + +There are probably millions of users, though it's difficult to obtain +an exact count. + +Python is available for free download, so there are no sales figures, +and it's available from many different sites and packaged with many +Linux distributions, so download statistics don't tell the whole story +either. + +The comp.lang.python newsgroup is very active, but not all Python +users post to the group or even read it. + + +Have any significant projects been done in Python? +-------------------------------------------------- + +See https://www.python.org/about/success for a list of projects that +use Python. Consulting the proceedings for past Python conferences +will reveal contributions from many different companies and +organizations. + +High-profile Python projects include the Mailman mailing list manager +and the Zope application server. Several Linux distributions, most +notably Red Hat, have written part or all of their installer and +system administration software in Python. Companies that use Python +internally include Google, Yahoo, and Lucasfilm Ltd. + + +What new developments are expected for Python in the future? +------------------------------------------------------------ + +See https://peps.python.org/ for the Python Enhancement Proposals +(PEPs). PEPs are design documents describing a suggested new feature +for Python, providing a concise technical specification and a +rationale. Look for a PEP titled "Python X.Y Release Schedule", where +X.Y is a version that hasn't been publicly released yet. + +New development is discussed on the python-dev mailing list. + + +Is it reasonable to propose incompatible changes to Python? +----------------------------------------------------------- + +In general, no. There are already millions of lines of Python code +around the world, so any change in the language that invalidates more +than a very small fraction of existing programs has to be frowned +upon. Even if you can provide a conversion program, there's still the +problem of updating all documentation; many books have been written +about Python, and we don't want to invalidate them all at a single +stroke. + +Providing a gradual upgrade path is necessary if a feature has to be +changed. **PEP 5** describes the procedure followed for introducing +backward-incompatible changes while minimizing disruption for users. + + +Is Python a good language for beginning programmers? +---------------------------------------------------- + +Yes. + +It is still common to start students with a procedural and statically +typed language such as Pascal, C, or a subset of C++ or Java. +Students may be better served by learning Python as their first +language. Python has a very simple and consistent syntax and a large +standard library and, most importantly, using Python in a beginning +programming course lets students concentrate on important programming +skills such as problem decomposition and data type design. With +Python, students can be quickly introduced to basic concepts such as +loops and procedures. They can probably even work with user-defined +objects in their very first course. + +For a student who has never programmed before, using a statically +typed language seems unnatural. It presents additional complexity +that the student must master and slows the pace of the course. The +students are trying to learn to think like a computer, decompose +problems, design consistent interfaces, and encapsulate data. While +learning to use a statically typed language is important in the long +term, it is not necessarily the best topic to address in the students' +first programming course. + +Many other aspects of Python make it a good first language. Like +Java, Python has a large standard library so that students can be +assigned programming projects very early in the course that *do* +something. Assignments aren't restricted to the standard four- +function calculator and check balancing programs. By using the +standard library, students can gain the satisfaction of working on +realistic applications as they learn the fundamentals of programming. +Using the standard library also teaches students about code reuse. +Third-party modules such as PyGame are also helpful in extending the +students' reach. + +Python's interactive interpreter enables students to test language +features while they're programming. They can keep a window with the +interpreter running while they enter their program's source in another +window. If they can't remember the methods for a list, they can do +something like this: + + >>> L = [] + >>> dir(L) + ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', + '__dir__', '__doc__', '__eq__', '__format__', '__ge__', + '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', + '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', + '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', + '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', + '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', + 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', + 'reverse', 'sort'] + >>> [d for d in dir(L) if '__' not in d] + ['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] + + >>> help(L.append) + Help on built-in function append: + + append(...) + L.append(object) -> None -- append object to end + + >>> L.append(1) + >>> L + [1] + +With the interpreter, documentation is never far from the student as +they are programming. + +There are also good IDEs for Python. IDLE is a cross-platform IDE for +Python that is written in Python using Tkinter. Emacs users will be +happy to know that there is a very good Python mode for Emacs. All of +these programming environments provide syntax highlighting, auto- +indenting, and access to the interactive interpreter while coding. +Consult the Python wiki for a full list of Python editing +environments. + +If you want to discuss Python's use in education, you may be +interested in joining the edu-sig mailing list. diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/gui.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/gui.txt new file mode 100644 index 0000000000..430be7034b --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/gui.txt @@ -0,0 +1,70 @@ +Graphic User Interface FAQ +************************** + + +General GUI Questions +===================== + + +What GUI toolkits exist for Python? +=================================== + +Standard builds of Python include an object-oriented interface to the +Tcl/Tk widget set, called tkinter. This is probably the easiest to +install (since it comes included with most binary distributions of +Python) and use. For more info about Tk, including pointers to the +source, see the Tcl/Tk home page. Tcl/Tk is fully portable to the +macOS, Windows, and Unix platforms. + +Depending on what platform(s) you are aiming at, there are also +several alternatives. A list of cross-platform and platform-specific +GUI frameworks can be found on the python wiki. + + +Tkinter questions +================= + + +How do I freeze Tkinter applications? +------------------------------------- + +Freeze is a tool to create stand-alone applications. When freezing +Tkinter applications, the applications will not be truly stand-alone, +as the application will still need the Tcl and Tk libraries. + +One solution is to ship the application with the Tcl and Tk libraries, +and point to them at run-time using the "TCL_LIBRARY" and "TK_LIBRARY" +environment variables. + +To get truly stand-alone applications, the Tcl scripts that form the +library have to be integrated into the application as well. One tool +supporting that is SAM (stand-alone modules), which is part of the Tix +distribution (https://tix.sourceforge.net/). + +Build Tix with SAM enabled, perform the appropriate call to +"Tclsam_init()", etc. inside Python's "Modules/tkappinit.c", and link +with libtclsam and libtksam (you might include the Tix libraries as +well). + + +Can I have Tk events handled while waiting for I/O? +--------------------------------------------------- + +On platforms other than Windows, yes, and you don't even need threads! +But you'll have to restructure your I/O code a bit. Tk has the +equivalent of Xt's "XtAddInput()" call, which allows you to register a +callback function which will be called from the Tk mainloop when I/O +is possible on a file descriptor. See File Handlers. + + +I can't get key bindings to work in Tkinter: why? +------------------------------------------------- + +An often-heard complaint is that event handlers bound to events with +the "bind()" method don't get handled even when the appropriate key is +pressed. + +The most common cause is that the widget to which the binding applies +doesn't have "keyboard focus". Check out the Tk documentation for the +focus command. Usually a widget is given the keyboard focus by +clicking in it (but not for labels; see the takefocus option). diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/index.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/index.txt new file mode 100644 index 0000000000..be6c0f72f9 --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/index.txt @@ -0,0 +1,18 @@ +Python Frequently Asked Questions +********************************* + +* General Python FAQ + +* Programming FAQ + +* Design and History FAQ + +* Library and Extension FAQ + +* Extending/Embedding FAQ + +* Python on Windows FAQ + +* Graphic User Interface FAQ + +* "Why is Python Installed on my Computer?" FAQ diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/installed.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/installed.txt new file mode 100644 index 0000000000..c95d16adf3 --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/installed.txt @@ -0,0 +1,61 @@ +"Why is Python Installed on my Computer?" FAQ +********************************************* + + +What is Python? +=============== + +Python is a programming language. It's used for many different +applications. It's used in some high schools and colleges as an +introductory programming language because Python is easy to learn, but +it's also used by professional software developers at places such as +Google, NASA, and Lucasfilm Ltd. + +If you wish to learn more about Python, start with the Beginner's +Guide to Python. + + +Why is Python installed on my machine? +====================================== + +If you find Python installed on your system but don't remember +installing it, there are several possible ways it could have gotten +there. + +* Perhaps another user on the computer wanted to learn programming and + installed it; you'll have to figure out who's been using the machine + and might have installed it. + +* A third-party application installed on the machine might have been + written in Python and included a Python installation. There are + many such applications, from GUI programs to network servers and + administrative scripts. + +* Some Windows machines also have Python installed. At this writing + we're aware of computers from Hewlett-Packard and Compaq that + include Python. Apparently some of HP/Compaq's administrative tools + are written in Python. + +* Many Unix-compatible operating systems, such as macOS and some Linux + distributions, have Python installed by default; it's included in + the base installation. + + +Can I delete Python? +==================== + +That depends on where Python came from. + +If someone installed it deliberately, you can remove it without +hurting anything. On Windows, use the Add/Remove Programs icon in the +Control Panel. + +If Python was installed by a third-party application, you can also +remove it, but that application will no longer work. You should use +that application's uninstaller rather than removing Python directly. + +If Python came with your operating system, removing it is not +recommended. If you remove it, whatever tools were written in Python +will no longer run, and some of them might be important to you. +Reinstalling the whole system would then be required to fix things +again. diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/library.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/library.txt new file mode 100644 index 0000000000..5376506afd --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/library.txt @@ -0,0 +1,730 @@ +Library and Extension FAQ +************************* + + +General Library Questions +========================= + + +How do I find a module or application to perform task X? +-------------------------------------------------------- + +Check the Library Reference to see if there's a relevant standard +library module. (Eventually you'll learn what's in the standard +library and will be able to skip this step.) + +For third-party packages, search the Python Package Index or try +Google or another web search engine. Searching for "Python" plus a +keyword or two for your topic of interest will usually find something +helpful. + + +Where is the math.py (socket.py, regex.py, etc.) source file? +------------------------------------------------------------- + +If you can't find a source file for a module it may be a built-in or +dynamically loaded module implemented in C, C++ or other compiled +language. In this case you may not have the source file or it may be +something like "mathmodule.c", somewhere in a C source directory (not +on the Python Path). + +There are (at least) three kinds of modules in Python: + +1. modules written in Python (.py); + +2. modules written in C and dynamically loaded (.dll, .pyd, .so, .sl, + etc); + +3. modules written in C and linked with the interpreter; to get a list + of these, type: + + import sys + print(sys.builtin_module_names) + + +How do I make a Python script executable on Unix? +------------------------------------------------- + +You need to do two things: the script file's mode must be executable +and the first line must begin with "#!" followed by the path of the +Python interpreter. + +The first is done by executing "chmod +x scriptfile" or perhaps "chmod +755 scriptfile". + +The second can be done in a number of ways. The most straightforward +way is to write + + #!/usr/local/bin/python + +as the very first line of your file, using the pathname for where the +Python interpreter is installed on your platform. + +If you would like the script to be independent of where the Python +interpreter lives, you can use the **env** program. Almost all Unix +variants support the following, assuming the Python interpreter is in +a directory on the user's "PATH": + + #!/usr/bin/env python + +*Don't* do this for CGI scripts. The "PATH" variable for CGI scripts +is often very minimal, so you need to use the actual absolute pathname +of the interpreter. + +Occasionally, a user's environment is so full that the +**/usr/bin/env** program fails; or there's no env program at all. In +that case, you can try the following hack (due to Alex Rezinsky): + + #! /bin/sh + """:" + exec python $0 ${1+"$@"} + """ + +The minor disadvantage is that this defines the script's __doc__ +string. However, you can fix that by adding + + __doc__ = """...Whatever...""" + + +Is there a curses/termcap package for Python? +--------------------------------------------- + +For Unix variants: The standard Python source distribution comes with +a curses module in the Modules subdirectory, though it's not compiled +by default. (Note that this is not available in the Windows +distribution -- there is no curses module for Windows.) + +The "curses" module supports basic curses features as well as many +additional functions from ncurses and SYSV curses such as colour, +alternative character set support, pads, and mouse support. This means +the module isn't compatible with operating systems that only have BSD +curses, but there don't seem to be any currently maintained OSes that +fall into this category. + + +Is there an equivalent to C's onexit() in Python? +------------------------------------------------- + +The "atexit" module provides a register function that is similar to +C's "onexit()". + + +Why don't my signal handlers work? +---------------------------------- + +The most common problem is that the signal handler is declared with +the wrong argument list. It is called as + + handler(signum, frame) + +so it should be declared with two parameters: + + def handler(signum, frame): + ... + + +Common tasks +============ + + +How do I test a Python program or component? +-------------------------------------------- + +Python comes with two testing frameworks. The "doctest" module finds +examples in the docstrings for a module and runs them, comparing the +output with the expected output given in the docstring. + +The "unittest" module is a fancier testing framework modelled on Java +and Smalltalk testing frameworks. + +To make testing easier, you should use good modular design in your +program. Your program should have almost all functionality +encapsulated in either functions or class methods -- and this +sometimes has the surprising and delightful effect of making the +program run faster (because local variable accesses are faster than +global accesses). Furthermore the program should avoid depending on +mutating global variables, since this makes testing much more +difficult to do. + +The "global main logic" of your program may be as simple as + + if __name__ == "__main__": + main_logic() + +at the bottom of the main module of your program. + +Once your program is organized as a tractable collection of function +and class behaviours, you should write test functions that exercise +the behaviours. A test suite that automates a sequence of tests can +be associated with each module. This sounds like a lot of work, but +since Python is so terse and flexible it's surprisingly easy. You can +make coding much more pleasant and fun by writing your test functions +in parallel with the "production code", since this makes it easy to +find bugs and even design flaws earlier. + +"Support modules" that are not intended to be the main module of a +program may include a self-test of the module. + + if __name__ == "__main__": + self_test() + +Even programs that interact with complex external interfaces may be +tested when the external interfaces are unavailable by using "fake" +interfaces implemented in Python. + + +How do I create documentation from doc strings? +----------------------------------------------- + +The "pydoc" module can create HTML from the doc strings in your Python +source code. An alternative for creating API documentation purely +from docstrings is epydoc. Sphinx can also include docstring content. + + +How do I get a single keypress at a time? +----------------------------------------- + +For Unix variants there are several solutions. It's straightforward +to do this using curses, but curses is a fairly large module to learn. + + +Threads +======= + + +How do I program using threads? +------------------------------- + +Be sure to use the "threading" module and not the "_thread" module. +The "threading" module builds convenient abstractions on top of the +low-level primitives provided by the "_thread" module. + + +None of my threads seem to run: why? +------------------------------------ + +As soon as the main thread exits, all threads are killed. Your main +thread is running too quickly, giving the threads no time to do any +work. + +A simple fix is to add a sleep to the end of the program that's long +enough for all the threads to finish: + + import threading, time + + def thread_task(name, n): + for i in range(n): + print(name, i) + + for i in range(10): + T = threading.Thread(target=thread_task, args=(str(i), i)) + T.start() + + time.sleep(10) # <---------------------------! + +But now (on many platforms) the threads don't run in parallel, but +appear to run sequentially, one at a time! The reason is that the OS +thread scheduler doesn't start a new thread until the previous thread +is blocked. + +A simple fix is to add a tiny sleep to the start of the run function: + + def thread_task(name, n): + time.sleep(0.001) # <--------------------! + for i in range(n): + print(name, i) + + for i in range(10): + T = threading.Thread(target=thread_task, args=(str(i), i)) + T.start() + + time.sleep(10) + +Instead of trying to guess a good delay value for "time.sleep()", it's +better to use some kind of semaphore mechanism. One idea is to use +the "queue" module to create a queue object, let each thread append a +token to the queue when it finishes, and let the main thread read as +many tokens from the queue as there are threads. + + +How do I parcel out work among a bunch of worker threads? +--------------------------------------------------------- + +The easiest way is to use the "concurrent.futures" module, especially +the "ThreadPoolExecutor" class. + +Or, if you want fine control over the dispatching algorithm, you can +write your own logic manually. Use the "queue" module to create a +queue containing a list of jobs. The "Queue" class maintains a list +of objects and has a ".put(obj)" method that adds items to the queue +and a ".get()" method to return them. The class will take care of the +locking necessary to ensure that each job is handed out exactly once. + +Here's a trivial example: + + import threading, queue, time + + # The worker thread gets jobs off the queue. When the queue is empty, it + # assumes there will be no more work and exits. + # (Realistically workers will run until terminated.) + def worker(): + print('Running worker') + time.sleep(0.1) + while True: + try: + arg = q.get(block=False) + except queue.Empty: + print('Worker', threading.current_thread(), end=' ') + print('queue empty') + break + else: + print('Worker', threading.current_thread(), end=' ') + print('running with argument', arg) + time.sleep(0.5) + + # Create queue + q = queue.Queue() + + # Start a pool of 5 workers + for i in range(5): + t = threading.Thread(target=worker, name='worker %i' % (i+1)) + t.start() + + # Begin adding work to the queue + for i in range(50): + q.put(i) + + # Give threads time to run + print('Main thread sleeping') + time.sleep(5) + +When run, this will produce the following output: + + Running worker + Running worker + Running worker + Running worker + Running worker + Main thread sleeping + Worker running with argument 0 + Worker running with argument 1 + Worker running with argument 2 + Worker running with argument 3 + Worker running with argument 4 + Worker running with argument 5 + ... + +Consult the module's documentation for more details; the "Queue" class +provides a featureful interface. + + +What kinds of global value mutation are thread-safe? +---------------------------------------------------- + +A *global interpreter lock* (GIL) is used internally to ensure that +only one thread runs in the Python VM at a time. In general, Python +offers to switch among threads only between bytecode instructions; how +frequently it switches can be set via "sys.setswitchinterval()". Each +bytecode instruction and therefore all the C implementation code +reached from each instruction is therefore atomic from the point of +view of a Python program. + +In theory, this means an exact accounting requires an exact +understanding of the PVM bytecode implementation. In practice, it +means that operations on shared variables of built-in data types +(ints, lists, dicts, etc) that "look atomic" really are. + +For example, the following operations are all atomic (L, L1, L2 are +lists, D, D1, D2 are dicts, x, y are objects, i, j are ints): + + L.append(x) + L1.extend(L2) + x = L[i] + x = L.pop() + L1[i:j] = L2 + L.sort() + x = y + x.field = y + D[x] = y + D1.update(D2) + D.keys() + +These aren't: + + i = i+1 + L.append(L[-1]) + L[i] = L[j] + D[x] = D[x] + 1 + +Operations that replace other objects may invoke those other objects' +"__del__()" method when their reference count reaches zero, and that +can affect things. This is especially true for the mass updates to +dictionaries and lists. When in doubt, use a mutex! + + +Can't we get rid of the Global Interpreter Lock? +------------------------------------------------ + +The *global interpreter lock* (GIL) is often seen as a hindrance to +Python's deployment on high-end multiprocessor server machines, +because a multi-threaded Python program effectively only uses one CPU, +due to the insistence that (almost) all Python code can only run while +the GIL is held. + +Back in the days of Python 1.5, Greg Stein actually implemented a +comprehensive patch set (the "free threading" patches) that removed +the GIL and replaced it with fine-grained locking. Adam Olsen +recently did a similar experiment in his python-safethread project. +Unfortunately, both experiments exhibited a sharp drop in single- +thread performance (at least 30% slower), due to the amount of fine- +grained locking necessary to compensate for the removal of the GIL. + +This doesn't mean that you can't make good use of Python on multi-CPU +machines! You just have to be creative with dividing the work up +between multiple *processes* rather than multiple *threads*. The +"ProcessPoolExecutor" class in the new "concurrent.futures" module +provides an easy way of doing so; the "multiprocessing" module +provides a lower-level API in case you want more control over +dispatching of tasks. + +Judicious use of C extensions will also help; if you use a C extension +to perform a time-consuming task, the extension can release the GIL +while the thread of execution is in the C code and allow other threads +to get some work done. Some standard library modules such as "zlib" +and "hashlib" already do this. + +It has been suggested that the GIL should be a per-interpreter-state +lock rather than truly global; interpreters then wouldn't be able to +share objects. Unfortunately, this isn't likely to happen either. It +would be a tremendous amount of work, because many object +implementations currently have global state. For example, small +integers and short strings are cached; these caches would have to be +moved to the interpreter state. Other object types have their own +free list; these free lists would have to be moved to the interpreter +state. And so on. + +And I doubt that it can even be done in finite time, because the same +problem exists for 3rd party extensions. It is likely that 3rd party +extensions are being written at a faster rate than you can convert +them to store all their global state in the interpreter state. + +And finally, once you have multiple interpreters not sharing any +state, what have you gained over running each interpreter in a +separate process? + + +Input and Output +================ + + +How do I delete a file? (And other file questions...) +----------------------------------------------------- + +Use "os.remove(filename)" or "os.unlink(filename)"; for documentation, +see the "os" module. The two functions are identical; "unlink()" is +simply the name of the Unix system call for this function. + +To remove a directory, use "os.rmdir()"; use "os.mkdir()" to create +one. "os.makedirs(path)" will create any intermediate directories in +"path" that don't exist. "os.removedirs(path)" will remove +intermediate directories as long as they're empty; if you want to +delete an entire directory tree and its contents, use +"shutil.rmtree()". + +To rename a file, use "os.rename(old_path, new_path)". + +To truncate a file, open it using "f = open(filename, "rb+")", and use +"f.truncate(offset)"; offset defaults to the current seek position. +There's also "os.ftruncate(fd, offset)" for files opened with +"os.open()", where *fd* is the file descriptor (a small integer). + +The "shutil" module also contains a number of functions to work on +files including "copyfile()", "copytree()", and "rmtree()". + + +How do I copy a file? +--------------------- + +The "shutil" module contains a "copyfile()" function. Note that on +Windows NTFS volumes, it does not copy alternate data streams nor +resource forks on macOS HFS+ volumes, though both are now rarely used. +It also doesn't copy file permissions and metadata, though using +"shutil.copy2()" instead will preserve most (though not all) of it. + + +How do I read (or write) binary data? +------------------------------------- + +To read or write complex binary data formats, it's best to use the +"struct" module. It allows you to take a string containing binary +data (usually numbers) and convert it to Python objects; and vice +versa. + +For example, the following code reads two 2-byte integers and one +4-byte integer in big-endian format from a file: + + import struct + + with open(filename, "rb") as f: + s = f.read(8) + x, y, z = struct.unpack(">hhl", s) + +The '>' in the format string forces big-endian data; the letter 'h' +reads one "short integer" (2 bytes), and 'l' reads one "long integer" +(4 bytes) from the string. + +For data that is more regular (e.g. a homogeneous list of ints or +floats), you can also use the "array" module. + +Note: + + To read and write binary data, it is mandatory to open the file in + binary mode (here, passing ""rb"" to "open()"). If you use ""r"" + instead (the default), the file will be open in text mode and + "f.read()" will return "str" objects rather than "bytes" objects. + + +I can't seem to use os.read() on a pipe created with os.popen(); why? +--------------------------------------------------------------------- + +"os.read()" is a low-level function which takes a file descriptor, a +small integer representing the opened file. "os.popen()" creates a +high-level file object, the same type returned by the built-in +"open()" function. Thus, to read *n* bytes from a pipe *p* created +with "os.popen()", you need to use "p.read(n)". + + +How do I access the serial (RS232) port? +---------------------------------------- + +For Win32, OSX, Linux, BSD, Jython, IronPython: + + pyserial + +For Unix, see a Usenet post by Mitch Chapman: + + https://groups.google.com/groups?selm=34A04430.CF9@ohioee.com + + +Why doesn't closing sys.stdout (stdin, stderr) really close it? +--------------------------------------------------------------- + +Python *file objects* are a high-level layer of abstraction on low- +level C file descriptors. + +For most file objects you create in Python via the built-in "open()" +function, "f.close()" marks the Python file object as being closed +from Python's point of view, and also arranges to close the underlying +C file descriptor. This also happens automatically in "f"'s +destructor, when "f" becomes garbage. + +But stdin, stdout and stderr are treated specially by Python, because +of the special status also given to them by C. Running +"sys.stdout.close()" marks the Python-level file object as being +closed, but does *not* close the associated C file descriptor. + +To close the underlying C file descriptor for one of these three, you +should first be sure that's what you really want to do (e.g., you may +confuse extension modules trying to do I/O). If it is, use +"os.close()": + + os.close(stdin.fileno()) + os.close(stdout.fileno()) + os.close(stderr.fileno()) + +Or you can use the numeric constants 0, 1 and 2, respectively. + + +Network/Internet Programming +============================ + + +What WWW tools are there for Python? +------------------------------------ + +See the chapters titled Internet Protocols and Support and Internet +Data Handling in the Library Reference Manual. Python has many +modules that will help you build server-side and client-side web +systems. + +A summary of available frameworks is maintained by Paul Boddie at +https://wiki.python.org/moin/WebProgramming. + +Cameron Laird maintains a useful set of pages about Python web +technologies at https://web.archive.org/web/20210224183619/http://pha +seit.net/claird/comp.lang.python/web_python. + + +How can I mimic CGI form submission (METHOD=POST)? +-------------------------------------------------- + +I would like to retrieve web pages that are the result of POSTing a +form. Is there existing code that would let me do this easily? + +Yes. Here's a simple example that uses "urllib.request": + + #!/usr/local/bin/python + + import urllib.request + + # build the query string + qs = "First=Josephine&MI=Q&Last=Public" + + # connect and send the server a path + req = urllib.request.urlopen('http://www.some-server.out-there' + '/cgi-bin/some-cgi-script', data=qs) + with req: + msg, hdrs = req.read(), req.info() + +Note that in general for percent-encoded POST operations, query +strings must be quoted using "urllib.parse.urlencode()". For example, +to send "name=Guy Steele, Jr.": + + >>> import urllib.parse + >>> urllib.parse.urlencode({'name': 'Guy Steele, Jr.'}) + 'name=Guy+Steele%2C+Jr.' + +See also: + + HOWTO Fetch Internet Resources Using The urllib Package for + extensive examples. + + +What module should I use to help with generating HTML? +------------------------------------------------------ + +You can find a collection of useful links on the Web Programming wiki +page. + + +How do I send mail from a Python script? +---------------------------------------- + +Use the standard library module "smtplib". + +Here's a very simple interactive mail sender that uses it. This +method will work on any host that supports an SMTP listener. + + import sys, smtplib + + fromaddr = input("From: ") + toaddrs = input("To: ").split(',') + print("Enter message, end with ^D:") + msg = '' + while True: + line = sys.stdin.readline() + if not line: + break + msg += line + + # The actual mail send + server = smtplib.SMTP('localhost') + server.sendmail(fromaddr, toaddrs, msg) + server.quit() + +A Unix-only alternative uses sendmail. The location of the sendmail +program varies between systems; sometimes it is "/usr/lib/sendmail", +sometimes "/usr/sbin/sendmail". The sendmail manual page will help +you out. Here's some sample code: + + import os + + SENDMAIL = "/usr/sbin/sendmail" # sendmail location + p = os.popen("%s -t -i" % SENDMAIL, "w") + p.write("To: receiver@example.com\n") + p.write("Subject: test\n") + p.write("\n") # blank line separating headers from body + p.write("Some text\n") + p.write("some more text\n") + sts = p.close() + if sts != 0: + print("Sendmail exit status", sts) + + +How do I avoid blocking in the connect() method of a socket? +------------------------------------------------------------ + +The "select" module is commonly used to help with asynchronous I/O on +sockets. + +To prevent the TCP connect from blocking, you can set the socket to +non-blocking mode. Then when you do the "connect()", you will either +connect immediately (unlikely) or get an exception that contains the +error number as ".errno". "errno.EINPROGRESS" indicates that the +connection is in progress, but hasn't finished yet. Different OSes +will return different values, so you're going to have to check what's +returned on your system. + +You can use the "connect_ex()" method to avoid creating an exception. +It will just return the errno value. To poll, you can call +"connect_ex()" again later -- "0" or "errno.EISCONN" indicate that +you're connected -- or you can pass this socket to "select.select()" +to check if it's writable. + +Note: + + The "asyncio" module provides a general purpose single-threaded and + concurrent asynchronous library, which can be used for writing non- + blocking network code. The third-party Twisted library is a popular + and feature-rich alternative. + + +Databases +========= + + +Are there any interfaces to database packages in Python? +-------------------------------------------------------- + +Yes. + +Interfaces to disk-based hashes such as "DBM" and "GDBM" are also +included with standard Python. There is also the "sqlite3" module, +which provides a lightweight disk-based relational database. + +Support for most relational databases is available. See the +DatabaseProgramming wiki page for details. + + +How do you implement persistent objects in Python? +-------------------------------------------------- + +The "pickle" library module solves this in a very general way (though +you still can't store things like open files, sockets or windows), and +the "shelve" library module uses pickle and (g)dbm to create +persistent mappings containing arbitrary Python objects. + + +Mathematics and Numerics +======================== + + +How do I generate random numbers in Python? +------------------------------------------- + +The standard module "random" implements a random number generator. +Usage is simple: + + import random + random.random() + +This returns a random floating-point number in the range [0, 1). + +There are also many other specialized generators in this module, such +as: + +* "randrange(a, b)" chooses an integer in the range [a, b). + +* "uniform(a, b)" chooses a floating-point number in the range [a, b). + +* "normalvariate(mean, sdev)" samples the normal (Gaussian) + distribution. + +Some higher-level functions operate on sequences directly, such as: + +* "choice(S)" chooses a random element from a given sequence. + +* "shuffle(L)" shuffles a list in-place, i.e. permutes it randomly. + +There's also a "Random" class you can instantiate to create +independent multiple random number generators. diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/programming.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/programming.txt new file mode 100644 index 0000000000..243f9f6ccc --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/programming.txt @@ -0,0 +1,2260 @@ +Programming FAQ +*************** + + +General Questions +================= + + +Is there a source code level debugger with breakpoints, single-stepping, etc.? +------------------------------------------------------------------------------ + +Yes. + +Several debuggers for Python are described below, and the built-in +function "breakpoint()" allows you to drop into any of them. + +The pdb module is a simple but adequate console-mode debugger for +Python. It is part of the standard Python library, and is "documented +in the Library Reference Manual". You can also write your own debugger +by using the code for pdb as an example. + +The IDLE interactive development environment, which is part of the +standard Python distribution (normally available as +Tools/scripts/idle3), includes a graphical debugger. + +PythonWin is a Python IDE that includes a GUI debugger based on pdb. +The PythonWin debugger colors breakpoints and has quite a few cool +features such as debugging non-PythonWin programs. PythonWin is +available as part of pywin32 project and as a part of the ActivePython +distribution. + +Eric is an IDE built on PyQt and the Scintilla editing component. + +trepan3k is a gdb-like debugger. + +Visual Studio Code is an IDE with debugging tools that integrates with +version-control software. + +There are a number of commercial Python IDEs that include graphical +debuggers. They include: + +* Wing IDE + +* Komodo IDE + +* PyCharm + + +Are there tools to help find bugs or perform static analysis? +------------------------------------------------------------- + +Yes. + +Pylint and Pyflakes do basic checking that will help you catch bugs +sooner. + +Static type checkers such as Mypy, Pyre, and Pytype can check type +hints in Python source code. + + +How can I create a stand-alone binary from a Python script? +----------------------------------------------------------- + +You don't need the ability to compile Python to C code if all you want +is a stand-alone program that users can download and run without +having to install the Python distribution first. There are a number +of tools that determine the set of modules required by a program and +bind these modules together with a Python binary to produce a single +executable. + +One is to use the freeze tool, which is included in the Python source +tree as Tools/freeze. It converts Python byte code to C arrays; with a +C compiler you can embed all your modules into a new program, which is +then linked with the standard Python modules. + +It works by scanning your source recursively for import statements (in +both forms) and looking for the modules in the standard Python path as +well as in the source directory (for built-in modules). It then turns +the bytecode for modules written in Python into C code (array +initializers that can be turned into code objects using the marshal +module) and creates a custom-made config file that only contains those +built-in modules which are actually used in the program. It then +compiles the generated C code and links it with the rest of the Python +interpreter to form a self-contained binary which acts exactly like +your script. + +The following packages can help with the creation of console and GUI +executables: + +* Nuitka (Cross-platform) + +* PyInstaller (Cross-platform) + +* PyOxidizer (Cross-platform) + +* cx_Freeze (Cross-platform) + +* py2app (macOS only) + +* py2exe (Windows only) + + +Are there coding standards or a style guide for Python programs? +---------------------------------------------------------------- + +Yes. The coding style required for standard library modules is +documented as **PEP 8**. + + +Core Language +============= + + +Why am I getting an UnboundLocalError when the variable has a value? +-------------------------------------------------------------------- + +It can be a surprise to get the "UnboundLocalError" in previously +working code when it is modified by adding an assignment statement +somewhere in the body of a function. + +This code: + +>>> x = 10 +>>> def bar(): +... print(x) +... +>>> bar() +10 + +works, but this code: + +>>> x = 10 +>>> def foo(): +... print(x) +... x += 1 + +results in an "UnboundLocalError": + +>>> foo() +Traceback (most recent call last): + ... +UnboundLocalError: local variable 'x' referenced before assignment + +This is because when you make an assignment to a variable in a scope, +that variable becomes local to that scope and shadows any similarly +named variable in the outer scope. Since the last statement in foo +assigns a new value to "x", the compiler recognizes it as a local +variable. Consequently when the earlier "print(x)" attempts to print +the uninitialized local variable and an error results. + +In the example above you can access the outer scope variable by +declaring it global: + +>>> x = 10 +>>> def foobar(): +... global x +... print(x) +... x += 1 +... +>>> foobar() +10 + +This explicit declaration is required in order to remind you that +(unlike the superficially analogous situation with class and instance +variables) you are actually modifying the value of the variable in the +outer scope: + +>>> print(x) +11 + +You can do a similar thing in a nested scope using the "nonlocal" +keyword: + +>>> def foo(): +... x = 10 +... def bar(): +... nonlocal x +... print(x) +... x += 1 +... bar() +... print(x) +... +>>> foo() +10 +11 + + +What are the rules for local and global variables in Python? +------------------------------------------------------------ + +In Python, variables that are only referenced inside a function are +implicitly global. If a variable is assigned a value anywhere within +the function's body, it's assumed to be a local unless explicitly +declared as global. + +Though a bit surprising at first, a moment's consideration explains +this. On one hand, requiring "global" for assigned variables provides +a bar against unintended side-effects. On the other hand, if "global" +was required for all global references, you'd be using "global" all +the time. You'd have to declare as global every reference to a built- +in function or to a component of an imported module. This clutter +would defeat the usefulness of the "global" declaration for +identifying side-effects. + + +Why do lambdas defined in a loop with different values all return the same result? +---------------------------------------------------------------------------------- + +Assume you use a for loop to define a few different lambdas (or even +plain functions), e.g.: + + >>> squares = [] + >>> for x in range(5): + ... squares.append(lambda: x**2) + +This gives you a list that contains 5 lambdas that calculate "x**2". +You might expect that, when called, they would return, respectively, +"0", "1", "4", "9", and "16". However, when you actually try you will +see that they all return "16": + + >>> squares[2]() + 16 + >>> squares[4]() + 16 + +This happens because "x" is not local to the lambdas, but is defined +in the outer scope, and it is accessed when the lambda is called --- +not when it is defined. At the end of the loop, the value of "x" is +"4", so all the functions now return "4**2", i.e. "16". You can also +verify this by changing the value of "x" and see how the results of +the lambdas change: + + >>> x = 8 + >>> squares[2]() + 64 + +In order to avoid this, you need to save the values in variables local +to the lambdas, so that they don't rely on the value of the global +"x": + + >>> squares = [] + >>> for x in range(5): + ... squares.append(lambda n=x: n**2) + +Here, "n=x" creates a new variable "n" local to the lambda and +computed when the lambda is defined so that it has the same value that +"x" had at that point in the loop. This means that the value of "n" +will be "0" in the first lambda, "1" in the second, "2" in the third, +and so on. Therefore each lambda will now return the correct result: + + >>> squares[2]() + 4 + >>> squares[4]() + 16 + +Note that this behaviour is not peculiar to lambdas, but applies to +regular functions too. + + +How do I share global variables across modules? +----------------------------------------------- + +The canonical way to share information across modules within a single +program is to create a special module (often called config or cfg). +Just import the config module in all modules of your application; the +module then becomes available as a global name. Because there is only +one instance of each module, any changes made to the module object get +reflected everywhere. For example: + +config.py: + + x = 0 # Default value of the 'x' configuration setting + +mod.py: + + import config + config.x = 1 + +main.py: + + import config + import mod + print(config.x) + +Note that using a module is also the basis for implementing the +singleton design pattern, for the same reason. + + +What are the "best practices" for using import in a module? +----------------------------------------------------------- + +In general, don't use "from modulename import *". Doing so clutters +the importer's namespace, and makes it much harder for linters to +detect undefined names. + +Import modules at the top of a file. Doing so makes it clear what +other modules your code requires and avoids questions of whether the +module name is in scope. Using one import per line makes it easy to +add and delete module imports, but using multiple imports per line +uses less screen space. + +It's good practice if you import modules in the following order: + +1. standard library modules -- e.g. "sys", "os", "argparse", "re" + +2. third-party library modules (anything installed in Python's site- + packages directory) -- e.g. "dateutil", "requests", "PIL.Image" + +3. locally developed modules + +It is sometimes necessary to move imports to a function or class to +avoid problems with circular imports. Gordon McMillan says: + + Circular imports are fine where both modules use the "import + " form of import. They fail when the 2nd module wants to + grab a name out of the first ("from module import name") and the + import is at the top level. That's because names in the 1st are + not yet available, because the first module is busy importing the + 2nd. + +In this case, if the second module is only used in one function, then +the import can easily be moved into that function. By the time the +import is called, the first module will have finished initializing, +and the second module can do its import. + +It may also be necessary to move imports out of the top level of code +if some of the modules are platform-specific. In that case, it may +not even be possible to import all of the modules at the top of the +file. In this case, importing the correct modules in the +corresponding platform-specific code is a good option. + +Only move imports into a local scope, such as inside a function +definition, if it's necessary to solve a problem such as avoiding a +circular import or are trying to reduce the initialization time of a +module. This technique is especially helpful if many of the imports +are unnecessary depending on how the program executes. You may also +want to move imports into a function if the modules are only ever used +in that function. Note that loading a module the first time may be +expensive because of the one time initialization of the module, but +loading a module multiple times is virtually free, costing only a +couple of dictionary lookups. Even if the module name has gone out of +scope, the module is probably available in "sys.modules". + + +Why are default values shared between objects? +---------------------------------------------- + +This type of bug commonly bites neophyte programmers. Consider this +function: + + def foo(mydict={}): # Danger: shared reference to one dict for all calls + ... compute something ... + mydict[key] = value + return mydict + +The first time you call this function, "mydict" contains a single +item. The second time, "mydict" contains two items because when +"foo()" begins executing, "mydict" starts out with an item already in +it. + +It is often expected that a function call creates new objects for +default values. This is not what happens. Default values are created +exactly once, when the function is defined. If that object is +changed, like the dictionary in this example, subsequent calls to the +function will refer to this changed object. + +By definition, immutable objects such as numbers, strings, tuples, and +"None", are safe from change. Changes to mutable objects such as +dictionaries, lists, and class instances can lead to confusion. + +Because of this feature, it is good programming practice to not use +mutable objects as default values. Instead, use "None" as the default +value and inside the function, check if the parameter is "None" and +create a new list/dictionary/whatever if it is. For example, don't +write: + + def foo(mydict={}): + ... + +but: + + def foo(mydict=None): + if mydict is None: + mydict = {} # create a new dict for local namespace + +This feature can be useful. When you have a function that's time- +consuming to compute, a common technique is to cache the parameters +and the resulting value of each call to the function, and return the +cached value if the same value is requested again. This is called +"memoizing", and can be implemented like this: + + # Callers can only provide two parameters and optionally pass _cache by keyword + def expensive(arg1, arg2, *, _cache={}): + if (arg1, arg2) in _cache: + return _cache[(arg1, arg2)] + + # Calculate the value + result = ... expensive computation ... + _cache[(arg1, arg2)] = result # Store result in the cache + return result + +You could use a global variable containing a dictionary instead of the +default value; it's a matter of taste. + + +How can I pass optional or keyword parameters from one function to another? +--------------------------------------------------------------------------- + +Collect the arguments using the "*" and "**" specifiers in the +function's parameter list; this gives you the positional arguments as +a tuple and the keyword arguments as a dictionary. You can then pass +these arguments when calling another function by using "*" and "**": + + def f(x, *args, **kwargs): + ... + kwargs['width'] = '14.3c' + ... + g(x, *args, **kwargs) + + +What is the difference between arguments and parameters? +-------------------------------------------------------- + +*Parameters* are defined by the names that appear in a function +definition, whereas *arguments* are the values actually passed to a +function when calling it. Parameters define what *kind of arguments* +a function can accept. For example, given the function definition: + + def func(foo, bar=None, **kwargs): + pass + +*foo*, *bar* and *kwargs* are parameters of "func". However, when +calling "func", for example: + + func(42, bar=314, extra=somevar) + +the values "42", "314", and "somevar" are arguments. + + +Why did changing list 'y' also change list 'x'? +----------------------------------------------- + +If you wrote code like: + + >>> x = [] + >>> y = x + >>> y.append(10) + >>> y + [10] + >>> x + [10] + +you might be wondering why appending an element to "y" changed "x" +too. + +There are two factors that produce this result: + +1. Variables are simply names that refer to objects. Doing "y = x" + doesn't create a copy of the list -- it creates a new variable "y" + that refers to the same object "x" refers to. This means that + there is only one object (the list), and both "x" and "y" refer to + it. + +2. Lists are *mutable*, which means that you can change their content. + +After the call to "append()", the content of the mutable object has +changed from "[]" to "[10]". Since both the variables refer to the +same object, using either name accesses the modified value "[10]". + +If we instead assign an immutable object to "x": + + >>> x = 5 # ints are immutable + >>> y = x + >>> x = x + 1 # 5 can't be mutated, we are creating a new object here + >>> x + 6 + >>> y + 5 + +we can see that in this case "x" and "y" are not equal anymore. This +is because integers are *immutable*, and when we do "x = x + 1" we are +not mutating the int "5" by incrementing its value; instead, we are +creating a new object (the int "6") and assigning it to "x" (that is, +changing which object "x" refers to). After this assignment we have +two objects (the ints "6" and "5") and two variables that refer to +them ("x" now refers to "6" but "y" still refers to "5"). + +Some operations (for example "y.append(10)" and "y.sort()") mutate the +object, whereas superficially similar operations (for example "y = y + +[10]" and "sorted(y)") create a new object. In general in Python (and +in all cases in the standard library) a method that mutates an object +will return "None" to help avoid getting the two types of operations +confused. So if you mistakenly write "y.sort()" thinking it will give +you a sorted copy of "y", you'll instead end up with "None", which +will likely cause your program to generate an easily diagnosed error. + +However, there is one class of operations where the same operation +sometimes has different behaviors with different types: the augmented +assignment operators. For example, "+=" mutates lists but not tuples +or ints ("a_list += [1, 2, 3]" is equivalent to "a_list.extend([1, 2, +3])" and mutates "a_list", whereas "some_tuple += (1, 2, 3)" and +"some_int += 1" create new objects). + +In other words: + +* If we have a mutable object ("list", "dict", "set", etc.), we can + use some specific operations to mutate it and all the variables that + refer to it will see the change. + +* If we have an immutable object ("str", "int", "tuple", etc.), all + the variables that refer to it will always see the same value, but + operations that transform that value into a new value always return + a new object. + +If you want to know if two variables refer to the same object or not, +you can use the "is" operator, or the built-in function "id()". + + +How do I write a function with output parameters (call by reference)? +--------------------------------------------------------------------- + +Remember that arguments are passed by assignment in Python. Since +assignment just creates references to objects, there's no alias +between an argument name in the caller and callee, and so no call-by- +reference per se. You can achieve the desired effect in a number of +ways. + +1. By returning a tuple of the results: + + >>> def func1(a, b): + ... a = 'new-value' # a and b are local names + ... b = b + 1 # assigned to new objects + ... return a, b # return new values + ... + >>> x, y = 'old-value', 99 + >>> func1(x, y) + ('new-value', 100) + + This is almost always the clearest solution. + +2. By using global variables. This isn't thread-safe, and is not + recommended. + +3. By passing a mutable (changeable in-place) object: + + >>> def func2(a): + ... a[0] = 'new-value' # 'a' references a mutable list + ... a[1] = a[1] + 1 # changes a shared object + ... + >>> args = ['old-value', 99] + >>> func2(args) + >>> args + ['new-value', 100] + +4. By passing in a dictionary that gets mutated: + + >>> def func3(args): + ... args['a'] = 'new-value' # args is a mutable dictionary + ... args['b'] = args['b'] + 1 # change it in-place + ... + >>> args = {'a': 'old-value', 'b': 99} + >>> func3(args) + >>> args + {'a': 'new-value', 'b': 100} + +5. Or bundle up values in a class instance: + + >>> class Namespace: + ... def __init__(self, /, **args): + ... for key, value in args.items(): + ... setattr(self, key, value) + ... + >>> def func4(args): + ... args.a = 'new-value' # args is a mutable Namespace + ... args.b = args.b + 1 # change object in-place + ... + >>> args = Namespace(a='old-value', b=99) + >>> func4(args) + >>> vars(args) + {'a': 'new-value', 'b': 100} + + There's almost never a good reason to get this complicated. + +Your best choice is to return a tuple containing the multiple results. + + +How do you make a higher order function in Python? +-------------------------------------------------- + +You have two choices: you can use nested scopes or you can use +callable objects. For example, suppose you wanted to define +"linear(a,b)" which returns a function "f(x)" that computes the value +"a*x+b". Using nested scopes: + + def linear(a, b): + def result(x): + return a * x + b + return result + +Or using a callable object: + + class linear: + + def __init__(self, a, b): + self.a, self.b = a, b + + def __call__(self, x): + return self.a * x + self.b + +In both cases, + + taxes = linear(0.3, 2) + +gives a callable object where "taxes(10e6) == 0.3 * 10e6 + 2". + +The callable object approach has the disadvantage that it is a bit +slower and results in slightly longer code. However, note that a +collection of callables can share their signature via inheritance: + + class exponential(linear): + # __init__ inherited + def __call__(self, x): + return self.a * (x ** self.b) + +Object can encapsulate state for several methods: + + class counter: + + value = 0 + + def set(self, x): + self.value = x + + def up(self): + self.value = self.value + 1 + + def down(self): + self.value = self.value - 1 + + count = counter() + inc, dec, reset = count.up, count.down, count.set + +Here "inc()", "dec()" and "reset()" act like functions which share the +same counting variable. + + +How do I copy an object in Python? +---------------------------------- + +In general, try "copy.copy()" or "copy.deepcopy()" for the general +case. Not all objects can be copied, but most can. + +Some objects can be copied more easily. Dictionaries have a "copy()" +method: + + newdict = olddict.copy() + +Sequences can be copied by slicing: + + new_l = l[:] + + +How can I find the methods or attributes of an object? +------------------------------------------------------ + +For an instance "x" of a user-defined class, "dir(x)" returns an +alphabetized list of the names containing the instance attributes and +methods and attributes defined by its class. + + +How can my code discover the name of an object? +----------------------------------------------- + +Generally speaking, it can't, because objects don't really have names. +Essentially, assignment always binds a name to a value; the same is +true of "def" and "class" statements, but in that case the value is a +callable. Consider the following code: + + >>> class A: + ... pass + ... + >>> B = A + >>> a = B() + >>> b = a + >>> print(b) + <__main__.A object at 0x16D07CC> + >>> print(a) + <__main__.A object at 0x16D07CC> + +Arguably the class has a name: even though it is bound to two names +and invoked through the name "B" the created instance is still +reported as an instance of class "A". However, it is impossible to +say whether the instance's name is "a" or "b", since both names are +bound to the same value. + +Generally speaking it should not be necessary for your code to "know +the names" of particular values. Unless you are deliberately writing +introspective programs, this is usually an indication that a change of +approach might be beneficial. + +In comp.lang.python, Fredrik Lundh once gave an excellent analogy in +answer to this question: + + The same way as you get the name of that cat you found on your + porch: the cat (object) itself cannot tell you its name, and it + doesn't really care -- so the only way to find out what it's called + is to ask all your neighbours (namespaces) if it's their cat + (object)... + + ....and don't be surprised if you'll find that it's known by many + names, or no name at all! + + +What's up with the comma operator's precedence? +----------------------------------------------- + +Comma is not an operator in Python. Consider this session: + + >>> "a" in "b", "a" + (False, 'a') + +Since the comma is not an operator, but a separator between +expressions the above is evaluated as if you had entered: + + ("a" in "b"), "a" + +not: + + "a" in ("b", "a") + +The same is true of the various assignment operators ("=", "+=" etc). +They are not truly operators but syntactic delimiters in assignment +statements. + + +Is there an equivalent of C's "?:" ternary operator? +---------------------------------------------------- + +Yes, there is. The syntax is as follows: + + [on_true] if [expression] else [on_false] + + x, y = 50, 25 + small = x if x < y else y + +Before this syntax was introduced in Python 2.5, a common idiom was to +use logical operators: + + [expression] and [on_true] or [on_false] + +However, this idiom is unsafe, as it can give wrong results when +*on_true* has a false boolean value. Therefore, it is always better +to use the "... if ... else ..." form. + + +Is it possible to write obfuscated one-liners in Python? +-------------------------------------------------------- + +Yes. Usually this is done by nesting "lambda" within "lambda". See +the following three examples, slightly adapted from Ulf Bartelt: + + from functools import reduce + + # Primes < 1000 + print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, + map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000))))) + + # First 10 Fibonacci numbers + print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: + f(x,f), range(10)))) + + # Mandelbrot set + print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y, + Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM, + Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro, + i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y + >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr( + 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy + ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)) + # \___ ___/ \___ ___/ | | |__ lines on screen + # V V | |______ columns on screen + # | | |__________ maximum of "iterations" + # | |_________________ range on y axis + # |____________________________ range on x axis + +Don't try this at home, kids! + + +What does the slash(/) in the parameter list of a function mean? +---------------------------------------------------------------- + +A slash in the argument list of a function denotes that the parameters +prior to it are positional-only. Positional-only parameters are the +ones without an externally usable name. Upon calling a function that +accepts positional-only parameters, arguments are mapped to parameters +based solely on their position. For example, "divmod()" is a function +that accepts positional-only parameters. Its documentation looks like +this: + + >>> help(divmod) + Help on built-in function divmod in module builtins: + + divmod(x, y, /) + Return the tuple (x//y, x%y). Invariant: div*y + mod == x. + +The slash at the end of the parameter list means that both parameters +are positional-only. Thus, calling "divmod()" with keyword arguments +would lead to an error: + + >>> divmod(x=3, y=4) + Traceback (most recent call last): + File "", line 1, in + TypeError: divmod() takes no keyword arguments + + +Numbers and strings +=================== + + +How do I specify hexadecimal and octal integers? +------------------------------------------------ + +To specify an octal digit, precede the octal value with a zero, and +then a lower or uppercase "o". For example, to set the variable "a" +to the octal value "10" (8 in decimal), type: + + >>> a = 0o10 + >>> a + 8 + +Hexadecimal is just as easy. Simply precede the hexadecimal number +with a zero, and then a lower or uppercase "x". Hexadecimal digits +can be specified in lower or uppercase. For example, in the Python +interpreter: + + >>> a = 0xa5 + >>> a + 165 + >>> b = 0XB2 + >>> b + 178 + + +Why does -22 // 10 return -3? +----------------------------- + +It's primarily driven by the desire that "i % j" have the same sign as +"j". If you want that, and also want: + + i == (i // j) * j + (i % j) + +then integer division has to return the floor. C also requires that +identity to hold, and then compilers that truncate "i // j" need to +make "i % j" have the same sign as "i". + +There are few real use cases for "i % j" when "j" is negative. When +"j" is positive, there are many, and in virtually all of them it's +more useful for "i % j" to be ">= 0". If the clock says 10 now, what +did it say 200 hours ago? "-190 % 12 == 2" is useful; "-190 % 12 == +-10" is a bug waiting to bite. + + +How do I get int literal attribute instead of SyntaxError? +---------------------------------------------------------- + +Trying to lookup an "int" literal attribute in the normal manner gives +a "SyntaxError" because the period is seen as a decimal point: + + >>> 1.__class__ + File "", line 1 + 1.__class__ + ^ + SyntaxError: invalid decimal literal + +The solution is to separate the literal from the period with either a +space or parentheses. + +>>> 1 .__class__ + +>>> (1).__class__ + + + +How do I convert a string to a number? +-------------------------------------- + +For integers, use the built-in "int()" type constructor, e.g. +"int('144') == 144". Similarly, "float()" converts to a floating- +point number, e.g. "float('144') == 144.0". + +By default, these interpret the number as decimal, so that +"int('0144') == 144" holds true, and "int('0x144')" raises +"ValueError". "int(string, base)" takes the base to convert from as a +second optional argument, so "int( '0x144', 16) == 324". If the base +is specified as 0, the number is interpreted using Python's rules: a +leading '0o' indicates octal, and '0x' indicates a hex number. + +Do not use the built-in function "eval()" if all you need is to +convert strings to numbers. "eval()" will be significantly slower and +it presents a security risk: someone could pass you a Python +expression that might have unwanted side effects. For example, +someone could pass "__import__('os').system("rm -rf $HOME")" which +would erase your home directory. + +"eval()" also has the effect of interpreting numbers as Python +expressions, so that e.g. "eval('09')" gives a syntax error because +Python does not allow leading '0' in a decimal number (except '0'). + + +How do I convert a number to a string? +-------------------------------------- + +To convert, e.g., the number "144" to the string "'144'", use the +built-in type constructor "str()". If you want a hexadecimal or octal +representation, use the built-in functions "hex()" or "oct()". For +fancy formatting, see the f-strings and Format String Syntax sections, +e.g. ""{:04d}".format(144)" yields "'0144'" and +""{:.3f}".format(1.0/3.0)" yields "'0.333'". + + +How do I modify a string in place? +---------------------------------- + +You can't, because strings are immutable. In most situations, you +should simply construct a new string from the various parts you want +to assemble it from. However, if you need an object with the ability +to modify in-place unicode data, try using an "io.StringIO" object or +the "array" module: + + >>> import io + >>> s = "Hello, world" + >>> sio = io.StringIO(s) + >>> sio.getvalue() + 'Hello, world' + >>> sio.seek(7) + 7 + >>> sio.write("there!") + 6 + >>> sio.getvalue() + 'Hello, there!' + + >>> import array + >>> a = array.array('u', s) + >>> print(a) + array('u', 'Hello, world') + >>> a[0] = 'y' + >>> print(a) + array('u', 'yello, world') + >>> a.tounicode() + 'yello, world' + + +How do I use strings to call functions/methods? +----------------------------------------------- + +There are various techniques. + +* The best is to use a dictionary that maps strings to functions. The + primary advantage of this technique is that the strings do not need + to match the names of the functions. This is also the primary + technique used to emulate a case construct: + + def a(): + pass + + def b(): + pass + + dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs + + dispatch[get_input()]() # Note trailing parens to call function + +* Use the built-in function "getattr()": + + import foo + getattr(foo, 'bar')() + + Note that "getattr()" works on any object, including classes, class + instances, modules, and so on. + + This is used in several places in the standard library, like this: + + class Foo: + def do_foo(self): + ... + + def do_bar(self): + ... + + f = getattr(foo_instance, 'do_' + opname) + f() + +* Use "locals()" to resolve the function name: + + def myFunc(): + print("hello") + + fname = "myFunc" + + f = locals()[fname] + f() + + +Is there an equivalent to Perl's chomp() for removing trailing newlines from strings? +------------------------------------------------------------------------------------- + +You can use "S.rstrip("\r\n")" to remove all occurrences of any line +terminator from the end of the string "S" without removing other +trailing whitespace. If the string "S" represents more than one line, +with several empty lines at the end, the line terminators for all the +blank lines will be removed: + + >>> lines = ("line 1 \r\n" + ... "\r\n" + ... "\r\n") + >>> lines.rstrip("\n\r") + 'line 1 ' + +Since this is typically only desired when reading text one line at a +time, using "S.rstrip()" this way works well. + + +Is there a scanf() or sscanf() equivalent? +------------------------------------------ + +Not as such. + +For simple input parsing, the easiest approach is usually to split the +line into whitespace-delimited words using the "split()" method of +string objects and then convert decimal strings to numeric values +using "int()" or "float()". "split()" supports an optional "sep" +parameter which is useful if the line uses something other than +whitespace as a separator. + +For more complicated input parsing, regular expressions are more +powerful than C's "sscanf" and better suited for the task. + + +What does 'UnicodeDecodeError' or 'UnicodeEncodeError' error mean? +------------------------------------------------------------------- + +See the Unicode HOWTO. + + +Can I end a raw string with an odd number of backslashes? +--------------------------------------------------------- + +A raw string ending with an odd number of backslashes will escape the +string's quote: + + >>> r'C:\this\will\not\work\' + File "", line 1 + r'C:\this\will\not\work\' + ^ + SyntaxError: unterminated string literal (detected at line 1) + +There are several workarounds for this. One is to use regular strings +and double the backslashes: + + >>> 'C:\\this\\will\\work\\' + 'C:\\this\\will\\work\\' + +Another is to concatenate a regular string containing an escaped +backslash to the raw string: + + >>> r'C:\this\will\work' '\\' + 'C:\\this\\will\\work\\' + +It is also possible to use "os.path.join()" to append a backslash on +Windows: + + >>> os.path.join(r'C:\this\will\work', '') + 'C:\\this\\will\\work\\' + +Note that while a backslash will "escape" a quote for the purposes of +determining where the raw string ends, no escaping occurs when +interpreting the value of the raw string. That is, the backslash +remains present in the value of the raw string: + + >>> r'backslash\'preserved' + "backslash\\'preserved" + +Also see the specification in the language reference. + + +Performance +=========== + + +My program is too slow. How do I speed it up? +--------------------------------------------- + +That's a tough one, in general. First, here are a list of things to +remember before diving further: + +* Performance characteristics vary across Python implementations. + This FAQ focuses on *CPython*. + +* Behaviour can vary across operating systems, especially when talking + about I/O or multi-threading. + +* You should always find the hot spots in your program *before* + attempting to optimize any code (see the "profile" module). + +* Writing benchmark scripts will allow you to iterate quickly when + searching for improvements (see the "timeit" module). + +* It is highly recommended to have good code coverage (through unit + testing or any other technique) before potentially introducing + regressions hidden in sophisticated optimizations. + +That being said, there are many tricks to speed up Python code. Here +are some general principles which go a long way towards reaching +acceptable performance levels: + +* Making your algorithms faster (or changing to faster ones) can yield + much larger benefits than trying to sprinkle micro-optimization + tricks all over your code. + +* Use the right data structures. Study documentation for the Built-in + Types and the "collections" module. + +* When the standard library provides a primitive for doing something, + it is likely (although not guaranteed) to be faster than any + alternative you may come up with. This is doubly true for + primitives written in C, such as builtins and some extension types. + For example, be sure to use either the "list.sort()" built-in method + or the related "sorted()" function to do sorting (and see the + Sorting Techniques for examples of moderately advanced usage). + +* Abstractions tend to create indirections and force the interpreter + to work more. If the levels of indirection outweigh the amount of + useful work done, your program will be slower. You should avoid + excessive abstraction, especially under the form of tiny functions + or methods (which are also often detrimental to readability). + +If you have reached the limit of what pure Python can allow, there are +tools to take you further away. For example, Cython can compile a +slightly modified version of Python code into a C extension, and can +be used on many different platforms. Cython can take advantage of +compilation (and optional type annotations) to make your code +significantly faster than when interpreted. If you are confident in +your C programming skills, you can also write a C extension module +yourself. + +See also: The wiki page devoted to performance tips. + + +What is the most efficient way to concatenate many strings together? +-------------------------------------------------------------------- + +"str" and "bytes" objects are immutable, therefore concatenating many +strings together is inefficient as each concatenation creates a new +object. In the general case, the total runtime cost is quadratic in +the total string length. + +To accumulate many "str" objects, the recommended idiom is to place +them into a list and call "str.join()" at the end: + + chunks = [] + for s in my_strings: + chunks.append(s) + result = ''.join(chunks) + +(another reasonably efficient idiom is to use "io.StringIO") + +To accumulate many "bytes" objects, the recommended idiom is to extend +a "bytearray" object using in-place concatenation (the "+=" operator): + + result = bytearray() + for b in my_bytes_objects: + result += b + + +Sequences (Tuples/Lists) +======================== + + +How do I convert between tuples and lists? +------------------------------------------ + +The type constructor "tuple(seq)" converts any sequence (actually, any +iterable) into a tuple with the same items in the same order. + +For example, "tuple([1, 2, 3])" yields "(1, 2, 3)" and "tuple('abc')" +yields "('a', 'b', 'c')". If the argument is a tuple, it does not +make a copy but returns the same object, so it is cheap to call +"tuple()" when you aren't sure that an object is already a tuple. + +The type constructor "list(seq)" converts any sequence or iterable +into a list with the same items in the same order. For example, +"list((1, 2, 3))" yields "[1, 2, 3]" and "list('abc')" yields "['a', +'b', 'c']". If the argument is a list, it makes a copy just like +"seq[:]" would. + + +What's a negative index? +------------------------ + +Python sequences are indexed with positive numbers and negative +numbers. For positive numbers 0 is the first index 1 is the second +index and so forth. For negative indices -1 is the last index and -2 +is the penultimate (next to last) index and so forth. Think of +"seq[-n]" as the same as "seq[len(seq)-n]". + +Using negative indices can be very convenient. For example "S[:-1]" +is all of the string except for its last character, which is useful +for removing the trailing newline from a string. + + +How do I iterate over a sequence in reverse order? +-------------------------------------------------- + +Use the "reversed()" built-in function: + + for x in reversed(sequence): + ... # do something with x ... + +This won't touch your original sequence, but build a new copy with +reversed order to iterate over. + + +How do you remove duplicates from a list? +----------------------------------------- + +See the Python Cookbook for a long discussion of many ways to do this: + + https://code.activestate.com/recipes/52560/ + +If you don't mind reordering the list, sort it and then scan from the +end of the list, deleting duplicates as you go: + + if mylist: + mylist.sort() + last = mylist[-1] + for i in range(len(mylist)-2, -1, -1): + if last == mylist[i]: + del mylist[i] + else: + last = mylist[i] + +If all elements of the list may be used as set keys (i.e. they are all +*hashable*) this is often faster + + mylist = list(set(mylist)) + +This converts the list into a set, thereby removing duplicates, and +then back into a list. + + +How do you remove multiple items from a list +-------------------------------------------- + +As with removing duplicates, explicitly iterating in reverse with a +delete condition is one possibility. However, it is easier and faster +to use slice replacement with an implicit or explicit forward +iteration. Here are three variations.: + + mylist[:] = filter(keep_function, mylist) + mylist[:] = (x for x in mylist if keep_condition) + mylist[:] = [x for x in mylist if keep_condition] + +The list comprehension may be fastest. + + +How do you make an array in Python? +----------------------------------- + +Use a list: + + ["this", 1, "is", "an", "array"] + +Lists are equivalent to C or Pascal arrays in their time complexity; +the primary difference is that a Python list can contain objects of +many different types. + +The "array" module also provides methods for creating arrays of fixed +types with compact representations, but they are slower to index than +lists. Also note that NumPy and other third party packages define +array-like structures with various characteristics as well. + +To get Lisp-style linked lists, you can emulate *cons cells* using +tuples: + + lisp_list = ("like", ("this", ("example", None) ) ) + +If mutability is desired, you could use lists instead of tuples. Here +the analogue of a Lisp *car* is "lisp_list[0]" and the analogue of +*cdr* is "lisp_list[1]". Only do this if you're sure you really need +to, because it's usually a lot slower than using Python lists. + + +How do I create a multidimensional list? +---------------------------------------- + +You probably tried to make a multidimensional array like this: + + >>> A = [[None] * 2] * 3 + +This looks correct if you print it: + + >>> A + [[None, None], [None, None], [None, None]] + +But when you assign a value, it shows up in multiple places: + + >>> A[0][0] = 5 + >>> A + [[5, None], [5, None], [5, None]] + +The reason is that replicating a list with "*" doesn't create copies, +it only creates references to the existing objects. The "*3" creates +a list containing 3 references to the same list of length two. +Changes to one row will show in all rows, which is almost certainly +not what you want. + +The suggested approach is to create a list of the desired length first +and then fill in each element with a newly created list: + + A = [None] * 3 + for i in range(3): + A[i] = [None] * 2 + +This generates a list containing 3 different lists of length two. You +can also use a list comprehension: + + w, h = 2, 3 + A = [[None] * w for i in range(h)] + +Or, you can use an extension that provides a matrix datatype; NumPy is +the best known. + + +How do I apply a method or function to a sequence of objects? +------------------------------------------------------------- + +To call a method or function and accumulate the return values is a +list, a *list comprehension* is an elegant solution: + + result = [obj.method() for obj in mylist] + + result = [function(obj) for obj in mylist] + +To just run the method or function without saving the return values, a +plain "for" loop will suffice: + + for obj in mylist: + obj.method() + + for obj in mylist: + function(obj) + + +Why does a_tuple[i] += ['item'] raise an exception when the addition works? +--------------------------------------------------------------------------- + +This is because of a combination of the fact that augmented assignment +operators are *assignment* operators, and the difference between +mutable and immutable objects in Python. + +This discussion applies in general when augmented assignment operators +are applied to elements of a tuple that point to mutable objects, but +we'll use a "list" and "+=" as our exemplar. + +If you wrote: + + >>> a_tuple = (1, 2) + >>> a_tuple[0] += 1 + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + +The reason for the exception should be immediately clear: "1" is added +to the object "a_tuple[0]" points to ("1"), producing the result +object, "2", but when we attempt to assign the result of the +computation, "2", to element "0" of the tuple, we get an error because +we can't change what an element of a tuple points to. + +Under the covers, what this augmented assignment statement is doing is +approximately this: + + >>> result = a_tuple[0] + 1 + >>> a_tuple[0] = result + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + +It is the assignment part of the operation that produces the error, +since a tuple is immutable. + +When you write something like: + + >>> a_tuple = (['foo'], 'bar') + >>> a_tuple[0] += ['item'] + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + +The exception is a bit more surprising, and even more surprising is +the fact that even though there was an error, the append worked: + + >>> a_tuple[0] + ['foo', 'item'] + +To see why this happens, you need to know that (a) if an object +implements an "__iadd__()" magic method, it gets called when the "+=" +augmented assignment is executed, and its return value is what gets +used in the assignment statement; and (b) for lists, "__iadd__()" is +equivalent to calling "extend()" on the list and returning the list. +That's why we say that for lists, "+=" is a "shorthand" for +"list.extend()": + + >>> a_list = [] + >>> a_list += [1] + >>> a_list + [1] + +This is equivalent to: + + >>> result = a_list.__iadd__([1]) + >>> a_list = result + +The object pointed to by a_list has been mutated, and the pointer to +the mutated object is assigned back to "a_list". The end result of +the assignment is a no-op, since it is a pointer to the same object +that "a_list" was previously pointing to, but the assignment still +happens. + +Thus, in our tuple example what is happening is equivalent to: + + >>> result = a_tuple[0].__iadd__(['item']) + >>> a_tuple[0] = result + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + +The "__iadd__()" succeeds, and thus the list is extended, but even +though "result" points to the same object that "a_tuple[0]" already +points to, that final assignment still results in an error, because +tuples are immutable. + + +I want to do a complicated sort: can you do a Schwartzian Transform in Python? +------------------------------------------------------------------------------ + +The technique, attributed to Randal Schwartz of the Perl community, +sorts the elements of a list by a metric which maps each element to +its "sort value". In Python, use the "key" argument for the +"list.sort()" method: + + Isorted = L[:] + Isorted.sort(key=lambda s: int(s[10:15])) + + +How can I sort one list by values from another list? +---------------------------------------------------- + +Merge them into an iterator of tuples, sort the resulting list, and +then pick out the element you want. + + >>> list1 = ["what", "I'm", "sorting", "by"] + >>> list2 = ["something", "else", "to", "sort"] + >>> pairs = zip(list1, list2) + >>> pairs = sorted(pairs) + >>> pairs + [("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')] + >>> result = [x[1] for x in pairs] + >>> result + ['else', 'sort', 'to', 'something'] + + +Objects +======= + + +What is a class? +---------------- + +A class is the particular object type created by executing a class +statement. Class objects are used as templates to create instance +objects, which embody both the data (attributes) and code (methods) +specific to a datatype. + +A class can be based on one or more other classes, called its base +class(es). It then inherits the attributes and methods of its base +classes. This allows an object model to be successively refined by +inheritance. You might have a generic "Mailbox" class that provides +basic accessor methods for a mailbox, and subclasses such as +"MboxMailbox", "MaildirMailbox", "OutlookMailbox" that handle various +specific mailbox formats. + + +What is a method? +----------------- + +A method is a function on some object "x" that you normally call as +"x.name(arguments...)". Methods are defined as functions inside the +class definition: + + class C: + def meth(self, arg): + return arg * 2 + self.attribute + + +What is self? +------------- + +Self is merely a conventional name for the first argument of a method. +A method defined as "meth(self, a, b, c)" should be called as +"x.meth(a, b, c)" for some instance "x" of the class in which the +definition occurs; the called method will think it is called as +"meth(x, a, b, c)". + +See also Why must 'self' be used explicitly in method definitions and +calls?. + + +How do I check if an object is an instance of a given class or of a subclass of it? +----------------------------------------------------------------------------------- + +Use the built-in function "isinstance(obj, cls)". You can check if an +object is an instance of any of a number of classes by providing a +tuple instead of a single class, e.g. "isinstance(obj, (class1, +class2, ...))", and can also check whether an object is one of +Python's built-in types, e.g. "isinstance(obj, str)" or +"isinstance(obj, (int, float, complex))". + +Note that "isinstance()" also checks for virtual inheritance from an +*abstract base class*. So, the test will return "True" for a +registered class even if hasn't directly or indirectly inherited from +it. To test for "true inheritance", scan the *MRO* of the class: + + from collections.abc import Mapping + + class P: + pass + + class C(P): + pass + + Mapping.register(P) + + >>> c = C() + >>> isinstance(c, C) # direct + True + >>> isinstance(c, P) # indirect + True + >>> isinstance(c, Mapping) # virtual + True + + # Actual inheritance chain + >>> type(c).__mro__ + (, , ) + + # Test for "true inheritance" + >>> Mapping in type(c).__mro__ + False + +Note that most programs do not use "isinstance()" on user-defined +classes very often. If you are developing the classes yourself, a +more proper object-oriented style is to define methods on the classes +that encapsulate a particular behaviour, instead of checking the +object's class and doing a different thing based on what class it is. +For example, if you have a function that does something: + + def search(obj): + if isinstance(obj, Mailbox): + ... # code to search a mailbox + elif isinstance(obj, Document): + ... # code to search a document + elif ... + +A better approach is to define a "search()" method on all the classes +and just call it: + + class Mailbox: + def search(self): + ... # code to search a mailbox + + class Document: + def search(self): + ... # code to search a document + + obj.search() + + +What is delegation? +------------------- + +Delegation is an object oriented technique (also called a design +pattern). Let's say you have an object "x" and want to change the +behaviour of just one of its methods. You can create a new class that +provides a new implementation of the method you're interested in +changing and delegates all other methods to the corresponding method +of "x". + +Python programmers can easily implement delegation. For example, the +following class implements a class that behaves like a file but +converts all written data to uppercase: + + class UpperOut: + + def __init__(self, outfile): + self._outfile = outfile + + def write(self, s): + self._outfile.write(s.upper()) + + def __getattr__(self, name): + return getattr(self._outfile, name) + +Here the "UpperOut" class redefines the "write()" method to convert +the argument string to uppercase before calling the underlying +"self._outfile.write()" method. All other methods are delegated to +the underlying "self._outfile" object. The delegation is accomplished +via the "__getattr__()" method; consult the language reference for +more information about controlling attribute access. + +Note that for more general cases delegation can get trickier. When +attributes must be set as well as retrieved, the class must define a +"__setattr__()" method too, and it must do so carefully. The basic +implementation of "__setattr__()" is roughly equivalent to the +following: + + class X: + ... + def __setattr__(self, name, value): + self.__dict__[name] = value + ... + +Most "__setattr__()" implementations must modify "self.__dict__" to +store local state for self without causing an infinite recursion. + + +How do I call a method defined in a base class from a derived class that extends it? +------------------------------------------------------------------------------------ + +Use the built-in "super()" function: + + class Derived(Base): + def meth(self): + super().meth() # calls Base.meth + +In the example, "super()" will automatically determine the instance +from which it was called (the "self" value), look up the *method +resolution order* (MRO) with "type(self).__mro__", and return the next +in line after "Derived" in the MRO: "Base". + + +How can I organize my code to make it easier to change the base class? +---------------------------------------------------------------------- + +You could assign the base class to an alias and derive from the alias. +Then all you have to change is the value assigned to the alias. +Incidentally, this trick is also handy if you want to decide +dynamically (e.g. depending on availability of resources) which base +class to use. Example: + + class Base: + ... + + BaseAlias = Base + + class Derived(BaseAlias): + ... + + +How do I create static class data and static class methods? +----------------------------------------------------------- + +Both static data and static methods (in the sense of C++ or Java) are +supported in Python. + +For static data, simply define a class attribute. To assign a new +value to the attribute, you have to explicitly use the class name in +the assignment: + + class C: + count = 0 # number of times C.__init__ called + + def __init__(self): + C.count = C.count + 1 + + def getcount(self): + return C.count # or return self.count + +"c.count" also refers to "C.count" for any "c" such that +"isinstance(c, C)" holds, unless overridden by "c" itself or by some +class on the base-class search path from "c.__class__" back to "C". + +Caution: within a method of C, an assignment like "self.count = 42" +creates a new and unrelated instance named "count" in "self"'s own +dict. Rebinding of a class-static data name must always specify the +class whether inside a method or not: + + C.count = 314 + +Static methods are possible: + + class C: + @staticmethod + def static(arg1, arg2, arg3): + # No 'self' parameter! + ... + +However, a far more straightforward way to get the effect of a static +method is via a simple module-level function: + + def getcount(): + return C.count + +If your code is structured so as to define one class (or tightly +related class hierarchy) per module, this supplies the desired +encapsulation. + + +How can I overload constructors (or methods) in Python? +------------------------------------------------------- + +This answer actually applies to all methods, but the question usually +comes up first in the context of constructors. + +In C++ you'd write + + class C { + C() { cout << "No arguments\n"; } + C(int i) { cout << "Argument is " << i << "\n"; } + } + +In Python you have to write a single constructor that catches all +cases using default arguments. For example: + + class C: + def __init__(self, i=None): + if i is None: + print("No arguments") + else: + print("Argument is", i) + +This is not entirely equivalent, but close enough in practice. + +You could also try a variable-length argument list, e.g. + + def __init__(self, *args): + ... + +The same approach works for all method definitions. + + +I try to use __spam and I get an error about _SomeClassName__spam. +------------------------------------------------------------------ + +Variable names with double leading underscores are "mangled" to +provide a simple but effective way to define class private variables. +Any identifier of the form "__spam" (at least two leading underscores, +at most one trailing underscore) is textually replaced with +"_classname__spam", where "classname" is the current class name with +any leading underscores stripped. + +The identifier can be used unchanged within the class, but to access +it outside the class, the mangled name must be used: + + class A: + def __one(self): + return 1 + def two(self): + return 2 * self.__one() + + class B(A): + def three(self): + return 3 * self._A__one() + + four = 4 * A()._A__one() + +In particular, this does not guarantee privacy since an outside user +can still deliberately access the private attribute; many Python +programmers never bother to use private variable names at all. + +See also: + + The private name mangling specifications for details and special + cases. + + +My class defines __del__ but it is not called when I delete the object. +----------------------------------------------------------------------- + +There are several possible reasons for this. + +The "del" statement does not necessarily call "__del__()" -- it simply +decrements the object's reference count, and if this reaches zero +"__del__()" is called. + +If your data structures contain circular links (e.g. a tree where each +child has a parent reference and each parent has a list of children) +the reference counts will never go back to zero. Once in a while +Python runs an algorithm to detect such cycles, but the garbage +collector might run some time after the last reference to your data +structure vanishes, so your "__del__()" method may be called at an +inconvenient and random time. This is inconvenient if you're trying to +reproduce a problem. Worse, the order in which object's "__del__()" +methods are executed is arbitrary. You can run "gc.collect()" to +force a collection, but there *are* pathological cases where objects +will never be collected. + +Despite the cycle collector, it's still a good idea to define an +explicit "close()" method on objects to be called whenever you're done +with them. The "close()" method can then remove attributes that refer +to subobjects. Don't call "__del__()" directly -- "__del__()" should +call "close()" and "close()" should make sure that it can be called +more than once for the same object. + +Another way to avoid cyclical references is to use the "weakref" +module, which allows you to point to objects without incrementing +their reference count. Tree data structures, for instance, should use +weak references for their parent and sibling references (if they need +them!). + +Finally, if your "__del__()" method raises an exception, a warning +message is printed to "sys.stderr". + + +How do I get a list of all instances of a given class? +------------------------------------------------------ + +Python does not keep track of all instances of a class (or of a built- +in type). You can program the class's constructor to keep track of all +instances by keeping a list of weak references to each instance. + + +Why does the result of "id()" appear to be not unique? +------------------------------------------------------ + +The "id()" builtin returns an integer that is guaranteed to be unique +during the lifetime of the object. Since in CPython, this is the +object's memory address, it happens frequently that after an object is +deleted from memory, the next freshly created object is allocated at +the same position in memory. This is illustrated by this example: + +>>> id(1000) +13901272 +>>> id(2000) +13901272 + +The two ids belong to different integer objects that are created +before, and deleted immediately after execution of the "id()" call. +To be sure that objects whose id you want to examine are still alive, +create another reference to the object: + +>>> a = 1000; b = 2000 +>>> id(a) +13901272 +>>> id(b) +13891296 + + +When can I rely on identity tests with the *is* operator? +--------------------------------------------------------- + +The "is" operator tests for object identity. The test "a is b" is +equivalent to "id(a) == id(b)". + +The most important property of an identity test is that an object is +always identical to itself, "a is a" always returns "True". Identity +tests are usually faster than equality tests. And unlike equality +tests, identity tests are guaranteed to return a boolean "True" or +"False". + +However, identity tests can *only* be substituted for equality tests +when object identity is assured. Generally, there are three +circumstances where identity is guaranteed: + +1) Assignments create new names but do not change object identity. +After the assignment "new = old", it is guaranteed that "new is old". + +2) Putting an object in a container that stores object references does +not change object identity. After the list assignment "s[0] = x", it +is guaranteed that "s[0] is x". + +3) If an object is a singleton, it means that only one instance of +that object can exist. After the assignments "a = None" and "b = +None", it is guaranteed that "a is b" because "None" is a singleton. + +In most other circumstances, identity tests are inadvisable and +equality tests are preferred. In particular, identity tests should +not be used to check constants such as "int" and "str" which aren't +guaranteed to be singletons: + + >>> a = 1000 + >>> b = 500 + >>> c = b + 500 + >>> a is c + False + + >>> a = 'Python' + >>> b = 'Py' + >>> c = b + 'thon' + >>> a is c + False + +Likewise, new instances of mutable containers are never identical: + + >>> a = [] + >>> b = [] + >>> a is b + False + +In the standard library code, you will see several common patterns for +correctly using identity tests: + +1) As recommended by **PEP 8**, an identity test is the preferred way +to check for "None". This reads like plain English in code and avoids +confusion with other objects that may have boolean values that +evaluate to false. + +2) Detecting optional arguments can be tricky when "None" is a valid +input value. In those situations, you can create a singleton sentinel +object guaranteed to be distinct from other objects. For example, +here is how to implement a method that behaves like "dict.pop()": + + _sentinel = object() + + def pop(self, key, default=_sentinel): + if key in self: + value = self[key] + del self[key] + return value + if default is _sentinel: + raise KeyError(key) + return default + +3) Container implementations sometimes need to augment equality tests +with identity tests. This prevents the code from being confused by +objects such as "float('NaN')" that are not equal to themselves. + +For example, here is the implementation of +"collections.abc.Sequence.__contains__()": + + def __contains__(self, value): + for v in self: + if v is value or v == value: + return True + return False + + +How can a subclass control what data is stored in an immutable instance? +------------------------------------------------------------------------ + +When subclassing an immutable type, override the "__new__()" method +instead of the "__init__()" method. The latter only runs *after* an +instance is created, which is too late to alter data in an immutable +instance. + +All of these immutable classes have a different signature than their +parent class: + + from datetime import date + + class FirstOfMonthDate(date): + "Always choose the first day of the month" + def __new__(cls, year, month, day): + return super().__new__(cls, year, month, 1) + + class NamedInt(int): + "Allow text names for some numbers" + xlat = {'zero': 0, 'one': 1, 'ten': 10} + def __new__(cls, value): + value = cls.xlat.get(value, value) + return super().__new__(cls, value) + + class TitleStr(str): + "Convert str to name suitable for a URL path" + def __new__(cls, s): + s = s.lower().replace(' ', '-') + s = ''.join([c for c in s if c.isalnum() or c == '-']) + return super().__new__(cls, s) + +The classes can be used like this: + + >>> FirstOfMonthDate(2012, 2, 14) + FirstOfMonthDate(2012, 2, 1) + >>> NamedInt('ten') + 10 + >>> NamedInt(20) + 20 + >>> TitleStr('Blog: Why Python Rocks') + 'blog-why-python-rocks' + + +How do I cache method calls? +---------------------------- + +The two principal tools for caching methods are +"functools.cached_property()" and "functools.lru_cache()". The former +stores results at the instance level and the latter at the class +level. + +The *cached_property* approach only works with methods that do not +take any arguments. It does not create a reference to the instance. +The cached method result will be kept only as long as the instance is +alive. + +The advantage is that when an instance is no longer used, the cached +method result will be released right away. The disadvantage is that +if instances accumulate, so too will the accumulated method results. +They can grow without bound. + +The *lru_cache* approach works with methods that have *hashable* +arguments. It creates a reference to the instance unless special +efforts are made to pass in weak references. + +The advantage of the least recently used algorithm is that the cache +is bounded by the specified *maxsize*. The disadvantage is that +instances are kept alive until they age out of the cache or until the +cache is cleared. + +This example shows the various techniques: + + class Weather: + "Lookup weather information on a government website" + + def __init__(self, station_id): + self._station_id = station_id + # The _station_id is private and immutable + + def current_temperature(self): + "Latest hourly observation" + # Do not cache this because old results + # can be out of date. + + @cached_property + def location(self): + "Return the longitude/latitude coordinates of the station" + # Result only depends on the station_id + + @lru_cache(maxsize=20) + def historic_rainfall(self, date, units='mm'): + "Rainfall on a given date" + # Depends on the station_id, date, and units. + +The above example assumes that the *station_id* never changes. If the +relevant instance attributes are mutable, the *cached_property* +approach can't be made to work because it cannot detect changes to the +attributes. + +To make the *lru_cache* approach work when the *station_id* is +mutable, the class needs to define the "__eq__()" and "__hash__()" +methods so that the cache can detect relevant attribute updates: + + class Weather: + "Example with a mutable station identifier" + + def __init__(self, station_id): + self.station_id = station_id + + def change_station(self, station_id): + self.station_id = station_id + + def __eq__(self, other): + return self.station_id == other.station_id + + def __hash__(self): + return hash(self.station_id) + + @lru_cache(maxsize=20) + def historic_rainfall(self, date, units='cm'): + 'Rainfall on a given date' + # Depends on the station_id, date, and units. + + +Modules +======= + + +How do I create a .pyc file? +---------------------------- + +When a module is imported for the first time (or when the source file +has changed since the current compiled file was created) a ".pyc" file +containing the compiled code should be created in a "__pycache__" +subdirectory of the directory containing the ".py" file. The ".pyc" +file will have a filename that starts with the same name as the ".py" +file, and ends with ".pyc", with a middle component that depends on +the particular "python" binary that created it. (See **PEP 3147** for +details.) + +One reason that a ".pyc" file may not be created is a permissions +problem with the directory containing the source file, meaning that +the "__pycache__" subdirectory cannot be created. This can happen, for +example, if you develop as one user but run as another, such as if you +are testing with a web server. + +Unless the "PYTHONDONTWRITEBYTECODE" environment variable is set, +creation of a .pyc file is automatic if you're importing a module and +Python has the ability (permissions, free space, etc...) to create a +"__pycache__" subdirectory and write the compiled module to that +subdirectory. + +Running Python on a top level script is not considered an import and +no ".pyc" will be created. For example, if you have a top-level +module "foo.py" that imports another module "xyz.py", when you run +"foo" (by typing "python foo.py" as a shell command), a ".pyc" will be +created for "xyz" because "xyz" is imported, but no ".pyc" file will +be created for "foo" since "foo.py" isn't being imported. + +If you need to create a ".pyc" file for "foo" -- that is, to create a +".pyc" file for a module that is not imported -- you can, using the +"py_compile" and "compileall" modules. + +The "py_compile" module can manually compile any module. One way is +to use the "compile()" function in that module interactively: + + >>> import py_compile + >>> py_compile.compile('foo.py') + +This will write the ".pyc" to a "__pycache__" subdirectory in the same +location as "foo.py" (or you can override that with the optional +parameter "cfile"). + +You can also automatically compile all files in a directory or +directories using the "compileall" module. You can do it from the +shell prompt by running "compileall.py" and providing the path of a +directory containing Python files to compile: + + python -m compileall . + + +How do I find the current module name? +-------------------------------------- + +A module can find out its own module name by looking at the predefined +global variable "__name__". If this has the value "'__main__'", the +program is running as a script. Many modules that are usually used by +importing them also provide a command-line interface or a self-test, +and only execute this code after checking "__name__": + + def main(): + print('Running test...') + ... + + if __name__ == '__main__': + main() + + +How can I have modules that mutually import each other? +------------------------------------------------------- + +Suppose you have the following modules: + +"foo.py": + + from bar import bar_var + foo_var = 1 + +"bar.py": + + from foo import foo_var + bar_var = 2 + +The problem is that the interpreter will perform the following steps: + +* main imports "foo" + +* Empty globals for "foo" are created + +* "foo" is compiled and starts executing + +* "foo" imports "bar" + +* Empty globals for "bar" are created + +* "bar" is compiled and starts executing + +* "bar" imports "foo" (which is a no-op since there already is a + module named "foo") + +* The import mechanism tries to read "foo_var" from "foo" globals, to + set "bar.foo_var = foo.foo_var" + +The last step fails, because Python isn't done with interpreting "foo" +yet and the global symbol dictionary for "foo" is still empty. + +The same thing happens when you use "import foo", and then try to +access "foo.foo_var" in global code. + +There are (at least) three possible workarounds for this problem. + +Guido van Rossum recommends avoiding all uses of "from import +...", and placing all code inside functions. Initializations of +global variables and class variables should use constants or built-in +functions only. This means everything from an imported module is +referenced as ".". + +Jim Roskind suggests performing steps in the following order in each +module: + +* exports (globals, functions, and classes that don't need imported + base classes) + +* "import" statements + +* active code (including globals that are initialized from imported + values). + +Van Rossum doesn't like this approach much because the imports appear +in a strange place, but it does work. + +Matthias Urlichs recommends restructuring your code so that the +recursive import is not necessary in the first place. + +These solutions are not mutually exclusive. + + +__import__('x.y.z') returns ; how do I get z? +--------------------------------------------------------- + +Consider using the convenience function "import_module()" from +"importlib" instead: + + z = importlib.import_module('x.y.z') + + +When I edit an imported module and reimport it, the changes don't show up. Why does this happen? +------------------------------------------------------------------------------------------------- + +For reasons of efficiency as well as consistency, Python only reads +the module file on the first time a module is imported. If it didn't, +in a program consisting of many modules where each one imports the +same basic module, the basic module would be parsed and re-parsed many +times. To force re-reading of a changed module, do this: + + import importlib + import modname + importlib.reload(modname) + +Warning: this technique is not 100% fool-proof. In particular, +modules containing statements like + + from modname import some_objects + +will continue to work with the old version of the imported objects. +If the module contains class definitions, existing class instances +will *not* be updated to use the new class definition. This can +result in the following paradoxical behaviour: + + >>> import importlib + >>> import cls + >>> c = cls.C() # Create an instance of C + >>> importlib.reload(cls) + + >>> isinstance(c, cls.C) # isinstance is false?!? + False + +The nature of the problem is made clear if you print out the +"identity" of the class objects: + + >>> hex(id(c.__class__)) + '0x7352a0' + >>> hex(id(cls.C)) + '0x4198d0' diff --git a/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/windows.txt b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/windows.txt new file mode 100644 index 0000000000..244a0c560d --- /dev/null +++ b/samples/msteams-FAQPlus/src/data/python-3.12.4-docs-text/faq/windows.txt @@ -0,0 +1,275 @@ +Python on Windows FAQ +********************* + + +How do I run a Python program under Windows? +============================================ + +This is not necessarily a straightforward question. If you are already +familiar with running programs from the Windows command line then +everything will seem obvious; otherwise, you might need a little more +guidance. + +Unless you use some sort of integrated development environment, you +will end up *typing* Windows commands into what is referred to as a +"Command prompt window". Usually you can create such a window from +your search bar by searching for "cmd". You should be able to +recognize when you have started such a window because you will see a +Windows "command prompt", which usually looks like this: + + C:\> + +The letter may be different, and there might be other things after it, +so you might just as easily see something like: + + D:\YourName\Projects\Python> + +depending on how your computer has been set up and what else you have +recently done with it. Once you have started such a window, you are +well on the way to running Python programs. + +You need to realize that your Python scripts have to be processed by +another program called the Python *interpreter*. The interpreter +reads your script, compiles it into bytecodes, and then executes the +bytecodes to run your program. So, how do you arrange for the +interpreter to handle your Python? + +First, you need to make sure that your command window recognises the +word "py" as an instruction to start the interpreter. If you have +opened a command window, you should try entering the command "py" and +hitting return: + + C:\Users\YourName> py + +You should then see something like: + + Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)] on win32 + Type "help", "copyright", "credits" or "license" for more information. + >>> + +You have started the interpreter in "interactive mode". That means you +can enter Python statements or expressions interactively and have them +executed or evaluated while you wait. This is one of Python's +strongest features. Check it by entering a few expressions of your +choice and seeing the results: + + >>> print("Hello") + Hello + >>> "Hello" * 3 + 'HelloHelloHello' + +Many people use the interactive mode as a convenient yet highly +programmable calculator. When you want to end your interactive Python +session, call the "exit()" function or hold the "Ctrl" key down while +you enter a "Z", then hit the ""Enter"" key to get back to your +Windows command prompt. + +You may also find that you have a Start-menu entry such as Start ‣ +Programs ‣ Python 3.x ‣ Python (command line) that results in you +seeing the ">>>" prompt in a new window. If so, the window will +disappear after you call the "exit()" function or enter the "Ctrl-Z" +character; Windows is running a single "python" command in the window, +and closes it when you terminate the interpreter. + +Now that we know the "py" command is recognized, you can give your +Python script to it. You'll have to give either an absolute or a +relative path to the Python script. Let's say your Python script is +located in your desktop and is named "hello.py", and your command +prompt is nicely opened in your home directory so you're seeing +something similar to: + + C:\Users\YourName> + +So now you'll ask the "py" command to give your script to Python by +typing "py" followed by your script path: + + C:\Users\YourName> py Desktop\hello.py + hello + + +How do I make Python scripts executable? +======================================== + +On Windows, the standard Python installer already associates the .py +extension with a file type (Python.File) and gives that file type an +open command that runs the interpreter ("D:\Program +Files\Python\python.exe "%1" %*"). This is enough to make scripts +executable from the command prompt as 'foo.py'. If you'd rather be +able to execute the script by simple typing 'foo' with no extension +you need to add .py to the PATHEXT environment variable. + + +Why does Python sometimes take so long to start? +================================================ + +Usually Python starts very quickly on Windows, but occasionally there +are bug reports that Python suddenly begins to take a long time to +start up. This is made even more puzzling because Python will work +fine on other Windows systems which appear to be configured +identically. + +The problem may be caused by a misconfiguration of virus checking +software on the problem machine. Some virus scanners have been known +to introduce startup overhead of two orders of magnitude when the +scanner is configured to monitor all reads from the filesystem. Try +checking the configuration of virus scanning software on your systems +to ensure that they are indeed configured identically. McAfee, when +configured to scan all file system read activity, is a particular +offender. + + +How do I make an executable from a Python script? +================================================= + +See How can I create a stand-alone binary from a Python script? for a +list of tools that can be used to make executables. + + +Is a "*.pyd" file the same as a DLL? +==================================== + +Yes, .pyd files are dll's, but there are a few differences. If you +have a DLL named "foo.pyd", then it must have a function +"PyInit_foo()". You can then write Python "import foo", and Python +will search for foo.pyd (as well as foo.py, foo.pyc) and if it finds +it, will attempt to call "PyInit_foo()" to initialize it. You do not +link your .exe with foo.lib, as that would cause Windows to require +the DLL to be present. + +Note that the search path for foo.pyd is PYTHONPATH, not the same as +the path that Windows uses to search for foo.dll. Also, foo.pyd need +not be present to run your program, whereas if you linked your program +with a dll, the dll is required. Of course, foo.pyd is required if +you want to say "import foo". In a DLL, linkage is declared in the +source code with "__declspec(dllexport)". In a .pyd, linkage is +defined in a list of available functions. + + +How can I embed Python into a Windows application? +================================================== + +Embedding the Python interpreter in a Windows app can be summarized as +follows: + +1. Do **not** build Python into your .exe file directly. On Windows, + Python must be a DLL to handle importing modules that are + themselves DLL's. (This is the first key undocumented fact.) + Instead, link to "python*NN*.dll"; it is typically installed in + "C:\Windows\System". *NN* is the Python version, a number such as + "33" for Python 3.3. + + You can link to Python in two different ways. Load-time linking + means linking against "python*NN*.lib", while run-time linking + means linking against "python*NN*.dll". (General note: + "python*NN*.lib" is the so-called "import lib" corresponding to + "python*NN*.dll". It merely defines symbols for the linker.) + + Run-time linking greatly simplifies link options; everything + happens at run time. Your code must load "python*NN*.dll" using + the Windows "LoadLibraryEx()" routine. The code must also use + access routines and data in "python*NN*.dll" (that is, Python's C + API's) using pointers obtained by the Windows "GetProcAddress()" + routine. Macros can make using these pointers transparent to any C + code that calls routines in Python's C API. + +2. If you use SWIG, it is easy to create a Python "extension module" + that will make the app's data and methods available to Python. + SWIG will handle just about all the grungy details for you. The + result is C code that you link *into* your .exe file (!) You do + **not** have to create a DLL file, and this also simplifies + linking. + +3. SWIG will create an init function (a C function) whose name depends + on the name of the extension module. For example, if the name of + the module is leo, the init function will be called initleo(). If + you use SWIG shadow classes, as you should, the init function will + be called initleoc(). This initializes a mostly hidden helper + class used by the shadow class. + + The reason you can link the C code in step 2 into your .exe file is + that calling the initialization function is equivalent to importing + the module into Python! (This is the second key undocumented fact.) + +4. In short, you can use the following code to initialize the Python + interpreter with your extension module. + + #include + ... + Py_Initialize(); // Initialize Python. + initmyAppc(); // Initialize (import) the helper class. + PyRun_SimpleString("import myApp"); // Import the shadow class. + +5. There are two problems with Python's C API which will become + apparent if you use a compiler other than MSVC, the compiler used + to build pythonNN.dll. + + Problem 1: The so-called "Very High Level" functions that take + "FILE *" arguments will not work in a multi-compiler environment + because each compiler's notion of a "struct FILE" will be + different. From an implementation standpoint these are very low + level functions. + + Problem 2: SWIG generates the following code when generating + wrappers to void functions: + + Py_INCREF(Py_None); + _resultobj = Py_None; + return _resultobj; + + Alas, Py_None is a macro that expands to a reference to a complex + data structure called _Py_NoneStruct inside pythonNN.dll. Again, + this code will fail in a mult-compiler environment. Replace such + code by: + + return Py_BuildValue(""); + + It may be possible to use SWIG's "%typemap" command to make the + change automatically, though I have not been able to get this to + work (I'm a complete SWIG newbie). + +6. Using a Python shell script to put up a Python interpreter window + from inside your Windows app is not a good idea; the resulting + window will be independent of your app's windowing system. Rather, + you (or the wxPythonWindow class) should create a "native" + interpreter window. It is easy to connect that window to the + Python interpreter. You can redirect Python's i/o to _any_ object + that supports read and write, so all you need is a Python object + (defined in your extension module) that contains read() and write() + methods. + + +How do I keep editors from inserting tabs into my Python source? +================================================================ + +The FAQ does not recommend using tabs, and the Python style guide, +**PEP 8**, recommends 4 spaces for distributed Python code; this is +also the Emacs python-mode default. + +Under any editor, mixing tabs and spaces is a bad idea. MSVC is no +different in this respect, and is easily configured to use spaces: +Take Tools ‣ Options ‣ Tabs, and for file type "Default" set "Tab +size" and "Indent size" to 4, and select the "Insert spaces" radio +button. + +Python raises "IndentationError" or "TabError" if mixed tabs and +spaces are causing problems in leading whitespace. You may also run +the "tabnanny" module to check a directory tree in batch mode. + + +How do I check for a keypress without blocking? +=============================================== + +Use the "msvcrt" module. This is a standard Windows-specific +extension module. It defines a function "kbhit()" which checks whether +a keyboard hit is present, and "getch()" which gets one character +without echoing it. + + +How do I solve the missing api-ms-win-crt-runtime-l1-1-0.dll error? +=================================================================== + +This can occur on Python 3.5 and later when using Windows 8.1 or +earlier without all updates having been installed. First ensure your +operating system is supported and is up to date, and if that does not +resolve the issue, visit the Microsoft support page for guidance on +manually installing the C Runtime update. diff --git a/samples/msteams-FAQPlus/src/indexer/URL.txt b/samples/msteams-FAQPlus/src/indexer/URL.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples/msteams-FAQPlus/src/indexer/data.py b/samples/msteams-FAQPlus/src/indexer/data.py new file mode 100644 index 0000000000..d163f181db --- /dev/null +++ b/samples/msteams-FAQPlus/src/indexer/data.py @@ -0,0 +1,135 @@ +import base64 +import os +from datetime import datetime +from typing import List, Dict, Any, Union +from PyPDF2 import PdfReader +from docx import Document +from pptx import Presentation +import requests +from bs4 import BeautifulSoup +import json +import re + + +def generate_id(path: str) -> str: + return base64.urlsafe_b64encode(path.encode()).decode().rstrip("=") + +def generate_title_from_content(content: str) -> str: + # Assume the title is the first line of the content + title = content.split('\n', 1)[0].strip() + return title if title else '' + +def read_pdf(file_path: str) -> str: + content = "" + with open(file_path, 'rb') as file: + reader = PdfReader(file) + for page in reader.pages: + page_text = page.extract_text() or "" + content += page_text + "\n" + return content + +def read_docx(file_path: str) -> str: + doc = Document(file_path) + content = "" + for para in doc.paragraphs: + content += para.text + "\n" + return content + +def read_pptx(file_path: str) -> str: + prs = Presentation(file_path) + content = "" + for slide in prs.slides: + for shape in slide.shapes: + if hasattr(shape, "text"): + content += shape.text + "\n" + return content + +def read_html(file_path: str) -> str: + with open(file_path, 'r') as file: + content = file.read() + soup = BeautifulSoup(content, "html.parser") + text = soup.get_text() + return text + +def read_json(file_path: str) -> str: + def format_json(obj: Any) -> str: + """Recursively format JSON objects into a string.""" + if isinstance(obj, dict): + return json.dumps({k: format_json(v) for k, v in obj.items()}, indent=2) + elif isinstance(obj, list): + return "\n".join(format_json(item) for item in obj) + else: + return json.dumps(obj) + with open(file_path, 'r', encoding='utf-8') as file: + data = json.load(file) + + return format_json(data) + +def get_file_data(folder_path: str, base_path: str) -> List[Dict[str, Any]]: + data_list = [] + for file_name in os.listdir(folder_path): + file_path = os.path.join(folder_path, file_name) + if os.path.isfile(file_path): + if file_name.endswith('.pdf'): + content = read_pdf(file_path) + elif file_name.endswith('.docx'): + content = read_docx(file_path) + elif file_name.endswith('.pptx'): + content = read_pptx(file_path) + elif file_name.endswith('.html'): + content = read_html(file_path) + else: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read().strip() + + relative_path = os.path.relpath(file_path, base_path) + data_list.append({ + 'id': generate_id(file_path), + 'content': content, + 'title': relative_path, + 'lastUpdated': datetime.fromtimestamp(os.path.getmtime(file_path)), + 'url': None + }) + elif os.path.isdir(file_path): + data_list.extend(get_file_data(file_path, base_path)) # Recursive call for directories + return data_list + +def fetch_url_content(url: str) -> str: + """Fetch content from a URL and return it as a string.""" + try: + response = requests.get(url) + response.raise_for_status() # Raise an error for HTTP issues + content = response.text + text = BeautifulSoup(content, "html.parser").get_text() + return text + except requests.RequestException as e: + print(f"Error fetching {url}: {e}") + return "" + +def add_urls_content(data_list: List[Dict[str, Any]], urls_file_path: str) -> None: + """Read URLs from a file, fetch page content, and update the data_list.""" + if not os.path.isfile(urls_file_path): + print(f"URLs file not found: {urls_file_path}") + return + + with open(urls_file_path, 'r') as urls_file: + urls = urls_file.readlines() + + for url in urls: + url = url.strip() + if url: + content = fetch_url_content(url) + data_list.append({ + 'id': generate_id(url), + 'content': content, + 'title': generate_title_from_content(content), + 'lastUpdated': datetime.now(), + 'url': url + }) + +def get_all_data() -> List[Dict[str, Any]]: + base_folder_path = os.path.join(os.path.dirname(__file__), '../data') + data_list = get_file_data(base_folder_path, base_folder_path) + urls_file_path = os.path.join(os.path.dirname(__file__), 'URL.txt') + add_urls_content(data_list, urls_file_path) + return data_list \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/indexer/delete.py b/samples/msteams-FAQPlus/src/indexer/delete.py new file mode 100644 index 0000000000..53538583fb --- /dev/null +++ b/samples/msteams-FAQPlus/src/indexer/delete.py @@ -0,0 +1,22 @@ +import os +import asyncio +from azure.core.credentials import AzureKeyCredential +from azure.search.documents.indexes.aio import SearchIndexClient +from utils import delete_index + +async def main(): + index_name = 'faq-index' + search_api_key = os.getenv('AZURE_SEARCH_KEY') + search_api_endpoint = os.getenv('AZURE_SEARCH_ENDPOINT') + + if not search_api_key or not search_api_endpoint: + raise ValueError("Azure Search key or endpoint is not set in environment variables.") + + credentials = AzureKeyCredential(search_api_key) + + # Use `async with` to ensure the client is properly closed + async with SearchIndexClient(endpoint=search_api_endpoint, credential=credentials) as search_index_client: + await delete_index(search_index_client, index_name) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/samples/msteams-FAQPlus/src/indexer/setup.py b/samples/msteams-FAQPlus/src/indexer/setup.py new file mode 100644 index 0000000000..3e0b0beee1 --- /dev/null +++ b/samples/msteams-FAQPlus/src/indexer/setup.py @@ -0,0 +1,33 @@ +import asyncio +import os +import time +from azure.core.credentials import AzureKeyCredential +from azure.search.documents.indexes.aio import SearchIndexClient +from azure.search.documents.aio import SearchClient +from utils import create_index_if_not_exists, delay, upsert_documents +from data import get_all_data + +async def main(): + index_name = 'faq-index' + + search_api_key = os.getenv('AZURE_SEARCH_KEY') + search_api_endpoint = os.getenv('AZURE_SEARCH_ENDPOINT') + + if not search_api_key or not search_api_endpoint: + raise ValueError("Azure Search key or endpoint is not set in environment variables.") + + credentials = AzureKeyCredential(search_api_key) + + # Use `async with` to ensure clients are properly closed + async with SearchIndexClient(endpoint=search_api_endpoint, credential=credentials) as search_index_client: + await create_index_if_not_exists(search_index_client, index_name) + + # Wait 5 seconds for the index to be created + await asyncio.sleep(5) + + async with SearchClient(endpoint=search_api_endpoint, index_name=index_name, credential=credentials) as search_client: + data = get_all_data() + await upsert_documents(search_client, data) + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/samples/msteams-FAQPlus/src/indexer/utils.py b/samples/msteams-FAQPlus/src/indexer/utils.py new file mode 100644 index 0000000000..9d65d9abe9 --- /dev/null +++ b/samples/msteams-FAQPlus/src/indexer/utils.py @@ -0,0 +1,57 @@ +import asyncio +from typing import List, Dict, Any +from azure.search.documents.indexes.aio import SearchIndexClient +from azure.search.documents.aio import SearchClient +from azure.search.documents.indexes.models import SearchIndex, SearchField, CorsOptions +from azure.search.documents.models import IndexingResult + +WAIT_TIME = 4000 + +def document_key_retriever(document: Dict[str, Any]) -> str: + return document["id"] + +async def delay(time_in_ms: int) -> None: + await asyncio.sleep(time_in_ms / 1000) + +async def delete_index(client: SearchIndexClient, name: str) -> None: + await client.delete_index(name) + +async def upsert_documents(client: SearchClient, documents: List[Dict[str, Any]]) -> List[IndexingResult]: + return await client.merge_or_upload_documents(documents=documents) + +async def create_index_if_not_exists(client: SearchIndexClient, name: str) -> None: + faq_index = SearchIndex( + name=name, + fields=[ + SearchField(name="content", type="Edm.String", searchable=True, filterable=False, retrievable=True, stored=True, sortable=False, facetable=False), + SearchField(name="title", type="Edm.String", searchable=True, filterable=False, retrievable=True, stored=True, sortable=False, facetable=False), + SearchField(name="id", type="Edm.String", key=True, searchable=False, filterable=True, retrievable=True, stored=True, sortable=True, facetable=False), + SearchField(name="lastUpdated", type="Edm.String", searchable=False, filterable=True, retrievable=True, stored=True, sortable=True, facetable=False), + SearchField(name="url", type="Edm.String", searchable=False, filterable=True, retrievable=True, stored=True, sortable=False, facetable=False) + ], + semantic_search={ + "defaultConfiguration": None, + "configurations": [ + { + "name": "default", + "prioritizedFields": { + "titleField": { + "fieldName": "title" + }, + "prioritizedContentFields": [ + { + "fieldName": "content" + } + ], + "prioritizedKeywordsFields": [] + } + } + ] + }, + similarity={"@odata.type": "#Microsoft.Azure.Search.BM25Similarity"}, + cors_options=CorsOptions( + allowed_origins=["*"] + ) + ) + + await client.create_or_update_index(faq_index) \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/prompts/chat/config.json b/samples/msteams-FAQPlus/src/prompts/chat/config.json new file mode 100644 index 0000000000..b264720f80 --- /dev/null +++ b/samples/msteams-FAQPlus/src/prompts/chat/config.json @@ -0,0 +1,20 @@ +{ + "schema": 1.1, + "description": "A bot that answers questions based on a database of QnA pairs.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 4096, + "max_tokens": 1000, + "temperature": 0.2, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "augmentation_type": "none" + } +} \ No newline at end of file diff --git a/samples/msteams-FAQPlus/src/prompts/chat/skprompt.txt b/samples/msteams-FAQPlus/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..25b79e2fc0 --- /dev/null +++ b/samples/msteams-FAQPlus/src/prompts/chat/skprompt.txt @@ -0,0 +1,2 @@ + +You are an AI assistant that answers queries over the given context. Keep your responses short. diff --git a/samples/msteams-FAQPlus/src/state.py b/samples/msteams-FAQPlus/src/state.py new file mode 100644 index 0000000000..e20d7f50cc --- /dev/null +++ b/samples/msteams-FAQPlus/src/state.py @@ -0,0 +1,31 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from typing import Optional + +from botbuilder.core import Storage, TurnContext +from teams.state import ConversationState, TempState, TurnState, UserState + + +class AppConversationState(ConversationState): + + @classmethod + async def load( + cls, context: TurnContext, storage: Optional[Storage] = None + ) -> "AppConversationState": + state = await super().load(context, storage) + return cls(**state) + + +class AppTurnState(TurnState[AppConversationState, UserState, TempState]): + conversation: AppConversationState + + @classmethod + async def load(cls, context: TurnContext, storage: Optional[Storage] = None) -> "AppTurnState": + return cls( + conversation=await AppConversationState.load(context, storage), + user=await UserState.load(context, storage), + temp=await TempState.load(context, storage), + ) diff --git a/samples/msteams-FAQPlus/teamsapp.local.yml b/samples/msteams-FAQPlus/teamsapp.local.yml new file mode 100644 index 0000000000..a7e8a6548f --- /dev/null +++ b/samples/msteams-FAQPlus/teamsapp.local.yml @@ -0,0 +1,81 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: FaqBot-${{TEAMSFX_ENV}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Azure Active Directory application for bot. + - uses: botAadApp/create + with: + # The Azure Active Directory application's display name + name: FaqBot-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + # The Azure Active Directory application's client id created for bot. + botId: BOT_ID + # The Azure Active Directory application's client secret created for bot. + botPassword: SECRET_BOT_PASSWORD + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: FaqBot + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Defines what the `deploy` lifecycle step does with Teams Toolkit. + # Runs after `provision` during Start Debugging (F5) or run manually using `teamsfx deploy --env local`. + # Provides the Teams Toolkit .env file values to the apps runtime so they can be accessed with `process.env`. + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.env + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + AZURE_OPENAI_KEY: ${{SECRET_AZURE_OPENAI_KEY}} + AZURE_OPENAI_ENDPOINT: ${{SECRET_AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_MODEL: ${{AZURE_OPENAI_MODEL}} + AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}} + AZURE_SEARCH_ENDPOINT: ${{SECRET_AZURE_SEARCH_ENDPOINT}} + AZURE_SEARCH_INDEX: ${{AZURE_SEARCH_INDEX}} + TEAMS_CHANNEL_ID: ${{TEAMS_CHANNEL_ID}} diff --git a/samples/msteams-FAQPlus/teamsapp.yml b/samples/msteams-FAQPlus/teamsapp.yml new file mode 100644 index 0000000000..7211187f1f --- /dev/null +++ b/samples/msteams-FAQPlus/teamsapp.yml @@ -0,0 +1,99 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsfx provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: FaqBot-${{TEAMSFX_ENV}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Azure Active Directory application for bot. + - uses: botAadApp/create + with: + # The Azure Active Directory application's display name + name: FaqBot-${{TEAMSFX_ENV}} + writeToEnvironmentFile: + # The Azure Active Directory application's client id created for bot. + botId: BOT_ID + # The Azure Active Directory application's client secret created for bot. + botPassword: SECRET_BOT_PASSWORD + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-tab + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsfx deploy' is executed +deploy: + - uses: script + with: + run: | + poetry export --without-hashes --format=requirements.txt --output=src/requirements.txt + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: src + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} \ No newline at end of file