├── .appveyor.yml ├── .coveragerc ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .pycodestyle.ini ├── .pydocstyle.ini ├── .pylint.ini ├── .readthedocs.yml ├── .scrutinizer.yml ├── .tool-versions ├── .verchew.ini ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── bin ├── checksum ├── open ├── update └── verchew ├── docs ├── about │ ├── changelog.md │ ├── contributing.md │ └── license.md ├── index.md ├── logging.md ├── options.md └── requirements.txt ├── log ├── __init__.py ├── filters.py ├── helpers.py ├── logger.py ├── py.typed ├── settings.py ├── state.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_api.py │ ├── test_helpers.py │ ├── test_logger.py │ └── test_utils.py └── utils.py ├── mkdocs.yml ├── notebooks └── profile_default │ ├── .gitignore │ ├── ipython_config.py │ └── startup │ ├── README │ └── ipython_startup.py ├── poetry.lock ├── pyproject.toml ├── scent.py └── tests ├── __init__.py ├── conftest.py ├── demo.py ├── other.py └── test_records.py /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | environment: 4 | matrix: 5 | - PYTHON_MAJOR: 3 6 | PYTHON_MINOR: 9 7 | 8 | cache: 9 | - .venv -> poetry.lock 10 | 11 | install: 12 | # Add Python to the PATH 13 | - set PATH=C:\Python%PYTHON_MAJOR%%PYTHON_MINOR%;%PATH% 14 | - set PATH=C:\Python%PYTHON_MAJOR%%PYTHON_MINOR%\Scripts;%PATH% 15 | # Install system dependencies 16 | - choco install make 17 | - curl -sSL https://install.python-poetry.org | python - 18 | - set PATH=%USERPROFILE%\AppData\Roaming\Python\Scripts;%PATH% 19 | - make doctor 20 | # Install project dependencies 21 | - make install 22 | 23 | build: off 24 | 25 | test_script: 26 | - make check 27 | - make test 28 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | 3 | branch = true 4 | 5 | data_file = .cache/coverage 6 | 7 | omit = 8 | .venv/* 9 | */tests/* 10 | */__main__.py 11 | 12 | [report] 13 | 14 | exclude_lines = 15 | pragma: no cover 16 | raise NotImplementedError 17 | except DistributionNotFound 18 | TYPE_CHECKING 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | CHANGELOG.md merge=union 3 | poetry.lock merge=binary 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ['3.9', '3.10', '3.11'] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - uses: Gr1N/setup-poetry@v8 22 | 23 | - name: Check dependencies 24 | run: make doctor 25 | 26 | - uses: actions/cache@v2 27 | with: 28 | path: .venv 29 | key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} 30 | 31 | - name: Install dependencies 32 | run: make install 33 | 34 | - name: Check code 35 | run: make check 36 | 37 | - name: Test code 38 | run: make test 39 | 40 | - name: Upload coverage 41 | uses: codecov/codecov-action@v4 42 | if: steps.fork-check.outputs.is-fork == 'false' 43 | with: 44 | token: ${{ secrets.CODECOV_TOKEN }} 45 | fail_ci_if_error: true 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Python files 2 | *.pyc 3 | *.egg-info/ 4 | __pycache__/ 5 | .ipynb_checkpoints/ 6 | setup.py 7 | pip-wheel-metadata/ 8 | 9 | # Temporary OS files 10 | Icon* 11 | 12 | # Temporary virtual environment files 13 | /.cache/ 14 | /.venv/ 15 | tmp/ 16 | 17 | # Temporary server files 18 | .env 19 | *.pid 20 | 21 | # Generated documentation 22 | /docs/gen/ 23 | /docs/apidocs/ 24 | /site/ 25 | /*.html 26 | /docs/*.png 27 | 28 | # Google Drive 29 | *.gdoc 30 | *.gsheet 31 | *.gslides 32 | *.gdraw 33 | 34 | # Testing and coverage results 35 | /.coverage 36 | /.coverage.* 37 | /htmlcov/ 38 | /prof/ 39 | coverage.xml 40 | 41 | # Build and release directories 42 | /build/ 43 | /dist/ 44 | *.spec 45 | 46 | # Sublime Text 47 | *.sublime-workspace 48 | 49 | # Eclipse 50 | .settings 51 | -------------------------------------------------------------------------------- /.pycodestyle.ini: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | 3 | # E401 multiple imports on one line (checked by PyLint) 4 | # E402 module level import not at top of file (checked by PyLint) 5 | # E501: line too long (checked by PyLint) 6 | # E711: comparison to None (used to improve test style) 7 | # E712: comparison to True (used to improve test style) 8 | ignore = E401,E402,E501,E711,E712 9 | -------------------------------------------------------------------------------- /.pydocstyle.ini: -------------------------------------------------------------------------------- 1 | [pydocstyle] 2 | 3 | # D211: No blank lines allowed before class docstring 4 | add_select = D211 5 | 6 | # D100: Missing docstring in public module 7 | # D101: Missing docstring in public class 8 | # D102: Missing docstring in public method 9 | # D103: Missing docstring in public function 10 | # D104: Missing docstring in public package 11 | # D105: Missing docstring in magic method 12 | # D107: Missing docstring in __init__ 13 | # D202: No blank lines allowed after function docstring 14 | add_ignore = D100,D101,D102,D103,D104,D105,D107,D202 15 | -------------------------------------------------------------------------------- /.pylint.ini: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=1 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins= 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # Allow loading of arbitrary C extensions. Extensions are imported into the 34 | # active Python interpreter and may run arbitrary code. 35 | unsafe-load-any-extension=no 36 | 37 | 38 | [MESSAGES CONTROL] 39 | 40 | # Only show warnings with the listed confidence levels. Leave empty to show 41 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 42 | confidence= 43 | 44 | # Disable the message, report, category or checker with the given id(s). You 45 | # can either give multiple identifiers separated by comma (,) or put this 46 | # option multiple times (only on the command line, not in the configuration 47 | # file where it should appear only once).You can also use "--disable=all" to 48 | # disable everything first and then reenable specific checks. For example, if 49 | # you want to run only the similarities checker, you can use "--disable=all 50 | # --enable=similarities". If you want to run only the classes checker, but have 51 | # no Warning level messages displayed, use"--disable=all --enable=classes 52 | # --disable=W" 53 | disable= 54 | fixme, 55 | global-statement, 56 | invalid-name, 57 | missing-docstring, 58 | redefined-outer-name, 59 | too-few-public-methods, 60 | too-many-locals, 61 | too-many-arguments, 62 | unnecessary-pass, 63 | broad-except, 64 | duplicate-code, 65 | too-many-branches, 66 | too-many-return-statements, 67 | too-many-public-methods, 68 | too-many-ancestors, 69 | too-many-instance-attributes, 70 | too-many-statements, 71 | attribute-defined-outside-init, 72 | unsupported-assignment-operation, 73 | unsupported-delete-operation, 74 | too-many-nested-blocks, 75 | protected-access, 76 | 77 | # Enable the message, report, category or checker with the given id(s). You can 78 | # either give multiple identifier separated by comma (,) or put this option 79 | # multiple time (only on the command line, not in the configuration file where 80 | # it should appear only once). See also the "--disable" option for examples. 81 | enable= 82 | 83 | 84 | [REPORTS] 85 | 86 | # Python expression which should return a note less than 10 (10 is the highest 87 | # note). You have access to the variables errors warning, statement which 88 | # respectively contain the number of errors / warnings messages and the total 89 | # number of statements analyzed. This is used by the global evaluation report 90 | # (RP0004). 91 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 92 | 93 | # Template used to display messages. This is a python new-style format string 94 | # used to format the message information. See doc for all details 95 | #msg-template= 96 | 97 | # Set the output format. Available formats are text, parseable, colorized, json 98 | # and msvs (visual studio).You can also give a reporter class, eg 99 | # mypackage.mymodule.MyReporterClass. 100 | output-format=text 101 | 102 | # Tells whether to display a full report or only the messages 103 | reports=no 104 | 105 | # Activate the evaluation score. 106 | score=no 107 | 108 | 109 | [REFACTORING] 110 | 111 | # Maximum number of nested blocks for function / method body 112 | max-nested-blocks=5 113 | 114 | 115 | [BASIC] 116 | 117 | # Regular expression matching correct argument names 118 | argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 119 | 120 | # Regular expression matching correct attribute names 121 | attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 122 | 123 | # Bad variable names which should always be refused, separated by a comma 124 | bad-names=foo,bar,baz,toto,tutu,tata 125 | 126 | # Regular expression matching correct class attribute names 127 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 128 | 129 | # Regular expression matching correct class names 130 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 131 | 132 | # Regular expression matching correct constant names 133 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 134 | 135 | # Minimum line length for functions/classes that require docstrings, shorter 136 | # ones are exempt. 137 | docstring-min-length=-1 138 | 139 | # Regular expression matching correct function names 140 | function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 141 | 142 | # Good variable names which should always be accepted, separated by a comma 143 | good-names=i,j,k,ex,Run,_ 144 | 145 | # Include a hint for the correct naming format with invalid-name 146 | include-naming-hint=no 147 | 148 | # Regular expression matching correct inline iteration names 149 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 150 | 151 | # Regular expression matching correct method names 152 | method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 153 | 154 | # Regular expression matching correct module names 155 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 156 | 157 | # Colon-delimited sets of names that determine each other's naming style when 158 | # the name regexes allow several styles. 159 | name-group= 160 | 161 | # Regular expression which should only match function or class names that do 162 | # not require a docstring. 163 | no-docstring-rgx=^_ 164 | 165 | # List of decorators that produce properties, such as abc.abstractproperty. Add 166 | # to this list to register other decorators that produce valid properties. 167 | property-classes=abc.abstractproperty 168 | 169 | # Regular expression matching correct variable names 170 | variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 171 | 172 | 173 | [FORMAT] 174 | 175 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 176 | expected-line-ending-format= 177 | 178 | # Regexp for a line that is allowed to be longer than the limit. 179 | ignore-long-lines=^.*((https?:)|(pragma:)|(TODO:)).*$ 180 | 181 | # Number of spaces of indent required inside a hanging or continued line. 182 | indent-after-paren=4 183 | 184 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 185 | # tab). 186 | indent-string=' ' 187 | 188 | # Maximum number of characters on a single line. 189 | max-line-length=88 190 | 191 | # Maximum number of lines in a module 192 | max-module-lines=1000 193 | 194 | # Allow the body of a class to be on the same line as the declaration if body 195 | # contains single statement. 196 | single-line-class-stmt=no 197 | 198 | # Allow the body of an if to be on the same line as the test if there is no 199 | # else. 200 | single-line-if-stmt=no 201 | 202 | 203 | [LOGGING] 204 | 205 | # Logging modules to check that the string format arguments are in logging 206 | # function parameter format 207 | logging-modules=logging 208 | 209 | 210 | [MISCELLANEOUS] 211 | 212 | # List of note tags to take in consideration, separated by a comma. 213 | notes=FIXME,XXX,TODO 214 | 215 | 216 | [SIMILARITIES] 217 | 218 | # Ignore comments when computing similarities. 219 | ignore-comments=yes 220 | 221 | # Ignore docstrings when computing similarities. 222 | ignore-docstrings=yes 223 | 224 | # Ignore imports when computing similarities. 225 | ignore-imports=no 226 | 227 | # Minimum lines number of a similarity. 228 | min-similarity-lines=4 229 | 230 | 231 | [SPELLING] 232 | 233 | # Spelling dictionary name. Available dictionaries: none. To make it working 234 | # install python-enchant package. 235 | spelling-dict= 236 | 237 | # List of comma separated words that should not be checked. 238 | spelling-ignore-words= 239 | 240 | # A path to a file that contains private dictionary; one word per line. 241 | spelling-private-dict-file= 242 | 243 | # Tells whether to store unknown words to indicated private dictionary in 244 | # --spelling-private-dict-file option instead of raising a message. 245 | spelling-store-unknown-words=no 246 | 247 | 248 | [TYPECHECK] 249 | 250 | # List of decorators that produce context managers, such as 251 | # contextlib.contextmanager. Add to this list to register other decorators that 252 | # produce valid context managers. 253 | contextmanager-decorators=contextlib.contextmanager 254 | 255 | # List of members which are set dynamically and missed by pylint inference 256 | # system, and so shouldn't trigger E1101 when accessed. Python regular 257 | # expressions are accepted. 258 | generated-members= 259 | 260 | # Tells whether missing members accessed in mixin class should be ignored. A 261 | # mixin class is detected if its name ends with "mixin" (case insensitive). 262 | ignore-mixin-members=yes 263 | 264 | # This flag controls whether pylint should warn about no-member and similar 265 | # checks whenever an opaque object is returned when inferring. The inference 266 | # can return multiple potential results while evaluating a Python object, but 267 | # some branches might not be evaluated, which results in partial inference. In 268 | # that case, it might be useful to still emit no-member and other checks for 269 | # the rest of the inferred objects. 270 | ignore-on-opaque-inference=yes 271 | 272 | # List of class names for which member attributes should not be checked (useful 273 | # for classes with dynamically set attributes). This supports the use of 274 | # qualified names. 275 | ignored-classes=optparse.Values,thread._local,_thread._local 276 | 277 | # List of module names for which member attributes should not be checked 278 | # (useful for modules/projects where namespaces are manipulated during runtime 279 | # and thus existing member attributes cannot be deduced by static analysis. It 280 | # supports qualified module names, as well as Unix pattern matching. 281 | ignored-modules= 282 | 283 | # Show a hint with possible names when a member name was not found. The aspect 284 | # of finding the hint is based on edit distance. 285 | missing-member-hint=yes 286 | 287 | # The minimum edit distance a name should have in order to be considered a 288 | # similar match for a missing member name. 289 | missing-member-hint-distance=1 290 | 291 | # The total number of similar names that should be taken in consideration when 292 | # showing a hint for a missing member. 293 | missing-member-max-choices=1 294 | 295 | 296 | [VARIABLES] 297 | 298 | # List of additional names supposed to be defined in builtins. Remember that 299 | # you should avoid to define new builtins when possible. 300 | additional-builtins= 301 | 302 | # Tells whether unused global variables should be treated as a violation. 303 | allow-global-unused-variables=yes 304 | 305 | # List of strings which can identify a callback function by name. A callback 306 | # name must start or end with one of those strings. 307 | callbacks=cb_,_cb 308 | 309 | # A regular expression matching the name of dummy variables (i.e. expectedly 310 | # not used). 311 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 312 | 313 | # Argument names that match this expression will be ignored. Default to name 314 | # with leading underscore 315 | ignored-argument-names=_.*|^ignored_|^unused_ 316 | 317 | # Tells whether we should check for unused import in __init__ files. 318 | init-import=no 319 | 320 | # List of qualified module names which can have objects that can redefine 321 | # builtins. 322 | redefining-builtins-modules=six.moves,future.builtins 323 | 324 | 325 | [CLASSES] 326 | 327 | # List of method names used to declare (i.e. assign) instance attributes. 328 | defining-attr-methods=__init__,__new__,setUp 329 | 330 | # List of member names, which should be excluded from the protected access 331 | # warning. 332 | exclude-protected=_asdict,_fields,_replace,_source,_make 333 | 334 | # List of valid names for the first argument in a class method. 335 | valid-classmethod-first-arg=cls 336 | 337 | # List of valid names for the first argument in a metaclass class method. 338 | valid-metaclass-classmethod-first-arg=mcs 339 | 340 | 341 | [DESIGN] 342 | 343 | # Maximum number of arguments for function / method 344 | max-args=5 345 | 346 | # Maximum number of attributes for a class (see R0902). 347 | max-attributes=7 348 | 349 | # Maximum number of boolean expressions in a if statement 350 | max-bool-expr=5 351 | 352 | # Maximum number of branch for function / method body 353 | max-branches=12 354 | 355 | # Maximum number of locals for function / method body 356 | max-locals=15 357 | 358 | # Maximum number of parents for a class (see R0901). 359 | max-parents=7 360 | 361 | # Maximum number of public methods for a class (see R0904). 362 | max-public-methods=20 363 | 364 | # Maximum number of return / yield for function / method body 365 | max-returns=6 366 | 367 | # Maximum number of statements in function / method body 368 | max-statements=50 369 | 370 | # Minimum number of public methods for a class (see R0903). 371 | min-public-methods=2 372 | 373 | 374 | [IMPORTS] 375 | 376 | # Allow wildcard imports from modules that define __all__. 377 | allow-wildcard-with-all=no 378 | 379 | # Analyse import fallback blocks. This can be used to support both Python 2 and 380 | # 3 compatible code, which means that the block might have code that exists 381 | # only in one or another interpreter, leading to false positives when analysed. 382 | analyse-fallback-blocks=no 383 | 384 | # Deprecated modules which should not be used, separated by a comma 385 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 386 | 387 | # Create a graph of external dependencies in the given file (report RP0402 must 388 | # not be disabled) 389 | ext-import-graph= 390 | 391 | # Create a graph of every (i.e. internal and external) dependencies in the 392 | # given file (report RP0402 must not be disabled) 393 | import-graph= 394 | 395 | # Create a graph of internal dependencies in the given file (report RP0402 must 396 | # not be disabled) 397 | int-import-graph= 398 | 399 | # Force import order to recognize a module as part of the standard 400 | # compatibility libraries. 401 | known-standard-library= 402 | 403 | # Force import order to recognize a module as part of a third party library. 404 | known-third-party=enchant 405 | 406 | 407 | [EXCEPTIONS] 408 | 409 | # Exceptions that will emit a warning when being caught. Defaults to 410 | # "Exception" 411 | overgeneral-exceptions=Exception 412 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | mkdocs: 9 | configuration: mkdocs.yml 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tests: 3 | override: 4 | - pylint-run --rcfile=.pylint.ini 5 | - py-scrutinizer-run 6 | checks: 7 | python: 8 | code_rating: true 9 | duplicate_code: true 10 | filter: 11 | excluded_paths: 12 | - "*/tests/*" 13 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.11.11 2 | poetry 2.0.1 3 | -------------------------------------------------------------------------------- /.verchew.ini: -------------------------------------------------------------------------------- 1 | [Make] 2 | 3 | cli = make 4 | version = GNU Make 5 | 6 | [Python] 7 | 8 | cli = python 9 | version = 3 10 | 11 | [Poetry] 12 | 13 | cli = poetry 14 | version = 2 15 | 16 | [Graphviz] 17 | 18 | cli = dot 19 | cli_version_arg = -V 20 | version = graphviz 21 | optional = true 22 | message = This is only needed to generate UML diagrams for documentation. 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".cache/": true, 4 | ".venv/": true, 5 | "*.egg-info": true, 6 | "pip-wheel-metadata/": true, 7 | "**/__pycache__": true, 8 | "**/*.pyc": true, 9 | "**/.ipynb_checkpoints": true, 10 | "**/tmp/": true, 11 | "dist/": true, 12 | "htmlcov/": true, 13 | "prof/": true, 14 | "site/": true, 15 | "geckodriver.log": true 16 | }, 17 | "python.defaultInterpreterPath": ".venv/bin/python", 18 | "editor.formatOnSave": true, 19 | "pylint.args": ["--rcfile=.pylint.ini"], 20 | "cSpell.words": [ 21 | "appex", 22 | "autorefs", 23 | "builtins", 24 | "choco", 25 | "classproperties", 26 | "codehilite", 27 | "completly", 28 | "cookiecutter", 29 | "coveragerc", 30 | "cygstart", 31 | "cygwin", 32 | "dataclass", 33 | "dataclasses", 34 | "datafile", 35 | "ensurepip", 36 | "findstr", 37 | "fontawesome", 38 | "freezegun", 39 | "gethostname", 40 | "getpid", 41 | "getplugin", 42 | "gitman", 43 | "gitsvn", 44 | "Graphviz", 45 | "iglob", 46 | "imac", 47 | "importlib", 48 | "ioreg", 49 | "iphoto", 50 | "ipython", 51 | "levelname", 52 | "logbreak", 53 | "macbook", 54 | "MDEF", 55 | "mkdocs", 56 | "mkdocstrings", 57 | "mrpossoms", 58 | "mylink", 59 | "mypy", 60 | "noclasses", 61 | "nohup", 62 | "pipx", 63 | "pluginmanager", 64 | "preserialization", 65 | "Preserialized", 66 | "Preserializing", 67 | "psutil", 68 | "repr", 69 | "ruamel", 70 | "rustup", 71 | "scalarstring", 72 | "showfspath", 73 | "startfile", 74 | "tomlkit", 75 | "tput", 76 | "tracebackhide", 77 | "Trilean", 78 | "trufflehog", 79 | "udevadm", 80 | "unparseable", 81 | "USERPROFILE", 82 | "venv", 83 | "verchew", 84 | "verchewrc", 85 | "webfonts", 86 | "YORM" 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 2.3.1 (2024-10-24) 4 | 5 | - Added `CRITICAL` log level to the package namespace. 6 | 7 | ## 2.3 (2023-12-09) 8 | 9 | - Added automatic formatting for dataclasses. 10 | 11 | ## 2.2 (2023-06-29) 12 | 13 | - Dropped support for Python 3.7. 14 | - Added `py.typed` for better `mypy` support. 15 | 16 | ## 2.1 (2022-03-06) 17 | 18 | - Dropped support for Python 3.6. 19 | - Added automatic formatting of non-string messages. 20 | 21 | ## 2.0.1 (2021-06-03) 22 | 23 | - Fixed logging within interactive sessions. 24 | 25 | ## 2.0 (2020-09-10) 26 | 27 | - Removed automatic call to `init()` when creating the first logging record. 28 | - Removed `reset=True` option for `log.init()`. Use `log.reset()` instead. 29 | 30 | ## 1.6 (2020-05-24) 31 | 32 | - Updated logging to use the source filename when the module is `'__main__'`. 33 | 34 | ## 1.5 (2020-03-28) 35 | 36 | - Fixed `init()` to handle invalid `verbosity` levels and default to **DEBUG**. 37 | 38 | ## 1.4.1 (2020-03-22) 39 | 40 | - Fixed new loggers to inherit the root logging level when their parent has none set. 41 | 42 | ## 1.4 (2020-02-15) 43 | 44 | - Deprecated `reset=True` option for `log.init()` in favor of a separate `log.reset()` function. 45 | 46 | ## 1.3 (2019-11-27) 47 | 48 | - Added support for Python 3.8. 49 | 50 | ## 1.2.6 (2019-11-14) 51 | 52 | - Fixed new loggers to inherit logging level from their parent. 53 | 54 | ## 1.2.5 (2019-10-19) 55 | 56 | - Fixed logging levels to use the default level for new loggers. 57 | 58 | ## 1.2.4 (2019-09-04) 59 | 60 | - Fixed `init()` to pass all arguments to `logging.Formatter`. 61 | 62 | ## 1.2.3 (2019-01-08) 63 | 64 | - Sped up logging by eliminating module lookup. 65 | 66 | ## 1.2.2 (2019-01-08) 67 | 68 | - Sped up logging by eliminating source file loading. 69 | 70 | ## 1.2.1 (2018-12-10) 71 | 72 | - Fixed missing `%(relpath)s` format for `pytest`. 73 | 74 | ## 1.2 (2018-12-09) 75 | 76 | - Fixed bug where logger name is unset when logging during imports. 77 | 78 | ## 1.1 (2018-10-26) 79 | 80 | - Added `%(relpath)s` logging format. 81 | - Added `verbosity` as `init()` option to work with Django admin commands. 82 | 83 | ## 1.0 (2018-09-27) 84 | 85 | - Initial stable release. 86 | 87 | ## 0.5 (2018-09-07) 88 | 89 | - Disabled automatic logging configuration when invoked by `pytest`. 90 | 91 | ## 0.4 (2018-4-28) 92 | 93 | - Added `reset=True` as `init()` option to replace all existing logging handlers. 94 | - Added `exception` logging API. 95 | - Added convenience alias: `log.c`, `log.exc`. 96 | 97 | ## 0.3.1 (2018-03-30) 98 | 99 | - Fixed bug where records were written for disabled levels. 100 | 101 | ## 0.3 (2018-03-15) 102 | 103 | - Exposed `logging` level constants on the `log` package. 104 | - Added `log.WARN` as an alias of `log.WARNING`. 105 | 106 | ## 0.2.1 (2018-03-04) 107 | 108 | - Removed the Python version check on installation. 109 | 110 | ## 0.2 (2018-03-03) 111 | 112 | - Added method to force logging format: `log.init(format="...")` 113 | - Added method to silenced named loggers: `log.silence('requests', allow_error=True)` 114 | - Added convenience aliases: `log.d`, `log.i`, `log.w`, `log.e` 115 | 116 | ## 0.1 (2018-03-03) 117 | 118 | - Initial release. 119 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guide 2 | 3 | ## Setup 4 | 5 | ### Requirements 6 | 7 | * Make: 8 | - macOS: `$ xcode-select --install` 9 | - Linux: [https://www.gnu.org](https://www.gnu.org/software/make) 10 | - Windows: `$ choco install make` [https://chocolatey.org](https://chocolatey.org/install) 11 | * Python: `$ asdf install` (https://asdf-vm.com)[https://asdf-vm.com/guide/getting-started.html] 12 | * Poetry: [https://python-poetry.org](https://python-poetry.org/docs/#installation) 13 | * Graphviz: 14 | * macOS: `$ brew install graphviz` 15 | * Linux: [https://graphviz.org/download](https://graphviz.org/download/) 16 | * Windows: [https://graphviz.org/download](https://graphviz.org/download/) 17 | 18 | To confirm these system dependencies are configured correctly: 19 | 20 | ```text 21 | $ make bootstrap 22 | $ make doctor 23 | ``` 24 | 25 | ### Installation 26 | 27 | Install project dependencies into a virtual environment: 28 | 29 | ```text 30 | $ make install 31 | ``` 32 | 33 | ## Development Tasks 34 | 35 | ### Manual 36 | 37 | Run the tests: 38 | 39 | ```text 40 | $ make test 41 | ``` 42 | 43 | Run static analysis: 44 | 45 | ```text 46 | $ make check 47 | ``` 48 | 49 | Build the documentation: 50 | 51 | ```text 52 | $ make docs 53 | ``` 54 | 55 | ### Automatic 56 | 57 | Keep all of the above tasks running on change: 58 | 59 | ```text 60 | $ make dev 61 | ``` 62 | 63 | > In order to have OS X notifications, `brew install terminal-notifier`. 64 | 65 | ### Continuous Integration 66 | 67 | The CI server will report overall build status: 68 | 69 | ```text 70 | $ make all 71 | ``` 72 | 73 | ## Demo Tasks 74 | 75 | Run the program: 76 | 77 | ```text 78 | $ make run 79 | ``` 80 | 81 | Launch an IPython session: 82 | 83 | ```text 84 | $ make shell 85 | ``` 86 | 87 | ## Release Tasks 88 | 89 | Release to PyPI: 90 | 91 | ```text 92 | $ make upload 93 | ``` 94 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | **The MIT License (MIT)** 2 | 3 | Copyright © 2018, Jace Browning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := minilog 2 | PACKAGE := log 3 | MODULES := $(wildcard $(PACKAGE)/*.py) 4 | 5 | # MAIN TASKS ################################################################## 6 | 7 | .PHONY: all 8 | all: doctor format check test mkdocs ## Run all tasks that determine CI status 9 | 10 | .PHONY: dev 11 | dev: install .clean-test ## Continuously run CI tasks when files chanage 12 | poetry run sniffer 13 | 14 | # SYSTEM DEPENDENCIES ######################################################### 15 | 16 | .PHONY: bootstrap 17 | bootstrap: ## Attempt to install system dependencies 18 | asdf plugin add python || asdf plugin update python 19 | asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git || asdf plugin update poetry 20 | asdf install 21 | 22 | .PHONY: doctor 23 | doctor: ## Confirm system dependencies are available 24 | bin/verchew 25 | 26 | # PROJECT DEPENDENCIES ######################################################## 27 | 28 | VIRTUAL_ENV ?= .venv 29 | DEPENDENCIES := $(VIRTUAL_ENV)/.poetry-$(shell bin/checksum pyproject.toml poetry.lock) 30 | 31 | .PHONY: install 32 | install: $(DEPENDENCIES) .cache ## Install project dependencies 33 | 34 | $(DEPENDENCIES): poetry.lock 35 | @ rm -rf $(VIRTUAL_ENV)/.poetry-* 36 | @ rm -rf ~/Library/Preferences/pypoetry 37 | @ poetry config virtualenvs.in-project true 38 | poetry install 39 | @ touch $@ 40 | 41 | ifndef CI 42 | poetry.lock: pyproject.toml 43 | poetry lock 44 | @ touch $@ 45 | endif 46 | 47 | .cache: 48 | @ mkdir -p .cache 49 | 50 | # TEST ######################################################################## 51 | 52 | RANDOM_SEED ?= $(shell date +%s) 53 | FAILURES := .cache/pytest/v/cache/lastfailed 54 | 55 | PYTEST_OPTIONS := 56 | ifndef DISABLE_COVERAGE 57 | PYTEST_OPTIONS += --cov=$(PACKAGE) 58 | endif 59 | ifdef CI 60 | PYTEST_OPTIONS += --cov-report=xml 61 | endif 62 | PYTEST_RERUN_OPTIONS := --last-failed --exitfirst 63 | 64 | .PHONY: test 65 | test: test-all ## Run unit and integration tests 66 | 67 | .PHONY: test-unit 68 | test-unit: install 69 | @ ( mv $(FAILURES) $(FAILURES).bak || true ) > /dev/null 2>&1 70 | poetry run pytest $(PACKAGE) $(PYTEST_OPTIONS) 71 | @ ( mv $(FAILURES).bak $(FAILURES) || true ) > /dev/null 2>&1 72 | ifndef DISABLE_COVERAGE 73 | poetry run coveragespace update unit 74 | endif 75 | 76 | .PHONY: test-int 77 | test-int: install 78 | @ if test -e $(FAILURES); then poetry run pytest tests $(PYTEST_RERUN_OPTIONS); fi 79 | @ rm -rf $(FAILURES) 80 | poetry run pytest tests $(PYTEST_OPTIONS) 81 | ifndef DISABLE_COVERAGE 82 | poetry run coveragespace update integration 83 | endif 84 | 85 | .PHONY: test-all 86 | test-all: install 87 | @ if test -e $(FAILURES); then poetry run pytest $(PACKAGE) tests $(PYTEST_RERUN_OPTIONS); fi 88 | @ rm -rf $(FAILURES) 89 | poetry run pytest $(PACKAGE) tests $(PYTEST_OPTIONS) 90 | ifndef DISABLE_COVERAGE 91 | poetry run coveragespace update overall 92 | endif 93 | 94 | .PHONY: read-coverage 95 | read-coverage: 96 | bin/open htmlcov/index.html 97 | 98 | # CHECK ####################################################################### 99 | 100 | .PHONY: format 101 | format: install 102 | poetry run isort $(PACKAGE) tests notebooks 103 | poetry run black $(PACKAGE) tests notebooks 104 | @ echo 105 | 106 | .PHONY: check 107 | check: install format ## Run formaters, linters, and static analysis 108 | ifdef CI 109 | git diff --exit-code 110 | endif 111 | poetry run mypy $(PACKAGE) tests 112 | poetry run pylint $(PACKAGE) tests --rcfile=.pylint.ini 113 | poetry run pydocstyle $(PACKAGE) tests 114 | 115 | # DOCUMENTATION ############################################################### 116 | 117 | MKDOCS_INDEX := site/index.html 118 | 119 | .PHONY: docs 120 | docs: mkdocs uml ## Generate documentation and UML 121 | ifndef CI 122 | @ eval "sleep 3; bin/open http://127.0.0.1:8000" & 123 | poetry run mkdocs serve 124 | endif 125 | 126 | .PHONY: mkdocs 127 | mkdocs: install $(MKDOCS_INDEX) 128 | $(MKDOCS_INDEX): docs/requirements.txt mkdocs.yml docs/*.md 129 | @ mkdir -p docs/about 130 | @ cd docs && ln -sf ../README.md index.md 131 | @ cd docs/about && ln -sf ../../CHANGELOG.md changelog.md 132 | @ cd docs/about && ln -sf ../../CONTRIBUTING.md contributing.md 133 | @ cd docs/about && ln -sf ../../LICENSE.md license.md 134 | poetry run mkdocs build --clean --strict 135 | 136 | docs/requirements.txt: poetry.lock 137 | @ poetry export --all-groups --without-hashes | grep mkdocs > $@ 138 | @ poetry export --all-groups --without-hashes | grep pygments >> $@ 139 | @ poetry export --all-groups --without-hashes | grep jinja2 >> $@ 140 | 141 | .PHONY: uml 142 | uml: install docs/*.png 143 | docs/*.png: $(MODULES) 144 | poetry run pyreverse $(PACKAGE) -p $(PACKAGE) -a 1 -f ALL -o png --ignore tests 145 | - mv -f classes_$(PACKAGE).png docs/classes.png 146 | - mv -f packages_$(PACKAGE).png docs/packages.png 147 | 148 | # DEMO ######################################################################## 149 | 150 | .PHONY: run 151 | run: install ## Start the program 152 | poetry run python $(PACKAGE)/__main__.py 153 | 154 | .PHONY: shell 155 | shell: install ## Launch an IPython session 156 | poetry run ipython --ipython-dir=notebooks 157 | 158 | # BUILD ####################################################################### 159 | 160 | DIST_FILES := dist/*.tar.gz dist/*.whl 161 | EXE_FILES := dist/$(PACKAGE).* 162 | 163 | .PHONY: dist 164 | dist: install $(DIST_FILES) 165 | $(DIST_FILES): $(MODULES) pyproject.toml 166 | rm -f $(DIST_FILES) 167 | poetry build 168 | 169 | .PHONY: exe 170 | exe: install $(EXE_FILES) 171 | $(EXE_FILES): $(MODULES) $(PACKAGE).spec 172 | poetry run pyinstaller $(PACKAGE).spec --noconfirm --clean 173 | 174 | $(PACKAGE).spec: 175 | poetry run pyi-makespec $(PACKAGE)/__main__.py --onefile --windowed --name=$(PACKAGE) 176 | 177 | # RELEASE ##################################################################### 178 | 179 | .PHONY: upload 180 | upload: dist ## Upload the current version to PyPI 181 | git diff --name-only --exit-code 182 | poetry publish 183 | bin/open https://pypi.org/project/$(PROJECT) 184 | 185 | # CLEANUP ##################################################################### 186 | 187 | .PHONY: clean 188 | clean: .clean-build .clean-docs .clean-test .clean-install ## Delete all generated and temporary files 189 | 190 | .PHONY: clean-all 191 | clean-all: clean 192 | rm -rf $(VIRTUAL_ENV) 193 | 194 | .PHONY: .clean-install 195 | .clean-install: 196 | find $(PACKAGE) tests -name '__pycache__' -delete 197 | rm -rf *.egg-info 198 | 199 | .PHONY: .clean-test 200 | .clean-test: 201 | rm -rf .cache .pytest .coverage htmlcov 202 | 203 | .PHONY: .clean-docs 204 | .clean-docs: 205 | rm -rf docs/*.png site 206 | 207 | .PHONY: .clean-build 208 | .clean-build: 209 | rm -rf *.spec dist build 210 | 211 | # HELP ######################################################################## 212 | 213 | .PHONY: help 214 | help: install 215 | @ grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 216 | 217 | .DEFAULT_GOAL := help 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minilog 2 | 3 | A minimalistic logging wrapper for Python. 4 | 5 | [![Linux Build](https://img.shields.io/github/actions/workflow/status/jacebrowning/minilog/main.yml?branch=main&label=linux)](https://github.com/jacebrowning/minilog/actions) 6 | [![Windows Build](https://img.shields.io/appveyor/ci/jacebrowning/minilog/main.svg?label=windows)](https://ci.appveyor.com/project/jacebrowning/minilog) 7 | [![Code Coverage](https://img.shields.io/codecov/c/github/jacebrowning/minilog)](https://codecov.io/gh/jacebrowning/minilog) 8 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/jacebrowning/minilog.svg)](https://scrutinizer-ci.com/g/jacebrowning/minilog) 9 | [![PyPI License](https://img.shields.io/pypi/l/minilog.svg)](https://pypi.org/project/minilog) 10 | [![PyPI Version](https://img.shields.io/pypi/v/minilog.svg)](https://pypi.org/project/minilog) 11 | [![PyPI Downloads](https://img.shields.io/pypi/dm/minilog.svg?color=orange)](https://pypistats.org/packages/minilog) 12 | 13 | ## Usage 14 | 15 | Every project should utilize logging, but for simple use cases, this requires a bit too much boilerplate. Instead of including all of this in your modules: 16 | 17 | ```python 18 | import logging 19 | 20 | log = logging.getLogger(__name__) 21 | 22 | def greet(name): 23 | log.info("Hello, %s!", name) 24 | 25 | if __name__ == "__main__": 26 | logging.basicConfig( 27 | level=logging.INFO, 28 | format="%(levelname)s: %(name)s: %(message)s", 29 | ) 30 | ``` 31 | 32 | with this package you can simply: 33 | 34 | ```python 35 | import log 36 | 37 | def greet(name): 38 | log.info("Hello, %s!", name) 39 | 40 | if __name__ == "__main__": 41 | log.init() 42 | ``` 43 | 44 | It will produce the exact same standard library `logging` records behind the scenes with automatic formatting for non-strings. 45 | 46 | ## Installation 47 | 48 | Install this library directly into an activated virtual environment: 49 | 50 | ```text 51 | $ pip install minilog 52 | ``` 53 | 54 | or add it to your [Poetry](https://poetry.eustace.io/) project: 55 | 56 | ```text 57 | $ poetry add minilog 58 | ``` 59 | 60 | ## Documentation 61 | 62 | To view additional options, please consult the [full documentation](https://minilog.readthedocs.io/en/latest/logging/). 63 | -------------------------------------------------------------------------------- /bin/checksum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import hashlib 5 | import sys 6 | 7 | 8 | def run(paths): 9 | sha = hashlib.sha1() 10 | 11 | for path in paths: 12 | try: 13 | with open(path, 'rb') as f: 14 | for chunk in iter(lambda: f.read(4096), b''): 15 | sha.update(chunk) 16 | except IOError: 17 | sha.update(path.encode()) 18 | 19 | print(sha.hexdigest()) 20 | 21 | 22 | if __name__ == '__main__': 23 | run(sys.argv[1:]) 24 | -------------------------------------------------------------------------------- /bin/open: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | 8 | COMMANDS = { 9 | 'linux': "open", 10 | 'win32': "cmd /c start", 11 | 'cygwin': "cygstart", 12 | 'darwin': "open", 13 | } 14 | 15 | 16 | def run(path): 17 | command = COMMANDS.get(sys.platform, "open") 18 | os.system(command + ' ' + path) 19 | 20 | 21 | if __name__ == '__main__': 22 | run(sys.argv[-1]) 23 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from contextlib import suppress 6 | import importlib 7 | import tempfile 8 | import shutil 9 | import subprocess 10 | import sys 11 | 12 | 13 | CWD = os.getcwd() 14 | TMP = tempfile.gettempdir() 15 | CONFIG = { 16 | "full_name": "Jace Browning", 17 | "email": "jacebrowning@gmail.com", 18 | "github_username": "jacebrowning", 19 | "github_repo": "minilog", 20 | "default_branch": "main", 21 | "project_name": "minilog", 22 | "package_name": "log", 23 | "project_short_description": "Minimalistic wrapper for Python logging.", 24 | "python_major_version": 3, 25 | "python_minor_version": 6, 26 | } 27 | 28 | 29 | def install(package="cookiecutter"): 30 | try: 31 | importlib.import_module(package) 32 | except ImportError: 33 | print("Installing cookiecutter") 34 | subprocess.check_call([sys.executable, "-m", "pip", "install", package]) 35 | 36 | 37 | def run(): 38 | print("Generating project") 39 | 40 | from cookiecutter.main import cookiecutter 41 | 42 | os.chdir(TMP) 43 | cookiecutter( 44 | "https://github.com/jacebrowning/template-python.git", 45 | no_input=True, 46 | overwrite_if_exists=True, 47 | extra_context=CONFIG, 48 | ) 49 | 50 | 51 | def copy(): 52 | for filename in [ 53 | os.path.join("bin", "update"), 54 | os.path.join("bin", "checksum"), 55 | os.path.join("bin", "open"), 56 | os.path.join("bin", "verchew"), 57 | ".appveyor.yml", 58 | ".coveragerc", 59 | ".gitattributes", 60 | ".gitignore", 61 | ".pydocstyle.ini", 62 | ".pylint.ini", 63 | ".scrutinizer.yml", 64 | ".tool-versions", 65 | ".verchew.ini", 66 | "CONTRIBUTING.md", 67 | "Makefile", 68 | "scent.py", 69 | ]: 70 | src = os.path.join(TMP, CONFIG["project_name"], filename) 71 | dst = os.path.join(CWD, filename) 72 | print("Updating " + filename) 73 | with suppress(FileNotFoundError): 74 | shutil.copy(src, dst) 75 | 76 | 77 | if __name__ == "__main__": 78 | install() 79 | run() 80 | copy() 81 | -------------------------------------------------------------------------------- /bin/verchew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # The MIT License (MIT) 5 | # Copyright © 2016, Jace Browning 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | # Source: https://github.com/jacebrowning/verchew 26 | # Documentation: https://verchew.readthedocs.io 27 | # Package: https://pypi.org/project/verchew 28 | 29 | 30 | from __future__ import unicode_literals 31 | 32 | import argparse 33 | import logging 34 | import os 35 | import re 36 | import sys 37 | from collections import OrderedDict 38 | from subprocess import PIPE, STDOUT, Popen 39 | 40 | 41 | PY2 = sys.version_info[0] == 2 42 | 43 | if PY2: 44 | import ConfigParser as configparser 45 | from urllib import urlretrieve 46 | else: 47 | import configparser 48 | from urllib.request import urlretrieve 49 | 50 | __version__ = '3.4' 51 | 52 | SCRIPT_URL = ( 53 | "https://raw.githubusercontent.com/jacebrowning/verchew/main/verchew/script.py" 54 | ) 55 | WRAPPER_URL = ( 56 | "https://raw.githubusercontent.com/jacebrowning/verchew/main/verchew/wrapper.sh" 57 | ) 58 | 59 | CONFIG_FILENAMES = ['verchew.ini', '.verchew.ini', '.verchewrc', '.verchew'] 60 | 61 | SAMPLE_CONFIG = """ 62 | [Python] 63 | 64 | cli = python 65 | version = Python 3.5 || Python 3.6 66 | 67 | [Legacy Python] 68 | 69 | cli = python2 70 | version = Python 2.7 71 | 72 | [virtualenv] 73 | 74 | cli = virtualenv 75 | version = 15 76 | message = Only required with Python 2. 77 | 78 | [Make] 79 | 80 | cli = make 81 | version = GNU Make 82 | optional = true 83 | 84 | """.strip() 85 | 86 | STYLE = { 87 | "~": "✔", 88 | "?": "▴", 89 | "x": "✘", 90 | "#": "䷉", 91 | } 92 | 93 | COLOR = { 94 | "~": "\033[92m", # green 95 | "?": "\033[93m", # yellow 96 | "x": "\033[91m", # red 97 | "#": "\033[96m", # cyan 98 | None: "\033[0m", # reset 99 | } 100 | 101 | QUIET = False 102 | 103 | log = logging.getLogger(__name__) 104 | 105 | 106 | def main(): 107 | global QUIET 108 | 109 | args = parse_args() 110 | configure_logging(args.verbose) 111 | if args.quiet: 112 | QUIET = True 113 | 114 | log.debug("PWD: %s", os.getenv('PWD')) 115 | log.debug("PATH: %s", os.getenv('PATH')) 116 | 117 | if args.vendor: 118 | vendor_script(SCRIPT_URL, args.vendor) 119 | vendor_script(WRAPPER_URL, args.vendor + "-wrapper") 120 | sys.exit(0) 121 | 122 | path = find_config(args.root, generate=args.init) 123 | config = parse_config(path) 124 | 125 | if not check_dependencies(config) and args.exit_code: 126 | sys.exit(1) 127 | 128 | 129 | def parse_args(): 130 | parser = argparse.ArgumentParser(description="System dependency version checker.",) 131 | 132 | version = "%(prog)s v" + __version__ 133 | parser.add_argument( 134 | '--version', action='version', version=version, 135 | ) 136 | parser.add_argument( 137 | '-r', '--root', metavar='PATH', help="specify a custom project root directory" 138 | ) 139 | parser.add_argument( 140 | '--exit-code', 141 | action='store_true', 142 | help="return a non-zero exit code on failure", 143 | ) 144 | 145 | group_logging = parser.add_mutually_exclusive_group() 146 | group_logging.add_argument( 147 | '-v', '--verbose', action='count', default=0, help="enable verbose logging" 148 | ) 149 | group_logging.add_argument( 150 | '-q', '--quiet', action='store_true', help="suppress all output on success" 151 | ) 152 | 153 | group_commands = parser.add_argument_group('commands') 154 | group_commands.add_argument( 155 | '--init', action='store_true', help="generate a sample configuration file" 156 | ) 157 | 158 | group_commands.add_argument( 159 | '--vendor', metavar='PATH', help="download the program for offline use" 160 | ) 161 | 162 | args = parser.parse_args() 163 | 164 | return args 165 | 166 | 167 | def configure_logging(count=0): 168 | if count == 0: 169 | level = logging.WARNING 170 | elif count == 1: 171 | level = logging.INFO 172 | else: 173 | level = logging.DEBUG 174 | 175 | logging.basicConfig(level=level, format="%(levelname)s: %(message)s") 176 | 177 | 178 | def vendor_script(url, path): 179 | root = os.path.abspath(os.path.join(path, os.pardir)) 180 | if not os.path.isdir(root): 181 | log.info("Creating directory %s", root) 182 | os.makedirs(root) 183 | 184 | log.info("Downloading %s to %s", url, path) 185 | urlretrieve(url, path) 186 | 187 | log.debug("Making %s executable", path) 188 | mode = os.stat(path).st_mode 189 | os.chmod(path, mode | 0o111) 190 | 191 | 192 | def find_config(root=None, filenames=None, generate=False): 193 | root = root or os.getcwd() 194 | filenames = filenames or CONFIG_FILENAMES 195 | 196 | path = None 197 | log.info("Looking for config file in: %s", root) 198 | log.debug("Filename options: %s", ", ".join(filenames)) 199 | for filename in os.listdir(root): 200 | if filename in filenames: 201 | path = os.path.join(root, filename) 202 | log.info("Found config file: %s", path) 203 | return path 204 | 205 | if generate: 206 | path = generate_config(root, filenames) 207 | return path 208 | 209 | msg = "No config file found in: {0}".format(root) 210 | raise RuntimeError(msg) 211 | 212 | 213 | def generate_config(root=None, filenames=None): 214 | root = root or os.getcwd() 215 | filenames = filenames or CONFIG_FILENAMES 216 | 217 | path = os.path.join(root, filenames[0]) 218 | 219 | log.info("Generating sample config: %s", path) 220 | with open(path, 'w') as config: 221 | config.write(SAMPLE_CONFIG + '\n') 222 | 223 | return path 224 | 225 | 226 | def parse_config(path): 227 | data = OrderedDict() # type: ignore 228 | 229 | log.info("Parsing config file: %s", path) 230 | config = configparser.ConfigParser() 231 | config.read(path) 232 | 233 | for section in config.sections(): 234 | data[section] = OrderedDict() 235 | for name, value in config.items(section): 236 | data[section][name] = value 237 | 238 | for name in data: 239 | version = data[name].get('version') or "" 240 | data[name]['version'] = version 241 | data[name]['patterns'] = [v.strip() for v in version.split('||')] 242 | 243 | data[name]['optional'] = data[name].get( 244 | 'optional', 'false' 245 | ).strip().lower() in ('true', 'yes', 'y', True) 246 | 247 | return data 248 | 249 | 250 | def check_dependencies(config): 251 | success = [] 252 | 253 | for name, settings in config.items(): 254 | show("Checking for {0}...".format(name), head=True) 255 | output = get_version(settings['cli'], settings.get('cli_version_arg')) 256 | 257 | for pattern in settings['patterns']: 258 | if match_version(pattern, output): 259 | show(_("~") + " MATCHED: {0}".format(pattern or "")) 260 | success.append(_("~")) 261 | break 262 | else: 263 | if settings.get('optional'): 264 | show(_("?") + " EXPECTED (OPTIONAL): {0}".format(settings['version'])) 265 | success.append(_("?")) 266 | else: 267 | if QUIET: 268 | if "not found" in output: 269 | actual = "Not found" 270 | else: 271 | actual = output.split('\n', maxsplit=1)[0].strip('.') 272 | expected = settings['version'] or "" 273 | print("{0}: {1}, EXPECTED: {2}".format(name, actual, expected)) 274 | show( 275 | _("x") 276 | + " EXPECTED: {0}".format(settings['version'] or "") 277 | ) 278 | success.append(_("x")) 279 | if settings.get('message'): 280 | show(_("#") + " MESSAGE: {0}".format(settings['message'])) 281 | 282 | show("Results: " + " ".join(success), head=True) 283 | 284 | return _("x") not in success 285 | 286 | 287 | def get_version(program, argument=None): 288 | if argument is None: 289 | args = [program, '--version'] 290 | elif argument: 291 | args = [program] + argument.split() 292 | else: 293 | args = [program] 294 | 295 | show("$ {0}".format(" ".join(args))) 296 | output = call(args) 297 | lines = output.splitlines() 298 | 299 | if lines: 300 | for line in lines: 301 | if any(char.isdigit() for char in line): 302 | show(line) 303 | break 304 | else: 305 | show(lines[0]) 306 | else: 307 | show("") 308 | 309 | return output 310 | 311 | 312 | def match_version(pattern, output): 313 | lines = output.splitlines() 314 | if "not found" in lines[0]: 315 | return False 316 | 317 | regex = pattern.replace('.', r'\.') + r'(\b|/)' 318 | 319 | for line in lines: 320 | log.debug("Matching %s: %s", regex, line) 321 | match = re.match(regex, line) 322 | if match is None: 323 | log.debug("Matching %s: %s", regex, line) 324 | match = re.match(r'.*[^\d.]' + regex, line) 325 | if match: 326 | return True 327 | 328 | return False 329 | 330 | 331 | def call(args): 332 | try: 333 | process = Popen(args, stdout=PIPE, stderr=STDOUT) 334 | except OSError: 335 | log.debug("Command not found: %s", args[0]) 336 | output = "sh: command not found: {0}".format(args[0]) 337 | else: 338 | raw = process.communicate()[0] 339 | output = raw.decode('utf-8').strip() 340 | log.debug("Command output: %r", output) 341 | 342 | return output 343 | 344 | 345 | def show(text, start='', end='\n', head=False): 346 | """Python 2 and 3 compatible version of print.""" 347 | if QUIET: 348 | return 349 | 350 | if head: 351 | start = '\n' 352 | end = '\n\n' 353 | 354 | if log.getEffectiveLevel() < logging.WARNING: 355 | log.info(text) 356 | else: 357 | formatted = start + text + end 358 | if PY2: 359 | formatted = formatted.encode('utf-8') 360 | sys.stdout.write(formatted) 361 | sys.stdout.flush() 362 | 363 | 364 | def _(word, is_tty=None, supports_utf8=None, supports_ansi=None): 365 | """Format and colorize a word based on available encoding.""" 366 | formatted = word 367 | 368 | if is_tty is None: 369 | is_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() 370 | if supports_utf8 is None: 371 | supports_utf8 = str(sys.stdout.encoding).lower() == 'utf-8' 372 | if supports_ansi is None: 373 | supports_ansi = sys.platform != 'win32' or 'ANSICON' in os.environ 374 | 375 | style_support = supports_utf8 376 | color_support = is_tty and supports_ansi 377 | 378 | if style_support: 379 | formatted = STYLE.get(word, word) 380 | 381 | if color_support and COLOR.get(word): 382 | formatted = COLOR[word] + formatted + COLOR[None] 383 | 384 | return formatted 385 | 386 | 387 | if __name__ == '__main__': # pragma: no cover 388 | main() 389 | -------------------------------------------------------------------------------- /docs/about/changelog.md: -------------------------------------------------------------------------------- 1 | ../../CHANGELOG.md -------------------------------------------------------------------------------- /docs/about/contributing.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/about/license.md: -------------------------------------------------------------------------------- 1 | ../../LICENSE.md -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | This package intends to be a drop-in replacement for `logging.Logger` objects. It supports the standard logging API: 4 | 5 | ```python 6 | import log 7 | 8 | log.debug(message, *args) 9 | log.info(message, *args) 10 | log.warning(message, *args) 11 | log.error(message, *args) 12 | log.critical(message, *args) 13 | 14 | log.exception(message, *args) 15 | 16 | log.log(level, message, *args) 17 | ``` 18 | 19 | As well as convenience methods: 20 | 21 | ```python 22 | import log 23 | 24 | log.warn(message, *args) # warning 25 | 26 | log.d(message, *args) # debug 27 | log.i(message, *args) # info 28 | log.w(message, *args) # warning 29 | log.e(message, *args) # error 30 | log.c(message, *args) # critical 31 | 32 | log.exc(message, *args) # exception 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/options.md: -------------------------------------------------------------------------------- 1 | # Initialization 2 | 3 | `minilog` defaults to INFO level using a simple log format. It supports the same initialization arguments as [`logging.basicConfig`](https://docs.python.org/3/library/logging.html#logging.basicConfig). 4 | 5 | To set the format for all logging handlers: 6 | 7 | ```python 8 | log.init(format="%(levelname)s: %(name)s: %(message)s") 9 | ``` 10 | 11 | To set the level for the root logging handler: 12 | 13 | ```python 14 | log.init(format=…, level=log.WARNING) 15 | ``` 16 | 17 | ### Debug Option 18 | 19 | To simply enable debug-level logging, a convenience option is provided: 20 | 21 | ```python 22 | log.init(format=…, debug=True) 23 | ``` 24 | 25 | ### Verbosity Option 26 | 27 | To work with frameworks that provide a `verbosity` level in their CLI frameworks (such as [Django](https://docs.djangoproject.com/en/4.0/ref/django-admin/#cmdoption-verbosity)), that can be used instead: 28 | 29 | ```python 30 | log.init(format=…, verbosity=verbosity) 31 | ``` 32 | 33 | | Verbosity | Level | 34 | |-----------|-------------| 35 | | `0` | **ERROR** | 36 | | `1` | **WARNING** | 37 | | `2` | **INFO** | 38 | | `3` | **DEBUG** | 39 | 40 | ### Silencing Loggers 41 | 42 | To hide logging for specific named loggers: 43 | 44 | ```python 45 | log.silence('selenium') 46 | log.silence('werkzeug', 'requests', allow_warning=True) 47 | ``` 48 | 49 | ### Reset Loggers 50 | 51 | Finally, if another package has already set the logging format or level, that can be reset so that `minilog` takes over: 52 | 53 | ```python 54 | log.reset() 55 | log.init(…) 56 | ``` 57 | 58 | # Records 59 | 60 | In addition to the standard [`LogRecord`](https://docs.python.org/3/library/logging.html#logrecord-attributes) attributes, the following additional patterns are available: 61 | 62 | | Logging Format | Description | 63 | |----------------|---------------------------------------------------------------------------------------------------------------| 64 | | `%(relpath)s` | Full pathname of the source file where the logging call was issued relative to the current working directory. | 65 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.2.3 ; python_version >= "3.8" and python_version < "4.0" 2 | pygments==2.15.0 ; python_version >= "3.8" and python_version < "4.0" 3 | jinja2==3.0.3 ; python_version >= "3.8" and python_version < "4.0" 4 | -------------------------------------------------------------------------------- /log/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=wildcard-import 2 | 3 | from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING 4 | 5 | from .helpers import * 6 | from .logger import * 7 | 8 | WARN = WARNING 9 | 10 | d = debug 11 | i = info 12 | w = warn = warning 13 | e = error 14 | c = critical 15 | exc = exception 16 | -------------------------------------------------------------------------------- /log/filters.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | 5 | 6 | class RelpathFormatFilter(logging.Filter): 7 | """Adds '%(relpath)s' as a 'LogRecord' attribute.""" 8 | 9 | def filter(self, record): 10 | pathname = record.pathname 11 | record.relpath = None 12 | abs_sys_paths = [os.path.abspath(p) for p in sys.path] 13 | for path in sorted(abs_sys_paths, key=len, reverse=True): 14 | if not path.endswith(os.sep): 15 | path += os.sep 16 | if pathname.startswith(path): 17 | record.relpath = os.path.relpath(pathname, path) 18 | break 19 | return True 20 | 21 | 22 | relpath_format_filter = RelpathFormatFilter() 23 | 24 | 25 | def install(logger): 26 | for handler in logger.handlers: 27 | handler.addFilter(relpath_format_filter) 28 | -------------------------------------------------------------------------------- /log/helpers.py: -------------------------------------------------------------------------------- 1 | """Wrappers to eliminate boilerplate `logging` activities.""" 2 | 3 | import logging 4 | import sys 5 | from importlib import reload 6 | 7 | from . import filters, settings, state 8 | 9 | __all__ = ["reset", "init", "silence"] 10 | 11 | VERBOSITY_TO_LEVEL = { 12 | 0: logging.ERROR, 13 | 1: logging.WARNING, 14 | 2: logging.INFO, 15 | 3: logging.DEBUG, 16 | } 17 | 18 | 19 | def reset(): 20 | logging.shutdown() 21 | reload(logging) 22 | state.initialized = False 23 | state.silenced.clear() 24 | 25 | 26 | def init(*, debug=False, verbosity=None, **kwargs): 27 | if debug: 28 | settings.DEFAULT_LEVEL = logging.DEBUG 29 | elif verbosity is not None: 30 | try: 31 | settings.DEFAULT_LEVEL = VERBOSITY_TO_LEVEL[verbosity] 32 | except KeyError: 33 | settings.DEFAULT_LEVEL = logging.DEBUG 34 | 35 | kwargs["level"] = kwargs.get("level", settings.DEFAULT_LEVEL) 36 | kwargs["format"] = kwargs.get("format", settings.DEFAULT_FORMAT) 37 | logging.basicConfig(**kwargs) 38 | 39 | custom_format = kwargs.get("format") 40 | if custom_format: 41 | formatter = logging.Formatter( 42 | fmt=custom_format, 43 | datefmt=kwargs.get("datefmt"), 44 | style=kwargs.get("style", "%"), 45 | ) 46 | for handler in logging.root.handlers: 47 | handler.setFormatter(formatter) 48 | 49 | filters.install(logging.root) 50 | state.initialized = True 51 | 52 | 53 | def silence(*names, allow_info=False, allow_warning=False, allow_error=False): 54 | if not state.initialized: 55 | if "pytest" in sys.modules: 56 | filters.install(logging.root) 57 | else: 58 | init() 59 | 60 | if allow_info: 61 | level = logging.INFO 62 | elif allow_warning: 63 | level = logging.WARNING 64 | elif allow_error: 65 | level = logging.ERROR 66 | else: 67 | level = logging.CRITICAL 68 | 69 | for name in names: 70 | logging.getLogger(name).setLevel(level) 71 | state.silenced.add(name) 72 | -------------------------------------------------------------------------------- /log/logger.py: -------------------------------------------------------------------------------- 1 | """Replicates some of the `logging.Logger` API.""" 2 | 3 | import logging 4 | import sys 5 | 6 | from . import utils 7 | 8 | __all__ = ["log", "debug", "info", "warning", "error", "critical", "exception"] 9 | 10 | 11 | def log(level, message, *args, **kwargs): 12 | utils.create_logger_record(level, message, *args, **kwargs) 13 | 14 | 15 | def debug(message, *args, **kwargs): 16 | log(logging.DEBUG, message, *args, **kwargs) 17 | 18 | 19 | def info(message, *args, **kwargs): 20 | log(logging.INFO, message, *args, **kwargs) 21 | 22 | 23 | def warning(message, *args, **kwargs): 24 | log(logging.WARNING, message, *args, **kwargs) 25 | 26 | 27 | def error(message, *args, **kwargs): 28 | log(logging.ERROR, message, *args, **kwargs) 29 | 30 | 31 | def critical(message, *args, **kwargs): 32 | log(logging.CRITICAL, message, *args, **kwargs) 33 | 34 | 35 | def exception(message, *args, **kwargs): 36 | kwargs["exc_info"] = kwargs.get("exc_info", sys.exc_info()) 37 | log(logging.ERROR, message, *args, **kwargs) 38 | -------------------------------------------------------------------------------- /log/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacebrowning/minilog/937f4de8327ce03b2b06d14693dc5ca3cddfb0c5/log/py.typed -------------------------------------------------------------------------------- /log/settings.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | DEFAULT_LEVEL = logging.INFO 4 | DEFAULT_FORMAT = "%(levelname)s: %(name)s: %(message)s" 5 | -------------------------------------------------------------------------------- /log/state.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | 3 | initialized = False 4 | 5 | silenced: Set[str] = set() 6 | -------------------------------------------------------------------------------- /log/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the package.""" 2 | -------------------------------------------------------------------------------- /log/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Unit tests configuration file.""" 2 | 3 | import logging 4 | 5 | 6 | def pytest_configure(config): 7 | """Disable verbose output when running tests.""" 8 | logging.basicConfig(level=logging.DEBUG) 9 | 10 | terminal = config.pluginmanager.getplugin("terminal") 11 | terminal.TerminalReporter.showfspath = False 12 | -------------------------------------------------------------------------------- /log/tests/test_api.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,singleton-comparison 2 | 3 | import pytest 4 | 5 | import log 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "name, levelname", 10 | [ 11 | ("c", "CRITICAL"), 12 | ("critical", "CRITICAL"), 13 | ("d", "DEBUG"), 14 | ("debug", "DEBUG"), 15 | ("e", "ERROR"), 16 | ("error", "ERROR"), 17 | ("exc", "ERROR"), 18 | ("exception", "ERROR"), 19 | ("i", "INFO"), 20 | ("info", "INFO"), 21 | ("w", "WARNING"), 22 | ("warn", "WARNING"), 23 | ("warning", "WARNING"), 24 | ], 25 | ) 26 | def test_level_mapping(expect, caplog, name, levelname): 27 | getattr(log, name)("message") 28 | expect(caplog.records[-1].levelname) == levelname 29 | -------------------------------------------------------------------------------- /log/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-variable,expression-not-assigned 2 | 3 | from unittest.mock import call, patch 4 | 5 | from log import helpers 6 | 7 | 8 | def describe_init(): 9 | @patch("logging.basicConfig") 10 | def with_verbosity_0(config, expect): 11 | helpers.init(format="%(message)s", verbosity=0) 12 | expect(config.mock_calls) == [call(format="%(message)s", level=40)] 13 | 14 | @patch("logging.basicConfig") 15 | def with_verbosity_1(config, expect): 16 | helpers.init(format="%(message)s", verbosity=1) 17 | expect(config.mock_calls) == [call(format="%(message)s", level=30)] 18 | 19 | @patch("logging.basicConfig") 20 | def with_verbosity_2(config, expect): 21 | helpers.init(format="%(message)s", verbosity=2) 22 | expect(config.mock_calls) == [call(format="%(message)s", level=20)] 23 | 24 | @patch("logging.basicConfig") 25 | def with_verbosity_3(config, expect): 26 | helpers.init(format="%(message)s", verbosity=3) 27 | expect(config.mock_calls) == [call(format="%(message)s", level=10)] 28 | 29 | @patch("logging.basicConfig") 30 | def with_verbosity_above_3(config, expect): 31 | helpers.init(format="%(message)s", verbosity=4) 32 | expect(config.mock_calls) == [call(format="%(message)s", level=10)] 33 | 34 | @patch("logging.basicConfig") 35 | def with_verbosity_0_and_debug(config, expect): 36 | helpers.init(format="%(message)s", verbosity=0, debug=True) 37 | expect(config.mock_calls) == [call(format="%(message)s", level=10)] 38 | -------------------------------------------------------------------------------- /log/tests/test_logger.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,singleton-comparison 2 | 3 | import logging 4 | 5 | from log import logger 6 | 7 | 8 | def describe_log(): 9 | def it_sets_level_and_message(expect, caplog): 10 | logger.log(logging.DEBUG, "foobar") 11 | expect(caplog.records[-1].levelname) == "DEBUG" 12 | expect(caplog.records[-1].message) == "foobar" 13 | -------------------------------------------------------------------------------- /log/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,singleton-comparison,disallowed-name 2 | 3 | import logging 4 | from dataclasses import dataclass 5 | 6 | from log.utils import create_logger_record, format_message, parse_name 7 | 8 | 9 | def describe_create_logger_record(): 10 | def it_uses_the_default_log_level_for_new_loggers(expect, monkeypatch): 11 | monkeypatch.setattr(logging.root, "level", logging.INFO) 12 | 13 | expect(create_logger_record(logging.DEBUG, "hello", name="new")) == False 14 | expect(create_logger_record(logging.INFO, "hello", name="new")) == True 15 | expect(create_logger_record(logging.WARNING, "hello", name="new")) == True 16 | 17 | def it_inherits_the_parent_logging_level(expect): 18 | logger = logging.getLogger("root") 19 | logger.level = logging.WARNING 20 | 21 | expect(create_logger_record(logging.DEBUG, "hello", name="root.new")) == False 22 | expect(create_logger_record(logging.INFO, "hello", name="root.new")) == False 23 | expect(create_logger_record(logging.WARNING, "hello", name="root.new")) == True 24 | 25 | 26 | def describe_parse_name(): 27 | def it_uses_the_filename_when_main(expect): 28 | frame_info = {"__file__": "my_package/my_module.py"} 29 | expect(parse_name("__main__", frame_info)[0]) == "my_package.my_module" 30 | 31 | def it_handles_interactive_sessions(expect): 32 | expect(parse_name("__main__", {})[0]) == "interactive" 33 | 34 | 35 | def describe_format_message(): 36 | def it_formats_structures(expect): 37 | data = {x: x * 10 for x in range(20)} 38 | expect(format_message(data).count("\n")) == 19 39 | 40 | def it_formats_dataclasses(expect): 41 | @dataclass 42 | class Example: 43 | foo: int = 1 44 | bar: str = "abc" 45 | 46 | example = Example() 47 | 48 | expect(format_message(example)) == "{'bar': 'abc', 'foo': 1}" 49 | 50 | def it_preserves_strings(expect): 51 | expect(format_message("foobar")) == "foobar" 52 | -------------------------------------------------------------------------------- /log/utils.py: -------------------------------------------------------------------------------- 1 | """Implements the "magic" to create `logging` records for the caller.""" 2 | 3 | import dataclasses 4 | import inspect 5 | import logging 6 | from pprint import pformat 7 | from typing import Dict, Tuple 8 | 9 | from . import state 10 | 11 | 12 | def create_logger_record( 13 | level, message, *args, name: str = "", exc_info=None, **kwargs 14 | ) -> bool: 15 | frame = inspect.currentframe().f_back.f_back.f_back # type: ignore 16 | assert frame 17 | 18 | name, parent_name = parse_name(name, frame.f_globals) 19 | if parent_name in state.silenced: 20 | return False 21 | 22 | logger = get_logger(name, parent_name) 23 | if not logger.isEnabledFor(level): 24 | return False 25 | 26 | record = logger.makeRecord( 27 | name, 28 | level, 29 | fn=parse_filename(frame.f_globals), 30 | lno=frame.f_lineno, 31 | msg=format_message(message), 32 | args=args, 33 | exc_info=exc_info, 34 | extra=kwargs, 35 | sinfo=None, 36 | ) 37 | logger.handle(record) 38 | return True 39 | 40 | 41 | def parse_name(custom_name: str, frame_info: Dict) -> Tuple[str, str]: 42 | module_name = custom_name or frame_info["__name__"] 43 | if module_name == "__main__": 44 | try: 45 | module_name = frame_info["__file__"].split(".")[0].replace("/", ".") 46 | except KeyError: 47 | module_name = "interactive" 48 | parent_module_name = module_name.split(".")[0] 49 | return module_name, parent_module_name 50 | 51 | 52 | def get_logger(name: str, parent_name: str): 53 | logger = logging.getLogger(name) 54 | if not logger.level: 55 | parent_logger = logging.getLogger(parent_name) 56 | logger.level = parent_logger.level 57 | if not logger.level: 58 | logger.level = logging.root.level 59 | return logger 60 | 61 | 62 | def parse_filename(frame_info: Dict) -> str: 63 | return frame_info.get("__file__", "interactive") 64 | 65 | 66 | def format_message(value) -> str: 67 | if dataclasses.is_dataclass(value): 68 | value = dataclasses.asdict(value) 69 | if not isinstance(value, str): 70 | value = pformat(value) 71 | return value 72 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: minilog 2 | site_description: Minimalistic wrapper for Python logging. 3 | site_author: Jace Browning 4 | 5 | repo_url: https://github.com/jacebrowning/minilog 6 | edit_uri: https://github.com/jacebrowning/minilog/edit/main/docs 7 | 8 | theme: readthedocs 9 | 10 | markdown_extensions: 11 | - codehilite 12 | 13 | nav: 14 | - Home: index.md 15 | - Logging: logging.md 16 | - Options: options.md 17 | - About: 18 | - Release Notes: about/changelog.md 19 | - Contributor Guide: about/contributing.md 20 | - License: about/license.md 21 | -------------------------------------------------------------------------------- /notebooks/profile_default/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /notebooks/profile_default/ipython_config.py: -------------------------------------------------------------------------------- 1 | # Configuration file for ipython. 2 | 3 | # ------------------------------------------------------------------------------ 4 | # InteractiveShellApp(Configurable) configuration 5 | # ------------------------------------------------------------------------------ 6 | 7 | ## A Mixin for applications that start InteractiveShell instances. 8 | # 9 | # Provides configurables for loading extensions and executing files as part of 10 | # configuring a Shell environment. 11 | # 12 | # The following methods should be called by the :meth:`initialize` method of the 13 | # subclass: 14 | # 15 | # - :meth:`init_path` 16 | # - :meth:`init_shell` (to be implemented by the subclass) 17 | # - :meth:`init_gui_pylab` 18 | # - :meth:`init_extensions` 19 | # - :meth:`init_code` 20 | 21 | ## Execute the given command string. 22 | # c.InteractiveShellApp.code_to_run = '' 23 | 24 | ## Run the file referenced by the PYTHONSTARTUP environment variable at IPython 25 | # startup. 26 | # c.InteractiveShellApp.exec_PYTHONSTARTUP = True 27 | 28 | ## List of files to run at IPython startup. 29 | # c.InteractiveShellApp.exec_files = [] 30 | 31 | ## lines of code to run at IPython startup. 32 | c.InteractiveShellApp.exec_lines = ["%autoreload 2"] 33 | 34 | ## A list of dotted module names of IPython extensions to load. 35 | c.InteractiveShellApp.extensions = ["autoreload"] 36 | 37 | ## dotted module name of an IPython extension to load. 38 | # c.InteractiveShellApp.extra_extension = '' 39 | 40 | ## A file to be run 41 | # c.InteractiveShellApp.file_to_run = '' 42 | 43 | ## Enable GUI event loop integration with any of ('asyncio', 'glut', 'gtk', 44 | # 'gtk2', 'gtk3', 'osx', 'pyglet', 'qt', 'qt4', 'qt5', 'tk', 'wx', 'gtk2', 45 | # 'qt4'). 46 | # c.InteractiveShellApp.gui = None 47 | 48 | ## Should variables loaded at startup (by startup files, exec_lines, etc.) be 49 | # hidden from tools like %who? 50 | # c.InteractiveShellApp.hide_initial_ns = True 51 | 52 | ## Configure matplotlib for interactive use with the default matplotlib backend. 53 | # c.InteractiveShellApp.matplotlib = None 54 | 55 | ## Run the module as a script. 56 | # c.InteractiveShellApp.module_to_run = '' 57 | 58 | ## Pre-load matplotlib and numpy for interactive use, selecting a particular 59 | # matplotlib backend and loop integration. 60 | # c.InteractiveShellApp.pylab = None 61 | 62 | ## If true, IPython will populate the user namespace with numpy, pylab, etc. and 63 | # an ``import *`` is done from numpy and pylab, when using pylab mode. 64 | # 65 | # When False, pylab mode should not import any names into the user namespace. 66 | # c.InteractiveShellApp.pylab_import_all = True 67 | 68 | ## Reraise exceptions encountered loading IPython extensions? 69 | # c.InteractiveShellApp.reraise_ipython_extension_failures = False 70 | 71 | # ------------------------------------------------------------------------------ 72 | # Application(SingletonConfigurable) configuration 73 | # ------------------------------------------------------------------------------ 74 | 75 | ## This is an application. 76 | 77 | ## The date format used by logging formatters for %(asctime)s 78 | # c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S' 79 | 80 | ## The Logging format template 81 | # c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s' 82 | 83 | ## Set the log level by value or name. 84 | # c.Application.log_level = 30 85 | 86 | # ------------------------------------------------------------------------------ 87 | # BaseIPythonApplication(Application) configuration 88 | # ------------------------------------------------------------------------------ 89 | 90 | ## IPython: an enhanced interactive Python shell. 91 | 92 | ## Whether to create profile dir if it doesn't exist 93 | # c.BaseIPythonApplication.auto_create = False 94 | 95 | ## Whether to install the default config files into the profile dir. If a new 96 | # profile is being created, and IPython contains config files for that profile, 97 | # then they will be staged into the new directory. Otherwise, default config 98 | # files will be automatically generated. 99 | # c.BaseIPythonApplication.copy_config_files = False 100 | 101 | ## Path to an extra config file to load. 102 | # 103 | # If specified, load this config file in addition to any other IPython config. 104 | # c.BaseIPythonApplication.extra_config_file = '' 105 | 106 | ## The name of the IPython directory. This directory is used for logging 107 | # configuration (through profiles), history storage, etc. The default is usually 108 | # $HOME/.ipython. This option can also be specified through the environment 109 | # variable IPYTHONDIR. 110 | # c.BaseIPythonApplication.ipython_dir = '' 111 | 112 | ## Whether to overwrite existing config files when copying 113 | # c.BaseIPythonApplication.overwrite = False 114 | 115 | ## The IPython profile to use. 116 | # c.BaseIPythonApplication.profile = 'default' 117 | 118 | ## Create a massive crash report when IPython encounters what may be an internal 119 | # error. The default is to append a short message to the usual traceback 120 | # c.BaseIPythonApplication.verbose_crash = False 121 | 122 | # ------------------------------------------------------------------------------ 123 | # TerminalIPythonApp(BaseIPythonApplication,InteractiveShellApp) configuration 124 | # ------------------------------------------------------------------------------ 125 | 126 | ## Whether to display a banner upon starting IPython. 127 | # c.TerminalIPythonApp.display_banner = True 128 | 129 | ## If a command or file is given via the command-line, e.g. 'ipython foo.py', 130 | # start an interactive shell after executing the file or command. 131 | # c.TerminalIPythonApp.force_interact = False 132 | 133 | ## Class to use to instantiate the TerminalInteractiveShell object. Useful for 134 | # custom Frontends 135 | # c.TerminalIPythonApp.interactive_shell_class = 'IPython.terminal.interactiveshell.TerminalInteractiveShell' 136 | 137 | ## Start IPython quickly by skipping the loading of config files. 138 | # c.TerminalIPythonApp.quick = False 139 | 140 | # ------------------------------------------------------------------------------ 141 | # InteractiveShell(SingletonConfigurable) configuration 142 | # ------------------------------------------------------------------------------ 143 | 144 | ## An enhanced, interactive shell for Python. 145 | 146 | ## 'all', 'last', 'last_expr' or 'none', 'last_expr_or_assign' specifying which 147 | # nodes should be run interactively (displaying output from expressions). 148 | # c.InteractiveShell.ast_node_interactivity = 'last_expr' 149 | 150 | ## A list of ast.NodeTransformer subclass instances, which will be applied to 151 | # user input before code is run. 152 | # c.InteractiveShell.ast_transformers = [] 153 | 154 | ## Automatically run await statement in the top level repl. 155 | # c.InteractiveShell.autoawait = True 156 | 157 | ## Make IPython automatically call any callable object even if you didn't type 158 | # explicit parentheses. For example, 'str 43' becomes 'str(43)' automatically. 159 | # The value can be '0' to disable the feature, '1' for 'smart' autocall, where 160 | # it is not applied if there are no more arguments on the line, and '2' for 161 | # 'full' autocall, where all callable objects are automatically called (even if 162 | # no arguments are present). 163 | # c.InteractiveShell.autocall = 0 164 | 165 | ## Autoindent IPython code entered interactively. 166 | # c.InteractiveShell.autoindent = True 167 | 168 | ## Enable magic commands to be called without the leading %. 169 | # c.InteractiveShell.automagic = True 170 | 171 | ## The part of the banner to be printed before the profile 172 | # c.InteractiveShell.banner1 = "Python 3.8.1 (default, Jan 9 2020, 14:37:22) \nType 'copyright', 'credits' or 'license' for more information\nIPython 7.12.0 -- An enhanced Interactive Python. Type '?' for help.\n" 173 | 174 | ## The part of the banner to be printed after the profile 175 | # c.InteractiveShell.banner2 = '' 176 | 177 | ## Set the size of the output cache. The default is 1000, you can change it 178 | # permanently in your config file. Setting it to 0 completely disables the 179 | # caching system, and the minimum value accepted is 3 (if you provide a value 180 | # less than 3, it is reset to 0 and a warning is issued). This limit is defined 181 | # because otherwise you'll spend more time re-flushing a too small cache than 182 | # working 183 | # c.InteractiveShell.cache_size = 1000 184 | 185 | ## Use colors for displaying information about objects. Because this information 186 | # is passed through a pager (like 'less'), and some pagers get confused with 187 | # color codes, this capability can be turned off. 188 | # c.InteractiveShell.color_info = True 189 | 190 | ## Set the color scheme (NoColor, Neutral, Linux, or LightBG). 191 | # c.InteractiveShell.colors = 'Neutral' 192 | 193 | ## 194 | # c.InteractiveShell.debug = False 195 | 196 | ## Don't call post-execute functions that have failed in the past. 197 | # c.InteractiveShell.disable_failing_post_execute = False 198 | 199 | ## If True, anything that would be passed to the pager will be displayed as 200 | # regular output instead. 201 | # c.InteractiveShell.display_page = False 202 | 203 | ## (Provisional API) enables html representation in mime bundles sent to pagers. 204 | # c.InteractiveShell.enable_html_pager = False 205 | 206 | ## Total length of command history 207 | # c.InteractiveShell.history_length = 10000 208 | 209 | ## The number of saved history entries to be loaded into the history buffer at 210 | # startup. 211 | # c.InteractiveShell.history_load_length = 1000 212 | 213 | ## 214 | # c.InteractiveShell.ipython_dir = '' 215 | 216 | ## Start logging to the given file in append mode. Use `logfile` to specify a log 217 | # file to **overwrite** logs to. 218 | # c.InteractiveShell.logappend = '' 219 | 220 | ## The name of the logfile to use. 221 | # c.InteractiveShell.logfile = '' 222 | 223 | ## Start logging to the default log file in overwrite mode. Use `logappend` to 224 | # specify a log file to **append** logs to. 225 | # c.InteractiveShell.logstart = False 226 | 227 | ## Select the loop runner that will be used to execute top-level asynchronous 228 | # code 229 | # c.InteractiveShell.loop_runner = 'IPython.core.interactiveshell._asyncio_runner' 230 | 231 | ## 232 | # c.InteractiveShell.object_info_string_level = 0 233 | 234 | ## Automatically call the pdb debugger after every exception. 235 | # c.InteractiveShell.pdb = False 236 | 237 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 238 | # TerminalInteractiveShell.prompts object directly. 239 | # c.InteractiveShell.prompt_in1 = 'In [\\#]: ' 240 | 241 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 242 | # TerminalInteractiveShell.prompts object directly. 243 | # c.InteractiveShell.prompt_in2 = ' .\\D.: ' 244 | 245 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 246 | # TerminalInteractiveShell.prompts object directly. 247 | # c.InteractiveShell.prompt_out = 'Out[\\#]: ' 248 | 249 | ## Deprecated since IPython 4.0 and ignored since 5.0, set 250 | # TerminalInteractiveShell.prompts object directly. 251 | # c.InteractiveShell.prompts_pad_left = True 252 | 253 | ## 254 | # c.InteractiveShell.quiet = False 255 | 256 | ## 257 | # c.InteractiveShell.separate_in = '\n' 258 | 259 | ## 260 | # c.InteractiveShell.separate_out = '' 261 | 262 | ## 263 | # c.InteractiveShell.separate_out2 = '' 264 | 265 | ## Show rewritten input, e.g. for autocall. 266 | # c.InteractiveShell.show_rewritten_input = True 267 | 268 | ## Enables rich html representation of docstrings. (This requires the docrepr 269 | # module). 270 | # c.InteractiveShell.sphinxify_docstring = False 271 | 272 | ## 273 | # c.InteractiveShell.wildcards_case_sensitive = True 274 | 275 | ## Switch modes for the IPython exception handlers. 276 | # c.InteractiveShell.xmode = 'Context' 277 | 278 | # ------------------------------------------------------------------------------ 279 | # TerminalInteractiveShell(InteractiveShell) configuration 280 | # ------------------------------------------------------------------------------ 281 | 282 | ## Autoformatter to reformat Terminal code. Can be `'black'` or `None` 283 | # c.TerminalInteractiveShell.autoformatter = None 284 | 285 | ## Set to confirm when you try to exit IPython with an EOF (Control-D in Unix, 286 | # Control-Z/Enter in Windows). By typing 'exit' or 'quit', you can force a 287 | # direct exit without any confirmation. 288 | # c.TerminalInteractiveShell.confirm_exit = True 289 | 290 | ## Options for displaying tab completions, 'column', 'multicolumn', and 291 | # 'readlinelike'. These options are for `prompt_toolkit`, see `prompt_toolkit` 292 | # documentation for more information. 293 | # c.TerminalInteractiveShell.display_completions = 'multicolumn' 294 | 295 | ## Shortcut style to use at the prompt. 'vi' or 'emacs'. 296 | # c.TerminalInteractiveShell.editing_mode = 'emacs' 297 | 298 | ## Set the editor used by IPython (default to $EDITOR/vi/notepad). 299 | # c.TerminalInteractiveShell.editor = 'vim' 300 | 301 | ## Allows to enable/disable the prompt toolkit history search 302 | # c.TerminalInteractiveShell.enable_history_search = True 303 | 304 | ## Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. This is 305 | # in addition to the F2 binding, which is always enabled. 306 | # c.TerminalInteractiveShell.extra_open_editor_shortcuts = False 307 | 308 | ## Provide an alternative handler to be called when the user presses Return. This 309 | # is an advanced option intended for debugging, which may be changed or removed 310 | # in later releases. 311 | # c.TerminalInteractiveShell.handle_return = None 312 | 313 | ## Highlight matching brackets. 314 | # c.TerminalInteractiveShell.highlight_matching_brackets = True 315 | 316 | ## The name or class of a Pygments style to use for syntax highlighting. To see 317 | # available styles, run `pygmentize -L styles`. 318 | # c.TerminalInteractiveShell.highlighting_style = traitlets.Undefined 319 | 320 | ## Override highlighting format for specific tokens 321 | # c.TerminalInteractiveShell.highlighting_style_overrides = {} 322 | 323 | ## 324 | # c.TerminalInteractiveShell.mime_renderers = {} 325 | 326 | ## Enable mouse support in the prompt (Note: prevents selecting text with the 327 | # mouse) 328 | # c.TerminalInteractiveShell.mouse_support = False 329 | 330 | ## Display the current vi mode (when using vi editing mode). 331 | # c.TerminalInteractiveShell.prompt_includes_vi_mode = True 332 | 333 | ## Class used to generate Prompt token for prompt_toolkit 334 | # c.TerminalInteractiveShell.prompts_class = 'IPython.terminal.prompts.Prompts' 335 | 336 | ## Use `raw_input` for the REPL, without completion and prompt colors. 337 | # 338 | # Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. 339 | # Known usage are: IPython own testing machinery, and emacs inferior-shell 340 | # integration through elpy. 341 | # 342 | # This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` environment 343 | # variable is set, or the current terminal is not a tty. 344 | # c.TerminalInteractiveShell.simple_prompt = False 345 | 346 | ## Number of line at the bottom of the screen to reserve for the completion menu 347 | # c.TerminalInteractiveShell.space_for_menu = 6 348 | 349 | ## Automatically set the terminal title 350 | # c.TerminalInteractiveShell.term_title = True 351 | 352 | ## Customize the terminal title format. This is a python format string. 353 | # Available substitutions are: {cwd}. 354 | # c.TerminalInteractiveShell.term_title_format = 'IPython: {cwd}' 355 | 356 | ## Use 24bit colors instead of 256 colors in prompt highlighting. If your 357 | # terminal supports true color, the following command should print 'TRUECOLOR' 358 | # in orange: printf "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m\n" 359 | # c.TerminalInteractiveShell.true_color = False 360 | 361 | # ------------------------------------------------------------------------------ 362 | # HistoryAccessor(HistoryAccessorBase) configuration 363 | # ------------------------------------------------------------------------------ 364 | 365 | ## Access the history database without adding to it. 366 | # 367 | # This is intended for use by standalone history tools. IPython shells use 368 | # HistoryManager, below, which is a subclass of this. 369 | 370 | ## Options for configuring the SQLite connection 371 | # 372 | # These options are passed as keyword args to sqlite3.connect when establishing 373 | # database connections. 374 | # c.HistoryAccessor.connection_options = {} 375 | 376 | ## enable the SQLite history 377 | # 378 | # set enabled=False to disable the SQLite history, in which case there will be 379 | # no stored history, no SQLite connection, and no background saving thread. 380 | # This may be necessary in some threaded environments where IPython is embedded. 381 | # c.HistoryAccessor.enabled = True 382 | 383 | ## Path to file to use for SQLite history database. 384 | # 385 | # By default, IPython will put the history database in the IPython profile 386 | # directory. If you would rather share one history among profiles, you can set 387 | # this value in each, so that they are consistent. 388 | # 389 | # Due to an issue with fcntl, SQLite is known to misbehave on some NFS mounts. 390 | # If you see IPython hanging, try setting this to something on a local disk, 391 | # e.g:: 392 | # 393 | # ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite 394 | # 395 | # you can also use the specific value `:memory:` (including the colon at both 396 | # end but not the back ticks), to avoid creating an history file. 397 | # c.HistoryAccessor.hist_file = '' 398 | 399 | # ------------------------------------------------------------------------------ 400 | # HistoryManager(HistoryAccessor) configuration 401 | # ------------------------------------------------------------------------------ 402 | 403 | ## A class to organize all history-related functionality in one place. 404 | 405 | ## Write to database every x commands (higher values save disk access & power). 406 | # Values of 1 or less effectively disable caching. 407 | # c.HistoryManager.db_cache_size = 0 408 | 409 | ## Should the history database include output? (default: no) 410 | # c.HistoryManager.db_log_output = False 411 | 412 | # ------------------------------------------------------------------------------ 413 | # ProfileDir(LoggingConfigurable) configuration 414 | # ------------------------------------------------------------------------------ 415 | 416 | ## An object to manage the profile directory and its resources. 417 | # 418 | # The profile directory is used by all IPython applications, to manage 419 | # configuration, logging and security. 420 | # 421 | # This object knows how to find, create and manage these directories. This 422 | # should be used by any code that wants to handle profiles. 423 | 424 | ## Set the profile location directly. This overrides the logic used by the 425 | # `profile` option. 426 | # c.ProfileDir.location = '' 427 | 428 | # ------------------------------------------------------------------------------ 429 | # BaseFormatter(Configurable) configuration 430 | # ------------------------------------------------------------------------------ 431 | 432 | ## A base formatter class that is configurable. 433 | # 434 | # This formatter should usually be used as the base class of all formatters. It 435 | # is a traited :class:`Configurable` class and includes an extensible API for 436 | # users to determine how their objects are formatted. The following logic is 437 | # used to find a function to format an given object. 438 | # 439 | # 1. The object is introspected to see if it has a method with the name 440 | # :attr:`print_method`. If is does, that object is passed to that method 441 | # for formatting. 442 | # 2. If no print method is found, three internal dictionaries are consulted 443 | # to find print method: :attr:`singleton_printers`, :attr:`type_printers` 444 | # and :attr:`deferred_printers`. 445 | # 446 | # Users should use these dictionaries to register functions that will be used to 447 | # compute the format data for their objects (if those objects don't have the 448 | # special print methods). The easiest way of using these dictionaries is through 449 | # the :meth:`for_type` and :meth:`for_type_by_name` methods. 450 | # 451 | # If no function/callable is found to compute the format data, ``None`` is 452 | # returned and this format type is not used. 453 | 454 | ## 455 | # c.BaseFormatter.deferred_printers = {} 456 | 457 | ## 458 | # c.BaseFormatter.enabled = True 459 | 460 | ## 461 | # c.BaseFormatter.singleton_printers = {} 462 | 463 | ## 464 | # c.BaseFormatter.type_printers = {} 465 | 466 | # ------------------------------------------------------------------------------ 467 | # PlainTextFormatter(BaseFormatter) configuration 468 | # ------------------------------------------------------------------------------ 469 | 470 | ## The default pretty-printer. 471 | # 472 | # This uses :mod:`IPython.lib.pretty` to compute the format data of the object. 473 | # If the object cannot be pretty printed, :func:`repr` is used. See the 474 | # documentation of :mod:`IPython.lib.pretty` for details on how to write pretty 475 | # printers. Here is a simple example:: 476 | # 477 | # def dtype_pprinter(obj, p, cycle): 478 | # if cycle: 479 | # return p.text('dtype(...)') 480 | # if hasattr(obj, 'fields'): 481 | # if obj.fields is None: 482 | # p.text(repr(obj)) 483 | # else: 484 | # p.begin_group(7, 'dtype([') 485 | # for i, field in enumerate(obj.descr): 486 | # if i > 0: 487 | # p.text(',') 488 | # p.breakable() 489 | # p.pretty(field) 490 | # p.end_group(7, '])') 491 | 492 | ## 493 | # c.PlainTextFormatter.float_precision = '' 494 | 495 | ## Truncate large collections (lists, dicts, tuples, sets) to this size. 496 | # 497 | # Set to 0 to disable truncation. 498 | # c.PlainTextFormatter.max_seq_length = 1000 499 | 500 | ## 501 | # c.PlainTextFormatter.max_width = 79 502 | 503 | ## 504 | # c.PlainTextFormatter.newline = '\n' 505 | 506 | ## 507 | # c.PlainTextFormatter.pprint = True 508 | 509 | ## 510 | # c.PlainTextFormatter.verbose = False 511 | 512 | # ------------------------------------------------------------------------------ 513 | # Completer(Configurable) configuration 514 | # ------------------------------------------------------------------------------ 515 | 516 | ## Enable unicode completions, e.g. \alpha . Includes completion of latex 517 | # commands, unicode names, and expanding unicode characters back to latex 518 | # commands. 519 | # c.Completer.backslash_combining_completions = True 520 | 521 | ## Enable debug for the Completer. Mostly print extra information for 522 | # experimental jedi integration. 523 | # c.Completer.debug = False 524 | 525 | ## Activate greedy completion PENDING DEPRECTION. this is now mostly taken care 526 | # of with Jedi. 527 | # 528 | # This will enable completion on elements of lists, results of function calls, 529 | # etc., but can be unsafe because the code is actually evaluated on TAB. 530 | # c.Completer.greedy = False 531 | 532 | ## Experimental: restrict time (in milliseconds) during which Jedi can compute 533 | # types. Set to 0 to stop computing types. Non-zero value lower than 100ms may 534 | # hurt performance by preventing jedi to build its cache. 535 | # c.Completer.jedi_compute_type_timeout = 400 536 | 537 | ## Experimental: Use Jedi to generate autocompletions. Default to True if jedi is 538 | # installed. 539 | # c.Completer.use_jedi = True 540 | 541 | # ------------------------------------------------------------------------------ 542 | # IPCompleter(Completer) configuration 543 | # ------------------------------------------------------------------------------ 544 | 545 | ## Extension of the completer class with IPython-specific features 546 | 547 | ## DEPRECATED as of version 5.0. 548 | # 549 | # Instruct the completer to use __all__ for the completion 550 | # 551 | # Specifically, when completing on ``object.``. 552 | # 553 | # When True: only those names in obj.__all__ will be included. 554 | # 555 | # When False [default]: the __all__ attribute is ignored 556 | # c.IPCompleter.limit_to__all__ = False 557 | 558 | ## Whether to merge completion results into a single list 559 | # 560 | # If False, only the completion results from the first non-empty completer will 561 | # be returned. 562 | # c.IPCompleter.merge_completions = True 563 | 564 | ## Instruct the completer to omit private method names 565 | # 566 | # Specifically, when completing on ``object.``. 567 | # 568 | # When 2 [default]: all names that start with '_' will be excluded. 569 | # 570 | # When 1: all 'magic' names (``__foo__``) will be excluded. 571 | # 572 | # When 0: nothing will be excluded. 573 | # c.IPCompleter.omit__names = 2 574 | 575 | # ------------------------------------------------------------------------------ 576 | # ScriptMagics(Magics) configuration 577 | # ------------------------------------------------------------------------------ 578 | 579 | ## Magics for talking to scripts 580 | # 581 | # This defines a base `%%script` cell magic for running a cell with a program in 582 | # a subprocess, and registers a few top-level magics that call %%script with 583 | # common interpreters. 584 | 585 | ## Extra script cell magics to define 586 | # 587 | # This generates simple wrappers of `%%script foo` as `%%foo`. 588 | # 589 | # If you want to add script magics that aren't on your path, specify them in 590 | # script_paths 591 | # c.ScriptMagics.script_magics = [] 592 | 593 | ## Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby' 594 | # 595 | # Only necessary for items in script_magics where the default path will not find 596 | # the right interpreter. 597 | # c.ScriptMagics.script_paths = {} 598 | 599 | # ------------------------------------------------------------------------------ 600 | # LoggingMagics(Magics) configuration 601 | # ------------------------------------------------------------------------------ 602 | 603 | ## Magics related to all logging machinery. 604 | 605 | ## Suppress output of log state when logging is enabled 606 | # c.LoggingMagics.quiet = False 607 | 608 | # ------------------------------------------------------------------------------ 609 | # StoreMagics(Magics) configuration 610 | # ------------------------------------------------------------------------------ 611 | 612 | ## Lightweight persistence for python variables. 613 | # 614 | # Provides the %store magic. 615 | 616 | ## If True, any %store-d variables will be automatically restored when IPython 617 | # starts. 618 | # c.StoreMagics.autorestore = False 619 | -------------------------------------------------------------------------------- /notebooks/profile_default/startup/README: -------------------------------------------------------------------------------- 1 | This is the IPython startup directory 2 | 3 | .py and .ipy files in this directory will be run *prior* to any code or files specified 4 | via the exec_lines or exec_files configurables whenever you load this profile. 5 | 6 | Files will be run in lexicographical order, so you can control the execution order of files 7 | with a prefix, e.g.:: 8 | 9 | 00-first.py 10 | 50-middle.py 11 | 99-last.ipy 12 | -------------------------------------------------------------------------------- /notebooks/profile_default/startup/ipython_startup.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacebrowning/minilog/937f4de8327ce03b2b06d14693dc5ca3cddfb0c5/notebooks/profile_default/startup/ipython_startup.py -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "altgraph" 5 | version = "0.17" 6 | description = "Python graph (network) package" 7 | optional = false 8 | python-versions = "*" 9 | groups = ["dev"] 10 | files = [ 11 | {file = "altgraph-0.17-py2.py3-none-any.whl", hash = "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"}, 12 | {file = "altgraph-0.17.tar.gz", hash = "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa"}, 13 | ] 14 | 15 | [[package]] 16 | name = "appnope" 17 | version = "0.1.2" 18 | description = "Disable App Nap on macOS >= 10.9" 19 | optional = false 20 | python-versions = "*" 21 | groups = ["dev"] 22 | markers = "sys_platform == \"darwin\"" 23 | files = [ 24 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, 25 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, 26 | ] 27 | 28 | [[package]] 29 | name = "astroid" 30 | version = "2.13.5" 31 | description = "An abstract syntax tree for Python with inference support." 32 | optional = false 33 | python-versions = ">=3.7.2" 34 | groups = ["dev"] 35 | files = [ 36 | {file = "astroid-2.13.5-py3-none-any.whl", hash = "sha256:6891f444625b6edb2ac798829b689e95297e100ddf89dbed5a8c610e34901501"}, 37 | {file = "astroid-2.13.5.tar.gz", hash = "sha256:df164d5ac811b9f44105a72b8f9d5edfb7b5b2d7e979b04ea377a77b3229114a"}, 38 | ] 39 | 40 | [package.dependencies] 41 | lazy-object-proxy = ">=1.4.0" 42 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} 43 | wrapt = [ 44 | {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, 45 | {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, 46 | ] 47 | 48 | [[package]] 49 | name = "attrs" 50 | version = "21.2.0" 51 | description = "Classes Without Boilerplate" 52 | optional = false 53 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 54 | groups = ["dev"] 55 | files = [ 56 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 57 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 58 | ] 59 | 60 | [package.extras] 61 | dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] 62 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 63 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] 64 | tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] 65 | 66 | [[package]] 67 | name = "backcall" 68 | version = "0.2.0" 69 | description = "Specifications for callback functions passed in to an API" 70 | optional = false 71 | python-versions = "*" 72 | groups = ["dev"] 73 | files = [ 74 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 75 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 76 | ] 77 | 78 | [[package]] 79 | name = "black" 80 | version = "24.3.0" 81 | description = "The uncompromising code formatter." 82 | optional = false 83 | python-versions = ">=3.8" 84 | groups = ["dev"] 85 | files = [ 86 | {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, 87 | {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, 88 | {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, 89 | {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, 90 | {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, 91 | {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, 92 | {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, 93 | {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, 94 | {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, 95 | {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, 96 | {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, 97 | {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, 98 | {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, 99 | {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, 100 | {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, 101 | {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, 102 | {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, 103 | {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, 104 | {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, 105 | {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, 106 | {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, 107 | {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, 108 | ] 109 | 110 | [package.dependencies] 111 | click = ">=8.0.0" 112 | mypy-extensions = ">=0.4.3" 113 | packaging = ">=22.0" 114 | pathspec = ">=0.9.0" 115 | platformdirs = ">=2" 116 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 117 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 118 | 119 | [package.extras] 120 | colorama = ["colorama (>=0.4.3)"] 121 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 122 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 123 | uvloop = ["uvloop (>=0.15.2)"] 124 | 125 | [[package]] 126 | name = "certifi" 127 | version = "2023.7.22" 128 | description = "Python package for providing Mozilla's CA Bundle." 129 | optional = false 130 | python-versions = ">=3.6" 131 | groups = ["dev"] 132 | files = [ 133 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 134 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 135 | ] 136 | 137 | [[package]] 138 | name = "charset-normalizer" 139 | version = "2.1.1" 140 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 141 | optional = false 142 | python-versions = ">=3.6.0" 143 | groups = ["dev"] 144 | files = [ 145 | {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, 146 | {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, 147 | ] 148 | 149 | [package.extras] 150 | unicode-backport = ["unicodedata2"] 151 | 152 | [[package]] 153 | name = "click" 154 | version = "8.0.1" 155 | description = "Composable command line interface toolkit" 156 | optional = false 157 | python-versions = ">=3.6" 158 | groups = ["dev"] 159 | files = [ 160 | {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, 161 | {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, 162 | ] 163 | 164 | [package.dependencies] 165 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 166 | 167 | [[package]] 168 | name = "colorama" 169 | version = "0.4.6" 170 | description = "Cross-platform colored terminal text." 171 | optional = false 172 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 173 | groups = ["dev"] 174 | files = [ 175 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 176 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 177 | ] 178 | 179 | [[package]] 180 | name = "coverage" 181 | version = "7.2.7" 182 | description = "Code coverage measurement for Python" 183 | optional = false 184 | python-versions = ">=3.7" 185 | groups = ["dev"] 186 | files = [ 187 | {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, 188 | {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, 189 | {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, 190 | {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, 191 | {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, 192 | {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, 193 | {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, 194 | {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, 195 | {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, 196 | {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, 197 | {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, 198 | {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, 199 | {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, 200 | {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, 201 | {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, 202 | {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, 203 | {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, 204 | {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, 205 | {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, 206 | {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, 207 | {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, 208 | {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, 209 | {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, 210 | {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, 211 | {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, 212 | {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, 213 | {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, 214 | {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, 215 | {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, 216 | {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, 217 | {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, 218 | {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, 219 | {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, 220 | {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, 221 | {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, 222 | {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, 223 | {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, 224 | {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, 225 | {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, 226 | {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, 227 | {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, 228 | {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, 229 | {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, 230 | {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, 231 | {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, 232 | {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, 233 | {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, 234 | {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, 235 | {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, 236 | {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, 237 | {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, 238 | {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, 239 | {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, 240 | {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, 241 | {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, 242 | {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, 243 | {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, 244 | {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, 245 | {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, 246 | {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, 247 | ] 248 | 249 | [package.dependencies] 250 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 251 | 252 | [package.extras] 253 | toml = ["tomli"] 254 | 255 | [[package]] 256 | name = "coveragespace" 257 | version = "6.0.1" 258 | description = "A place to track your code coverage metrics." 259 | optional = false 260 | python-versions = ">=3.8,<4.0" 261 | groups = ["dev"] 262 | files = [ 263 | {file = "coveragespace-6.0.1-py3-none-any.whl", hash = "sha256:993753f809752ea8c7403bbedcb81309853228611d4e6a766e890ad797be3fe8"}, 264 | {file = "coveragespace-6.0.1.tar.gz", hash = "sha256:c72ed7e59a5a8ddd55ebe5187578472567a02f8859ee643ed0c58a9aba8f8415"}, 265 | ] 266 | 267 | [package.dependencies] 268 | colorama = ">=0.4" 269 | coverage = ">=4.0" 270 | docopt = ">=0.6" 271 | minilog = ">=2.0" 272 | requests = ">=2.28,<3.0" 273 | 274 | [[package]] 275 | name = "decorator" 276 | version = "5.0.9" 277 | description = "Decorators for Humans" 278 | optional = false 279 | python-versions = ">=3.5" 280 | groups = ["dev"] 281 | files = [ 282 | {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, 283 | {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, 284 | ] 285 | 286 | [[package]] 287 | name = "dill" 288 | version = "0.3.6" 289 | description = "serialize all of python" 290 | optional = false 291 | python-versions = ">=3.7" 292 | groups = ["dev"] 293 | files = [ 294 | {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, 295 | {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, 296 | ] 297 | 298 | [package.extras] 299 | graph = ["objgraph (>=1.7.2)"] 300 | 301 | [[package]] 302 | name = "docopt" 303 | version = "0.6.2" 304 | description = "Pythonic argument parser, that will make you smile" 305 | optional = false 306 | python-versions = "*" 307 | groups = ["dev"] 308 | files = [ 309 | {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, 310 | ] 311 | 312 | [[package]] 313 | name = "exceptiongroup" 314 | version = "1.0.4" 315 | description = "Backport of PEP 654 (exception groups)" 316 | optional = false 317 | python-versions = ">=3.7" 318 | groups = ["dev"] 319 | markers = "python_version < \"3.11\"" 320 | files = [ 321 | {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, 322 | {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, 323 | ] 324 | 325 | [package.extras] 326 | test = ["pytest (>=6)"] 327 | 328 | [[package]] 329 | name = "freezegun" 330 | version = "1.1.0" 331 | description = "Let your Python tests travel through time" 332 | optional = false 333 | python-versions = ">=3.5" 334 | groups = ["dev"] 335 | files = [ 336 | {file = "freezegun-1.1.0-py2.py3-none-any.whl", hash = "sha256:2ae695f7eb96c62529f03a038461afe3c692db3465e215355e1bb4b0ab408712"}, 337 | {file = "freezegun-1.1.0.tar.gz", hash = "sha256:177f9dd59861d871e27a484c3332f35a6e3f5d14626f2bf91be37891f18927f3"}, 338 | ] 339 | 340 | [package.dependencies] 341 | python-dateutil = ">=2.7" 342 | 343 | [[package]] 344 | name = "future" 345 | version = "0.18.3" 346 | description = "Clean single-source support for Python 3 and 2" 347 | optional = false 348 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 349 | groups = ["dev"] 350 | markers = "sys_platform == \"win32\"" 351 | files = [ 352 | {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, 353 | ] 354 | 355 | [[package]] 356 | name = "ghp-import" 357 | version = "2.0.2" 358 | description = "Copy your docs directly to the gh-pages branch." 359 | optional = false 360 | python-versions = "*" 361 | groups = ["dev"] 362 | files = [ 363 | {file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"}, 364 | {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"}, 365 | ] 366 | 367 | [package.dependencies] 368 | python-dateutil = ">=2.8.1" 369 | 370 | [package.extras] 371 | dev = ["flake8", "markdown", "twine", "wheel"] 372 | 373 | [[package]] 374 | name = "gprof2dot" 375 | version = "2021.2.21" 376 | description = "Generate a dot graph from the output of several profilers." 377 | optional = false 378 | python-versions = "*" 379 | groups = ["dev"] 380 | files = [ 381 | {file = "gprof2dot-2021.2.21.tar.gz", hash = "sha256:1223189383b53dcc8ecfd45787ac48c0ed7b4dbc16ee8b88695d053eea1acabf"}, 382 | ] 383 | 384 | [[package]] 385 | name = "idna" 386 | version = "2.10" 387 | description = "Internationalized Domain Names in Applications (IDNA)" 388 | optional = false 389 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 390 | groups = ["dev"] 391 | files = [ 392 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 393 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 394 | ] 395 | 396 | [[package]] 397 | name = "importlib-metadata" 398 | version = "4.4.0" 399 | description = "Read metadata from Python packages" 400 | optional = false 401 | python-versions = ">=3.6" 402 | groups = ["dev"] 403 | files = [ 404 | {file = "importlib_metadata-4.4.0-py3-none-any.whl", hash = "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786"}, 405 | {file = "importlib_metadata-4.4.0.tar.gz", hash = "sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5"}, 406 | ] 407 | 408 | [package.dependencies] 409 | zipp = ">=0.5" 410 | 411 | [package.extras] 412 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 413 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 414 | 415 | [[package]] 416 | name = "iniconfig" 417 | version = "1.1.1" 418 | description = "iniconfig: brain-dead simple config-ini parsing" 419 | optional = false 420 | python-versions = "*" 421 | groups = ["dev"] 422 | files = [ 423 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 424 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 425 | ] 426 | 427 | [[package]] 428 | name = "ipython" 429 | version = "7.16.3" 430 | description = "IPython: Productive Interactive Computing" 431 | optional = false 432 | python-versions = ">=3.6" 433 | groups = ["dev"] 434 | files = [ 435 | {file = "ipython-7.16.3-py3-none-any.whl", hash = "sha256:c0427ed8bc33ac481faf9d3acf7e84e0010cdaada945e0badd1e2e74cc075833"}, 436 | {file = "ipython-7.16.3.tar.gz", hash = "sha256:5ac47dc9af66fc2f5530c12069390877ae372ac905edca75a92a6e363b5d7caa"}, 437 | ] 438 | 439 | [package.dependencies] 440 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 441 | backcall = "*" 442 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 443 | decorator = "*" 444 | jedi = ">=0.10,<=0.17.2" 445 | pexpect = {version = "*", markers = "sys_platform != \"win32\""} 446 | pickleshare = "*" 447 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 448 | pygments = "*" 449 | setuptools = ">=18.5" 450 | traitlets = ">=4.2" 451 | 452 | [package.extras] 453 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 454 | doc = ["Sphinx (>=1.3)"] 455 | kernel = ["ipykernel"] 456 | nbconvert = ["nbconvert"] 457 | nbformat = ["nbformat"] 458 | notebook = ["ipywidgets", "notebook"] 459 | parallel = ["ipyparallel"] 460 | qtconsole = ["qtconsole"] 461 | test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.14)", "pygments", "requests", "testpath"] 462 | 463 | [[package]] 464 | name = "isort" 465 | version = "5.10.1" 466 | description = "A Python utility / library to sort Python imports." 467 | optional = false 468 | python-versions = ">=3.6.1,<4.0" 469 | groups = ["dev"] 470 | files = [ 471 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 472 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 473 | ] 474 | 475 | [package.extras] 476 | colors = ["colorama (>=0.4.3,<0.5.0)"] 477 | pipfile-deprecated-finder = ["pipreqs", "requirementslib"] 478 | plugins = ["setuptools"] 479 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 480 | 481 | [[package]] 482 | name = "jedi" 483 | version = "0.17.2" 484 | description = "An autocompletion tool for Python that can be used for text editors." 485 | optional = false 486 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 487 | groups = ["dev"] 488 | files = [ 489 | {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, 490 | {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, 491 | ] 492 | 493 | [package.dependencies] 494 | parso = ">=0.7.0,<0.8.0" 495 | 496 | [package.extras] 497 | qa = ["flake8 (==3.7.9)"] 498 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] 499 | 500 | [[package]] 501 | name = "jinja2" 502 | version = "3.0.3" 503 | description = "A very fast and expressive template engine." 504 | optional = false 505 | python-versions = ">=3.6" 506 | groups = ["dev"] 507 | files = [ 508 | {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, 509 | {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, 510 | ] 511 | 512 | [package.dependencies] 513 | MarkupSafe = ">=2.0" 514 | 515 | [package.extras] 516 | i18n = ["Babel (>=2.7)"] 517 | 518 | [[package]] 519 | name = "lazy-object-proxy" 520 | version = "1.4.3" 521 | description = "A fast and thorough lazy object proxy." 522 | optional = false 523 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 524 | groups = ["dev"] 525 | files = [ 526 | {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, 527 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, 528 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, 529 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, 530 | {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, 531 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, 532 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, 533 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, 534 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, 535 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, 536 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, 537 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, 538 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, 539 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, 540 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, 541 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, 542 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, 543 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, 544 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, 545 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, 546 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, 547 | ] 548 | 549 | [[package]] 550 | name = "macfsevents" 551 | version = "0.8.1" 552 | description = "Thread-based interface to file system observation primitives." 553 | optional = false 554 | python-versions = "*" 555 | groups = ["dev"] 556 | markers = "sys_platform == \"darwin\"" 557 | files = [ 558 | {file = "MacFSEvents-0.8.1.tar.gz", hash = "sha256:1324b66b356051de662ba87d84f73ada062acd42b047ed1246e60a449f833e10"}, 559 | ] 560 | 561 | [[package]] 562 | name = "macholib" 563 | version = "1.14" 564 | description = "Mach-O header analysis and editing" 565 | optional = false 566 | python-versions = "*" 567 | groups = ["dev"] 568 | markers = "sys_platform == \"darwin\"" 569 | files = [ 570 | {file = "macholib-1.14-py2.py3-none-any.whl", hash = "sha256:c500f02867515e6c60a27875b408920d18332ddf96b4035ef03beddd782d4281"}, 571 | {file = "macholib-1.14.tar.gz", hash = "sha256:0c436bc847e7b1d9bda0560351bf76d7caf930fb585a828d13608839ef42c432"}, 572 | ] 573 | 574 | [package.dependencies] 575 | altgraph = ">=0.15" 576 | 577 | [[package]] 578 | name = "markdown" 579 | version = "3.3.4" 580 | description = "Python implementation of Markdown." 581 | optional = false 582 | python-versions = ">=3.6" 583 | groups = ["dev"] 584 | files = [ 585 | {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, 586 | {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, 587 | ] 588 | 589 | [package.extras] 590 | testing = ["coverage", "pyyaml"] 591 | 592 | [[package]] 593 | name = "markupsafe" 594 | version = "2.0.1" 595 | description = "Safely add untrusted strings to HTML/XML markup." 596 | optional = false 597 | python-versions = ">=3.6" 598 | groups = ["dev"] 599 | files = [ 600 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, 601 | {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, 602 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, 603 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, 604 | {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, 605 | {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, 606 | {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, 607 | {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, 608 | {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, 609 | {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, 610 | {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, 611 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, 612 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, 613 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, 614 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, 615 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, 616 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, 617 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, 618 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, 619 | {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, 620 | {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, 621 | {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, 622 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, 623 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, 624 | {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, 625 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, 626 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, 627 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, 628 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, 629 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, 630 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, 631 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, 632 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, 633 | {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, 634 | {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, 635 | {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, 636 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, 637 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, 638 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, 639 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, 640 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, 641 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, 642 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, 643 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, 644 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, 645 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, 646 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, 647 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, 648 | {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, 649 | {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, 650 | {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, 651 | {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, 652 | {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, 653 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, 654 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, 655 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, 656 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, 657 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, 658 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, 659 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, 660 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, 661 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, 662 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, 663 | {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, 664 | {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, 665 | {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, 666 | {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, 667 | {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, 668 | {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, 669 | ] 670 | 671 | [[package]] 672 | name = "mccabe" 673 | version = "0.6.1" 674 | description = "McCabe checker, plugin for flake8" 675 | optional = false 676 | python-versions = "*" 677 | groups = ["dev"] 678 | files = [ 679 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 680 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 681 | ] 682 | 683 | [[package]] 684 | name = "mergedeep" 685 | version = "1.3.4" 686 | description = "A deep merge function for 🐍." 687 | optional = false 688 | python-versions = ">=3.6" 689 | groups = ["dev"] 690 | files = [ 691 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 692 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 693 | ] 694 | 695 | [[package]] 696 | name = "mkdocs" 697 | version = "1.2.3" 698 | description = "Project documentation with Markdown." 699 | optional = false 700 | python-versions = ">=3.6" 701 | groups = ["dev"] 702 | files = [ 703 | {file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"}, 704 | {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, 705 | ] 706 | 707 | [package.dependencies] 708 | click = ">=3.3" 709 | ghp-import = ">=1.0" 710 | importlib-metadata = ">=3.10" 711 | Jinja2 = ">=2.10.1" 712 | Markdown = ">=3.2.1" 713 | mergedeep = ">=1.3.4" 714 | packaging = ">=20.5" 715 | PyYAML = ">=3.10" 716 | pyyaml-env-tag = ">=0.1" 717 | watchdog = ">=2.0" 718 | 719 | [package.extras] 720 | i18n = ["babel (>=2.9.0)"] 721 | 722 | [[package]] 723 | name = "mypy" 724 | version = "1.4.1" 725 | description = "Optional static typing for Python" 726 | optional = false 727 | python-versions = ">=3.7" 728 | groups = ["dev"] 729 | files = [ 730 | {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, 731 | {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, 732 | {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, 733 | {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, 734 | {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, 735 | {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, 736 | {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, 737 | {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, 738 | {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, 739 | {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, 740 | {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, 741 | {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, 742 | {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, 743 | {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, 744 | {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, 745 | {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, 746 | {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, 747 | {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, 748 | {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, 749 | {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, 750 | {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, 751 | {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, 752 | {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, 753 | {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, 754 | {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, 755 | {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, 756 | ] 757 | 758 | [package.dependencies] 759 | mypy-extensions = ">=1.0.0" 760 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 761 | typing-extensions = ">=4.1.0" 762 | 763 | [package.extras] 764 | dmypy = ["psutil (>=4.0)"] 765 | install-types = ["pip"] 766 | python2 = ["typed-ast (>=1.4.0,<2)"] 767 | reports = ["lxml"] 768 | 769 | [[package]] 770 | name = "mypy-extensions" 771 | version = "1.0.0" 772 | description = "Type system extensions for programs checked with the mypy type checker." 773 | optional = false 774 | python-versions = ">=3.5" 775 | groups = ["dev"] 776 | files = [ 777 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 778 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 779 | ] 780 | 781 | [[package]] 782 | name = "nose" 783 | version = "1.3.7" 784 | description = "nose extends unittest to make testing easier" 785 | optional = false 786 | python-versions = "*" 787 | groups = ["dev"] 788 | files = [ 789 | {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"}, 790 | {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"}, 791 | {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"}, 792 | ] 793 | 794 | [[package]] 795 | name = "packaging" 796 | version = "24.0" 797 | description = "Core utilities for Python packages" 798 | optional = false 799 | python-versions = ">=3.7" 800 | groups = ["dev"] 801 | files = [ 802 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, 803 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, 804 | ] 805 | 806 | [[package]] 807 | name = "parso" 808 | version = "0.7.1" 809 | description = "A Python Parser" 810 | optional = false 811 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 812 | groups = ["dev"] 813 | files = [ 814 | {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, 815 | {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, 816 | ] 817 | 818 | [package.extras] 819 | testing = ["docopt", "pytest (>=3.0.7)"] 820 | 821 | [[package]] 822 | name = "pathspec" 823 | version = "0.9.0" 824 | description = "Utility library for gitignore style pattern matching of file paths." 825 | optional = false 826 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 827 | groups = ["dev"] 828 | files = [ 829 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 830 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 831 | ] 832 | 833 | [[package]] 834 | name = "pefile" 835 | version = "2021.5.24" 836 | description = "Python PE parsing module" 837 | optional = false 838 | python-versions = ">=3.6.0" 839 | groups = ["dev"] 840 | markers = "sys_platform == \"win32\"" 841 | files = [ 842 | {file = "pefile-2021.5.24.tar.gz", hash = "sha256:ed79b2353daa58421459abf4d685953bde0adf9f6e188944f97ba9795f100246"}, 843 | ] 844 | 845 | [package.dependencies] 846 | future = "*" 847 | 848 | [[package]] 849 | name = "pexpect" 850 | version = "4.8.0" 851 | description = "Pexpect allows easy control of interactive console applications." 852 | optional = false 853 | python-versions = "*" 854 | groups = ["dev"] 855 | markers = "sys_platform != \"win32\"" 856 | files = [ 857 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 858 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 859 | ] 860 | 861 | [package.dependencies] 862 | ptyprocess = ">=0.5" 863 | 864 | [[package]] 865 | name = "pickleshare" 866 | version = "0.7.5" 867 | description = "Tiny 'shelve'-like database with concurrency support" 868 | optional = false 869 | python-versions = "*" 870 | groups = ["dev"] 871 | files = [ 872 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 873 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 874 | ] 875 | 876 | [[package]] 877 | name = "platformdirs" 878 | version = "2.4.1" 879 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 880 | optional = false 881 | python-versions = ">=3.7" 882 | groups = ["dev"] 883 | files = [ 884 | {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, 885 | {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, 886 | ] 887 | 888 | [package.extras] 889 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 890 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 891 | 892 | [[package]] 893 | name = "pluggy" 894 | version = "0.13.1" 895 | description = "plugin and hook calling mechanisms for python" 896 | optional = false 897 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 898 | groups = ["dev"] 899 | files = [ 900 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 901 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 902 | ] 903 | 904 | [package.extras] 905 | dev = ["pre-commit", "tox"] 906 | 907 | [[package]] 908 | name = "prompt-toolkit" 909 | version = "3.0.3" 910 | description = "Library for building powerful interactive command lines in Python" 911 | optional = false 912 | python-versions = ">=3.6" 913 | groups = ["dev"] 914 | files = [ 915 | {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, 916 | {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, 917 | ] 918 | 919 | [package.dependencies] 920 | wcwidth = "*" 921 | 922 | [[package]] 923 | name = "ptyprocess" 924 | version = "0.7.0" 925 | description = "Run a subprocess in a pseudo terminal" 926 | optional = false 927 | python-versions = "*" 928 | groups = ["dev"] 929 | markers = "sys_platform != \"win32\"" 930 | files = [ 931 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 932 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 933 | ] 934 | 935 | [[package]] 936 | name = "pydocstyle" 937 | version = "6.1.1" 938 | description = "Python docstring style checker" 939 | optional = false 940 | python-versions = ">=3.6" 941 | groups = ["dev"] 942 | files = [ 943 | {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, 944 | {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, 945 | ] 946 | 947 | [package.dependencies] 948 | snowballstemmer = "*" 949 | 950 | [package.extras] 951 | toml = ["toml"] 952 | 953 | [[package]] 954 | name = "pygments" 955 | version = "2.15.0" 956 | description = "Pygments is a syntax highlighting package written in Python." 957 | optional = false 958 | python-versions = ">=3.7" 959 | groups = ["dev"] 960 | files = [ 961 | {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, 962 | {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, 963 | ] 964 | 965 | [package.extras] 966 | plugins = ["importlib-metadata"] 967 | 968 | [[package]] 969 | name = "pyinstaller" 970 | version = "4.5.1" 971 | description = "PyInstaller bundles a Python application and all its dependencies into a single package." 972 | optional = false 973 | python-versions = ">=3.6" 974 | groups = ["dev"] 975 | files = [ 976 | {file = "pyinstaller-4.5.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:ecc2baadeeefd2b6fbf39d13c65d4aa603afdda1c6aaaebc4577ba72893fee9e"}, 977 | {file = "pyinstaller-4.5.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d848cd782ee0893d7ad9fe2bfe535206a79f0b6760cecc5f2add831258b9322"}, 978 | {file = "pyinstaller-4.5.1-py3-none-manylinux2014_i686.whl", hash = "sha256:8f747b190e6ad30e2d2fd5da9a64636f61aac8c038c0b7f685efa92c782ea14f"}, 979 | {file = "pyinstaller-4.5.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c587da8f521a7ce1b9efb4e3d0117cd63c92dc6cedff24590aeef89372f53012"}, 980 | {file = "pyinstaller-4.5.1-py3-none-win32.whl", hash = "sha256:fed9f5e4802769a416a8f2ca171c6be961d1861cc05a0b71d20dfe05423137e9"}, 981 | {file = "pyinstaller-4.5.1-py3-none-win_amd64.whl", hash = "sha256:aae456205c68355f9597411090576bb31b614e53976b4c102d072bbe5db8392a"}, 982 | {file = "pyinstaller-4.5.1.tar.gz", hash = "sha256:30733baaf8971902286a0ddf77e5499ac5f7bf8e7c39163e83d4f8c696ef265e"}, 983 | ] 984 | 985 | [package.dependencies] 986 | altgraph = "*" 987 | macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} 988 | pefile = {version = ">=2017.8.1", markers = "sys_platform == \"win32\""} 989 | pyinstaller-hooks-contrib = ">=2020.6" 990 | pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} 991 | setuptools = "*" 992 | 993 | [package.extras] 994 | encryption = ["tinyaes (>=1.0.0)"] 995 | hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] 996 | 997 | [[package]] 998 | name = "pyinstaller-hooks-contrib" 999 | version = "2021.1" 1000 | description = "Community maintained hooks for PyInstaller" 1001 | optional = false 1002 | python-versions = "*" 1003 | groups = ["dev"] 1004 | files = [ 1005 | {file = "pyinstaller-hooks-contrib-2021.1.tar.gz", hash = "sha256:892310e6363655838485ee748bf1c5e5cade7963686d9af8650ee218a3e0b031"}, 1006 | {file = "pyinstaller_hooks_contrib-2021.1-py2.py3-none-any.whl", hash = "sha256:27558072021857d89524c42136feaa2ffe4f003f1bdf0278f9b24f6902c1759c"}, 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "pylint" 1011 | version = "2.15.10" 1012 | description = "python code static checker" 1013 | optional = false 1014 | python-versions = ">=3.7.2" 1015 | groups = ["dev"] 1016 | files = [ 1017 | {file = "pylint-2.15.10-py3-none-any.whl", hash = "sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e"}, 1018 | {file = "pylint-2.15.10.tar.gz", hash = "sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5"}, 1019 | ] 1020 | 1021 | [package.dependencies] 1022 | astroid = ">=2.12.13,<=2.14.0-dev0" 1023 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 1024 | dill = [ 1025 | {version = ">=0.2", markers = "python_version < \"3.11\""}, 1026 | {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, 1027 | ] 1028 | isort = ">=4.2.5,<6" 1029 | mccabe = ">=0.6,<0.8" 1030 | platformdirs = ">=2.2.0" 1031 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 1032 | tomlkit = ">=0.10.1" 1033 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 1034 | 1035 | [package.extras] 1036 | spelling = ["pyenchant (>=3.2,<4.0)"] 1037 | testutils = ["gitpython (>3)"] 1038 | 1039 | [[package]] 1040 | name = "pync" 1041 | version = "2.0.3" 1042 | description = "Python Wrapper for Mac OS 10.10 Notification Center" 1043 | optional = false 1044 | python-versions = "*" 1045 | groups = ["dev"] 1046 | markers = "sys_platform == \"darwin\"" 1047 | files = [ 1048 | {file = "pync-2.0.3.tar.gz", hash = "sha256:38b9e61735a3161f9211a5773c5f5ea698f36af4ff7f77fa03e8d1ff0caa117f"}, 1049 | ] 1050 | 1051 | [package.dependencies] 1052 | python-dateutil = ">=2.0" 1053 | 1054 | [[package]] 1055 | name = "pytest" 1056 | version = "7.2.0" 1057 | description = "pytest: simple powerful testing with Python" 1058 | optional = false 1059 | python-versions = ">=3.7" 1060 | groups = ["dev"] 1061 | files = [ 1062 | {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, 1063 | {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, 1064 | ] 1065 | 1066 | [package.dependencies] 1067 | attrs = ">=19.2.0" 1068 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 1069 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 1070 | iniconfig = "*" 1071 | packaging = "*" 1072 | pluggy = ">=0.12,<2.0" 1073 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 1074 | 1075 | [package.extras] 1076 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 1077 | 1078 | [[package]] 1079 | name = "pytest-cov" 1080 | version = "4.1.0" 1081 | description = "Pytest plugin for measuring coverage." 1082 | optional = false 1083 | python-versions = ">=3.7" 1084 | groups = ["dev"] 1085 | files = [ 1086 | {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, 1087 | {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, 1088 | ] 1089 | 1090 | [package.dependencies] 1091 | coverage = {version = ">=5.2.1", extras = ["toml"]} 1092 | pytest = ">=4.6" 1093 | 1094 | [package.extras] 1095 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 1096 | 1097 | [[package]] 1098 | name = "pytest-describe" 1099 | version = "2.0.1" 1100 | description = "Describe-style plugin for pytest" 1101 | optional = false 1102 | python-versions = "*" 1103 | groups = ["dev"] 1104 | files = [ 1105 | {file = "pytest-describe-2.0.1.tar.gz", hash = "sha256:e5cbaa31169f0060348ad5ca0191027e5f1f41f3f27fdeef208365e09c55eb9a"}, 1106 | {file = "pytest_describe-2.0.1-py3-none-any.whl", hash = "sha256:ea347838bdf774b498ee7cb4a0b802a40be89e667a399fb63d860e3223bf4183"}, 1107 | ] 1108 | 1109 | [package.dependencies] 1110 | pytest = ">=4.0.0" 1111 | 1112 | [[package]] 1113 | name = "pytest-expecter" 1114 | version = "3.0" 1115 | description = "Better testing with expecter and pytest." 1116 | optional = false 1117 | python-versions = ">=3.8,<4.0" 1118 | groups = ["dev"] 1119 | files = [ 1120 | {file = "pytest-expecter-3.0.tar.gz", hash = "sha256:be8f3e9f823af6d6713e3f552ed47560061a2fd243a78952180f5df61a2b76a4"}, 1121 | {file = "pytest_expecter-3.0-py3-none-any.whl", hash = "sha256:98fe65ecc1ddb7ca29084dc68ec07983dbbdb20b566fd14140b0b5f4b7c84cc8"}, 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "pytest-ordering" 1126 | version = "0.6" 1127 | description = "pytest plugin to run your tests in a specific order" 1128 | optional = false 1129 | python-versions = "*" 1130 | groups = ["dev"] 1131 | files = [ 1132 | {file = "pytest-ordering-0.6.tar.gz", hash = "sha256:561ad653626bb171da78e682f6d39ac33bb13b3e272d406cd555adb6b006bda6"}, 1133 | {file = "pytest_ordering-0.6-py2-none-any.whl", hash = "sha256:27fba3fc265f5d0f8597e7557885662c1bdc1969497cd58aff6ed21c3b617de2"}, 1134 | {file = "pytest_ordering-0.6-py3-none-any.whl", hash = "sha256:3f314a178dbeb6777509548727dc69edf22d6d9a2867bf2d310ab85c403380b6"}, 1135 | ] 1136 | 1137 | [package.dependencies] 1138 | pytest = "*" 1139 | 1140 | [[package]] 1141 | name = "pytest-profiling" 1142 | version = "1.7.0" 1143 | description = "Profiling plugin for py.test" 1144 | optional = false 1145 | python-versions = "*" 1146 | groups = ["dev"] 1147 | files = [ 1148 | {file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"}, 1149 | {file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"}, 1150 | ] 1151 | 1152 | [package.dependencies] 1153 | gprof2dot = "*" 1154 | pytest = "*" 1155 | six = "*" 1156 | 1157 | [package.extras] 1158 | tests = ["pytest-virtualenv"] 1159 | 1160 | [[package]] 1161 | name = "pytest-repeat" 1162 | version = "0.9.1" 1163 | description = "pytest plugin for repeating tests" 1164 | optional = false 1165 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1166 | groups = ["dev"] 1167 | files = [ 1168 | {file = "pytest-repeat-0.9.1.tar.gz", hash = "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b"}, 1169 | {file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"}, 1170 | ] 1171 | 1172 | [package.dependencies] 1173 | pytest = ">=3.6" 1174 | 1175 | [[package]] 1176 | name = "python-dateutil" 1177 | version = "2.8.1" 1178 | description = "Extensions to the standard Python datetime module" 1179 | optional = false 1180 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1181 | groups = ["dev"] 1182 | files = [ 1183 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1184 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1185 | ] 1186 | 1187 | [package.dependencies] 1188 | six = ">=1.5" 1189 | 1190 | [[package]] 1191 | name = "python-termstyle" 1192 | version = "0.1.10" 1193 | description = "console colouring for python" 1194 | optional = false 1195 | python-versions = "*" 1196 | groups = ["dev"] 1197 | files = [ 1198 | {file = "python-termstyle-0.1.10.tar.gz", hash = "sha256:f42a6bb16fbfc5e2c66d553e7ad46524ea833872f75ee5d827c15115fafc94e2"}, 1199 | {file = "python-termstyle-0.1.10.tgz", hash = "sha256:6faf42ba42f2826c38cf70dacb3ac51f248a418e48afc0e36593df11cf3ab1d2"}, 1200 | ] 1201 | 1202 | [package.dependencies] 1203 | setuptools = "*" 1204 | 1205 | [[package]] 1206 | name = "pywin32-ctypes" 1207 | version = "0.2.0" 1208 | description = "" 1209 | optional = false 1210 | python-versions = "*" 1211 | groups = ["dev"] 1212 | markers = "sys_platform == \"win32\"" 1213 | files = [ 1214 | {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, 1215 | {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "pyyaml" 1220 | version = "6.0.1" 1221 | description = "YAML parser and emitter for Python" 1222 | optional = false 1223 | python-versions = ">=3.6" 1224 | groups = ["dev"] 1225 | files = [ 1226 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 1227 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 1228 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 1229 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 1230 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 1231 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, 1232 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 1233 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 1234 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 1235 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 1236 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 1237 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 1238 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 1239 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, 1240 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 1241 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 1242 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, 1243 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, 1244 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, 1245 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, 1246 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, 1247 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, 1248 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, 1249 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 1250 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 1251 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 1252 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 1253 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 1254 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 1255 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 1256 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 1257 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 1258 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 1259 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 1260 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 1261 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 1262 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 1263 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 1264 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 1265 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, 1266 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 1267 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 1268 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 1269 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 1270 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 1271 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 1272 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 1273 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, 1274 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 1275 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 1276 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "pyyaml-env-tag" 1281 | version = "0.1" 1282 | description = "A custom YAML tag for referencing environment variables in YAML files. " 1283 | optional = false 1284 | python-versions = ">=3.6" 1285 | groups = ["dev"] 1286 | files = [ 1287 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 1288 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 1289 | ] 1290 | 1291 | [package.dependencies] 1292 | pyyaml = "*" 1293 | 1294 | [[package]] 1295 | name = "requests" 1296 | version = "2.31.0" 1297 | description = "Python HTTP for Humans." 1298 | optional = false 1299 | python-versions = ">=3.7" 1300 | groups = ["dev"] 1301 | files = [ 1302 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 1303 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 1304 | ] 1305 | 1306 | [package.dependencies] 1307 | certifi = ">=2017.4.17" 1308 | charset-normalizer = ">=2,<4" 1309 | idna = ">=2.5,<4" 1310 | urllib3 = ">=1.21.1,<3" 1311 | 1312 | [package.extras] 1313 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1314 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1315 | 1316 | [[package]] 1317 | name = "setuptools" 1318 | version = "65.6.3" 1319 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1320 | optional = false 1321 | python-versions = ">=3.7" 1322 | groups = ["dev"] 1323 | files = [ 1324 | {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, 1325 | {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, 1326 | ] 1327 | 1328 | [package.extras] 1329 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 1330 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 1331 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 1332 | 1333 | [[package]] 1334 | name = "six" 1335 | version = "1.16.0" 1336 | description = "Python 2 and 3 compatibility utilities" 1337 | optional = false 1338 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1339 | groups = ["dev"] 1340 | files = [ 1341 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1342 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "sniffer" 1347 | version = "0.4.1" 1348 | description = "An automatic test runner. Supports nose out of the box." 1349 | optional = false 1350 | python-versions = "*" 1351 | groups = ["dev"] 1352 | files = [ 1353 | {file = "sniffer-0.4.1-py2.py3-none-any.whl", hash = "sha256:f120843fe152d0e380402fc11313b151e2044c47fdd36895de2efedc8624dbb8"}, 1354 | {file = "sniffer-0.4.1.tar.gz", hash = "sha256:b37665053fb83d7790bf9e51d616c11970863d14b5ea5a51155a4e95759d1529"}, 1355 | ] 1356 | 1357 | [package.dependencies] 1358 | colorama = "*" 1359 | nose = "*" 1360 | python-termstyle = "*" 1361 | 1362 | [package.extras] 1363 | growl = ["gntp (==0.7)"] 1364 | libnotify = ["py-notify (==0.3.1)"] 1365 | linux = ["pyinotify (==0.9.0)"] 1366 | osx = ["MacFSEvents (==0.2.8)"] 1367 | 1368 | [[package]] 1369 | name = "snowballstemmer" 1370 | version = "2.1.0" 1371 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 1372 | optional = false 1373 | python-versions = "*" 1374 | groups = ["dev"] 1375 | files = [ 1376 | {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, 1377 | {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, 1378 | ] 1379 | 1380 | [[package]] 1381 | name = "tomli" 1382 | version = "2.0.0" 1383 | description = "A lil' TOML parser" 1384 | optional = false 1385 | python-versions = ">=3.7" 1386 | groups = ["dev"] 1387 | markers = "python_version < \"3.11\"" 1388 | files = [ 1389 | {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, 1390 | {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "tomlkit" 1395 | version = "0.11.8" 1396 | description = "Style preserving TOML library" 1397 | optional = false 1398 | python-versions = ">=3.7" 1399 | groups = ["dev"] 1400 | files = [ 1401 | {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, 1402 | {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "traitlets" 1407 | version = "5.9.0" 1408 | description = "Traitlets Python configuration system" 1409 | optional = false 1410 | python-versions = ">=3.7" 1411 | groups = ["dev"] 1412 | files = [ 1413 | {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, 1414 | {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, 1415 | ] 1416 | 1417 | [package.extras] 1418 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 1419 | test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] 1420 | 1421 | [[package]] 1422 | name = "types-freezegun" 1423 | version = "1.1.6" 1424 | description = "Typing stubs for freezegun" 1425 | optional = false 1426 | python-versions = "*" 1427 | groups = ["dev"] 1428 | files = [ 1429 | {file = "types-freezegun-1.1.6.tar.gz", hash = "sha256:5c70a4b7444b8c7dd2800e0063d6fe721ab11209399264fa0f77af253dd8b14f"}, 1430 | {file = "types_freezegun-1.1.6-py3-none-any.whl", hash = "sha256:eaa4ccac7f4ff92762b6e5d34c3c4e41a7763b6d09a8595e0224ff1f24c9d4e1"}, 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "typing-extensions" 1435 | version = "4.7.0" 1436 | description = "Backported and Experimental Type Hints for Python 3.7+" 1437 | optional = false 1438 | python-versions = ">=3.7" 1439 | groups = ["dev"] 1440 | files = [ 1441 | {file = "typing_extensions-4.7.0-py3-none-any.whl", hash = "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771"}, 1442 | {file = "typing_extensions-4.7.0.tar.gz", hash = "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82"}, 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "urllib3" 1447 | version = "1.26.18" 1448 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1449 | optional = false 1450 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 1451 | groups = ["dev"] 1452 | files = [ 1453 | {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, 1454 | {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, 1455 | ] 1456 | 1457 | [package.extras] 1458 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 1459 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 1460 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 1461 | 1462 | [[package]] 1463 | name = "watchdog" 1464 | version = "2.1.6" 1465 | description = "Filesystem events monitoring" 1466 | optional = false 1467 | python-versions = ">=3.6" 1468 | groups = ["dev"] 1469 | files = [ 1470 | {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, 1471 | {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, 1472 | {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, 1473 | {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, 1474 | {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, 1475 | {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, 1476 | {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, 1477 | {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, 1478 | {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, 1479 | {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, 1480 | {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, 1481 | {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, 1482 | {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, 1483 | {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, 1484 | {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, 1485 | {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, 1486 | {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, 1487 | {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, 1488 | {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, 1489 | {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, 1490 | {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, 1491 | {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, 1492 | {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, 1493 | ] 1494 | 1495 | [package.extras] 1496 | watchmedo = ["PyYAML (>=3.10)"] 1497 | 1498 | [[package]] 1499 | name = "wcwidth" 1500 | version = "0.2.5" 1501 | description = "Measures the displayed width of unicode strings in a terminal" 1502 | optional = false 1503 | python-versions = "*" 1504 | groups = ["dev"] 1505 | files = [ 1506 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1507 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "wrapt" 1512 | version = "1.15.0" 1513 | description = "Module for decorators, wrappers and monkey patching." 1514 | optional = false 1515 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 1516 | groups = ["dev"] 1517 | files = [ 1518 | {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, 1519 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, 1520 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, 1521 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, 1522 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, 1523 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, 1524 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, 1525 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, 1526 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, 1527 | {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, 1528 | {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, 1529 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, 1530 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, 1531 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, 1532 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, 1533 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, 1534 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, 1535 | {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, 1536 | {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, 1537 | {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, 1538 | {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, 1539 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, 1540 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, 1541 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, 1542 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, 1543 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, 1544 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, 1545 | {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, 1546 | {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, 1547 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, 1548 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, 1549 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, 1550 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, 1551 | {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, 1552 | {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, 1553 | {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, 1554 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, 1555 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, 1556 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, 1557 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, 1558 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, 1559 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, 1560 | {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, 1561 | {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, 1562 | {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, 1563 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, 1564 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, 1565 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, 1566 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, 1567 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, 1568 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, 1569 | {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, 1570 | {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, 1571 | {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, 1572 | {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, 1573 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, 1574 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, 1575 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, 1576 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, 1577 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, 1578 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, 1579 | {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, 1580 | {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, 1581 | {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, 1582 | {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, 1583 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, 1584 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, 1585 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, 1586 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, 1587 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, 1588 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, 1589 | {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, 1590 | {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, 1591 | {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, 1592 | {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "zipp" 1597 | version = "3.4.1" 1598 | description = "Backport of pathlib-compatible object wrapper for zip files" 1599 | optional = false 1600 | python-versions = ">=3.6" 1601 | groups = ["dev"] 1602 | files = [ 1603 | {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, 1604 | {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, 1605 | ] 1606 | 1607 | [package.extras] 1608 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 1609 | testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler", "pytest-flake8", "pytest-mypy"] 1610 | 1611 | [metadata] 1612 | lock-version = "2.1" 1613 | python-versions = "^3.8" 1614 | content-hash = "a7b77b16bed0c1472c8ce15935562da9b62cc211ed688a5c9dbfc734e1e736f5" 1615 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "minilog" 4 | version = "2.3.1" 5 | description = "Minimalistic wrapper for Python logging." 6 | 7 | license = "MIT" 8 | 9 | authors = ["Jace Browning "] 10 | 11 | readme = "README.md" 12 | packages = [{ include = "log" }] 13 | 14 | homepage = "https://pypi.org/project/minilog" 15 | documentation = "https://minilog.readthedocs.io" 16 | repository = "https://github.com/jacebrowning/minilog" 17 | 18 | keywords = ["logging"] 19 | classifiers = [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Intended Audience :: Developers", 22 | "License :: OSI Approved :: MIT License", 23 | "Natural Language :: English", 24 | "Operating System :: OS Independent", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | "Programming Language :: Python", 30 | "Topic :: Software Development", 31 | "Topic :: System :: Logging", 32 | ] 33 | 34 | [tool.poetry.dependencies] 35 | 36 | python = "^3.8" 37 | 38 | [tool.poetry.group.dev.dependencies] 39 | 40 | # Formatters 41 | black = "^24.3" 42 | isort = "^5.10" 43 | 44 | # Linters 45 | pylint = "~2.15" 46 | pydocstyle = "*" 47 | mypy = "^1.3" 48 | types-freezegun = "*" 49 | 50 | # Testing 51 | pytest = "^7.2" 52 | pytest-describe = "^2.0" 53 | pytest-expecter = "^3.0" 54 | pytest-ordering = "*" 55 | pytest-cov = "^4.1" 56 | pytest-repeat = "*" 57 | pytest-profiling = "*" 58 | freezegun = "*" 59 | 60 | # Reports 61 | coveragespace = "^6.0.1" 62 | 63 | # Documentation 64 | mkdocs = "~1.2" 65 | pygments = "^2.15.0" 66 | jinja2 = "~3.0" 67 | 68 | # Tooling 69 | pyinstaller = "*" 70 | sniffer = "*" 71 | MacFSEvents = { version = "*", platform = "darwin" } 72 | pync = { version = "*", platform = "darwin" } 73 | ipython = "^7.16.3" 74 | 75 | [tool.poetry.requires-plugins] 76 | 77 | poetry-plugin-export = ">=1.8" 78 | 79 | [tool.black] 80 | 81 | quiet = true 82 | 83 | [tool.isort] 84 | 85 | profile = "black" 86 | 87 | [tool.mypy] 88 | 89 | ignore_missing_imports = true 90 | no_implicit_optional = true 91 | check_untyped_defs = true 92 | 93 | cache_dir = ".cache/mypy/" 94 | 95 | [tool.pytest.ini_options] 96 | 97 | addopts = """ 98 | --strict-markers 99 | 100 | -r sxX 101 | --show-capture=log 102 | 103 | --cov-report=html 104 | --cov-report=term-missing:skip-covered 105 | --no-cov-on-fail 106 | """ 107 | 108 | cache_dir = ".cache/pytest/" 109 | 110 | markers = ["first", "last"] 111 | 112 | [build-system] 113 | 114 | requires = ["poetry-core>=1.0.0"] 115 | build-backend = "poetry.core.masonry.api" 116 | -------------------------------------------------------------------------------- /scent.py: -------------------------------------------------------------------------------- 1 | """Configuration file for sniffer.""" 2 | 3 | import time 4 | import subprocess 5 | 6 | from sniffer.api import select_runnable, file_validator, runnable 7 | try: 8 | from pync import Notifier 9 | except ImportError: 10 | notify = None 11 | else: 12 | notify = Notifier.notify 13 | 14 | 15 | watch_paths = ["log", "tests"] 16 | 17 | 18 | class Options: 19 | group = int(time.time()) # unique per run 20 | show_coverage = False 21 | rerun_args = None 22 | 23 | targets = [ 24 | (("make", "test-unit", "DISABLE_COVERAGE=true"), "Unit Tests", True), 25 | (("make", "test-all"), "Integration Tests", False), 26 | (("make", "check"), "Static Analysis", True), 27 | (("make", "docs", "CI=true"), None, True), 28 | ] 29 | 30 | 31 | @select_runnable("run_targets") 32 | @file_validator 33 | def python_files(filename): 34 | return filename.endswith(".py") and ".py." not in filename 35 | 36 | 37 | @select_runnable("run_targets") 38 | @file_validator 39 | def html_files(filename): 40 | return filename.split(".")[-1] in ["html", "css", "js"] 41 | 42 | 43 | @runnable 44 | def run_targets(*args): 45 | """Run targets for Python.""" 46 | Options.show_coverage = "coverage" in args 47 | 48 | count = 0 49 | for count, (command, title, retry) in enumerate(Options.targets, start=1): 50 | 51 | success = call(command, title, retry) 52 | if not success: 53 | message = "✅ " * (count - 1) + "❌" 54 | show_notification(message, title) 55 | 56 | return False 57 | 58 | message = "✅ " * count 59 | title = "All Targets" 60 | show_notification(message, title) 61 | show_coverage() 62 | 63 | return True 64 | 65 | 66 | def call(command, title, retry): 67 | """Run a command-line program and display the result.""" 68 | if Options.rerun_args: 69 | command, title, retry = Options.rerun_args 70 | Options.rerun_args = None 71 | success = call(command, title, retry) 72 | if not success: 73 | return False 74 | 75 | print("") 76 | print("$ %s" % " ".join(command)) 77 | failure = subprocess.call(command) 78 | 79 | if failure and retry: 80 | Options.rerun_args = command, title, retry 81 | 82 | return not failure 83 | 84 | 85 | def show_notification(message, title): 86 | """Show a user notification.""" 87 | if notify and title: 88 | notify(message, title=title, group=Options.group) 89 | 90 | 91 | def show_coverage(): 92 | """Launch the coverage report.""" 93 | if Options.show_coverage: 94 | subprocess.call(["make", "read-coverage"]) 95 | 96 | Options.show_coverage = False 97 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Integration tests for the package.""" 2 | 3 | try: 4 | from IPython.terminal.debugger import TerminalPdb as Debugger 5 | except ImportError: 6 | from pdb import Pdb as Debugger 7 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Integration tests configuration file.""" 2 | 3 | # pylint: disable=unused-import 4 | from log.tests.conftest import pytest_configure 5 | -------------------------------------------------------------------------------- /tests/demo.py: -------------------------------------------------------------------------------- 1 | import log 2 | 3 | 4 | def greet(name): 5 | log.error("Hello, %s!", name) 6 | -------------------------------------------------------------------------------- /tests/other.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger("3rd-party") 4 | 5 | 6 | def do_3rd_party_thing(): 7 | log.debug(1) 8 | log.info(2) 9 | log.warning(3) 10 | log.error(4) 11 | -------------------------------------------------------------------------------- /tests/test_records.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,singleton-comparison 2 | 3 | import os 4 | 5 | import pytest 6 | from freezegun import freeze_time 7 | 8 | import log 9 | 10 | from . import demo, other 11 | 12 | 13 | def describe_text(): 14 | @pytest.mark.first 15 | def it_includes_the_caller_location(expect, caplog): 16 | demo.greet("caller") 17 | 18 | expect(caplog.text) == "ERROR tests.demo:demo.py:5 Hello, caller!\n" 19 | 20 | @freeze_time("2019-01-15") 21 | def it_can_be_formatted_with_init(expect, caplog): 22 | log.init( 23 | level=log.WARNING, 24 | format="%(asctime)s %(relpath)s:%(lineno)s: %(message)s", 25 | datefmt="%Y-%m", 26 | ) 27 | 28 | demo.greet("format") 29 | 30 | if os.name == "nt": 31 | expect(caplog.text) == "2019-01 tests\\demo.py:5: Hello, format!\n" 32 | else: 33 | expect(caplog.text) == "2019-01 tests/demo.py:5: Hello, format!\n" 34 | 35 | def it_can_include_exceptions(expect, caplog): 36 | try: 37 | print(1 / 0) 38 | except ZeroDivisionError: 39 | log.exception("exception") 40 | 41 | expect(caplog.text).contains("Traceback ") 42 | expect(caplog.text).contains('test_records.py", line 37, ') 43 | expect(caplog.text).contains("ZeroDivisionError") 44 | 45 | 46 | def describe_silence(): 47 | def when_off(expect, caplog): 48 | log.silence("3rd-party") 49 | 50 | other.do_3rd_party_thing() 51 | 52 | expect(caplog.records) == [] 53 | 54 | def with_errors(expect, caplog): 55 | log.silence("3rd-party", allow_error=True) 56 | 57 | other.do_3rd_party_thing() 58 | 59 | expect(len(caplog.records)) == 1 60 | 61 | def with_warnings(expect, caplog): 62 | log.silence("3rd-party", allow_warning=True) 63 | 64 | other.do_3rd_party_thing() 65 | 66 | expect(len(caplog.records)) == 2 67 | 68 | def with_infos(expect, caplog): 69 | log.silence("3rd-party", allow_info=True) 70 | 71 | other.do_3rd_party_thing() 72 | 73 | expect(len(caplog.records)) == 3 74 | --------------------------------------------------------------------------------