update devchat 11.15

This commit is contained in:
bobo.yang 2023-11-15 15:46:46 +08:00
parent ce89bf904f
commit e6983c11d7
403 changed files with 10084 additions and 3426 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
../../../bin/pygmentize,sha256=wsADOQiAcHvmlzoSNLltpIj4Ojmnqr6civ8b6z776Fo,246 ../../../bin/pygmentize,sha256=OIIEh-Vdkj0JRZedbDaff_MLhLrP2UiVlOFKMU24hr8,246
Pygments-2.16.1.dist-info/AUTHORS,sha256=dK80VJCrS-ONy4SVzPGhUP1zA_Jb1xbVKkNJbq8jEOw,10205 Pygments-2.16.1.dist-info/AUTHORS,sha256=dK80VJCrS-ONy4SVzPGhUP1zA_Jb1xbVKkNJbq8jEOw,10205
Pygments-2.16.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 Pygments-2.16.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Pygments-2.16.1.dist-info/LICENSE,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331 Pygments-2.16.1.dist-info/LICENSE,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
../../../bin/normalizer,sha256=YB3DZvwaeDaco97__ogRgzUD6BWcQfR4FIDywnfeBc0,264 ../../../bin/normalizer,sha256=kihdyWtCGlWGyMKjHiPs_eLP1DXyuK3r8cvuk2gln6Y,264
charset_normalizer-3.3.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 charset_normalizer-3.3.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
charset_normalizer-3.3.2.dist-info/LICENSE,sha256=6zGgxaT7Cbik4yBV0lweX5w1iidS_vPNcgIT0cz-4kE,1070 charset_normalizer-3.3.2.dist-info/LICENSE,sha256=6zGgxaT7Cbik4yBV0lweX5w1iidS_vPNcgIT0cz-4kE,1070
charset_normalizer-3.3.2.dist-info/METADATA,sha256=cfLhl5A6SI-F0oclm8w8ux9wshL1nipdeCdVnYb4AaA,33550 charset_normalizer-3.3.2.dist-info/METADATA,sha256=cfLhl5A6SI-F0oclm8w8ux9wshL1nipdeCdVnYb4AaA,33550

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,6 @@ click-8.1.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvF
click-8.1.7.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 click-8.1.7.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
click-8.1.7.dist-info/METADATA,sha256=qIMevCxGA9yEmJOM_4WHuUJCwWpsIEVbCPOhs45YPN4,3014 click-8.1.7.dist-info/METADATA,sha256=qIMevCxGA9yEmJOM_4WHuUJCwWpsIEVbCPOhs45YPN4,3014
click-8.1.7.dist-info/RECORD,, click-8.1.7.dist-info/RECORD,,
click-8.1.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
click-8.1.7.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92 click-8.1.7.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
click-8.1.7.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 click-8.1.7.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
click/__init__.py,sha256=YDDbjm406dTOA0V8bTtdGnhN7zj5j-_dFRewZF_pLvw,3138 click/__init__.py,sha256=YDDbjm406dTOA0V8bTtdGnhN7zj5j-_dFRewZF_pLvw,3138

View File

@ -0,0 +1,441 @@
Metadata-Version: 2.1
Name: colorama
Version: 0.4.6
Summary: Cross-platform colored terminal text.
Project-URL: Homepage, https://github.com/tartley/colorama
Author-email: Jonathan Hartley <tartley@tartley.com>
License-File: LICENSE.txt
Keywords: ansi,color,colour,crossplatform,terminal,text,windows,xplatform
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Terminals
Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
Description-Content-Type: text/x-rst
.. image:: https://img.shields.io/pypi/v/colorama.svg
:target: https://pypi.org/project/colorama/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/pyversions/colorama.svg
:target: https://pypi.org/project/colorama/
:alt: Supported Python versions
.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg
:target: https://github.com/tartley/colorama/actions/workflows/test.yml
:alt: Build Status
Colorama
========
Makes ANSI escape character sequences (for producing colored terminal text and
cursor positioning) work under MS Windows.
.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif
:target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama&currency_code=USD
:alt: Donate with Paypal
`PyPI for releases <https://pypi.org/project/colorama/>`_ |
`Github for source <https://github.com/tartley/colorama>`_ |
`Colorama for enterprise on Tidelift <https://github.com/tartley/colorama/blob/master/ENTERPRISE.md>`_
If you find Colorama useful, please |donate| to the authors. Thank you!
Installation
------------
Tested on CPython 2.7, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.8.
No requirements other than the standard library.
.. code-block:: bash
pip install colorama
# or
conda install -c anaconda colorama
Description
-----------
ANSI escape character sequences have long been used to produce colored terminal
text and cursor positioning on Unix and Macs. Colorama makes this work on
Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which
would appear as gobbledygook in the output), and converting them into the
appropriate win32 calls to modify the state of the terminal. On other platforms,
Colorama does nothing.
This has the upshot of providing a simple cross-platform API for printing
colored terminal text from Python, and has the happy side-effect that existing
applications or libraries which use ANSI sequences to produce colored output on
Linux or Macs can now also work on Windows, simply by calling
``colorama.just_fix_windows_console()`` (since v0.4.6) or ``colorama.init()``
(all versions, but may have other side-effects see below).
An alternative approach is to install ``ansi.sys`` on Windows machines, which
provides the same behaviour for all applications running in terminals. Colorama
is intended for situations where that isn't easy (e.g., maybe your app doesn't
have an installer.)
Demo scripts in the source code repository print some colored text using
ANSI sequences. Compare their output under Gnome-terminal's built in ANSI
handling, versus on Windows Command-Prompt using Colorama:
.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png
:width: 661
:height: 357
:alt: ANSI sequences on Ubuntu under gnome-terminal.
.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png
:width: 668
:height: 325
:alt: Same ANSI sequences on Windows, using Colorama.
These screenshots show that, on Windows, Colorama does not support ANSI 'dim
text'; it looks the same as 'normal text'.
Usage
-----
Initialisation
..............
If the only thing you want from Colorama is to get ANSI escapes to work on
Windows, then run:
.. code-block:: python
from colorama import just_fix_windows_console
just_fix_windows_console()
If you're on a recent version of Windows 10 or better, and your stdout/stderr
are pointing to a Windows console, then this will flip the magic configuration
switch to enable Windows' built-in ANSI support.
If you're on an older version of Windows, and your stdout/stderr are pointing to
a Windows console, then this will wrap ``sys.stdout`` and/or ``sys.stderr`` in a
magic file object that intercepts ANSI escape sequences and issues the
appropriate Win32 calls to emulate them.
In all other circumstances, it does nothing whatsoever. Basically the idea is
that this makes Windows act like Unix with respect to ANSI escape handling.
It's safe to call this function multiple times. It's safe to call this function
on non-Windows platforms, but it won't do anything. It's safe to call this
function when one or both of your stdout/stderr are redirected to a file it
won't do anything to those streams.
Alternatively, you can use the older interface with more features (but also more
potential footguns):
.. code-block:: python
from colorama import init
init()
This does the same thing as ``just_fix_windows_console``, except for the
following differences:
- It's not safe to call ``init`` multiple times; you can end up with multiple
layers of wrapping and broken ANSI support.
- Colorama will apply a heuristic to guess whether stdout/stderr support ANSI,
and if it thinks they don't, then it will wrap ``sys.stdout`` and
``sys.stderr`` in a magic file object that strips out ANSI escape sequences
before printing them. This happens on all platforms, and can be convenient if
you want to write your code to emit ANSI escape sequences unconditionally, and
let Colorama decide whether they should actually be output. But note that
Colorama's heuristic is not particularly clever.
- ``init`` also accepts explicit keyword args to enable/disable various
functionality see below.
To stop using Colorama before your program exits, simply call ``deinit()``.
This will restore ``stdout`` and ``stderr`` to their original values, so that
Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is
cheaper than calling ``init()`` again (but does the same thing).
Most users should depend on ``colorama >= 0.4.6``, and use
``just_fix_windows_console``. The old ``init`` interface will be supported
indefinitely for backwards compatibility, but we don't plan to fix any issues
with it, also for backwards compatibility.
Colored Output
..............
Cross-platform printing of colored text can then be done using Colorama's
constant shorthand for ANSI escape sequences. These are deliberately
rudimentary, see below.
.. code-block:: python
from colorama import Fore, Back, Style
print(Fore.RED + 'some red text')
print(Back.GREEN + 'and with a green background')
print(Style.DIM + 'and in dim text')
print(Style.RESET_ALL)
print('back to normal now')
...or simply by manually printing ANSI sequences from your own code:
.. code-block:: python
print('\033[31m' + 'some red text')
print('\033[39m') # and reset to default color
...or, Colorama can be used in conjunction with existing ANSI libraries
such as the venerable `Termcolor <https://pypi.org/project/termcolor/>`_
the fabulous `Blessings <https://pypi.org/project/blessings/>`_,
or the incredible `_Rich <https://pypi.org/project/rich/>`_.
If you wish Colorama's Fore, Back and Style constants were more capable,
then consider using one of the above highly capable libraries to generate
colors, etc, and use Colorama just for its primary purpose: to convert
those ANSI sequences to also work on Windows:
SIMILARLY, do not send PRs adding the generation of new ANSI types to Colorama.
We are only interested in converting ANSI codes to win32 API calls, not
shortcuts like the above to generate ANSI characters.
.. code-block:: python
from colorama import just_fix_windows_console
from termcolor import colored
# use Colorama to make Termcolor work on Windows too
just_fix_windows_console()
# then use Termcolor for all colored text output
print(colored('Hello, World!', 'green', 'on_red'))
Available formatting constants are::
Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
Style: DIM, NORMAL, BRIGHT, RESET_ALL
``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will
perform this reset automatically on program exit.
These are fairly well supported, but not part of the standard::
Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX
Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX
Cursor Positioning
..................
ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for
an example of how to generate them.
Init Keyword Args
.................
``init()`` accepts some ``**kwargs`` to override default behaviour.
init(autoreset=False):
If you find yourself repeatedly sending reset sequences to turn off color
changes at the end of every print, then ``init(autoreset=True)`` will
automate that:
.. code-block:: python
from colorama import init
init(autoreset=True)
print(Fore.RED + 'some red text')
print('automatically back to default color again')
init(strip=None):
Pass ``True`` or ``False`` to override whether ANSI codes should be
stripped from the output. The default behaviour is to strip if on Windows
or if output is redirected (not a tty).
init(convert=None):
Pass ``True`` or ``False`` to override whether to convert ANSI codes in the
output into win32 calls. The default behaviour is to convert if on Windows
and output is to a tty (terminal).
init(wrap=True):
On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr``
with proxy objects, which override the ``.write()`` method to do their work.
If this wrapping causes you problems, then this can be disabled by passing
``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or
``strip`` or ``convert`` are True.
When wrapping is disabled, colored printing on non-Windows platforms will
continue to work as normal. To do cross-platform colored output, you can
use Colorama's ``AnsiToWin32`` proxy directly:
.. code-block:: python
import sys
from colorama import init, AnsiToWin32
init(wrap=False)
stream = AnsiToWin32(sys.stderr).stream
# Python 2
print >>stream, Fore.BLUE + 'blue text on stderr'
# Python 3
print(Fore.BLUE + 'blue text on stderr', file=stream)
Recognised ANSI Sequences
.........................
ANSI sequences generally take the form::
ESC [ <param> ; <param> ... <command>
Where ``<param>`` is an integer, and ``<command>`` is a single letter. Zero or
more params are passed to a ``<command>``. If no params are passed, it is
generally synonymous with passing a single zero. No spaces exist in the
sequence; they have been inserted here simply to read more easily.
The only ANSI sequences that Colorama converts into win32 calls are::
ESC [ 0 m # reset all (colors and brightness)
ESC [ 1 m # bright
ESC [ 2 m # dim (looks same as normal brightness)
ESC [ 22 m # normal brightness
# FOREGROUND:
ESC [ 30 m # black
ESC [ 31 m # red
ESC [ 32 m # green
ESC [ 33 m # yellow
ESC [ 34 m # blue
ESC [ 35 m # magenta
ESC [ 36 m # cyan
ESC [ 37 m # white
ESC [ 39 m # reset
# BACKGROUND
ESC [ 40 m # black
ESC [ 41 m # red
ESC [ 42 m # green
ESC [ 43 m # yellow
ESC [ 44 m # blue
ESC [ 45 m # magenta
ESC [ 46 m # cyan
ESC [ 47 m # white
ESC [ 49 m # reset
# cursor positioning
ESC [ y;x H # position cursor at x across, y down
ESC [ y;x f # position cursor at x across, y down
ESC [ n A # move cursor n lines up
ESC [ n B # move cursor n lines down
ESC [ n C # move cursor n characters forward
ESC [ n D # move cursor n characters backward
# clear the screen
ESC [ mode J # clear the screen
# clear the line
ESC [ mode K # clear the line
Multiple numeric params to the ``'m'`` command can be combined into a single
sequence::
ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background
All other ANSI sequences of the form ``ESC [ <param> ; <param> ... <command>``
are silently stripped from the output on Windows.
Any other form of ANSI sequence, such as single-character codes or alternative
initial characters, are not recognised or stripped. It would be cool to add
them though. Let me know if it would be useful for you, via the Issues on
GitHub.
Status & Known Problems
-----------------------
I've personally only tested it on Windows XP (CMD, Console2), Ubuntu
(gnome-terminal, xterm), and OS X.
Some valid ANSI sequences aren't recognised.
If you're hacking on the code, see `README-hacking.md`_. ESPECIALLY, see the
explanation there of why we do not want PRs that allow Colorama to generate new
types of ANSI codes.
See outstanding issues and wish-list:
https://github.com/tartley/colorama/issues
If anything doesn't work for you, or doesn't do what you expected or hoped for,
I'd love to hear about it on that issues list, would be delighted by patches,
and would be happy to grant commit access to anyone who submits a working patch
or two.
.. _README-hacking.md: README-hacking.md
License
-------
Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see
LICENSE file.
Professional support
--------------------
.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png
:alt: Tidelift
:target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme
.. list-table::
:widths: 10 100
* - |tideliftlogo|
- Professional support for colorama is available as part of the
`Tidelift Subscription`_.
Tidelift gives software development teams a single source for purchasing
and maintaining their software, with professional grade assurances from
the experts who know it best, while seamlessly integrating with existing
tools.
.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme
Thanks
------
See the CHANGELOG for more thanks!
* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5.
* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``,
providing a solution to issue #7's setuptools/distutils debate,
and other fixes.
* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``.
* Matthew McCormick for politely pointing out a longstanding crash on non-Win.
* Ben Hoyt, for a magnificent fix under 64-bit Windows.
* Jesse at Empty Square for submitting a fix for examples in the README.
* User 'jamessp', an observant documentation fix for cursor positioning.
* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7
fix.
* Julien Stuyck, for wisely suggesting Python3 compatible updates to README.
* Daniel Griffith for multiple fabulous patches.
* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty
output.
* Roger Binns, for many suggestions, valuable feedback, & bug reports.
* Tim Golden for thought and much appreciated feedback on the initial idea.
* User 'Zearin' for updates to the README file.
* John Szakmeister for adding support for light colors
* Charles Merriam for adding documentation to demos
* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes
* Florian Bruhin for a fix when stdout or stderr are None
* Thomas Weininger for fixing ValueError on Windows
* Remi Rampin for better Github integration and fixes to the README file
* Simeon Visser for closing a file handle using 'with' and updating classifiers
to include Python 3.3 and 3.4
* Andy Neff for fixing RESET of LIGHT_EX colors.
* Jonathan Hartley for the initial idea and implementation.

View File

@ -0,0 +1,31 @@
colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158
colorama-0.4.6.dist-info/RECORD,,
colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105
colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491
colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266
colorama/__pycache__/__init__.cpython-39.pyc,,
colorama/__pycache__/ansi.cpython-39.pyc,,
colorama/__pycache__/ansitowin32.cpython-39.pyc,,
colorama/__pycache__/initialise.cpython-39.pyc,,
colorama/__pycache__/win32.cpython-39.pyc,,
colorama/__pycache__/winterm.cpython-39.pyc,,
colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522
colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128
colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325
colorama/tests/__init__.py,sha256=MkgPAEzGQd-Rq0w0PZXSX2LadRWhUECcisJY8lSrm4Q,75
colorama/tests/__pycache__/__init__.cpython-39.pyc,,
colorama/tests/__pycache__/ansi_test.cpython-39.pyc,,
colorama/tests/__pycache__/ansitowin32_test.cpython-39.pyc,,
colorama/tests/__pycache__/initialise_test.cpython-39.pyc,,
colorama/tests/__pycache__/isatty_test.cpython-39.pyc,,
colorama/tests/__pycache__/utils.cpython-39.pyc,,
colorama/tests/__pycache__/winterm_test.cpython-39.pyc,,
colorama/tests/ansi_test.py,sha256=FeViDrUINIZcr505PAxvU4AjXz1asEiALs9GXMhwRaE,2839
colorama/tests/ansitowin32_test.py,sha256=RN7AIhMJ5EqDsYaCjVo-o4u8JzDD4ukJbmevWKS70rY,10678
colorama/tests/initialise_test.py,sha256=BbPy-XfyHwJ6zKozuQOvNvQZzsx9vdb_0bYXn7hsBTc,6741
colorama/tests/isatty_test.py,sha256=Pg26LRpv0yQDB5Ac-sxgVXG7hsA1NYvapFgApZfYzZg,1866
colorama/tests/utils.py,sha256=1IIRylG39z5-dzq09R_ngufxyPZxgldNbrxKxUGwGKE,1079
colorama/tests/winterm_test.py,sha256=qoWFPEjym5gm2RuMwpf3pOis3a5r_PJZFCzK254JL8A,3709
colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181
colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134

View File

@ -1,5 +1,5 @@
Wheel-Version: 1.0 Wheel-Version: 1.0
Generator: bdist_wheel (0.41.2) Generator: hatchling 1.11.1
Root-Is-Purelib: true Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any Tag: py3-none-any

View File

@ -0,0 +1,27 @@
Copyright (c) 2010 Jonathan Hartley
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holders, nor those of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,7 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console
from .ansi import Fore, Back, Style, Cursor
from .ansitowin32 import AnsiToWin32
__version__ = '0.4.6'

View File

@ -0,0 +1,102 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
'''
This module generates ANSI character codes to printing colors to terminals.
See: http://en.wikipedia.org/wiki/ANSI_escape_code
'''
CSI = '\033['
OSC = '\033]'
BEL = '\a'
def code_to_chars(code):
return CSI + str(code) + 'm'
def set_title(title):
return OSC + '2;' + title + BEL
def clear_screen(mode=2):
return CSI + str(mode) + 'J'
def clear_line(mode=2):
return CSI + str(mode) + 'K'
class AnsiCodes(object):
def __init__(self):
# the subclasses declare class attributes which are numbers.
# Upon instantiation we define instance attributes, which are the same
# as the class attributes but wrapped with the ANSI escape sequence
for name in dir(self):
if not name.startswith('_'):
value = getattr(self, name)
setattr(self, name, code_to_chars(value))
class AnsiCursor(object):
def UP(self, n=1):
return CSI + str(n) + 'A'
def DOWN(self, n=1):
return CSI + str(n) + 'B'
def FORWARD(self, n=1):
return CSI + str(n) + 'C'
def BACK(self, n=1):
return CSI + str(n) + 'D'
def POS(self, x=1, y=1):
return CSI + str(y) + ';' + str(x) + 'H'
class AnsiFore(AnsiCodes):
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
RESET = 39
# These are fairly well supported, but not part of the standard.
LIGHTBLACK_EX = 90
LIGHTRED_EX = 91
LIGHTGREEN_EX = 92
LIGHTYELLOW_EX = 93
LIGHTBLUE_EX = 94
LIGHTMAGENTA_EX = 95
LIGHTCYAN_EX = 96
LIGHTWHITE_EX = 97
class AnsiBack(AnsiCodes):
BLACK = 40
RED = 41
GREEN = 42
YELLOW = 43
BLUE = 44
MAGENTA = 45
CYAN = 46
WHITE = 47
RESET = 49
# These are fairly well supported, but not part of the standard.
LIGHTBLACK_EX = 100
LIGHTRED_EX = 101
LIGHTGREEN_EX = 102
LIGHTYELLOW_EX = 103
LIGHTBLUE_EX = 104
LIGHTMAGENTA_EX = 105
LIGHTCYAN_EX = 106
LIGHTWHITE_EX = 107
class AnsiStyle(AnsiCodes):
BRIGHT = 1
DIM = 2
NORMAL = 22
RESET_ALL = 0
Fore = AnsiFore()
Back = AnsiBack()
Style = AnsiStyle()
Cursor = AnsiCursor()

View File

@ -0,0 +1,277 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import re
import sys
import os
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle
from .win32 import windll, winapi_test
winterm = None
if windll is not None:
winterm = WinTerm()
class StreamWrapper(object):
'''
Wraps a stream (such as stdout), acting as a transparent proxy for all
attribute access apart from method 'write()', which is delegated to our
Converter instance.
'''
def __init__(self, wrapped, converter):
# double-underscore everything to prevent clashes with names of
# attributes on the wrapped stream object.
self.__wrapped = wrapped
self.__convertor = converter
def __getattr__(self, name):
return getattr(self.__wrapped, name)
def __enter__(self, *args, **kwargs):
# special method lookup bypasses __getattr__/__getattribute__, see
# https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
# thus, contextlib magic methods are not proxied via __getattr__
return self.__wrapped.__enter__(*args, **kwargs)
def __exit__(self, *args, **kwargs):
return self.__wrapped.__exit__(*args, **kwargs)
def __setstate__(self, state):
self.__dict__ = state
def __getstate__(self):
return self.__dict__
def write(self, text):
self.__convertor.write(text)
def isatty(self):
stream = self.__wrapped
if 'PYCHARM_HOSTED' in os.environ:
if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
return True
try:
stream_isatty = stream.isatty
except AttributeError:
return False
else:
return stream_isatty()
@property
def closed(self):
stream = self.__wrapped
try:
return stream.closed
# AttributeError in the case that the stream doesn't support being closed
# ValueError for the case that the stream has already been detached when atexit runs
except (AttributeError, ValueError):
return True
class AnsiToWin32(object):
'''
Implements a 'write()' method which, on Windows, will strip ANSI character
sequences from the text, and if outputting to a tty, will convert them into
win32 function calls.
'''
ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
# The wrapped stream (normally sys.stdout or sys.stderr)
self.wrapped = wrapped
# should we reset colors to defaults after every .write()
self.autoreset = autoreset
# create the proxy wrapping our output stream
self.stream = StreamWrapper(wrapped, self)
on_windows = os.name == 'nt'
# We test if the WinAPI works, because even if we are on Windows
# we may be using a terminal that doesn't support the WinAPI
# (e.g. Cygwin Terminal). In this case it's up to the terminal
# to support the ANSI codes.
conversion_supported = on_windows and winapi_test()
try:
fd = wrapped.fileno()
except Exception:
fd = -1
system_has_native_ansi = not on_windows or enable_vt_processing(fd)
have_tty = not self.stream.closed and self.stream.isatty()
need_conversion = conversion_supported and not system_has_native_ansi
# should we strip ANSI sequences from our output?
if strip is None:
strip = need_conversion or not have_tty
self.strip = strip
# should we should convert ANSI sequences into win32 calls?
if convert is None:
convert = need_conversion and have_tty
self.convert = convert
# dict of ansi codes to win32 functions and parameters
self.win32_calls = self.get_win32_calls()
# are we wrapping stderr?
self.on_stderr = self.wrapped is sys.stderr
def should_wrap(self):
'''
True if this class is actually needed. If false, then the output
stream will not be affected, nor will win32 calls be issued, so
wrapping stdout is not actually required. This will generally be
False on non-Windows platforms, unless optional functionality like
autoreset has been requested using kwargs to init()
'''
return self.convert or self.strip or self.autoreset
def get_win32_calls(self):
if self.convert and winterm:
return {
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
AnsiFore.RED: (winterm.fore, WinColor.RED),
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
AnsiFore.RESET: (winterm.fore, ),
AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
AnsiBack.RED: (winterm.back, WinColor.RED),
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
AnsiBack.RESET: (winterm.back, ),
AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
}
return dict()
def write(self, text):
if self.strip or self.convert:
self.write_and_convert(text)
else:
self.wrapped.write(text)
self.wrapped.flush()
if self.autoreset:
self.reset_all()
def reset_all(self):
if self.convert:
self.call_win32('m', (0,))
elif not self.strip and not self.stream.closed:
self.wrapped.write(Style.RESET_ALL)
def write_and_convert(self, text):
'''
Write the given text to our wrapped stream, stripping any ANSI
sequences from the text, and optionally converting them into win32
calls.
'''
cursor = 0
text = self.convert_osc(text)
for match in self.ANSI_CSI_RE.finditer(text):
start, end = match.span()
self.write_plain_text(text, cursor, start)
self.convert_ansi(*match.groups())
cursor = end
self.write_plain_text(text, cursor, len(text))
def write_plain_text(self, text, start, end):
if start < end:
self.wrapped.write(text[start:end])
self.wrapped.flush()
def convert_ansi(self, paramstring, command):
if self.convert:
params = self.extract_params(command, paramstring)
self.call_win32(command, params)
def extract_params(self, command, paramstring):
if command in 'Hf':
params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
while len(params) < 2:
# defaults:
params = params + (1,)
else:
params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
if len(params) == 0:
# defaults:
if command in 'JKm':
params = (0,)
elif command in 'ABCD':
params = (1,)
return params
def call_win32(self, command, params):
if command == 'm':
for param in params:
if param in self.win32_calls:
func_args = self.win32_calls[param]
func = func_args[0]
args = func_args[1:]
kwargs = dict(on_stderr=self.on_stderr)
func(*args, **kwargs)
elif command in 'J':
winterm.erase_screen(params[0], on_stderr=self.on_stderr)
elif command in 'K':
winterm.erase_line(params[0], on_stderr=self.on_stderr)
elif command in 'Hf': # cursor position - absolute
winterm.set_cursor_position(params, on_stderr=self.on_stderr)
elif command in 'ABCD': # cursor position - relative
n = params[0]
# A - up, B - down, C - forward, D - back
x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
def convert_osc(self, text):
for match in self.ANSI_OSC_RE.finditer(text):
start, end = match.span()
text = text[:start] + text[end:]
paramstring, command = match.groups()
if command == BEL:
if paramstring.count(";") == 1:
params = paramstring.split(";")
# 0 - change title and icon (we will only change title)
# 1 - change icon (we don't support this)
# 2 - change title
if params[0] in '02':
winterm.set_title(params[1])
return text
def flush(self):
self.wrapped.flush()

View File

@ -0,0 +1,121 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import atexit
import contextlib
import sys
from .ansitowin32 import AnsiToWin32
def _wipe_internal_state_for_tests():
global orig_stdout, orig_stderr
orig_stdout = None
orig_stderr = None
global wrapped_stdout, wrapped_stderr
wrapped_stdout = None
wrapped_stderr = None
global atexit_done
atexit_done = False
global fixed_windows_console
fixed_windows_console = False
try:
# no-op if it wasn't registered
atexit.unregister(reset_all)
except AttributeError:
# python 2: no atexit.unregister. Oh well, we did our best.
pass
def reset_all():
if AnsiToWin32 is not None: # Issue #74: objects might become None at exit
AnsiToWin32(orig_stdout).reset_all()
def init(autoreset=False, convert=None, strip=None, wrap=True):
if not wrap and any([autoreset, convert, strip]):
raise ValueError('wrap=False conflicts with any other arg=True')
global wrapped_stdout, wrapped_stderr
global orig_stdout, orig_stderr
orig_stdout = sys.stdout
orig_stderr = sys.stderr
if sys.stdout is None:
wrapped_stdout = None
else:
sys.stdout = wrapped_stdout = \
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
if sys.stderr is None:
wrapped_stderr = None
else:
sys.stderr = wrapped_stderr = \
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
global atexit_done
if not atexit_done:
atexit.register(reset_all)
atexit_done = True
def deinit():
if orig_stdout is not None:
sys.stdout = orig_stdout
if orig_stderr is not None:
sys.stderr = orig_stderr
def just_fix_windows_console():
global fixed_windows_console
if sys.platform != "win32":
return
if fixed_windows_console:
return
if wrapped_stdout is not None or wrapped_stderr is not None:
# Someone already ran init() and it did stuff, so we won't second-guess them
return
# On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the
# native ANSI support in the console as a side-effect. We only need to actually
# replace sys.stdout/stderr if we're in the old-style conversion mode.
new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False)
if new_stdout.convert:
sys.stdout = new_stdout
new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False)
if new_stderr.convert:
sys.stderr = new_stderr
fixed_windows_console = True
@contextlib.contextmanager
def colorama_text(*args, **kwargs):
init(*args, **kwargs)
try:
yield
finally:
deinit()
def reinit():
if wrapped_stdout is not None:
sys.stdout = wrapped_stdout
if wrapped_stderr is not None:
sys.stderr = wrapped_stderr
def wrap_stream(stream, convert, strip, autoreset, wrap):
if wrap:
wrapper = AnsiToWin32(stream,
convert=convert, strip=strip, autoreset=autoreset)
if wrapper.should_wrap():
stream = wrapper.stream
return stream
# Use this for initial setup as well, to reduce code duplication
_wipe_internal_state_for_tests()

View File

@ -0,0 +1 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.

View File

@ -0,0 +1,76 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import sys
from unittest import TestCase, main
from ..ansi import Back, Fore, Style
from ..ansitowin32 import AnsiToWin32
stdout_orig = sys.stdout
stderr_orig = sys.stderr
class AnsiTest(TestCase):
def setUp(self):
# sanity check: stdout should be a file or StringIO object.
# It will only be AnsiToWin32 if init() has previously wrapped it
self.assertNotEqual(type(sys.stdout), AnsiToWin32)
self.assertNotEqual(type(sys.stderr), AnsiToWin32)
def tearDown(self):
sys.stdout = stdout_orig
sys.stderr = stderr_orig
def testForeAttributes(self):
self.assertEqual(Fore.BLACK, '\033[30m')
self.assertEqual(Fore.RED, '\033[31m')
self.assertEqual(Fore.GREEN, '\033[32m')
self.assertEqual(Fore.YELLOW, '\033[33m')
self.assertEqual(Fore.BLUE, '\033[34m')
self.assertEqual(Fore.MAGENTA, '\033[35m')
self.assertEqual(Fore.CYAN, '\033[36m')
self.assertEqual(Fore.WHITE, '\033[37m')
self.assertEqual(Fore.RESET, '\033[39m')
# Check the light, extended versions.
self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m')
self.assertEqual(Fore.LIGHTRED_EX, '\033[91m')
self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m')
self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m')
self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m')
self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m')
self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m')
self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m')
def testBackAttributes(self):
self.assertEqual(Back.BLACK, '\033[40m')
self.assertEqual(Back.RED, '\033[41m')
self.assertEqual(Back.GREEN, '\033[42m')
self.assertEqual(Back.YELLOW, '\033[43m')
self.assertEqual(Back.BLUE, '\033[44m')
self.assertEqual(Back.MAGENTA, '\033[45m')
self.assertEqual(Back.CYAN, '\033[46m')
self.assertEqual(Back.WHITE, '\033[47m')
self.assertEqual(Back.RESET, '\033[49m')
# Check the light, extended versions.
self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m')
self.assertEqual(Back.LIGHTRED_EX, '\033[101m')
self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m')
self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m')
self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m')
self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m')
self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m')
self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m')
def testStyleAttributes(self):
self.assertEqual(Style.DIM, '\033[2m')
self.assertEqual(Style.NORMAL, '\033[22m')
self.assertEqual(Style.BRIGHT, '\033[1m')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,294 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
from io import StringIO, TextIOWrapper
from unittest import TestCase, main
try:
from contextlib import ExitStack
except ImportError:
# python 2
from contextlib2 import ExitStack
try:
from unittest.mock import MagicMock, Mock, patch
except ImportError:
from mock import MagicMock, Mock, patch
from ..ansitowin32 import AnsiToWin32, StreamWrapper
from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING
from .utils import osname
class StreamWrapperTest(TestCase):
def testIsAProxy(self):
mockStream = Mock()
wrapper = StreamWrapper(mockStream, None)
self.assertTrue( wrapper.random_attr is mockStream.random_attr )
def testDelegatesWrite(self):
mockStream = Mock()
mockConverter = Mock()
wrapper = StreamWrapper(mockStream, mockConverter)
wrapper.write('hello')
self.assertTrue(mockConverter.write.call_args, (('hello',), {}))
def testDelegatesContext(self):
mockConverter = Mock()
s = StringIO()
with StreamWrapper(s, mockConverter) as fp:
fp.write(u'hello')
self.assertTrue(s.closed)
def testProxyNoContextManager(self):
mockStream = MagicMock()
mockStream.__enter__.side_effect = AttributeError()
mockConverter = Mock()
with self.assertRaises(AttributeError) as excinfo:
with StreamWrapper(mockStream, mockConverter) as wrapper:
wrapper.write('hello')
def test_closed_shouldnt_raise_on_closed_stream(self):
stream = StringIO()
stream.close()
wrapper = StreamWrapper(stream, None)
self.assertEqual(wrapper.closed, True)
def test_closed_shouldnt_raise_on_detached_stream(self):
stream = TextIOWrapper(StringIO())
stream.detach()
wrapper = StreamWrapper(stream, None)
self.assertEqual(wrapper.closed, True)
class AnsiToWin32Test(TestCase):
def testInit(self):
mockStdout = Mock()
auto = Mock()
stream = AnsiToWin32(mockStdout, autoreset=auto)
self.assertEqual(stream.wrapped, mockStdout)
self.assertEqual(stream.autoreset, auto)
@patch('colorama.ansitowin32.winterm', None)
@patch('colorama.ansitowin32.winapi_test', lambda *_: True)
def testStripIsTrueOnWindows(self):
with osname('nt'):
mockStdout = Mock()
stream = AnsiToWin32(mockStdout)
self.assertTrue(stream.strip)
def testStripIsFalseOffWindows(self):
with osname('posix'):
mockStdout = Mock(closed=False)
stream = AnsiToWin32(mockStdout)
self.assertFalse(stream.strip)
def testWriteStripsAnsi(self):
mockStdout = Mock()
stream = AnsiToWin32(mockStdout)
stream.wrapped = Mock()
stream.write_and_convert = Mock()
stream.strip = True
stream.write('abc')
self.assertFalse(stream.wrapped.write.called)
self.assertEqual(stream.write_and_convert.call_args, (('abc',), {}))
def testWriteDoesNotStripAnsi(self):
mockStdout = Mock()
stream = AnsiToWin32(mockStdout)
stream.wrapped = Mock()
stream.write_and_convert = Mock()
stream.strip = False
stream.convert = False
stream.write('abc')
self.assertFalse(stream.write_and_convert.called)
self.assertEqual(stream.wrapped.write.call_args, (('abc',), {}))
def assert_autoresets(self, convert, autoreset=True):
stream = AnsiToWin32(Mock())
stream.convert = convert
stream.reset_all = Mock()
stream.autoreset = autoreset
stream.winterm = Mock()
stream.write('abc')
self.assertEqual(stream.reset_all.called, autoreset)
def testWriteAutoresets(self):
self.assert_autoresets(convert=True)
self.assert_autoresets(convert=False)
self.assert_autoresets(convert=True, autoreset=False)
self.assert_autoresets(convert=False, autoreset=False)
def testWriteAndConvertWritesPlainText(self):
stream = AnsiToWin32(Mock())
stream.write_and_convert( 'abc' )
self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) )
def testWriteAndConvertStripsAllValidAnsi(self):
stream = AnsiToWin32(Mock())
stream.call_win32 = Mock()
data = [
'abc\033[mdef',
'abc\033[0mdef',
'abc\033[2mdef',
'abc\033[02mdef',
'abc\033[002mdef',
'abc\033[40mdef',
'abc\033[040mdef',
'abc\033[0;1mdef',
'abc\033[40;50mdef',
'abc\033[50;30;40mdef',
'abc\033[Adef',
'abc\033[0Gdef',
'abc\033[1;20;128Hdef',
]
for datum in data:
stream.wrapped.write.reset_mock()
stream.write_and_convert( datum )
self.assertEqual(
[args[0] for args in stream.wrapped.write.call_args_list],
[ ('abc',), ('def',) ]
)
def testWriteAndConvertSkipsEmptySnippets(self):
stream = AnsiToWin32(Mock())
stream.call_win32 = Mock()
stream.write_and_convert( '\033[40m\033[41m' )
self.assertFalse( stream.wrapped.write.called )
def testWriteAndConvertCallsWin32WithParamsAndCommand(self):
stream = AnsiToWin32(Mock())
stream.convert = True
stream.call_win32 = Mock()
stream.extract_params = Mock(return_value='params')
data = {
'abc\033[adef': ('a', 'params'),
'abc\033[;;bdef': ('b', 'params'),
'abc\033[0cdef': ('c', 'params'),
'abc\033[;;0;;Gdef': ('G', 'params'),
'abc\033[1;20;128Hdef': ('H', 'params'),
}
for datum, expected in data.items():
stream.call_win32.reset_mock()
stream.write_and_convert( datum )
self.assertEqual( stream.call_win32.call_args[0], expected )
def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self):
stream = StringIO()
converter = AnsiToWin32(stream)
stream.close()
converter.reset_all()
def test_wrap_shouldnt_raise_on_closed_orig_stdout(self):
stream = StringIO()
stream.close()
with \
patch("colorama.ansitowin32.os.name", "nt"), \
patch("colorama.ansitowin32.winapi_test", lambda: True):
converter = AnsiToWin32(stream)
self.assertTrue(converter.strip)
self.assertFalse(converter.convert)
def test_wrap_shouldnt_raise_on_missing_closed_attr(self):
with \
patch("colorama.ansitowin32.os.name", "nt"), \
patch("colorama.ansitowin32.winapi_test", lambda: True):
converter = AnsiToWin32(object())
self.assertTrue(converter.strip)
self.assertFalse(converter.convert)
def testExtractParams(self):
stream = AnsiToWin32(Mock())
data = {
'': (0,),
';;': (0,),
'2': (2,),
';;002;;': (2,),
'0;1': (0, 1),
';;003;;456;;': (3, 456),
'11;22;33;44;55': (11, 22, 33, 44, 55),
}
for datum, expected in data.items():
self.assertEqual(stream.extract_params('m', datum), expected)
def testCallWin32UsesLookup(self):
listener = Mock()
stream = AnsiToWin32(listener)
stream.win32_calls = {
1: (lambda *_, **__: listener(11),),
2: (lambda *_, **__: listener(22),),
3: (lambda *_, **__: listener(33),),
}
stream.call_win32('m', (3, 1, 99, 2))
self.assertEqual(
[a[0][0] for a in listener.call_args_list],
[33, 11, 22] )
def test_osc_codes(self):
mockStdout = Mock()
stream = AnsiToWin32(mockStdout, convert=True)
with patch('colorama.ansitowin32.winterm') as winterm:
data = [
'\033]0\x07', # missing arguments
'\033]0;foo\x08', # wrong OSC command
'\033]0;colorama_test_title\x07', # should work
'\033]1;colorama_test_title\x07', # wrong set command
'\033]2;colorama_test_title\x07', # should work
'\033]' + ';' * 64 + '\x08', # see issue #247
]
for code in data:
stream.write(code)
self.assertEqual(winterm.set_title.call_count, 2)
def test_native_windows_ansi(self):
with ExitStack() as stack:
def p(a, b):
stack.enter_context(patch(a, b, create=True))
# Pretend to be on Windows
p("colorama.ansitowin32.os.name", "nt")
p("colorama.ansitowin32.winapi_test", lambda: True)
p("colorama.win32.winapi_test", lambda: True)
p("colorama.winterm.win32.windll", "non-None")
p("colorama.winterm.get_osfhandle", lambda _: 1234)
# Pretend that our mock stream has native ANSI support
p(
"colorama.winterm.win32.GetConsoleMode",
lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING,
)
SetConsoleMode = Mock()
p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
stdout = Mock()
stdout.closed = False
stdout.isatty.return_value = True
stdout.fileno.return_value = 1
# Our fake console says it has native vt support, so AnsiToWin32 should
# enable that support and do nothing else.
stream = AnsiToWin32(stdout)
SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
self.assertFalse(stream.strip)
self.assertFalse(stream.convert)
self.assertFalse(stream.should_wrap())
# Now let's pretend we're on an old Windows console, that doesn't have
# native ANSI support.
p("colorama.winterm.win32.GetConsoleMode", lambda _: 0)
SetConsoleMode = Mock()
p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
stream = AnsiToWin32(stdout)
SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
self.assertTrue(stream.strip)
self.assertTrue(stream.convert)
self.assertTrue(stream.should_wrap())
if __name__ == '__main__':
main()

View File

@ -0,0 +1,189 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import sys
from unittest import TestCase, main, skipUnless
try:
from unittest.mock import patch, Mock
except ImportError:
from mock import patch, Mock
from ..ansitowin32 import StreamWrapper
from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests
from .utils import osname, replace_by
orig_stdout = sys.stdout
orig_stderr = sys.stderr
class InitTest(TestCase):
@skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty")
def setUp(self):
# sanity check
self.assertNotWrapped()
def tearDown(self):
_wipe_internal_state_for_tests()
sys.stdout = orig_stdout
sys.stderr = orig_stderr
def assertWrapped(self):
self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped')
self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped')
self.assertTrue(isinstance(sys.stdout, StreamWrapper),
'bad stdout wrapper')
self.assertTrue(isinstance(sys.stderr, StreamWrapper),
'bad stderr wrapper')
def assertNotWrapped(self):
self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped')
self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped')
@patch('colorama.initialise.reset_all')
@patch('colorama.ansitowin32.winapi_test', lambda *_: True)
@patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False)
def testInitWrapsOnWindows(self, _):
with osname("nt"):
init()
self.assertWrapped()
@patch('colorama.initialise.reset_all')
@patch('colorama.ansitowin32.winapi_test', lambda *_: False)
def testInitDoesntWrapOnEmulatedWindows(self, _):
with osname("nt"):
init()
self.assertNotWrapped()
def testInitDoesntWrapOnNonWindows(self):
with osname("posix"):
init()
self.assertNotWrapped()
def testInitDoesntWrapIfNone(self):
with replace_by(None):
init()
# We can't use assertNotWrapped here because replace_by(None)
# changes stdout/stderr already.
self.assertIsNone(sys.stdout)
self.assertIsNone(sys.stderr)
def testInitAutoresetOnWrapsOnAllPlatforms(self):
with osname("posix"):
init(autoreset=True)
self.assertWrapped()
def testInitWrapOffDoesntWrapOnWindows(self):
with osname("nt"):
init(wrap=False)
self.assertNotWrapped()
def testInitWrapOffIncompatibleWithAutoresetOn(self):
self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False))
@patch('colorama.win32.SetConsoleTextAttribute')
@patch('colorama.initialise.AnsiToWin32')
def testAutoResetPassedOn(self, mockATW32, _):
with osname("nt"):
init(autoreset=True)
self.assertEqual(len(mockATW32.call_args_list), 2)
self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True)
self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True)
@patch('colorama.initialise.AnsiToWin32')
def testAutoResetChangeable(self, mockATW32):
with osname("nt"):
init()
init(autoreset=True)
self.assertEqual(len(mockATW32.call_args_list), 4)
self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True)
self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True)
init()
self.assertEqual(len(mockATW32.call_args_list), 6)
self.assertEqual(
mockATW32.call_args_list[4][1]['autoreset'], False)
self.assertEqual(
mockATW32.call_args_list[5][1]['autoreset'], False)
@patch('colorama.initialise.atexit.register')
def testAtexitRegisteredOnlyOnce(self, mockRegister):
init()
self.assertTrue(mockRegister.called)
mockRegister.reset_mock()
init()
self.assertFalse(mockRegister.called)
class JustFixWindowsConsoleTest(TestCase):
def _reset(self):
_wipe_internal_state_for_tests()
sys.stdout = orig_stdout
sys.stderr = orig_stderr
def tearDown(self):
self._reset()
@patch("colorama.ansitowin32.winapi_test", lambda: True)
def testJustFixWindowsConsole(self):
if sys.platform != "win32":
# just_fix_windows_console should be a no-op
just_fix_windows_console()
self.assertIs(sys.stdout, orig_stdout)
self.assertIs(sys.stderr, orig_stderr)
else:
def fake_std():
# Emulate stdout=not a tty, stderr=tty
# to check that we handle both cases correctly
stdout = Mock()
stdout.closed = False
stdout.isatty.return_value = False
stdout.fileno.return_value = 1
sys.stdout = stdout
stderr = Mock()
stderr.closed = False
stderr.isatty.return_value = True
stderr.fileno.return_value = 2
sys.stderr = stderr
for native_ansi in [False, True]:
with patch(
'colorama.ansitowin32.enable_vt_processing',
lambda *_: native_ansi
):
self._reset()
fake_std()
# Regular single-call test
prev_stdout = sys.stdout
prev_stderr = sys.stderr
just_fix_windows_console()
self.assertIs(sys.stdout, prev_stdout)
if native_ansi:
self.assertIs(sys.stderr, prev_stderr)
else:
self.assertIsNot(sys.stderr, prev_stderr)
# second call without resetting is always a no-op
prev_stdout = sys.stdout
prev_stderr = sys.stderr
just_fix_windows_console()
self.assertIs(sys.stdout, prev_stdout)
self.assertIs(sys.stderr, prev_stderr)
self._reset()
fake_std()
# If init() runs first, just_fix_windows_console should be a no-op
init()
prev_stdout = sys.stdout
prev_stderr = sys.stderr
just_fix_windows_console()
self.assertIs(prev_stdout, sys.stdout)
self.assertIs(prev_stderr, sys.stderr)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,57 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import sys
from unittest import TestCase, main
from ..ansitowin32 import StreamWrapper, AnsiToWin32
from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY
def is_a_tty(stream):
return StreamWrapper(stream, None).isatty()
class IsattyTest(TestCase):
def test_TTY(self):
tty = StreamTTY()
self.assertTrue(is_a_tty(tty))
with pycharm():
self.assertTrue(is_a_tty(tty))
def test_nonTTY(self):
non_tty = StreamNonTTY()
self.assertFalse(is_a_tty(non_tty))
with pycharm():
self.assertFalse(is_a_tty(non_tty))
def test_withPycharm(self):
with pycharm():
self.assertTrue(is_a_tty(sys.stderr))
self.assertTrue(is_a_tty(sys.stdout))
def test_withPycharmTTYOverride(self):
tty = StreamTTY()
with pycharm(), replace_by(tty):
self.assertTrue(is_a_tty(tty))
def test_withPycharmNonTTYOverride(self):
non_tty = StreamNonTTY()
with pycharm(), replace_by(non_tty):
self.assertFalse(is_a_tty(non_tty))
def test_withPycharmNoneOverride(self):
with pycharm():
with replace_by(None), replace_original_by(None):
self.assertFalse(is_a_tty(None))
self.assertFalse(is_a_tty(StreamNonTTY()))
self.assertTrue(is_a_tty(StreamTTY()))
def test_withPycharmStreamWrapped(self):
with pycharm():
self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty())
self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty())
self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty())
self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty())
if __name__ == '__main__':
main()

View File

@ -0,0 +1,49 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
from contextlib import contextmanager
from io import StringIO
import sys
import os
class StreamTTY(StringIO):
def isatty(self):
return True
class StreamNonTTY(StringIO):
def isatty(self):
return False
@contextmanager
def osname(name):
orig = os.name
os.name = name
yield
os.name = orig
@contextmanager
def replace_by(stream):
orig_stdout = sys.stdout
orig_stderr = sys.stderr
sys.stdout = stream
sys.stderr = stream
yield
sys.stdout = orig_stdout
sys.stderr = orig_stderr
@contextmanager
def replace_original_by(stream):
orig_stdout = sys.__stdout__
orig_stderr = sys.__stderr__
sys.__stdout__ = stream
sys.__stderr__ = stream
yield
sys.__stdout__ = orig_stdout
sys.__stderr__ = orig_stderr
@contextmanager
def pycharm():
os.environ["PYCHARM_HOSTED"] = "1"
non_tty = StreamNonTTY()
with replace_by(non_tty), replace_original_by(non_tty):
yield
del os.environ["PYCHARM_HOSTED"]

View File

@ -0,0 +1,131 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
import sys
from unittest import TestCase, main, skipUnless
try:
from unittest.mock import Mock, patch
except ImportError:
from mock import Mock, patch
from ..winterm import WinColor, WinStyle, WinTerm
class WinTermTest(TestCase):
@patch('colorama.winterm.win32')
def testInit(self, mockWin32):
mockAttr = Mock()
mockAttr.wAttributes = 7 + 6 * 16 + 8
mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
term = WinTerm()
self.assertEqual(term._fore, 7)
self.assertEqual(term._back, 6)
self.assertEqual(term._style, 8)
@skipUnless(sys.platform.startswith("win"), "requires Windows")
def testGetAttrs(self):
term = WinTerm()
term._fore = 0
term._back = 0
term._style = 0
self.assertEqual(term.get_attrs(), 0)
term._fore = WinColor.YELLOW
self.assertEqual(term.get_attrs(), WinColor.YELLOW)
term._back = WinColor.MAGENTA
self.assertEqual(
term.get_attrs(),
WinColor.YELLOW + WinColor.MAGENTA * 16)
term._style = WinStyle.BRIGHT
self.assertEqual(
term.get_attrs(),
WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT)
@patch('colorama.winterm.win32')
def testResetAll(self, mockWin32):
mockAttr = Mock()
mockAttr.wAttributes = 1 + 2 * 16 + 8
mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
term = WinTerm()
term.set_console = Mock()
term._fore = -1
term._back = -1
term._style = -1
term.reset_all()
self.assertEqual(term._fore, 1)
self.assertEqual(term._back, 2)
self.assertEqual(term._style, 8)
self.assertEqual(term.set_console.called, True)
@skipUnless(sys.platform.startswith("win"), "requires Windows")
def testFore(self):
term = WinTerm()
term.set_console = Mock()
term._fore = 0
term.fore(5)
self.assertEqual(term._fore, 5)
self.assertEqual(term.set_console.called, True)
@skipUnless(sys.platform.startswith("win"), "requires Windows")
def testBack(self):
term = WinTerm()
term.set_console = Mock()
term._back = 0
term.back(5)
self.assertEqual(term._back, 5)
self.assertEqual(term.set_console.called, True)
@skipUnless(sys.platform.startswith("win"), "requires Windows")
def testStyle(self):
term = WinTerm()
term.set_console = Mock()
term._style = 0
term.style(22)
self.assertEqual(term._style, 22)
self.assertEqual(term.set_console.called, True)
@patch('colorama.winterm.win32')
def testSetConsole(self, mockWin32):
mockAttr = Mock()
mockAttr.wAttributes = 0
mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
term = WinTerm()
term.windll = Mock()
term.set_console()
self.assertEqual(
mockWin32.SetConsoleTextAttribute.call_args,
((mockWin32.STDOUT, term.get_attrs()), {})
)
@patch('colorama.winterm.win32')
def testSetConsoleOnStderr(self, mockWin32):
mockAttr = Mock()
mockAttr.wAttributes = 0
mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
term = WinTerm()
term.windll = Mock()
term.set_console(on_stderr=True)
self.assertEqual(
mockWin32.SetConsoleTextAttribute.call_args,
((mockWin32.STDERR, term.get_attrs()), {})
)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,180 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
# from winbase.h
STDOUT = -11
STDERR = -12
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
try:
import ctypes
from ctypes import LibraryLoader
windll = LibraryLoader(ctypes.WinDLL)
from ctypes import wintypes
except (AttributeError, ImportError):
windll = None
SetConsoleTextAttribute = lambda *_: None
winapi_test = lambda *_: None
else:
from ctypes import byref, Structure, c_char, POINTER
COORD = wintypes._COORD
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
"""struct in wincon.h."""
_fields_ = [
("dwSize", COORD),
("dwCursorPosition", COORD),
("wAttributes", wintypes.WORD),
("srWindow", wintypes.SMALL_RECT),
("dwMaximumWindowSize", COORD),
]
def __str__(self):
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
self.dwSize.Y, self.dwSize.X
, self.dwCursorPosition.Y, self.dwCursorPosition.X
, self.wAttributes
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
)
_GetStdHandle = windll.kernel32.GetStdHandle
_GetStdHandle.argtypes = [
wintypes.DWORD,
]
_GetStdHandle.restype = wintypes.HANDLE
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
_GetConsoleScreenBufferInfo.argtypes = [
wintypes.HANDLE,
POINTER(CONSOLE_SCREEN_BUFFER_INFO),
]
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
_SetConsoleTextAttribute.argtypes = [
wintypes.HANDLE,
wintypes.WORD,
]
_SetConsoleTextAttribute.restype = wintypes.BOOL
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
_SetConsoleCursorPosition.argtypes = [
wintypes.HANDLE,
COORD,
]
_SetConsoleCursorPosition.restype = wintypes.BOOL
_FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
_FillConsoleOutputCharacterA.argtypes = [
wintypes.HANDLE,
c_char,
wintypes.DWORD,
COORD,
POINTER(wintypes.DWORD),
]
_FillConsoleOutputCharacterA.restype = wintypes.BOOL
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
_FillConsoleOutputAttribute.argtypes = [
wintypes.HANDLE,
wintypes.WORD,
wintypes.DWORD,
COORD,
POINTER(wintypes.DWORD),
]
_FillConsoleOutputAttribute.restype = wintypes.BOOL
_SetConsoleTitleW = windll.kernel32.SetConsoleTitleW
_SetConsoleTitleW.argtypes = [
wintypes.LPCWSTR
]
_SetConsoleTitleW.restype = wintypes.BOOL
_GetConsoleMode = windll.kernel32.GetConsoleMode
_GetConsoleMode.argtypes = [
wintypes.HANDLE,
POINTER(wintypes.DWORD)
]
_GetConsoleMode.restype = wintypes.BOOL
_SetConsoleMode = windll.kernel32.SetConsoleMode
_SetConsoleMode.argtypes = [
wintypes.HANDLE,
wintypes.DWORD
]
_SetConsoleMode.restype = wintypes.BOOL
def _winapi_test(handle):
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
handle, byref(csbi))
return bool(success)
def winapi_test():
return any(_winapi_test(h) for h in
(_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
handle = _GetStdHandle(stream_id)
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
handle, byref(csbi))
return csbi
def SetConsoleTextAttribute(stream_id, attrs):
handle = _GetStdHandle(stream_id)
return _SetConsoleTextAttribute(handle, attrs)
def SetConsoleCursorPosition(stream_id, position, adjust=True):
position = COORD(*position)
# If the position is out of range, do nothing.
if position.Y <= 0 or position.X <= 0:
return
# Adjust for Windows' SetConsoleCursorPosition:
# 1. being 0-based, while ANSI is 1-based.
# 2. expecting (x,y), while ANSI uses (y,x).
adjusted_position = COORD(position.Y - 1, position.X - 1)
if adjust:
# Adjust for viewport's scroll position
sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
adjusted_position.Y += sr.Top
adjusted_position.X += sr.Left
# Resume normal processing
handle = _GetStdHandle(stream_id)
return _SetConsoleCursorPosition(handle, adjusted_position)
def FillConsoleOutputCharacter(stream_id, char, length, start):
handle = _GetStdHandle(stream_id)
char = c_char(char.encode())
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
# Note that this is hard-coded for ANSI (vs wide) bytes.
success = _FillConsoleOutputCharacterA(
handle, char, length, start, byref(num_written))
return num_written.value
def FillConsoleOutputAttribute(stream_id, attr, length, start):
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
handle = _GetStdHandle(stream_id)
attribute = wintypes.WORD(attr)
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
# Note that this is hard-coded for ANSI (vs wide) bytes.
return _FillConsoleOutputAttribute(
handle, attribute, length, start, byref(num_written))
def SetConsoleTitle(title):
return _SetConsoleTitleW(title)
def GetConsoleMode(handle):
mode = wintypes.DWORD()
success = _GetConsoleMode(handle, byref(mode))
if not success:
raise ctypes.WinError()
return mode.value
def SetConsoleMode(handle, mode):
success = _SetConsoleMode(handle, mode)
if not success:
raise ctypes.WinError()

View File

@ -0,0 +1,195 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
try:
from msvcrt import get_osfhandle
except ImportError:
def get_osfhandle(_):
raise OSError("This isn't windows!")
from . import win32
# from wincon.h
class WinColor(object):
BLACK = 0
BLUE = 1
GREEN = 2
CYAN = 3
RED = 4
MAGENTA = 5
YELLOW = 6
GREY = 7
# from wincon.h
class WinStyle(object):
NORMAL = 0x00 # dim text, dim background
BRIGHT = 0x08 # bright text, dim background
BRIGHT_BACKGROUND = 0x80 # dim text, bright background
class WinTerm(object):
def __init__(self):
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
self.set_attrs(self._default)
self._default_fore = self._fore
self._default_back = self._back
self._default_style = self._style
# In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
# So that LIGHT_EX colors and BRIGHT style do not clobber each other,
# we track them separately, since LIGHT_EX is overwritten by Fore/Back
# and BRIGHT is overwritten by Style codes.
self._light = 0
def get_attrs(self):
return self._fore + self._back * 16 + (self._style | self._light)
def set_attrs(self, value):
self._fore = value & 7
self._back = (value >> 4) & 7
self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
def reset_all(self, on_stderr=None):
self.set_attrs(self._default)
self.set_console(attrs=self._default)
self._light = 0
def fore(self, fore=None, light=False, on_stderr=False):
if fore is None:
fore = self._default_fore
self._fore = fore
# Emulate LIGHT_EX with BRIGHT Style
if light:
self._light |= WinStyle.BRIGHT
else:
self._light &= ~WinStyle.BRIGHT
self.set_console(on_stderr=on_stderr)
def back(self, back=None, light=False, on_stderr=False):
if back is None:
back = self._default_back
self._back = back
# Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
if light:
self._light |= WinStyle.BRIGHT_BACKGROUND
else:
self._light &= ~WinStyle.BRIGHT_BACKGROUND
self.set_console(on_stderr=on_stderr)
def style(self, style=None, on_stderr=False):
if style is None:
style = self._default_style
self._style = style
self.set_console(on_stderr=on_stderr)
def set_console(self, attrs=None, on_stderr=False):
if attrs is None:
attrs = self.get_attrs()
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
win32.SetConsoleTextAttribute(handle, attrs)
def get_position(self, handle):
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
# Because Windows coordinates are 0-based,
# and win32.SetConsoleCursorPosition expects 1-based.
position.X += 1
position.Y += 1
return position
def set_cursor_position(self, position=None, on_stderr=False):
if position is None:
# I'm not currently tracking the position, so there is no default.
# position = self.get_position()
return
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
win32.SetConsoleCursorPosition(handle, position)
def cursor_adjust(self, x, y, on_stderr=False):
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
position = self.get_position(handle)
adjusted_position = (position.Y + y, position.X + x)
win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
def erase_screen(self, mode=0, on_stderr=False):
# 0 should clear from the cursor to the end of the screen.
# 1 should clear from the cursor to the beginning of the screen.
# 2 should clear the entire screen, and move cursor to (1,1)
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
csbi = win32.GetConsoleScreenBufferInfo(handle)
# get the number of character cells in the current buffer
cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
# get number of character cells before current cursor position
cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = cells_in_screen - cells_before_cursor
elif mode == 1:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_before_cursor
elif mode == 2:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_in_screen
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
if mode == 2:
# put the cursor where needed
win32.SetConsoleCursorPosition(handle, (1, 1))
def erase_line(self, mode=0, on_stderr=False):
# 0 should clear from the cursor to the end of the line.
# 1 should clear from the cursor to the beginning of the line.
# 2 should clear the entire line.
handle = win32.STDOUT
if on_stderr:
handle = win32.STDERR
csbi = win32.GetConsoleScreenBufferInfo(handle)
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
elif mode == 1:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwCursorPosition.X
elif mode == 2:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwSize.X
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
def set_title(self, title):
win32.SetConsoleTitle(title)
def enable_vt_processing(fd):
if win32.windll is None or not win32.winapi_test():
return False
try:
handle = get_osfhandle(fd)
mode = win32.GetConsoleMode(handle)
win32.SetConsoleMode(
handle,
mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
)
mode = win32.GetConsoleMode(handle)
if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
return True
# Can get TypeError in testsuite where 'fd' is a Mock()
except (OSError, TypeError):
return False

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: devchat Name: devchat
Version: 0.2.9 Version: 0.2.10
Summary: DevChat is an open-source tool that helps developers write prompts to generate code and documentation. Summary: DevChat is an open-source tool that helps developers write prompts to generate code and documentation.
Home-page: https://github.com/devchat-ai/devchat Home-page: https://github.com/devchat-ai/devchat
License: Apache-2.0 License: Apache-2.0
@ -20,12 +20,13 @@ Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development Classifier: Topic :: Software Development
Requires-Dist: colorama (>=0.4.6,<0.5.0)
Requires-Dist: gitpython (>=3.1.32,<4.0.0) Requires-Dist: gitpython (>=3.1.32,<4.0.0)
Requires-Dist: importlib-metadata (>=6.8.0,<7.0.0) Requires-Dist: importlib-metadata (>=6.8.0,<7.0.0)
Requires-Dist: networkx (>=3.1,<4.0) Requires-Dist: networkx (>=3.1,<4.0)
Requires-Dist: openai (>=1.0rc,<2.0) Requires-Dist: openai (>=1.0rc,<2.0)
Requires-Dist: oyaml (>=1.0,<2.0) Requires-Dist: oyaml (>=1.0,<2.0)
Requires-Dist: pydantic (>=1.10.7,<2.0.0) Requires-Dist: pydantic (==1.10.13)
Requires-Dist: rich_click (>=1.6.1,<2.0.0) Requires-Dist: rich_click (>=1.6.1,<2.0.0)
Requires-Dist: tiktoken (>=0.4.0,<0.5.0) Requires-Dist: tiktoken (>=0.4.0,<0.5.0)
Requires-Dist: tinydb (>=4.7.1,<5.0.0) Requires-Dist: tinydb (>=4.7.1,<5.0.0)

View File

@ -1,14 +1,14 @@
../../../bin/devchat,sha256=vIdrM1HVCLtIIPguIbzu4QV0CKMZvbRJqEBeLF9xyXE,247 ../../../bin/devchat,sha256=suM8Tlq7_32mdUbhBivdAeg9W---BD14D3E7bhy9LOc,247
devchat-0.2.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 devchat-0.2.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
devchat-0.2.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357 devchat-0.2.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
devchat-0.2.9.dist-info/METADATA,sha256=WJiWT9Wr22Ze88H5U3Y_QtXhoveDeXuhlYNdB86-iT0,9739 devchat-0.2.10.dist-info/METADATA,sha256=NxGPR5qcRawRtREwinMGazaQIaefdeOOui9A60ynYo0,9775
devchat-0.2.9.dist-info/RECORD,, devchat-0.2.10.dist-info/RECORD,,
devchat-0.2.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 devchat-0.2.10.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
devchat-0.2.9.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88 devchat-0.2.10.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
devchat-0.2.9.dist-info/direct_url.json,sha256=zd3JCXQPIQyScA8lV44s-V6X6GM2woo8TWVYFvT3e9Y,59 devchat-0.2.10.dist-info/direct_url.json,sha256=zd3JCXQPIQyScA8lV44s-V6X6GM2woo8TWVYFvT3e9Y,59
devchat-0.2.9.dist-info/entry_points.txt,sha256=Glu9CHUNBjYbZXTQc3YW2rZFr2S_3AvqKu50gj3aWT4,50 devchat-0.2.10.dist-info/entry_points.txt,sha256=Glu9CHUNBjYbZXTQc3YW2rZFr2S_3AvqKu50gj3aWT4,50
devchat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 devchat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
devchat/__main__.py,sha256=KOQA2JI9HBmG32X3K8Uisa_tmkIn_gT9z1QPSnxzAOs,109 devchat/__main__.py,sha256=RY7_u5N5S0Ye2YtBWeYkk9n8zia_z9oe3DnS1SHRxZA,110
devchat/__pycache__/__init__.cpython-39.pyc,, devchat/__pycache__/__init__.cpython-39.pyc,,
devchat/__pycache__/__main__.cpython-39.pyc,, devchat/__pycache__/__main__.cpython-39.pyc,,
devchat/__pycache__/assistant.cpython-39.pyc,, devchat/__pycache__/assistant.cpython-39.pyc,,
@ -29,14 +29,14 @@ devchat/_cli/__pycache__/utils.cpython-39.pyc,,
devchat/_cli/log.py,sha256=GAYVrNPprDQEB6G2Z1J97jjDU-EbYlJBHnz-Lz6nzvo,3106 devchat/_cli/log.py,sha256=GAYVrNPprDQEB6G2Z1J97jjDU-EbYlJBHnz-Lz6nzvo,3106
devchat/_cli/main.py,sha256=_uJ6KOiV19kATA0CYUtJZ1gGX9IGcX_8pjQyZ9J8wSU,659 devchat/_cli/main.py,sha256=_uJ6KOiV19kATA0CYUtJZ1gGX9IGcX_8pjQyZ9J8wSU,659
devchat/_cli/prompt.py,sha256=uvug9x7zclL0P1xbT_xjFsSCx2PjjLtmGUf0O8Sx1Ek,3923 devchat/_cli/prompt.py,sha256=uvug9x7zclL0P1xbT_xjFsSCx2PjjLtmGUf0O8Sx1Ek,3923
devchat/_cli/run.py,sha256=52eABhjkUuNgd44CaGyoHRfgAdO9tr6MZBSFikqts_g,4129 devchat/_cli/run.py,sha256=nCCqNV7IuTxp7XrroHShR772qKWDXGmx8vM388QjPW8,4849
devchat/_cli/topic.py,sha256=CLE8y2Vox_5igtoSfsnFgaCa7YtJE-rcDtoNhnnedyQ,1455 devchat/_cli/topic.py,sha256=CLE8y2Vox_5igtoSfsnFgaCa7YtJE-rcDtoNhnnedyQ,1455
devchat/_cli/utils.py,sha256=MQUpQ4JYB9nhlgKwBLUNnpsMZfGiJ2xLST2RgZUKWOU,4993 devchat/_cli/utils.py,sha256=u43D4lqihdil1BEenaryzP-NUp5CQo4jTmtq640gTLY,5975
devchat/anthropic/__init__.py,sha256=xaFR1uXxn0sVHBhCJdJKuWKVVgPnSLw3mlaCFFivD_8,97 devchat/anthropic/__init__.py,sha256=xaFR1uXxn0sVHBhCJdJKuWKVVgPnSLw3mlaCFFivD_8,97
devchat/anthropic/__pycache__/__init__.cpython-39.pyc,, devchat/anthropic/__pycache__/__init__.cpython-39.pyc,,
devchat/anthropic/__pycache__/anthropic_chat.cpython-39.pyc,, devchat/anthropic/__pycache__/anthropic_chat.cpython-39.pyc,,
devchat/anthropic/anthropic_chat.py,sha256=OujoXOQywPQf4gjLhdZBYTwKoRDs8hujktss3hN-BNk,423 devchat/anthropic/anthropic_chat.py,sha256=OujoXOQywPQf4gjLhdZBYTwKoRDs8hujktss3hN-BNk,423
devchat/assistant.py,sha256=VOpfvX6Tbq57EYqoOo2V-HJ91D02-s2URc2ZdM7wRlM,6077 devchat/assistant.py,sha256=qOU8u0nrRbruTmH0FS7Ax2H8aOws5uLOnVC8v-WzFoU,6033
devchat/chat.py,sha256=TEO8OndmL4hpJ1D-QAFKO-JB_7w1kTeUC3VVwL9FSUQ,1676 devchat/chat.py,sha256=TEO8OndmL4hpJ1D-QAFKO-JB_7w1kTeUC3VVwL9FSUQ,1676
devchat/config.py,sha256=3lvhi-YRbCOM2Ye28GJF14n10mEYczD3sllhz_ZwZS8,6348 devchat/config.py,sha256=3lvhi-YRbCOM2Ye28GJF14n10mEYczD3sllhz_ZwZS8,6348
devchat/engine/__init__.py,sha256=sXaM_4kQtG-VV7NxMDj7a7v4rbNg7dJHEMF8BOz9NtI,262 devchat/engine/__init__.py,sha256=sXaM_4kQtG-VV7NxMDj7a7v4rbNg7dJHEMF8BOz9NtI,262
@ -53,9 +53,9 @@ devchat/openai/__pycache__/__init__.cpython-39.pyc,,
devchat/openai/__pycache__/openai_chat.cpython-39.pyc,, devchat/openai/__pycache__/openai_chat.cpython-39.pyc,,
devchat/openai/__pycache__/openai_message.cpython-39.pyc,, devchat/openai/__pycache__/openai_message.cpython-39.pyc,,
devchat/openai/__pycache__/openai_prompt.cpython-39.pyc,, devchat/openai/__pycache__/openai_prompt.cpython-39.pyc,,
devchat/openai/openai_chat.py,sha256=ELEa31w-138cyZ08eHqG9FGtwNzsBCy1UfSXhtdnOT0,3684 devchat/openai/openai_chat.py,sha256=aME5qfzvZsnoUKJ344uaUJ27okTk9if46nF3T9DeMK0,3826
devchat/openai/openai_message.py,sha256=xTmglsj5Iyvcytn3pUYhwkuiyJSx932N88fS4OCJ7Qk,3293 devchat/openai/openai_message.py,sha256=xTmglsj5Iyvcytn3pUYhwkuiyJSx932N88fS4OCJ7Qk,3293
devchat/openai/openai_prompt.py,sha256=Jikny5lkjt_u3LaKxlzXgUdR8ttZrUMzIVghr63VlYk,10848 devchat/openai/openai_prompt.py,sha256=M9NIBP5W9DwzFJyMO0L9DYM470383wQOZJwZv4KvQfs,10856
devchat/prompt.py,sha256=WAHa6LmVU1xvBp6AGalQ1TzQuVwt1lsdBd70OazVoW0,9523 devchat/prompt.py,sha256=WAHa6LmVU1xvBp6AGalQ1TzQuVwt1lsdBd70OazVoW0,9523
devchat/store.py,sha256=pdZ3TjzNcAyPcvyD9Roc12SY5znYMh3waM0z9yF9o2w,9845 devchat/store.py,sha256=PI2HvMyZmIV1XyyjIr5rPayagBQWJUWsEdpUCBZ7xLU,9879
devchat/utils.py,sha256=rUQc-iy7d7DL8xBNj_1C1W1cag9jD8xeaqdnDaFnZ9U,7509 devchat/utils.py,sha256=_-FUAC-4ZKoF0q7eg6xWz6hrj0rKJKLHenK4S_uZvkE,7643

Binary file not shown.

View File

@ -4,7 +4,10 @@ import shutil
import sys import sys
from typing import List from typing import List
import rich_click as click import rich_click as click
from git import Repo, GitCommandError try:
from git import Repo, GitCommandError
except Exception:
pass
from devchat._cli.utils import init_dir, handle_errors, valid_git_repo, clone_git_repo from devchat._cli.utils import init_dir, handle_errors, valid_git_repo, clone_git_repo
from devchat._cli.utils import download_and_extract_workflow from devchat._cli.utils import download_and_extract_workflow
from devchat.engine import Namespace, CommandParser, RecursivePrompter from devchat.engine import Namespace, CommandParser, RecursivePrompter
@ -89,8 +92,8 @@ def _clone_or_pull_git_repo(target_dir: str, repo_urls: List[str], zip_urls: Lis
try: try:
download_and_extract_workflow(url, target_dir) download_and_extract_workflow(url, target_dir)
break break
except Exception as e: except Exception as err:
logger.exception("Failed to download and extract workflow: %s", e) logger.exception("Failed to download and extract workflow: %s", err)
return return
if os.path.exists(target_dir): if os.path.exists(target_dir):

View File

@ -3,11 +3,13 @@ import os
import sys import sys
import json import json
import shutil import shutil
import requests
import zipfile
from urllib.parse import urlparse
from typing import Tuple, List, Optional, Any from typing import Tuple, List, Optional, Any
from git import Repo, InvalidGitRepositoryError, GitCommandError import zipfile
import requests
try:
from git import Repo, InvalidGitRepositoryError, GitCommandError
except Exception:
pass
import rich_click as click import rich_click as click
from devchat.config import ConfigManager, OpenAIModelConfig from devchat.config import ConfigManager, OpenAIModelConfig
from devchat.utils import find_root_dir, add_gitignore, setup_logger, get_logger from devchat.utils import find_root_dir, add_gitignore, setup_logger, get_logger
@ -22,20 +24,20 @@ def download_and_extract_workflow(workflow_url, target_dir):
# Downaload file to temp dir # Downaload file to temp dir
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
zip_path = os.path.join(target_dir, 'workflow.zip') zip_path = os.path.join(target_dir, 'workflow.zip')
with open(zip_path, 'wb') as f: with open(zip_path, 'wb') as file_handle:
for chunk in response.iter_content(chunk_size=8192): for chunk in response.iter_content(chunk_size=8192):
if chunk: if chunk:
f.write(chunk) file_handle.write(chunk)
# Extract the zip file # Extract the zip file
parent_dir = os.path.dirname(target_dir) parent_dir = os.path.dirname(target_dir)
with zipfile.ZipFile(zip_path, 'r') as zip_ref: with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(parent_dir) zip_ref.extractall(parent_dir)
# Delete target directory if exists # Delete target directory if exists
if os.path.exists(target_dir): if os.path.exists(target_dir):
shutil.rmtree(target_dir) shutil.rmtree(target_dir)
# Rename extracted directory to target directory # Rename extracted directory to target directory
extracted_dir = os.path.join(parent_dir, 'workflows-main') extracted_dir = os.path.join(parent_dir, 'workflows-main')
os.rename(extracted_dir, target_dir) os.rename(extracted_dir, target_dir)

View File

@ -214,9 +214,9 @@ class OpenAIPrompt(Prompt):
self.responses[index].stream_from_dict(delta) self.responses[index].stream_from_dict(delta)
if 'function_call' in delta: if 'function_call' in delta:
if 'name' in delta['function_call']: if 'name' in delta['function_call'] and \
self.responses[index].function_call.get('name', '') == '':
self.responses[index].function_call['name'] = \ self.responses[index].function_call['name'] = \
self.responses[index].function_call.get('name', '') + \
delta['function_call']['name'] delta['function_call']['name']
if 'arguments' in delta['function_call']: if 'arguments' in delta['function_call']:
self.responses[index].function_call['arguments'] = \ self.responses[index].function_call['arguments'] = \

View File

@ -131,7 +131,11 @@ def get_user_info() -> Tuple[str, str]:
cmd = ['git', 'config', 'user.name'] cmd = ['git', 'config', 'user.name']
user_name = subprocess.check_output(cmd, encoding='utf-8').strip() user_name = subprocess.check_output(cmd, encoding='utf-8').strip()
except Exception: except Exception:
user_name = getpass.getuser() try:
user_name = getpass.getuser()
except Exception:
user_dir = os.path.expanduser("~")
user_name = user_dir.split(os.sep)[-1]
try: try:
cmd = ['git', 'config', 'user.email'] cmd = ['git', 'config', 'user.email']

View File

@ -1,4 +1,4 @@
../../../bin/distro,sha256=dnDD1oUCxitRiZYscod_zaN_Jx9vaObsAqxc7z0ISzY,243 ../../../bin/distro,sha256=MvzdEoL8w1_5cIA9f0DnTWI5Nedce0jHhqtxkt8PE_4,243
distro-1.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 distro-1.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
distro-1.8.0.dist-info/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325 distro-1.8.0.dist-info/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
distro-1.8.0.dist-info/METADATA,sha256=NhYw94UPXb78_Z3_VtLxTJ1zQgUUKoTndg10uKJX800,6915 distro-1.8.0.dist-info/METADATA,sha256=NhYw94UPXb78_Z3_VtLxTJ1zQgUUKoTndg10uKJX800,6915

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: httpcore Name: httpcore
Version: 0.18.0 Version: 1.0.2
Summary: A minimal low-level HTTP client. Summary: A minimal low-level HTTP client.
Project-URL: Documentation, https://www.encode.io/httpcore Project-URL: Documentation, https://www.encode.io/httpcore
Project-URL: Homepage, https://www.encode.io/httpcore/ Project-URL: Homepage, https://www.encode.io/httpcore/
@ -21,16 +21,19 @@ Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.8 Requires-Python: >=3.8
Requires-Dist: anyio<5.0,>=3.0
Requires-Dist: certifi Requires-Dist: certifi
Requires-Dist: h11<0.15,>=0.13 Requires-Dist: h11<0.15,>=0.13
Requires-Dist: sniffio==1.* Provides-Extra: asyncio
Requires-Dist: anyio<5.0,>=4.0; extra == 'asyncio'
Provides-Extra: http2 Provides-Extra: http2
Requires-Dist: h2<5,>=3; extra == 'http2' Requires-Dist: h2<5,>=3; extra == 'http2'
Provides-Extra: socks Provides-Extra: socks
Requires-Dist: socksio==1.*; extra == 'socks' Requires-Dist: socksio==1.*; extra == 'socks'
Provides-Extra: trio
Requires-Dist: trio<0.23.0,>=0.22.0; extra == 'trio'
Description-Content-Type: text/markdown Description-Content-Type: text/markdown
# HTTP Core # HTTP Core
@ -70,16 +73,10 @@ For HTTP/1.1 only support, install with:
$ pip install httpcore $ pip install httpcore
``` ```
For HTTP/1.1 and HTTP/2 support, install with: There are also a number of optional extras available...
```shell ```shell
$ pip install httpcore[http2] $ pip install httpcore['asyncio,trio,http2,socks']
```
For SOCKS proxy support, install with:
```shell
$ pip install httpcore[socks]
``` ```
# Sending requests # Sending requests
@ -124,12 +121,59 @@ The motivation for `httpcore` is:
* To provide a reusable low-level client library, that other packages can then build on top of. * To provide a reusable low-level client library, that other packages can then build on top of.
* To provide a *really clear interface split* between the networking code and client logic, * To provide a *really clear interface split* between the networking code and client logic,
so that each is easier to understand and reason about in isolation. so that each is easier to understand and reason about in isolation.
## Dependencies
The `httpcore` package has the following dependencies...
* `h11`
* `certifi`
And the following optional extras...
* `anyio` - Required by `pip install httpcore['asyncio']`.
* `trio` - Required by `pip install httpcore['trio']`.
* `h2` - Required by `pip install httpcore['http2']`.
* `socksio` - Required by `pip install httpcore['socks']`.
## Versioning
We use [SEMVER for our versioning policy](https://semver.org/).
For changes between package versions please see our [project changelog](CHANGELOG.md).
We recommend pinning your requirements either the most current major version, or a more specific version range:
```python
pip install 'httpcore==1.*'
```
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 1.0.2 (November 10th, 2023)
- Fix `float("inf")` timeouts in `Event.wait` function. (#846)
## 1.0.1 (November 3rd, 2023)
- Fix pool timeout to account for the total time spent retrying. (#823)
- Raise a neater RuntimeError when the correct async deps are not installed. (#826)
- Add support for synchronous TLS-in-TLS streams. (#840)
## 1.0.0 (October 6th, 2023)
From version 1.0 our async support is now optional, as the package has minimal dependencies by default.
For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`.
The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/.
- Async support becomes fully optional. (#809)
- Add support for Python 3.12. (#807)
## 0.18.0 (September 8th, 2023) ## 0.18.0 (September 8th, 2023)
- Add support for HTTPS proxies. (#745, #786) - Add support for HTTPS proxies. (#745, #786)

View File

@ -1,9 +1,9 @@
httpcore-0.18.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 httpcore-1.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
httpcore-0.18.0.dist-info/METADATA,sha256=HYLl3BBm0kYpuqAPEqFCT0ErYv1rHgvxkY4-EuwPlEY,18914 httpcore-1.0.2.dist-info/METADATA,sha256=APYVcc50lK6LWlbwft616GXXpg3gRxKY1Srm-g0xEHM,20442
httpcore-0.18.0.dist-info/RECORD,, httpcore-1.0.2.dist-info/RECORD,,
httpcore-0.18.0.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87 httpcore-1.0.2.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
httpcore-0.18.0.dist-info/licenses/LICENSE.md,sha256=_ctZFUx0y6uhahEkL3dAvqnyPW_rVUeRfYxflKgDkqU,1518 httpcore-1.0.2.dist-info/licenses/LICENSE.md,sha256=_ctZFUx0y6uhahEkL3dAvqnyPW_rVUeRfYxflKgDkqU,1518
httpcore/__init__.py,sha256=VfaTITS2e1pevgXk6fF0cZSH3f4YUt_iXqGYSsbyyY0,3338 httpcore/__init__.py,sha256=_nBsHMRwZbRGG3ci9bZPcyBwL5z3_SM1EEEafTw37EA,3337
httpcore/__pycache__/__init__.cpython-39.pyc,, httpcore/__pycache__/__init__.cpython-39.pyc,,
httpcore/__pycache__/_api.cpython-39.pyc,, httpcore/__pycache__/_api.cpython-39.pyc,,
httpcore/__pycache__/_exceptions.cpython-39.pyc,, httpcore/__pycache__/_exceptions.cpython-39.pyc,,
@ -23,7 +23,7 @@ httpcore/_async/__pycache__/http_proxy.cpython-39.pyc,,
httpcore/_async/__pycache__/interfaces.cpython-39.pyc,, httpcore/_async/__pycache__/interfaces.cpython-39.pyc,,
httpcore/_async/__pycache__/socks_proxy.cpython-39.pyc,, httpcore/_async/__pycache__/socks_proxy.cpython-39.pyc,,
httpcore/_async/connection.py,sha256=klHOqiHVo4TUC_X9IsKEnfMHKQcH-WajYAkszBuLXOA,8619 httpcore/_async/connection.py,sha256=klHOqiHVo4TUC_X9IsKEnfMHKQcH-WajYAkszBuLXOA,8619
httpcore/_async/connection_pool.py,sha256=hj1viqcWZivNmoRu-QZjyuOvAFx3-Ae2rMpuK6OZhEM,14305 httpcore/_async/connection_pool.py,sha256=k9pkv4w_tntmxzDtkHuM54nJmHT3aabxf_7ou5b-9L4,14767
httpcore/_async/http11.py,sha256=t7I91I77Zh06UVopMfqvrGskFdpwnQLo5kt04qTCI7U,12441 httpcore/_async/http11.py,sha256=t7I91I77Zh06UVopMfqvrGskFdpwnQLo5kt04qTCI7U,12441
httpcore/_async/http2.py,sha256=KXwWZxZ-43vxIWzr1aTLErhaCodDzFr-XAvzc4fUb10,23879 httpcore/_async/http2.py,sha256=KXwWZxZ-43vxIWzr1aTLErhaCodDzFr-XAvzc4fUb10,23879
httpcore/_async/http_proxy.py,sha256=hl4t-PahlAuCGtKNYRx4LSgjx1ZuspE9oDBaL6BOess,14851 httpcore/_async/http_proxy.py,sha256=hl4t-PahlAuCGtKNYRx4LSgjx1ZuspE9oDBaL6BOess,14851
@ -38,10 +38,10 @@ httpcore/_backends/__pycache__/mock.cpython-39.pyc,,
httpcore/_backends/__pycache__/sync.cpython-39.pyc,, httpcore/_backends/__pycache__/sync.cpython-39.pyc,,
httpcore/_backends/__pycache__/trio.cpython-39.pyc,, httpcore/_backends/__pycache__/trio.cpython-39.pyc,,
httpcore/_backends/anyio.py,sha256=mU8gtunBSLxESGkU0Iy1ZMgumDlAeMkwBjFE3kZiCnc,5208 httpcore/_backends/anyio.py,sha256=mU8gtunBSLxESGkU0Iy1ZMgumDlAeMkwBjFE3kZiCnc,5208
httpcore/_backends/auto.py,sha256=8r0ipGxSwXoCb_xKQAyRwL1UzfXVbO4Ee2y8vYQv3Ic,1654 httpcore/_backends/auto.py,sha256=Q_iQjNuwJseqBxeYJYtiaGzFs08_LGI3K_egYrixEqE,1683
httpcore/_backends/base.py,sha256=Qsb8b_PSiVP1ldHHGXHxQzJ1Qlzj2r8KR9KQeANkSbE,3218 httpcore/_backends/base.py,sha256=Qsb8b_PSiVP1ldHHGXHxQzJ1Qlzj2r8KR9KQeANkSbE,3218
httpcore/_backends/mock.py,sha256=S4IADhC6kE22ge_jR_WHlEUkD6QAsXnwz26DSWZLcG4,4179 httpcore/_backends/mock.py,sha256=S4IADhC6kE22ge_jR_WHlEUkD6QAsXnwz26DSWZLcG4,4179
httpcore/_backends/sync.py,sha256=z4emZ__8qOAWewBtpkkl3gkpR210RN1l3J8Nud0kZc8,8347 httpcore/_backends/sync.py,sha256=LAomvc-MAlot5-S9CCFxnr561aDp9yhyfs_65WeCkZ4,8086
httpcore/_backends/trio.py,sha256=INOeHEkA8pO6AsSqjColWcayM0FQSyGi1hpaQghjrCs,6078 httpcore/_backends/trio.py,sha256=INOeHEkA8pO6AsSqjColWcayM0FQSyGi1hpaQghjrCs,6078
httpcore/_exceptions.py,sha256=7zb3KNiG0qmfUNIdFgdaUSbn2Pu3oztghi6Vg7i-LJU,1185 httpcore/_exceptions.py,sha256=7zb3KNiG0qmfUNIdFgdaUSbn2Pu3oztghi6Vg7i-LJU,1185
httpcore/_models.py,sha256=GTqsbLHxsd_lx0cvtgUBf7OltodKHjIrNKs-DbSc67k,16370 httpcore/_models.py,sha256=GTqsbLHxsd_lx0cvtgUBf7OltodKHjIrNKs-DbSc67k,16370
@ -56,13 +56,13 @@ httpcore/_sync/__pycache__/http_proxy.cpython-39.pyc,,
httpcore/_sync/__pycache__/interfaces.cpython-39.pyc,, httpcore/_sync/__pycache__/interfaces.cpython-39.pyc,,
httpcore/_sync/__pycache__/socks_proxy.cpython-39.pyc,, httpcore/_sync/__pycache__/socks_proxy.cpython-39.pyc,,
httpcore/_sync/connection.py,sha256=luocU3Tv3jlvOCaro33xrHZAwBkpn991LWdL7BC8Bkg,8408 httpcore/_sync/connection.py,sha256=luocU3Tv3jlvOCaro33xrHZAwBkpn991LWdL7BC8Bkg,8408
httpcore/_sync/connection_pool.py,sha256=1iwYLdiq3pi9LBvpMZ8O8gWdb56qqPlm6rp35zeORBQ,13928 httpcore/_sync/connection_pool.py,sha256=JUt-Rpg2l_17JbckVMk2TCs-EbBeUf1swH3EYC5AZVU,14384
httpcore/_sync/http11.py,sha256=1VRRlpqQgIKjxt9xQAeUbDH1Mq3280pC1AU_gwu19VQ,12102 httpcore/_sync/http11.py,sha256=1VRRlpqQgIKjxt9xQAeUbDH1Mq3280pC1AU_gwu19VQ,12102
httpcore/_sync/http2.py,sha256=lkpHesGkrwzIA4oHLyClJf5IAwRLcaAFMnmffAahAK4,23343 httpcore/_sync/http2.py,sha256=lkpHesGkrwzIA4oHLyClJf5IAwRLcaAFMnmffAahAK4,23343
httpcore/_sync/http_proxy.py,sha256=82oin8vjt2a7YmmVvz7sXEZSBuajK-mHDF-EwnR_pJ0,14613 httpcore/_sync/http_proxy.py,sha256=82oin8vjt2a7YmmVvz7sXEZSBuajK-mHDF-EwnR_pJ0,14613
httpcore/_sync/interfaces.py,sha256=EM4PTf-rgkclzisFcrTyx1G8FwraoffE8rbckOznX_o,4365 httpcore/_sync/interfaces.py,sha256=EM4PTf-rgkclzisFcrTyx1G8FwraoffE8rbckOznX_o,4365
httpcore/_sync/socks_proxy.py,sha256=T13QSceeEAg1PM9Yh7Nk-DoqI28TIUqDS-9O3OSC9Uc,13707 httpcore/_sync/socks_proxy.py,sha256=T13QSceeEAg1PM9Yh7Nk-DoqI28TIUqDS-9O3OSC9Uc,13707
httpcore/_synchronization.py,sha256=_d_vHqylvzm1Jh58_0G7i-1VwCg3Gu39Cgd4nWASvP0,8751 httpcore/_synchronization.py,sha256=ABAtHNAESvZqu43FP_lTKpuJ84bavUbAZoN2q8E0W3o,8054
httpcore/_trace.py,sha256=akf5PsWVq3rZjqmXniomU59OY37K7JHoeNDCQ4GU84E,3954 httpcore/_trace.py,sha256=akf5PsWVq3rZjqmXniomU59OY37K7JHoeNDCQ4GU84E,3954
httpcore/_utils.py,sha256=9QPh5ib4JilWX4dBCC_XO6wdBY4b0kbUGgfV3QfBANc,1525 httpcore/_utils.py,sha256=9QPh5ib4JilWX4dBCC_XO6wdBY4b0kbUGgfV3QfBANc,1525
httpcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 httpcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

Binary file not shown.

View File

@ -130,7 +130,7 @@ __all__ = [
"WriteError", "WriteError",
] ]
__version__ = "0.18.0" __version__ = "1.0.2"
__locals = locals() __locals = locals()

Binary file not shown.

View File

@ -1,11 +1,12 @@
import ssl import ssl
import sys import sys
import time
from types import TracebackType from types import TracebackType
from typing import AsyncIterable, AsyncIterator, Iterable, List, Optional, Type from typing import AsyncIterable, AsyncIterator, Iterable, List, Optional, Type
from .._backends.auto import AutoBackend from .._backends.auto import AutoBackend
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol from .._exceptions import ConnectionNotAvailable, PoolTimeout, UnsupportedProtocol
from .._models import Origin, Request, Response from .._models import Origin, Request, Response
from .._synchronization import AsyncEvent, AsyncLock, AsyncShieldCancellation from .._synchronization import AsyncEvent, AsyncLock, AsyncShieldCancellation
from .connection import AsyncHTTPConnection from .connection import AsyncHTTPConnection
@ -220,6 +221,13 @@ class AsyncConnectionPool(AsyncRequestInterface):
) )
status = RequestStatus(request) status = RequestStatus(request)
timeouts = request.extensions.get("timeout", {})
timeout = timeouts.get("pool", None)
if timeout is not None:
deadline = time.monotonic() + timeout
else:
deadline = float("inf")
async with self._pool_lock: async with self._pool_lock:
self._requests.append(status) self._requests.append(status)
@ -227,8 +235,6 @@ class AsyncConnectionPool(AsyncRequestInterface):
await self._attempt_to_acquire_connection(status) await self._attempt_to_acquire_connection(status)
while True: while True:
timeouts = request.extensions.get("timeout", {})
timeout = timeouts.get("pool", None)
try: try:
connection = await status.wait_for_connection(timeout=timeout) connection = await status.wait_for_connection(timeout=timeout)
except BaseException as exc: except BaseException as exc:
@ -263,6 +269,10 @@ class AsyncConnectionPool(AsyncRequestInterface):
else: else:
break break
timeout = deadline - time.monotonic()
if timeout < 0:
raise PoolTimeout # pragma: nocover
# When we return the response, we wrap the stream in a special class # When we return the response, we wrap the stream in a special class
# that handles notifying the connection pool once the response # that handles notifying the connection pool once the response
# has been released. # has been released.
@ -316,6 +326,10 @@ class AsyncConnectionPool(AsyncRequestInterface):
self._requests = [] self._requests = []
async def __aenter__(self) -> "AsyncConnectionPool": async def __aenter__(self) -> "AsyncConnectionPool":
# Acquiring the pool lock here ensures that we have the
# correct dependencies installed as early as possible.
async with self._pool_lock:
pass
return self return self
async def __aexit__( async def __aexit__(

Binary file not shown.

View File

@ -1,15 +1,14 @@
import typing import typing
from typing import Optional from typing import Optional
import sniffio from .._synchronization import current_async_library
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
class AutoBackend(AsyncNetworkBackend): class AutoBackend(AsyncNetworkBackend):
async def _init_backend(self) -> None: async def _init_backend(self) -> None:
if not (hasattr(self, "_backend")): if not (hasattr(self, "_backend")):
backend = sniffio.current_async_library() backend = current_async_library()
if backend == "trio": if backend == "trio":
from .trio import TrioBackend from .trio import TrioBackend

View File

@ -145,12 +145,6 @@ class SyncStream(NetworkStream):
server_hostname: typing.Optional[str] = None, server_hostname: typing.Optional[str] = None,
timeout: typing.Optional[float] = None, timeout: typing.Optional[float] = None,
) -> NetworkStream: ) -> NetworkStream:
if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover
raise RuntimeError(
"Attempted to add a TLS layer on top of the existing "
"TLS stream, which is not supported by httpcore package"
)
exc_map: ExceptionMapping = { exc_map: ExceptionMapping = {
socket.timeout: ConnectTimeout, socket.timeout: ConnectTimeout,
OSError: ConnectError, OSError: ConnectError,

Binary file not shown.

View File

@ -1,11 +1,12 @@
import ssl import ssl
import sys import sys
import time
from types import TracebackType from types import TracebackType
from typing import Iterable, Iterator, Iterable, List, Optional, Type from typing import Iterable, Iterator, Iterable, List, Optional, Type
from .._backends.sync import SyncBackend from .._backends.sync import SyncBackend
from .._backends.base import SOCKET_OPTION, NetworkBackend from .._backends.base import SOCKET_OPTION, NetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol from .._exceptions import ConnectionNotAvailable, PoolTimeout, UnsupportedProtocol
from .._models import Origin, Request, Response from .._models import Origin, Request, Response
from .._synchronization import Event, Lock, ShieldCancellation from .._synchronization import Event, Lock, ShieldCancellation
from .connection import HTTPConnection from .connection import HTTPConnection
@ -220,6 +221,13 @@ class ConnectionPool(RequestInterface):
) )
status = RequestStatus(request) status = RequestStatus(request)
timeouts = request.extensions.get("timeout", {})
timeout = timeouts.get("pool", None)
if timeout is not None:
deadline = time.monotonic() + timeout
else:
deadline = float("inf")
with self._pool_lock: with self._pool_lock:
self._requests.append(status) self._requests.append(status)
@ -227,8 +235,6 @@ class ConnectionPool(RequestInterface):
self._attempt_to_acquire_connection(status) self._attempt_to_acquire_connection(status)
while True: while True:
timeouts = request.extensions.get("timeout", {})
timeout = timeouts.get("pool", None)
try: try:
connection = status.wait_for_connection(timeout=timeout) connection = status.wait_for_connection(timeout=timeout)
except BaseException as exc: except BaseException as exc:
@ -263,6 +269,10 @@ class ConnectionPool(RequestInterface):
else: else:
break break
timeout = deadline - time.monotonic()
if timeout < 0:
raise PoolTimeout # pragma: nocover
# When we return the response, we wrap the stream in a special class # When we return the response, we wrap the stream in a special class
# that handles notifying the connection pool once the response # that handles notifying the connection pool once the response
# has been released. # has been released.
@ -316,6 +326,10 @@ class ConnectionPool(RequestInterface):
self._requests = [] self._requests = []
def __enter__(self) -> "ConnectionPool": def __enter__(self) -> "ConnectionPool":
# Acquiring the pool lock here ensures that we have the
# correct dependencies installed as early as possible.
with self._pool_lock:
pass
return self return self
def __exit__( def __exit__(

View File

@ -2,8 +2,6 @@ import threading
from types import TracebackType from types import TracebackType
from typing import Optional, Type from typing import Optional, Type
import sniffio
from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions
# Our async synchronization primatives use either 'anyio' or 'trio' depending # Our async synchronization primatives use either 'anyio' or 'trio' depending
@ -20,6 +18,32 @@ except ImportError: # pragma: nocover
anyio = None # type: ignore anyio = None # type: ignore
def current_async_library() -> str:
# Determine if we're running under trio or asyncio.
# See https://sniffio.readthedocs.io/en/latest/
try:
import sniffio
except ImportError: # pragma: nocover
environment = "asyncio"
else:
environment = sniffio.current_async_library()
if environment not in ("asyncio", "trio"): # pragma: nocover
raise RuntimeError("Running under an unsupported async environment.")
if environment == "asyncio" and anyio is None: # pragma: nocover
raise RuntimeError(
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)
if environment == "trio" and trio is None: # pragma: nocover
raise RuntimeError(
"Running with trio requires installation of 'httpcore[trio]'."
)
return environment
class AsyncLock: class AsyncLock:
def __init__(self) -> None: def __init__(self) -> None:
self._backend = "" self._backend = ""
@ -29,18 +53,10 @@ class AsyncLock:
Detect if we're running under 'asyncio' or 'trio' and create Detect if we're running under 'asyncio' or 'trio' and create
a lock with the correct implementation. a lock with the correct implementation.
""" """
self._backend = sniffio.current_async_library() self._backend = current_async_library()
if self._backend == "trio": if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio, requires the 'trio' package to be installed."
)
self._trio_lock = trio.Lock() self._trio_lock = trio.Lock()
else: elif self._backend == "asyncio":
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
)
self._anyio_lock = anyio.Lock() self._anyio_lock = anyio.Lock()
async def __aenter__(self) -> "AsyncLock": async def __aenter__(self) -> "AsyncLock":
@ -49,7 +65,7 @@ class AsyncLock:
if self._backend == "trio": if self._backend == "trio":
await self._trio_lock.acquire() await self._trio_lock.acquire()
else: elif self._backend == "asyncio":
await self._anyio_lock.acquire() await self._anyio_lock.acquire()
return self return self
@ -62,7 +78,7 @@ class AsyncLock:
) -> None: ) -> None:
if self._backend == "trio": if self._backend == "trio":
self._trio_lock.release() self._trio_lock.release()
else: elif self._backend == "asyncio":
self._anyio_lock.release() self._anyio_lock.release()
@ -75,18 +91,10 @@ class AsyncEvent:
Detect if we're running under 'asyncio' or 'trio' and create Detect if we're running under 'asyncio' or 'trio' and create
a lock with the correct implementation. a lock with the correct implementation.
""" """
self._backend = sniffio.current_async_library() self._backend = current_async_library()
if self._backend == "trio": if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
)
self._trio_event = trio.Event() self._trio_event = trio.Event()
else: elif self._backend == "asyncio":
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
)
self._anyio_event = anyio.Event() self._anyio_event = anyio.Event()
def set(self) -> None: def set(self) -> None:
@ -95,7 +103,7 @@ class AsyncEvent:
if self._backend == "trio": if self._backend == "trio":
self._trio_event.set() self._trio_event.set()
else: elif self._backend == "asyncio":
self._anyio_event.set() self._anyio_event.set()
async def wait(self, timeout: Optional[float] = None) -> None: async def wait(self, timeout: Optional[float] = None) -> None:
@ -103,22 +111,12 @@ class AsyncEvent:
self.setup() self.setup()
if self._backend == "trio": if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
)
trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout} trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout}
timeout_or_inf = float("inf") if timeout is None else timeout timeout_or_inf = float("inf") if timeout is None else timeout
with map_exceptions(trio_exc_map): with map_exceptions(trio_exc_map):
with trio.fail_after(timeout_or_inf): with trio.fail_after(timeout_or_inf):
await self._trio_event.wait() await self._trio_event.wait()
else: elif self._backend == "asyncio":
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
)
anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout} anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout}
with map_exceptions(anyio_exc_map): with map_exceptions(anyio_exc_map):
with anyio.fail_after(timeout): with anyio.fail_after(timeout):
@ -135,22 +133,12 @@ class AsyncSemaphore:
Detect if we're running under 'asyncio' or 'trio' and create Detect if we're running under 'asyncio' or 'trio' and create
a semaphore with the correct implementation. a semaphore with the correct implementation.
""" """
self._backend = sniffio.current_async_library() self._backend = current_async_library()
if self._backend == "trio": if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
)
self._trio_semaphore = trio.Semaphore( self._trio_semaphore = trio.Semaphore(
initial_value=self._bound, max_value=self._bound initial_value=self._bound, max_value=self._bound
) )
else: elif self._backend == "asyncio":
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
)
self._anyio_semaphore = anyio.Semaphore( self._anyio_semaphore = anyio.Semaphore(
initial_value=self._bound, max_value=self._bound initial_value=self._bound, max_value=self._bound
) )
@ -161,13 +149,13 @@ class AsyncSemaphore:
if self._backend == "trio": if self._backend == "trio":
await self._trio_semaphore.acquire() await self._trio_semaphore.acquire()
else: elif self._backend == "asyncio":
await self._anyio_semaphore.acquire() await self._anyio_semaphore.acquire()
async def release(self) -> None: async def release(self) -> None:
if self._backend == "trio": if self._backend == "trio":
self._trio_semaphore.release() self._trio_semaphore.release()
else: elif self._backend == "asyncio":
self._anyio_semaphore.release() self._anyio_semaphore.release()
@ -184,27 +172,17 @@ class AsyncShieldCancellation:
Detect if we're running under 'asyncio' or 'trio' and create Detect if we're running under 'asyncio' or 'trio' and create
a shielded scope with the correct implementation. a shielded scope with the correct implementation.
""" """
self._backend = sniffio.current_async_library() self._backend = current_async_library()
if self._backend == "trio": if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
)
self._trio_shield = trio.CancelScope(shield=True) self._trio_shield = trio.CancelScope(shield=True)
else: elif self._backend == "asyncio":
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
)
self._anyio_shield = anyio.CancelScope(shield=True) self._anyio_shield = anyio.CancelScope(shield=True)
def __enter__(self) -> "AsyncShieldCancellation": def __enter__(self) -> "AsyncShieldCancellation":
if self._backend == "trio": if self._backend == "trio":
self._trio_shield.__enter__() self._trio_shield.__enter__()
else: elif self._backend == "asyncio":
self._anyio_shield.__enter__() self._anyio_shield.__enter__()
return self return self
@ -216,7 +194,7 @@ class AsyncShieldCancellation:
) -> None: ) -> None:
if self._backend == "trio": if self._backend == "trio":
self._trio_shield.__exit__(exc_type, exc_value, traceback) self._trio_shield.__exit__(exc_type, exc_value, traceback)
else: elif self._backend == "asyncio":
self._anyio_shield.__exit__(exc_type, exc_value, traceback) self._anyio_shield.__exit__(exc_type, exc_value, traceback)
@ -248,6 +226,8 @@ class Event:
self._event.set() self._event.set()
def wait(self, timeout: Optional[float] = None) -> None: def wait(self, timeout: Optional[float] = None) -> None:
if timeout == float("inf"): # pragma: no cover
timeout = None
if not self._event.wait(timeout=timeout): if not self._event.wait(timeout=timeout):
raise PoolTimeout() # pragma: nocover raise PoolTimeout() # pragma: nocover

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: httpx Name: httpx
Version: 0.25.0 Version: 0.25.1
Summary: The next generation HTTP client. Summary: The next generation HTTP client.
Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md
Project-URL: Documentation, https://www.python-httpx.org Project-URL: Documentation, https://www.python-httpx.org
@ -22,10 +22,12 @@ Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.8 Requires-Python: >=3.8
Requires-Dist: anyio
Requires-Dist: certifi Requires-Dist: certifi
Requires-Dist: httpcore<0.19.0,>=0.18.0 Requires-Dist: httpcore
Requires-Dist: idna Requires-Dist: idna
Requires-Dist: sniffio Requires-Dist: sniffio
Provides-Extra: brotli Provides-Extra: brotli
@ -192,23 +194,14 @@ inspiration around the lower-level networking details.
## Release Information ## Release Information
### Removed ### 0.25.1 (3rd November, 2023)
* Drop support for Python 3.7. (#2813) * Add support for Python 3.12. (#2854)
* Add support for httpcore 1.0 (#2885)
### Added
* Support HTTPS proxies. (#2845)
* Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#2803)
* Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716)
* The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776)
### Fixed ### Fixed
* Return `500` error response instead of exceptions when `raise_app_exceptions=False` is set on `ASGITransport`. (#2669) * Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852)
* Ensure all `WSGITransport` environs have a `SERVER_PROTOCOL`. (#2708)
* Always encode forward slashes as `%2F` in query parameters (#2723)
* Use Mozilla documentation instead of `httpstatuses.com` for HTTP error reference (#2768)
--- ---

View File

@ -1,10 +1,10 @@
../../../bin/httpx,sha256=KIP6KGLZF0RAHfVjqe3qTPD8GRYex0qWFBxARqKqu94,235 ../../../bin/httpx,sha256=Ug7WqeS1tnOXHYhEuY-5dLJW9cLyfsYkhpAZ_-JlYc0,235
httpx-0.25.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 httpx-0.25.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
httpx-0.25.0.dist-info/METADATA,sha256=8ZnffZ9BAo4gzHpfUFlvx2tQYM3-Z3FRwOh4BsgKgLk,7630 httpx-0.25.1.dist-info/METADATA,sha256=F-1gg84Cs1LReAfx4NmiNXp-qE9DqJKD3pHD1B64y5A,7095
httpx-0.25.0.dist-info/RECORD,, httpx-0.25.1.dist-info/RECORD,,
httpx-0.25.0.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87 httpx-0.25.1.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
httpx-0.25.0.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37 httpx-0.25.1.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37
httpx-0.25.0.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508 httpx-0.25.1.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508
httpx/__init__.py,sha256=oCxVAsePEy5DE9eLhGAAq9H3RBGZUDaUROtGEyzbBRo,3210 httpx/__init__.py,sha256=oCxVAsePEy5DE9eLhGAAq9H3RBGZUDaUROtGEyzbBRo,3210
httpx/__pycache__/__init__.cpython-39.pyc,, httpx/__pycache__/__init__.cpython-39.pyc,,
httpx/__pycache__/__version__.cpython-39.pyc,, httpx/__pycache__/__version__.cpython-39.pyc,,
@ -24,18 +24,18 @@ httpx/__pycache__/_types.cpython-39.pyc,,
httpx/__pycache__/_urlparse.cpython-39.pyc,, httpx/__pycache__/_urlparse.cpython-39.pyc,,
httpx/__pycache__/_urls.cpython-39.pyc,, httpx/__pycache__/_urls.cpython-39.pyc,,
httpx/__pycache__/_utils.cpython-39.pyc,, httpx/__pycache__/_utils.cpython-39.pyc,,
httpx/__version__.py,sha256=hVLEi3Fe6ziNKrXxfBIkYicKi_9NvZZvMQJMGUkSjIU,108 httpx/__version__.py,sha256=Qdj8HFntcoM-E84xA8Y05EMH5ocBUPDZX722C_2Tvi0,108
httpx/_api.py,sha256=cVU9ErzaXve5rqoPoSHr9yJbovHtICrcxR7yBoNSeOw,13011 httpx/_api.py,sha256=cVU9ErzaXve5rqoPoSHr9yJbovHtICrcxR7yBoNSeOw,13011
httpx/_auth.py,sha256=58FA-xqqp-XgLZ7Emd4-et-XXuTRaa5buiBYB2MzyvE,11773 httpx/_auth.py,sha256=4ZLi3wfSttjqxOQFxys7hZ55b-8xq3ZKypa-sEz3d-Q,12013
httpx/_client.py,sha256=A9MPP_d1ZlqcO5CeGLgyzVwdHgCpROYSdjoAUA6rpYE,68131 httpx/_client.py,sha256=A9MPP_d1ZlqcO5CeGLgyzVwdHgCpROYSdjoAUA6rpYE,68131
httpx/_compat.py,sha256=S4sL2QocE1gj0qn6F7egl7dYhY809rXE5P9YD3e_zDg,1602 httpx/_compat.py,sha256=rJERfjHkRvvHFVfltbHyCVcAboNsfEeN6j_00Z2C4k8,1563
httpx/_config.py,sha256=3AKbxOVuh2UeZi95UqALtoO1thrmHUhTxvcFAyspKPw,12491 httpx/_config.py,sha256=_NFrJwZr0yFysMaTt3rCnJqHvV5vQQG3yUg1kxSomjs,12334
httpx/_content.py,sha256=olbWqawdWWweXeW6gDYHPiEGjip5lqFZKv9OmVd-zIg,8092 httpx/_content.py,sha256=olbWqawdWWweXeW6gDYHPiEGjip5lqFZKv9OmVd-zIg,8092
httpx/_decoders.py,sha256=dd8GSkEAe45BzRUF47zH_lg3-BcwXtxzPBSGP5Y4F90,9739 httpx/_decoders.py,sha256=dd8GSkEAe45BzRUF47zH_lg3-BcwXtxzPBSGP5Y4F90,9739
httpx/_exceptions.py,sha256=xKw-U6vW7zmdReUAGYHMegYWZuDAuE5039L087SHe4Q,7880 httpx/_exceptions.py,sha256=xKw-U6vW7zmdReUAGYHMegYWZuDAuE5039L087SHe4Q,7880
httpx/_main.py,sha256=m9C4RuqjOB6UqL3FFHMjmC45f4SDSO-iOREFLdw4IdM,15784 httpx/_main.py,sha256=m9C4RuqjOB6UqL3FFHMjmC45f4SDSO-iOREFLdw4IdM,15784
httpx/_models.py,sha256=gfrZvx3B0R2U7bLT7JfNKt4DMnUoT8leHOiij6Te13A,42791 httpx/_models.py,sha256=6lIr7avUeJo2qp0vN4HSOgI96TPzbq1HtGSHNRwO-gg,42896
httpx/_multipart.py,sha256=qzt35jAgapaRPwdq-lTKSA5YY6ayrfDIsZLdr3t4NWc,8972 httpx/_multipart.py,sha256=yTaczu2EcFX5GcOmDW8_2x2w2d1j4_8qFcsUCyYLSUI,8960
httpx/_status_codes.py,sha256=XKArMrSoo8oKBQCHdFGA-wsM2PcSTaHE8svDYOUcwWk,5584 httpx/_status_codes.py,sha256=XKArMrSoo8oKBQCHdFGA-wsM2PcSTaHE8svDYOUcwWk,5584
httpx/_transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 httpx/_transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
httpx/_transports/__pycache__/__init__.cpython-39.pyc,, httpx/_transports/__pycache__/__init__.cpython-39.pyc,,
@ -46,11 +46,11 @@ httpx/_transports/__pycache__/mock.cpython-39.pyc,,
httpx/_transports/__pycache__/wsgi.cpython-39.pyc,, httpx/_transports/__pycache__/wsgi.cpython-39.pyc,,
httpx/_transports/asgi.py,sha256=ZoIHy1-Wu09vZRkjzVzXJFgw9sh0Dq5cG8zfgtqK-SA,5469 httpx/_transports/asgi.py,sha256=ZoIHy1-Wu09vZRkjzVzXJFgw9sh0Dq5cG8zfgtqK-SA,5469
httpx/_transports/base.py,sha256=0BM8yZZEkdFT4tXXSm0h0dK0cSYA4hLgInj_BljGEGw,2510 httpx/_transports/base.py,sha256=0BM8yZZEkdFT4tXXSm0h0dK0cSYA4hLgInj_BljGEGw,2510
httpx/_transports/default.py,sha256=u99Nctd3mDNHfOc-S_ldaEwSvlp8NGLwIW2rjY0VgP4,13192 httpx/_transports/default.py,sha256=Kn-RztYMwpHT3rBna3UVYE39W67ipEXnUr1FJTc0-9s,13175
httpx/_transports/mock.py,sha256=sDt3BDXbz8-W94kC8OXtGzF1PWH0y73h1De7Q-XkVtg,1179 httpx/_transports/mock.py,sha256=sDt3BDXbz8-W94kC8OXtGzF1PWH0y73h1De7Q-XkVtg,1179
httpx/_transports/wsgi.py,sha256=Zt3EhTagyF3o-HC2oPMp-hTy3M3kQThL1ECJRc8eXEM,4797 httpx/_transports/wsgi.py,sha256=Zt3EhTagyF3o-HC2oPMp-hTy3M3kQThL1ECJRc8eXEM,4797
httpx/_types.py,sha256=W_lOq_3FnHmZGQuXaGm5JDykFoC0WoqhnfH92nRDNGQ,3367 httpx/_types.py,sha256=W_lOq_3FnHmZGQuXaGm5JDykFoC0WoqhnfH92nRDNGQ,3367
httpx/_urlparse.py,sha256=UQbI0l39smQh5UplxFAtLYfuxSx1cC_JPivhBPBSWgk,16774 httpx/_urlparse.py,sha256=Vpz_ydrcGCy5ReNqrU9lhuqsZJLER3hE-Wq5crYCb48,16777
httpx/_urls.py,sha256=JAONd-2reXpB_WuQ7WuvhUcLuebiQeYJQPyszADmCow,21840 httpx/_urls.py,sha256=JAONd-2reXpB_WuQ7WuvhUcLuebiQeYJQPyszADmCow,21840
httpx/_utils.py,sha256=I_m2rFyEpoU2j8lS0GwdWcUTBTQ8cjvFnZ8ROmnCpR8,15403 httpx/_utils.py,sha256=ugUzRBmO4i_wDXSmyaGhOiQ6w4WAXCopG-uxrdn16dk,14109
httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

Binary file not shown.

View File

@ -1,3 +1,3 @@
__title__ = "httpx" __title__ = "httpx"
__description__ = "A next generation HTTP client, for Python 3." __description__ = "A next generation HTTP client, for Python 3."
__version__ = "0.25.0" __version__ = "0.25.1"

View File

@ -1,5 +1,4 @@
import hashlib import hashlib
import netrc
import os import os
import re import re
import time import time
@ -8,7 +7,7 @@ from base64 import b64encode
from urllib.request import parse_http_list from urllib.request import parse_http_list
from ._exceptions import ProtocolError from ._exceptions import ProtocolError
from ._models import Request, Response from ._models import Cookies, Request, Response
from ._utils import to_bytes, to_str, unquote from ._utils import to_bytes, to_str, unquote
if typing.TYPE_CHECKING: # pragma: no cover if typing.TYPE_CHECKING: # pragma: no cover
@ -148,6 +147,10 @@ class NetRCAuth(Auth):
""" """
def __init__(self, file: typing.Optional[str] = None): def __init__(self, file: typing.Optional[str] = None):
# Lazily import 'netrc'.
# There's no need for us to load this module unless 'NetRCAuth' is being used.
import netrc
self._netrc_info = netrc.netrc(file) self._netrc_info = netrc.netrc(file)
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
@ -217,6 +220,8 @@ class DigestAuth(Auth):
request.headers["Authorization"] = self._build_auth_header( request.headers["Authorization"] = self._build_auth_header(
request, self._last_challenge request, self._last_challenge
) )
if response.cookies:
Cookies(response.cookies).set_cookie_header(request=request)
yield request yield request
def _parse_challenge( def _parse_challenge(

View File

@ -16,9 +16,7 @@ except ImportError: # pragma: no cover
except ImportError: except ImportError:
brotli = None brotli = None
if sys.version_info >= (3, 10) or ( if sys.version_info >= (3, 10) or ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7):
sys.version_info >= (3, 8) and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7)
):
def set_minimum_tls_version_1_2(context: ssl.SSLContext) -> None: def set_minimum_tls_version_1_2(context: ssl.SSLContext) -> None:
# The OP_NO_SSL* and OP_NO_TLS* become deprecated in favor of # The OP_NO_SSL* and OP_NO_TLS* become deprecated in favor of

View File

@ -1,7 +1,6 @@
import logging import logging
import os import os
import ssl import ssl
import sys
import typing import typing
from pathlib import Path from pathlib import Path
@ -128,11 +127,10 @@ class SSLConfig:
# Signal to server support for PHA in TLS 1.3. Raises an # Signal to server support for PHA in TLS 1.3. Raises an
# AttributeError if only read-only access is implemented. # AttributeError if only read-only access is implemented.
if sys.version_info >= (3, 8): # pragma: no cover try:
try: context.post_handshake_auth = True
context.post_handshake_auth = True except AttributeError: # pragma: no cover
except AttributeError: # pragma: no cover pass
pass
# Disable using 'commonName' for SSLContext.check_hostname # Disable using 'commonName' for SSLContext.check_hostname
# when the 'subjectAltName' extension isn't available. # when the 'subjectAltName' extension isn't available.
@ -168,10 +166,9 @@ class SSLConfig:
alpn_idents = ["http/1.1", "h2"] if self.http2 else ["http/1.1"] alpn_idents = ["http/1.1", "h2"] if self.http2 else ["http/1.1"]
context.set_alpn_protocols(alpn_idents) context.set_alpn_protocols(alpn_idents)
if sys.version_info >= (3, 8): # pragma: no cover keylogfile = os.environ.get("SSLKEYLOGFILE")
keylogfile = os.environ.get("SSLKEYLOGFILE") if keylogfile and self.trust_env:
if keylogfile and self.trust_env: context.keylog_filename = keylogfile
context.keylog_filename = keylogfile
return context return context

View File

@ -43,7 +43,6 @@ from ._types import (
) )
from ._urls import URL from ._urls import URL
from ._utils import ( from ._utils import (
guess_json_utf,
is_known_encoding, is_known_encoding,
normalize_header_key, normalize_header_key,
normalize_header_value, normalize_header_value,
@ -603,6 +602,16 @@ class Response:
@encoding.setter @encoding.setter
def encoding(self, value: str) -> None: def encoding(self, value: str) -> None:
"""
Set the encoding to use for decoding the byte content into text.
If the `text` attribute has been accessed, attempting to set the
encoding will throw a ValueError.
"""
if hasattr(self, "_text"):
raise ValueError(
"Setting encoding after `text` has been accessed is not allowed."
)
self._encoding = value self._encoding = value
@property @property
@ -749,11 +758,7 @@ class Response:
raise HTTPStatusError(message, request=request, response=self) raise HTTPStatusError(message, request=request, response=self)
def json(self, **kwargs: typing.Any) -> typing.Any: def json(self, **kwargs: typing.Any) -> typing.Any:
if self.charset_encoding is None and self.content and len(self.content) > 3: return jsonlib.loads(self.content, **kwargs)
encoding = guess_json_utf(self.content)
if encoding is not None:
return jsonlib.loads(self.content.decode(encoding), **kwargs)
return jsonlib.loads(self.text, **kwargs)
@property @property
def cookies(self) -> "Cookies": def cookies(self) -> "Cookies":

View File

@ -1,4 +1,3 @@
import binascii
import io import io
import os import os
import typing import typing
@ -200,7 +199,7 @@ class MultipartStream(SyncByteStream, AsyncByteStream):
boundary: typing.Optional[bytes] = None, boundary: typing.Optional[bytes] = None,
) -> None: ) -> None:
if boundary is None: if boundary is None:
boundary = binascii.hexlify(os.urandom(16)) boundary = os.urandom(16).hex().encode("ascii")
self.boundary = boundary self.boundary = boundary
self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( self.content_type = "multipart/form-data; boundary=%s" % boundary.decode(

Binary file not shown.

View File

@ -64,7 +64,7 @@ SOCKET_OPTION = typing.Union[
def map_httpcore_exceptions() -> typing.Iterator[None]: def map_httpcore_exceptions() -> typing.Iterator[None]:
try: try:
yield yield
except Exception as exc: # noqa: PIE-786 except Exception as exc:
mapped_exc = None mapped_exc = None
for from_exc, to_exc in HTTPCORE_EXC_MAP.items(): for from_exc, to_exc in HTTPCORE_EXC_MAP.items():

View File

@ -87,7 +87,7 @@ COMPONENT_REGEX = {
# We use these simple regexs as a first pass before handing off to # We use these simple regexs as a first pass before handing off to
# the stdlib 'ipaddress' module for IP address validation. # the stdlib 'ipaddress' module for IP address validation.
IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+.[0-9]+.[0-9]+.[0-9]+$") IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$")
IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$") IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$")

View File

@ -91,41 +91,6 @@ def format_form_param(name: str, value: str) -> bytes:
return f'{name}="{value}"'.encode() return f'{name}="{value}"'.encode()
# Null bytes; no need to recreate these on each call to guess_json_utf
_null = b"\x00"
_null2 = _null * 2
_null3 = _null * 3
def guess_json_utf(data: bytes) -> typing.Optional[str]:
# JSON always starts with two ASCII characters, so detection is as
# easy as counting the nulls and from their location and count
# determine the encoding. Also detect a BOM, if present.
sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
return "utf-32" # BOM included
if sample[:3] == codecs.BOM_UTF8:
return "utf-8-sig" # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
return "utf-16" # BOM included
nullcount = sample.count(_null)
if nullcount == 0:
return "utf-8"
if nullcount == 2:
if sample[::2] == _null2: # 1st and 3rd are null
return "utf-16-be"
if sample[1::2] == _null2: # 2nd and 4th are null
return "utf-16-le"
# Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3:
if sample[:3] == _null3:
return "utf-32-be"
if sample[1:] == _null3:
return "utf-32-le"
# Did not detect a valid UTF-32 ascii-range character
return None
def get_ca_bundle_from_env() -> typing.Optional[str]: def get_ca_bundle_from_env() -> typing.Optional[str]:
if "SSL_CERT_FILE" in os.environ: if "SSL_CERT_FILE" in os.environ:
ssl_file = Path(os.environ["SSL_CERT_FILE"]) ssl_file = Path(os.environ["SSL_CERT_FILE"])

Binary file not shown.

Binary file not shown.

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,104 +0,0 @@
Metadata-Version: 2.1
Name: importlib-resources
Version: 6.1.0
Summary: Read resources from Python packages
Home-page: https://github.com/python/importlib_resources
Author: Barry Warsaw
Author-email: barry@python.org
Project-URL: Documentation, https://importlib-resources.readthedocs.io/
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.8
License-File: LICENSE
Requires-Dist: zipp >=3.1.0 ; python_version < "3.10"
Provides-Extra: docs
Requires-Dist: sphinx >=3.5 ; extra == 'docs'
Requires-Dist: sphinx <7.2.5 ; extra == 'docs'
Requires-Dist: jaraco.packaging >=9.3 ; extra == 'docs'
Requires-Dist: rst.linker >=1.9 ; extra == 'docs'
Requires-Dist: furo ; extra == 'docs'
Requires-Dist: sphinx-lint ; extra == 'docs'
Requires-Dist: jaraco.tidelift >=1.4 ; extra == 'docs'
Provides-Extra: testing
Requires-Dist: pytest >=6 ; extra == 'testing'
Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'testing'
Requires-Dist: pytest-cov ; extra == 'testing'
Requires-Dist: pytest-enabler >=2.2 ; extra == 'testing'
Requires-Dist: pytest-ruff ; extra == 'testing'
Requires-Dist: zipp >=3.17 ; extra == 'testing'
Requires-Dist: pytest-black >=0.3.7 ; (platform_python_implementation != "PyPy") and extra == 'testing'
Requires-Dist: pytest-mypy >=0.9.1 ; (platform_python_implementation != "PyPy") and extra == 'testing'
.. image:: https://img.shields.io/pypi/v/importlib_resources.svg
:target: https://pypi.org/project/importlib_resources
.. image:: https://img.shields.io/pypi/pyversions/importlib_resources.svg
.. image:: https://github.com/python/importlib_resources/workflows/tests/badge.svg
:target: https://github.com/python/importlib_resources/actions?query=workflow%3A%22tests%22
:alt: tests
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Ruff
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: Code style: Black
.. image:: https://readthedocs.org/projects/importlib-resources/badge/?version=latest
:target: https://importlib-resources.readthedocs.io/en/latest/?badge=latest
.. image:: https://img.shields.io/badge/skeleton-2023-informational
:target: https://blog.jaraco.com/skeleton
.. image:: https://tidelift.com/badges/package/pypi/importlib-resources
:target: https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=readme
``importlib_resources`` is a backport of Python standard library
`importlib.resources
<https://docs.python.org/3/library/importlib.html#module-importlib.resources>`_
module for older Pythons.
The key goal of this module is to replace parts of `pkg_resources
<https://setuptools.readthedocs.io/en/latest/pkg_resources.html>`_ with a
solution in Python's stdlib that relies on well-defined APIs. This makes
reading resources included in packages easier, with more stable and consistent
semantics.
Compatibility
=============
New features are introduced in this third-party library and later merged
into CPython. The following table indicates which versions of this library
were contributed to different versions in the standard library:
.. list-table::
:header-rows: 1
* - importlib_resources
- stdlib
* - 6.0
- 3.13
* - 5.12
- 3.12
* - 5.7
- 3.11
* - 5.0
- 3.10
* - 1.3
- 3.9
* - 0.5 (?)
- 3.7
For Enterprise
==============
Available as part of the Tidelift Subscription.
This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
`Learn more <https://tidelift.com/subscription/pkg/pypi-importlib-resources?utm_source=pypi-importlib-resources&utm_medium=referral&utm_campaign=github>`_.

View File

@ -1,72 +0,0 @@
importlib_resources-6.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
importlib_resources-6.1.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
importlib_resources-6.1.0.dist-info/METADATA,sha256=sv8t8W7Di2p_H14Lu2B9GY_dPyup1R-xqDOCMR--jHI,4122
importlib_resources-6.1.0.dist-info/RECORD,,
importlib_resources-6.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources-6.1.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
importlib_resources-6.1.0.dist-info/top_level.txt,sha256=fHIjHU1GZwAjvcydpmUnUrTnbvdiWjG4OEVZK8by0TQ,20
importlib_resources/__init__.py,sha256=t3v1sx-q_TzszzsOs3dqNOjqBQVbSB_KY-BjCvf65qQ,226
importlib_resources/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/__pycache__/_adapters.cpython-39.pyc,,
importlib_resources/__pycache__/_common.cpython-39.pyc,,
importlib_resources/__pycache__/_compat.cpython-39.pyc,,
importlib_resources/__pycache__/_itertools.cpython-39.pyc,,
importlib_resources/__pycache__/abc.cpython-39.pyc,,
importlib_resources/__pycache__/readers.cpython-39.pyc,,
importlib_resources/__pycache__/simple.cpython-39.pyc,,
importlib_resources/_adapters.py,sha256=vprJGbUeHbajX6XCuMP6J3lMrqCi-P_MTlziJUR7jfk,4482
importlib_resources/_common.py,sha256=jSC4xfLdcMNbtbWHtpzbFkNa0W7kvf__nsYn14C_AEU,5457
importlib_resources/_compat.py,sha256=6MduVulDb7YTwL5f1KAzJUYdYUs12ZAnew_nnCqq0_8,3304
importlib_resources/_itertools.py,sha256=eDisV6RqiNZOogLSXf6LOGHOYc79FGgPrKNLzFLmCrU,1277
importlib_resources/abc.py,sha256=Icr2IJ2QtH7vvAB9vC5WRJ9KBoaDyJa7KUs8McuROzo,5140
importlib_resources/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/readers.py,sha256=ytBi6cqzs_viuvwhEOh0rTI9b8-QLnMaxW7E_LQI1Pc,5409
importlib_resources/simple.py,sha256=0__2TQBTQoqkajYmNPt1HxERcReAT6boVKJA328pr04,2576
importlib_resources/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/__pycache__/_compat.cpython-39.pyc,,
importlib_resources/tests/__pycache__/_path.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_compatibilty_files.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_contents.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_custom.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_files.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_open.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_path.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_read.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_reader.cpython-39.pyc,,
importlib_resources/tests/__pycache__/test_resource.cpython-39.pyc,,
importlib_resources/tests/__pycache__/util.cpython-39.pyc,,
importlib_resources/tests/__pycache__/zip.cpython-39.pyc,,
importlib_resources/tests/_compat.py,sha256=YTSB0U1R9oADnh6GrQcOCgojxcF_N6H1LklymEWf9SQ,708
importlib_resources/tests/_path.py,sha256=nkv3ek7D1U898v921rYbldDCtKri2oyYOi3EJqGjEGU,1289
importlib_resources/tests/data01/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/data01/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/data01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
importlib_resources/tests/data01/subdirectory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/data01/subdirectory/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/data01/subdirectory/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
importlib_resources/tests/data01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
importlib_resources/tests/data01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
importlib_resources/tests/data02/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/data02/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/data02/one/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/data02/one/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/data02/one/resource1.txt,sha256=10flKac7c-XXFzJ3t-AB5MJjlBy__dSZvPE_dOm2q6U,13
importlib_resources/tests/data02/subdirectory/subsubdir/resource.txt,sha256=jnrBBztxYrtQck7cmVnc4xQVO4-agzAZDGSFkAWtlFw,10
importlib_resources/tests/data02/two/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_resources/tests/data02/two/__pycache__/__init__.cpython-39.pyc,,
importlib_resources/tests/data02/two/resource2.txt,sha256=lt2jbN3TMn9QiFKM832X39bU_62UptDdUkoYzkvEbl0,13
importlib_resources/tests/namespacedata01/binary.file,sha256=BU7ewdAhH2JP7Qy8qdT5QAsOSRxDdCryxbCr6_DJkNg,4
importlib_resources/tests/namespacedata01/utf-16.file,sha256=t5q9qhxX0rYqItBOM8D3ylwG-RHrnOYteTLtQr6sF7g,44
importlib_resources/tests/namespacedata01/utf-8.file,sha256=kwWgYG4yQ-ZF2X_WA66EjYPmxJRn-w8aSOiS9e8tKYY,20
importlib_resources/tests/test_compatibilty_files.py,sha256=95N_R7aik8cvnE6sBJpsxmP0K5plOWRIJDgbalD-Hpw,3314
importlib_resources/tests/test_contents.py,sha256=V1Xfk3lqTDdvUsZuV18Kndf0CT_tkM2oEIwk9Vv0rhg,968
importlib_resources/tests/test_custom.py,sha256=jVYg9idEVdUN6idHUfDDlZ-zDWl56qYNbj5QrcZO76Y,1124
importlib_resources/tests/test_files.py,sha256=W5XoBWSTr84Ke15UtjqWLet2iUDUyJfQxbST4PDlj2w,3283
importlib_resources/tests/test_open.py,sha256=9qvdC6Eu2Kn3mh3xDR5HUEQoePSKIecTxU4vnH9veO8,2671
importlib_resources/tests/test_path.py,sha256=XR5RI7_zndI_Nqw9eHU1tDmSGIo29N1GP8INodPc584,2142
importlib_resources/tests/test_read.py,sha256=ZEUosdzSMHxF6s7u9sWI9M-LbIJXyFtR34tpA6eGzrs,2476
importlib_resources/tests/test_reader.py,sha256=WHlZKJBa3MOHnWQG2CB02l8LQ4QjQ0kG_KdcVqZSZ4o,4945
importlib_resources/tests/test_resource.py,sha256=3rY9zrUKAlOwSuHmgrVw-tGPpQ9HxRDeGwcFYwbtKHc,7188
importlib_resources/tests/util.py,sha256=ZJ9ouR8UOZRbgQ6_ZeXxXBvcjOlSVae4ckIMqzhyAZg,4784
importlib_resources/tests/zip.py,sha256=2MKmF8-osXBJSnqcUTuAUek_-tSB3iKmIT9qPhcsOsM,783

Some files were not shown because too many files have changed in this diff Show More