pip 버전 업데이트 및 인코딩 방법 수정정

This commit is contained in:
3minbe 2025-02-20 13:16:49 +09:00
parent 02df1b0ba7
commit e27abe2a32
115 changed files with 13520 additions and 14586 deletions

View File

@ -555,7 +555,7 @@ class MainView(QtWidgets.QMainWindow):
self.updateAlertText(f"변환 성공", [file_path]) self.updateAlertText(f"변환 성공", [file_path])
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.updateAlertText(f"변환 실패: {e}", [file_path]) self.updateAlertText(f"변환 실패\n {e}", [file_path])
self.progress_bar.setValue(int((index + 1) / total_files * 100)) self.progress_bar.setValue(int((index + 1) / total_files * 100))
self.status_bar.showMessage("변환 완료") self.status_bar.showMessage("변환 완료")
self.progress_bar.setVisible(False) self.progress_bar.setVisible(False)

View File

@ -1,67 +1,75 @@
import re, sys import re
import sys
def load_dbc_file(file_path): def load_dbc_file(file_path):
try: encodings = ['utf-8', 'cp949', 'latin1'] # 시도할 인코딩 목록
with open(file_path, 'r') as file:
content = file.read()
# print(f"[INFO] Successfully loaded DBC file: {file_path}")
# 메시지와 시그널을 추출하는 정규 표현식 for encoding in encodings:
message_pattern = re.compile(r'BO_\s+(\d+)\s+(\w+)\s*:\s*(\d+)\s+(\w+)') try:
signal_pattern = re.compile(r'SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@(\d+)([+-])\s*\(([^,]+),\s*([^)]+)\)\s*\[[^\]]+\]\s*\"[^\"]*\"\s+(\w+)') with open(file_path, 'r', encoding=encoding) as file:
content = file.read()
print(f"[INFO] Successfully loaded DBC file with encoding: {encoding}")
break # 파일을 성공적으로 읽으면 루프를 종료
except (UnicodeDecodeError, FileNotFoundError) as e:
print(f"[WARNING] Failed to read file with encoding {encoding}: {e}")
content = None
# 메시지와 시그널 매칭 if content is None:
messages = {} print(f"[ERROR] Failed to load DBC file with all attempted encodings.")
current_message = None
for line in content.splitlines():
line = line.strip()
if not line:
current_message = None
continue
message_match = message_pattern.match(line)
if message_match:
decimal_id = int(message_match.group(1))
hex_id = f"0x{decimal_id:03X}"
message_name = message_match.group(2)
dlc = int(message_match.group(3))
tx_ecu_name = message_match.group(4)
current_message = message_name
messages[current_message] = {
"ID": hex_id,
"DLC": dlc,
"TX ECU name": tx_ecu_name,
"Signals": []
}
elif current_message:
signal_match = signal_pattern.match(line)
if signal_match:
signal_name = signal_match.group(1)
msb = int(signal_match.group(2))
length = int(signal_match.group(3))
byte_order = int(signal_match.group(4))
sign = signal_match.group(5)
factor = float(signal_match.group(6))
offset = float(signal_match.group(7))
rx_ecu_name = signal_match.group(8)
messages[current_message]["Signals"].append({
"Signal name": signal_name,
"msb": msb,
"Length": length,
"Byte order": byte_order,
"Sign": sign,
"Factor": factor,
"Offset": offset,
"RX ECU name": rx_ecu_name
})
return messages
except Exception as e:
print(f"[ERROR] Failed to parse DBC file: {e}")
return None return None
# 메시지와 시그널을 추출하는 정규 표현식
message_pattern = re.compile(r'BO_\s+(\d+)\s+(\w+)\s*:\s*(\d+)\s+(\w+)')
signal_pattern = re.compile(r'SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@(\d+)([+-])\s*\(([^,]+),\s*([^)]+)\)\s*\[[^\]]+\]\s*\"[^\"]*\"\s+(\w+)')
# 메시지와 시그널 매칭
messages = {}
current_message = None
for line in content.splitlines():
line = line.strip()
if not line:
current_message = None
continue
message_match = message_pattern.match(line)
if message_match:
decimal_id = int(message_match.group(1))
hex_id = f"0x{decimal_id:03X}"
message_name = message_match.group(2)
dlc = int(message_match.group(3))
tx_ecu_name = message_match.group(4)
current_message = message_name
messages[current_message] = {
"ID": hex_id,
"DLC": dlc,
"TX ECU name": tx_ecu_name,
"Signals": []
}
elif current_message:
signal_match = signal_pattern.match(line)
if signal_match:
signal_name = signal_match.group(1)
msb = int(signal_match.group(2))
length = int(signal_match.group(3))
byte_order = int(signal_match.group(4))
sign = signal_match.group(5)
factor = float(signal_match.group(6))
offset = float(signal_match.group(7))
rx_ecu_name = signal_match.group(8)
messages[current_message]["Signals"].append({
"Signal name": signal_name,
"msb": msb,
"Length": length,
"Byte order": byte_order,
"Sign": sign,
"Factor": factor,
"Offset": offset,
"RX ECU name": rx_ecu_name
})
return messages
if __name__ == "__main__": if __name__ == "__main__":
file_path = sys.argv[1] file_path = sys.argv[1]
messages = load_dbc_file(file_path) messages = load_dbc_file(file_path)

Binary file not shown.

View File

@ -1,799 +0,0 @@
@Switch01
A_Rog
Aakanksha Agrawal
Abhinav Sagar
ABHYUDAY PRATAP SINGH
abs51295
AceGentile
Adam Chainz
Adam Tse
Adam Wentz
admin
Adolfo Ochagavía
Adrien Morison
Agus
ahayrapetyan
Ahilya
AinsworthK
Akash Srivastava
Alan Yee
Albert Tugushev
Albert-Guan
albertg
Alberto Sottile
Aleks Bunin
Ales Erjavec
Alethea Flowers
Alex Gaynor
Alex Grönholm
Alex Hedges
Alex Loosley
Alex Morega
Alex Stachowiak
Alexander Shtyrov
Alexandre Conrad
Alexey Popravka
Aleš Erjavec
Alli
Ami Fischman
Ananya Maiti
Anatoly Techtonik
Anders Kaseorg
Andre Aguiar
Andreas Lutro
Andrei Geacar
Andrew Gaul
Andrew Shymanel
Andrey Bienkowski
Andrey Bulgakov
Andrés Delfino
Andy Freeland
Andy Kluger
Ani Hayrapetyan
Aniruddha Basak
Anish Tambe
Anrs Hu
Anthony Sottile
Antoine Musso
Anton Ovchinnikov
Anton Patrushev
Anton Zelenov
Antonio Alvarado Hernandez
Antony Lee
Antti Kaihola
Anubhav Patel
Anudit Nagar
Anuj Godase
AQNOUCH Mohammed
AraHaan
arena
arenasys
Arindam Choudhury
Armin Ronacher
Arnon Yaari
Artem
Arun Babu Neelicattu
Ashley Manton
Ashwin Ramaswami
atse
Atsushi Odagiri
Avinash Karhana
Avner Cohen
Awit (Ah-Wit) Ghirmai
Baptiste Mispelon
Barney Gale
barneygale
Bartek Ogryczak
Bastian Venthur
Ben Bodenmiller
Ben Darnell
Ben Hoyt
Ben Mares
Ben Rosser
Bence Nagy
Benjamin Peterson
Benjamin VanEvery
Benoit Pierre
Berker Peksag
Bernard
Bernard Tyers
Bernardo B. Marques
Bernhard M. Wiedemann
Bertil Hatt
Bhavam Vidyarthi
Blazej Michalik
Bogdan Opanchuk
BorisZZZ
Brad Erickson
Bradley Ayers
Branch Vincent
Brandon L. Reiss
Brandt Bucher
Brannon Dorsey
Brett Randall
Brett Rosen
Brian Cristante
Brian Rosner
briantracy
BrownTruck
Bruno Oliveira
Bruno Renié
Bruno S
Bstrdsmkr
Buck Golemon
burrows
Bussonnier Matthias
bwoodsend
c22
Caleb Martinez
Calvin Smith
Carl Meyer
Carlos Liam
Carol Willing
Carter Thayer
Cass
Chandrasekhar Atina
Charlie Marsh
Chih-Hsuan Yen
Chris Brinker
Chris Hunt
Chris Jerdonek
Chris Kuehl
Chris Markiewicz
Chris McDonough
Chris Pawley
Chris Pryer
Chris Wolfe
Christian Clauss
Christian Heimes
Christian Oudard
Christoph Reiter
Christopher Hunt
Christopher Snyder
chrysle
cjc7373
Clark Boylan
Claudio Jolowicz
Clay McClure
Cody
Cody Soyland
Colin Watson
Collin Anderson
Connor Osborn
Cooper Lees
Cooper Ry Lees
Cory Benfield
Cory Wright
Craig Kerstiens
Cristian Sorinel
Cristina
Cristina Muñoz
ctg123
Curtis Doty
cytolentino
Daan De Meyer
Dale
Damian
Damian Quiroga
Damian Shaw
Dan Black
Dan Savilonis
Dan Sully
Dane Hillard
daniel
Daniel Collins
Daniel Hahler
Daniel Holth
Daniel Jost
Daniel Katz
Daniel Shaulov
Daniele Esposti
Daniele Nicolodi
Daniele Procida
Daniil Konovalenko
Danny Hermes
Danny McClanahan
Darren Kavanagh
Dav Clark
Dave Abrahams
Dave Jones
David Aguilar
David Black
David Bordeynik
David Caro
David D Lowe
David Evans
David Hewitt
David Linke
David Poggi
David Poznik
David Pursehouse
David Runge
David Tucker
David Wales
Davidovich
ddelange
Deepak Sharma
Deepyaman Datta
Denise Yu
dependabot[bot]
derwolfe
Desetude
Devesh Kumar Singh
devsagul
Diego Caraballo
Diego Ramirez
DiegoCaraballo
Dimitri Merejkowsky
Dimitri Papadopoulos
Dimitri Papadopoulos Orfanos
Dirk Stolle
Dmitry Gladkov
Dmitry Volodin
Domen Kožar
Dominic Davis-Foster
Donald Stufft
Dongweiming
doron zarhi
Dos Moonen
Douglas Thor
DrFeathers
Dustin Ingram
Dustin Rodrigues
Dwayne Bailey
Ed Morley
Edgar Ramírez
Edgar Ramírez Mondragón
Ee Durbin
Efflam Lemaillet
efflamlemaillet
Eitan Adler
ekristina
elainechan
Eli Schwartz
Elisha Hollander
Ellen Marie Dash
Emil Burzo
Emil Styrke
Emmanuel Arias
Endoh Takanao
enoch
Erdinc Mutlu
Eric Cousineau
Eric Gillingham
Eric Hanchrow
Eric Hopper
Erik M. Bray
Erik Rose
Erwin Janssen
Eugene Vereshchagin
everdimension
Federico
Felipe Peter
Felix Yan
fiber-space
Filip Kokosiński
Filipe Laíns
Finn Womack
finnagin
Flavio Amurrio
Florian Briand
Florian Rathgeber
Francesco
Francesco Montesano
Fredrik Orderud
Frost Ming
Gabriel Curio
Gabriel de Perthuis
Garry Polley
gavin
gdanielson
Geoffrey Sneddon
George Song
Georgi Valkov
Georgy Pchelkin
ghost
Giftlin Rajaiah
gizmoguy1
gkdoc
Godefroid Chapelle
Gopinath M
GOTO Hayato
gousaiyang
gpiks
Greg Roodt
Greg Ward
Guilherme Espada
Guillaume Seguin
gutsytechster
Guy Rozendorn
Guy Tuval
gzpan123
Hanjun Kim
Hari Charan
Harsh Vardhan
harupy
Harutaka Kawamura
hauntsaninja
Henrich Hartzer
Henry Schreiner
Herbert Pfennig
Holly Stotelmyer
Honnix
Hsiaoming Yang
Hugo Lopes Tavares
Hugo van Kemenade
Hugues Bruant
Hynek Schlawack
Ian Bicking
Ian Cordasco
Ian Lee
Ian Stapleton Cordasco
Ian Wienand
Igor Kuzmitshov
Igor Sobreira
Ikko Ashimine
Ilan Schnell
Illia Volochii
Ilya Baryshev
Inada Naoki
Ionel Cristian Mărieș
Ionel Maries Cristian
Itamar Turner-Trauring
Ivan Pozdeev
J. Nick Koston
Jacob Kim
Jacob Walls
Jaime Sanz
jakirkham
Jakub Kuczys
Jakub Stasiak
Jakub Vysoky
Jakub Wilk
James Cleveland
James Curtin
James Firth
James Gerity
James Polley
Jan Pokorný
Jannis Leidel
Jarek Potiuk
jarondl
Jason Curtis
Jason R. Coombs
JasonMo
JasonMo1
Jay Graves
Jean Abou Samra
Jean-Christophe Fillion-Robin
Jeff Barber
Jeff Dairiki
Jeff Widman
Jelmer Vernooij
jenix21
Jeremy Fleischman
Jeremy Stanley
Jeremy Zafran
Jesse Rittner
Jiashuo Li
Jim Fisher
Jim Garrison
Jinzhe Zeng
Jiun Bae
Jivan Amara
Joe Bylund
Joe Michelini
John Paton
John Sirois
John T. Wodder II
John-Scott Atlakson
johnthagen
Jon Banafato
Jon Dufresne
Jon Parise
Jonas Nockert
Jonathan Herbert
Joonatan Partanen
Joost Molenaar
Jorge Niedbalski
Joseph Bylund
Joseph Long
Josh Bronson
Josh Cannon
Josh Hansen
Josh Schneier
Joshua
Juan Luis Cano Rodríguez
Juanjo Bazán
Judah Rand
Julian Berman
Julian Gethmann
Julien Demoor
Jussi Kukkonen
jwg4
Jyrki Pulliainen
Kai Chen
Kai Mueller
Kamal Bin Mustafa
kasium
kaustav haldar
keanemind
Keith Maxwell
Kelsey Hightower
Kenneth Belitzky
Kenneth Reitz
Kevin Burke
Kevin Carter
Kevin Frommelt
Kevin R Patterson
Kexuan Sun
Kit Randel
Klaas van Schelven
KOLANICH
konstin
kpinc
Krishna Oza
Kumar McMillan
Kuntal Majumder
Kurt McKee
Kyle Persohn
lakshmanaram
Laszlo Kiss-Kollar
Laurent Bristiel
Laurent LAPORTE
Laurie O
Laurie Opperman
layday
Leon Sasson
Lev Givon
Lincoln de Sousa
Lipis
lorddavidiii
Loren Carvalho
Lucas Cimon
Ludovic Gasc
Luis Medel
Lukas Geiger
Lukas Juhrich
Luke Macken
Luo Jiebin
luojiebin
luz.paz
László Kiss Kollár
M00nL1ght
Marc Abramowitz
Marc Tamlyn
Marcus Smith
Mariatta
Mark Kohler
Mark McLoughlin
Mark Williams
Markus Hametner
Martey Dodoo
Martin Fischer
Martin Häcker
Martin Pavlasek
Masaki
Masklinn
Matej Stuchlik
Mathew Jennings
Mathieu Bridon
Mathieu Kniewallner
Matt Bacchi
Matt Good
Matt Maker
Matt Robenolt
Matt Wozniski
matthew
Matthew Einhorn
Matthew Feickert
Matthew Gilliard
Matthew Hughes
Matthew Iversen
Matthew Treinish
Matthew Trumbell
Matthew Willson
Matthias Bussonnier
mattip
Maurits van Rees
Max W Chase
Maxim Kurnikov
Maxime Rouyrre
mayeut
mbaluna
mdebi
memoselyk
meowmeowcat
Michael
Michael Aquilina
Michael E. Karpeles
Michael Klich
Michael Mintz
Michael Williamson
michaelpacer
Michał Górny
Mickaël Schoentgen
Miguel Araujo Perez
Mihir Singh
Mike
Mike Hendricks
Min RK
MinRK
Miro Hrončok
Monica Baluna
montefra
Monty Taylor
morotti
mrKazzila
Muha Ajjan
Nadav Wexler
Nahuel Ambrosini
Nate Coraor
Nate Prewitt
Nathan Houghton
Nathaniel J. Smith
Nehal J Wani
Neil Botelho
Nguyễn Gia Phong
Nicholas Serra
Nick Coghlan
Nick Stenning
Nick Timkovich
Nicolas Bock
Nicole Harris
Nikhil Benesch
Nikhil Ladha
Nikita Chepanov
Nikolay Korolev
Nipunn Koorapati
Nitesh Sharma
Niyas Sait
Noah
Noah Gorny
Nowell Strite
NtaleGrey
nvdv
OBITORASU
Ofek Lev
ofrinevo
Oliver Freund
Oliver Jeeves
Oliver Mannion
Oliver Tonnhofer
Olivier Girardot
Olivier Grisel
Ollie Rutherfurd
OMOTO Kenji
Omry Yadan
onlinejudge95
Oren Held
Oscar Benjamin
Oz N Tiram
Pachwenko
Patrick Dubroy
Patrick Jenkins
Patrick Lawson
patricktokeeffe
Patrik Kopkan
Paul Ganssle
Paul Kehrer
Paul Moore
Paul Nasrat
Paul Oswald
Paul van der Linden
Paulus Schoutsen
Pavel Safronov
Pavithra Eswaramoorthy
Pawel Jasinski
Paweł Szramowski
Pekka Klärck
Peter Gessler
Peter Lisák
Peter Shen
Peter Waller
Petr Viktorin
petr-tik
Phaneendra Chiruvella
Phil Elson
Phil Freo
Phil Pennock
Phil Whelan
Philip Jägenstedt
Philip Molloy
Philippe Ombredanne
Pi Delport
Pierre-Yves Rofes
Pieter Degroote
pip
Prabakaran Kumaresshan
Prabhjyotsing Surjit Singh Sodhi
Prabhu Marappan
Pradyun Gedam
Prashant Sharma
Pratik Mallya
pre-commit-ci[bot]
Preet Thakkar
Preston Holmes
Przemek Wrzos
Pulkit Goyal
q0w
Qiangning Hong
Qiming Xu
Quentin Lee
Quentin Pradet
R. David Murray
Rafael Caricio
Ralf Schmitt
Ran Benita
Razzi Abuissa
rdb
Reece Dunham
Remi Rampin
Rene Dudfield
Riccardo Magliocchetti
Riccardo Schirone
Richard Jones
Richard Si
Ricky Ng-Adam
Rishi
rmorotti
RobberPhex
Robert Collins
Robert McGibbon
Robert Pollak
Robert T. McGibbon
robin elisha robinson
Roey Berman
Rohan Jain
Roman Bogorodskiy
Roman Donchenko
Romuald Brunet
ronaudinho
Ronny Pfannschmidt
Rory McCann
Ross Brattain
Roy Wellington Ⅳ
Ruairidh MacLeod
Russell Keith-Magee
Ryan Shepherd
Ryan Wooden
ryneeverett
S. Guliaev
Sachi King
Salvatore Rinchiera
sandeepkiran-js
Sander Van Balen
Savio Jomton
schlamar
Scott Kitterman
Sean
seanj
Sebastian Jordan
Sebastian Schaetz
Segev Finer
SeongSoo Cho
Sergey Vasilyev
Seth Michael Larson
Seth Woodworth
Shahar Epstein
Shantanu
shenxianpeng
shireenrao
Shivansh-007
Shixian Sheng
Shlomi Fish
Shovan Maity
Simeon Visser
Simon Cross
Simon Pichugin
sinoroc
sinscary
snook92
socketubs
Sorin Sbarnea
Srinivas Nyayapati
Srishti Hegde
Stavros Korokithakis
Stefan Scherfke
Stefano Rivera
Stephan Erb
Stephen Rosen
stepshal
Steve (Gadget) Barnes
Steve Barnes
Steve Dower
Steve Kowalik
Steven Myint
Steven Silvester
stonebig
studioj
Stéphane Bidoul
Stéphane Bidoul (ACSONE)
Stéphane Klein
Sumana Harihareswara
Surbhi Sharma
Sviatoslav Sydorenko
Sviatoslav Sydorenko (Святослав Сидоренко)
Swat009
Sylvain
Takayuki SHIMIZUKAWA
Taneli Hukkinen
tbeswick
Thiago
Thijs Triemstra
Thomas Fenzl
Thomas Grainger
Thomas Guettler
Thomas Johansson
Thomas Kluyver
Thomas Smith
Thomas VINCENT
Tim D. Smith
Tim Gates
Tim Harder
Tim Heap
tim smith
tinruufu
Tobias Hermann
Tom Forbes
Tom Freudenheim
Tom V
Tomas Hrnciar
Tomas Orsava
Tomer Chachamu
Tommi Enenkel | AnB
Tomáš Hrnčiar
Tony Beswick
Tony Narlock
Tony Zhaocheng Tan
TonyBeswick
toonarmycaptain
Toshio Kuratomi
toxinu
Travis Swicegood
Tushar Sadhwani
Tzu-ping Chung
Valentin Haenel
Victor Stinner
victorvpaulo
Vikram - Google
Viktor Szépe
Ville Skyttä
Vinay Sajip
Vincent Philippon
Vinicyus Macedo
Vipul Kumar
Vitaly Babiy
Vladimir Fokow
Vladimir Rutsky
W. Trevor King
Wil Tan
Wilfred Hughes
William Edwards
William ML Leslie
William T Olson
William Woodruff
Wilson Mo
wim glenn
Winson Luk
Wolfgang Maier
Wu Zhenyu
XAMES3
Xavier Fernandez
Xianpeng Shen
xoviat
xtreak
YAMAMOTO Takashi
Yen Chi Hsuan
Yeray Diaz Diaz
Yoval P
Yu Jian
Yuan Jing Vincent Yan
Yusuke Hayashi
Zearin
Zhiping Deng
ziebam
Zvezdan Petkovic
Łukasz Langa
Роман Донченко
Семён Марьясин

View File

@ -1,20 +0,0 @@
Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,90 +0,0 @@
Metadata-Version: 2.1
Name: pip
Version: 24.3.1
Summary: The PyPA recommended tool for installing Python packages.
Author-email: The pip developers <distutils-sig@python.org>
License: MIT
Project-URL: Homepage, https://pip.pypa.io/
Project-URL: Documentation, https://pip.pypa.io
Project-URL: Source, https://github.com/pypa/pip
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Build Tools
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
License-File: AUTHORS.txt
pip - The Python Package Installer
==================================
.. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg
:target: https://pypi.org/project/pip/
:alt: PyPI
.. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip
:target: https://pypi.org/project/pip
:alt: PyPI - Python Version
.. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest
:target: https://pip.pypa.io/en/latest
:alt: Documentation
|pypi-version| |python-versions| |docs-badge|
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
Please take a look at our documentation for how to install and use pip:
* `Installation`_
* `Usage`_
We release updates regularly, with a new version every 3 months. Find more details in our documentation:
* `Release notes`_
* `Release process`_
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
* `Issue tracking`_
* `Discourse channel`_
* `User IRC`_
If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
* `GitHub page`_
* `Development documentation`_
* `Development IRC`_
Code of Conduct
---------------
Everyone interacting in the pip project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _package installer: https://packaging.python.org/guides/tool-recommendations/
.. _Python Package Index: https://pypi.org
.. _Installation: https://pip.pypa.io/en/stable/installation/
.. _Usage: https://pip.pypa.io/en/stable/
.. _Release notes: https://pip.pypa.io/en/stable/news.html
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
.. _GitHub page: https://github.com/pypa/pip
.. _Development documentation: https://pip.pypa.io/en/latest/development
.. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md

View File

@ -1,853 +0,0 @@
../../Scripts/pip.exe,sha256=yAvBcsW_HIuubmDWZJy0oARyd_kEN7UBNAhIA8k2FnM,108431
../../Scripts/pip3.13.exe,sha256=yAvBcsW_HIuubmDWZJy0oARyd_kEN7UBNAhIA8k2FnM,108431
../../Scripts/pip3.exe,sha256=yAvBcsW_HIuubmDWZJy0oARyd_kEN7UBNAhIA8k2FnM,108431
pip-24.3.1.dist-info/AUTHORS.txt,sha256=Cbb630k8EL9FkBzX9Vpi6hpYWrLSlh08eXodL5u0eLI,10925
pip-24.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip-24.3.1.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093
pip-24.3.1.dist-info/METADATA,sha256=V8iCNK1GYbC82PWsLMsASDh9AO4veocRlM4Pn9q2KFI,3677
pip-24.3.1.dist-info/RECORD,,
pip-24.3.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip-24.3.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
pip-24.3.1.dist-info/entry_points.txt,sha256=eeIjuzfnfR2PrhbjnbzFU6MnSS70kZLxwaHHq6M-bD0,87
pip-24.3.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip/__init__.py,sha256=faXY_neeYrA_88plEhkyhwAaYeds7wu5U1iGwP24J0s,357
pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854
pip/__pip-runner__.py,sha256=cPPWuJ6NK_k-GzfvlejLFgwzmYUROmpAR6QC3Q-vkXQ,1450
pip/__pycache__/__init__.cpython-313.pyc,,
pip/__pycache__/__main__.cpython-313.pyc,,
pip/__pycache__/__pip-runner__.cpython-313.pyc,,
pip/_internal/__init__.py,sha256=MfcoOluDZ8QMCFYal04IqOJ9q6m2V7a0aOsnI-WOxUo,513
pip/_internal/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/__pycache__/build_env.cpython-313.pyc,,
pip/_internal/__pycache__/cache.cpython-313.pyc,,
pip/_internal/__pycache__/configuration.cpython-313.pyc,,
pip/_internal/__pycache__/exceptions.cpython-313.pyc,,
pip/_internal/__pycache__/main.cpython-313.pyc,,
pip/_internal/__pycache__/pyproject.cpython-313.pyc,,
pip/_internal/__pycache__/self_outdated_check.cpython-313.pyc,,
pip/_internal/__pycache__/wheel_builder.cpython-313.pyc,,
pip/_internal/build_env.py,sha256=wsTPOWyPTKvUREUcO585OU01kbQufpdigY8fVHv3WIw,10584
pip/_internal/cache.py,sha256=Jb698p5PNigRtpW5o26wQNkkUv4MnQ94mc471wL63A0,10369
pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132
pip/_internal/cli/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/cli/__pycache__/autocompletion.cpython-313.pyc,,
pip/_internal/cli/__pycache__/base_command.cpython-313.pyc,,
pip/_internal/cli/__pycache__/cmdoptions.cpython-313.pyc,,
pip/_internal/cli/__pycache__/command_context.cpython-313.pyc,,
pip/_internal/cli/__pycache__/index_command.cpython-313.pyc,,
pip/_internal/cli/__pycache__/main.cpython-313.pyc,,
pip/_internal/cli/__pycache__/main_parser.cpython-313.pyc,,
pip/_internal/cli/__pycache__/parser.cpython-313.pyc,,
pip/_internal/cli/__pycache__/progress_bars.cpython-313.pyc,,
pip/_internal/cli/__pycache__/req_command.cpython-313.pyc,,
pip/_internal/cli/__pycache__/spinners.cpython-313.pyc,,
pip/_internal/cli/__pycache__/status_codes.cpython-313.pyc,,
pip/_internal/cli/autocompletion.py,sha256=Lli3Mr6aDNu7ZkJJFFvwD2-hFxNI6Avz8OwMyS5TVrs,6865
pip/_internal/cli/base_command.py,sha256=F8nUcSM-Y-MQljJUe724-yxmc5viFXHyM_zH70NmIh4,8289
pip/_internal/cli/cmdoptions.py,sha256=mDqBr0d0hoztbRJs-PWtcKpqNAc7khU6ZpoesZKocT8,30110
pip/_internal/cli/command_context.py,sha256=RHgIPwtObh5KhMrd3YZTkl8zbVG-6Okml7YbFX4Ehg0,774
pip/_internal/cli/index_command.py,sha256=-0oPTruZGkLSMrWDleZ6UtcKP3G-SImRRuhH0RfVE3o,5631
pip/_internal/cli/main.py,sha256=BDZef-bWe9g9Jpr4OVs4dDf-845HJsKw835T7AqEnAc,2817
pip/_internal/cli/main_parser.py,sha256=laDpsuBDl6kyfywp9eMMA9s84jfH2TJJn-vmL0GG90w,4338
pip/_internal/cli/parser.py,sha256=VCMtduzECUV87KaHNu-xJ-wLNL82yT3x16V4XBxOAqI,10825
pip/_internal/cli/progress_bars.py,sha256=VgydyqjZvfhqpuNcFDn00QNuA9GxRe9CKrRG8jhPuKU,2723
pip/_internal/cli/req_command.py,sha256=DqeFhmUMs6o6Ev8qawAcOoYNdAZsfyKS0MZI5jsJYwQ,12250
pip/_internal/cli/spinners.py,sha256=hIJ83GerdFgFCdobIA23Jggetegl_uC4Sp586nzFbPE,5118
pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116
pip/_internal/commands/__init__.py,sha256=5oRO9O3dM2vGuh0bFw4HOVletryrz5HHMmmPWwJrH9U,3882
pip/_internal/commands/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/commands/__pycache__/cache.cpython-313.pyc,,
pip/_internal/commands/__pycache__/check.cpython-313.pyc,,
pip/_internal/commands/__pycache__/completion.cpython-313.pyc,,
pip/_internal/commands/__pycache__/configuration.cpython-313.pyc,,
pip/_internal/commands/__pycache__/debug.cpython-313.pyc,,
pip/_internal/commands/__pycache__/download.cpython-313.pyc,,
pip/_internal/commands/__pycache__/freeze.cpython-313.pyc,,
pip/_internal/commands/__pycache__/hash.cpython-313.pyc,,
pip/_internal/commands/__pycache__/help.cpython-313.pyc,,
pip/_internal/commands/__pycache__/index.cpython-313.pyc,,
pip/_internal/commands/__pycache__/inspect.cpython-313.pyc,,
pip/_internal/commands/__pycache__/install.cpython-313.pyc,,
pip/_internal/commands/__pycache__/list.cpython-313.pyc,,
pip/_internal/commands/__pycache__/search.cpython-313.pyc,,
pip/_internal/commands/__pycache__/show.cpython-313.pyc,,
pip/_internal/commands/__pycache__/uninstall.cpython-313.pyc,,
pip/_internal/commands/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/commands/cache.py,sha256=xg76_ZFEBC6zoQ3gXLRfMZJft4z2a0RwH4GEFZC6nnU,7944
pip/_internal/commands/check.py,sha256=Hr_4eiMd9cgVDgEvjtIdw915NmL7ROIWW8enkr8slPQ,2268
pip/_internal/commands/completion.py,sha256=HT4lD0bgsflHq2IDgYfiEdp7IGGtE7s6MgI3xn0VQEw,4287
pip/_internal/commands/configuration.py,sha256=n98enwp6y0b5G6fiRQjaZo43FlJKYve_daMhN-4BRNc,9766
pip/_internal/commands/debug.py,sha256=DNDRgE9YsKrbYzU0s3VKi8rHtKF4X13CJ_br_8PUXO0,6797
pip/_internal/commands/download.py,sha256=0qB0nys6ZEPsog451lDsjL5Bx7Z97t-B80oFZKhpzKM,5273
pip/_internal/commands/freeze.py,sha256=2Vt72BYTSm9rzue6d8dNzt8idxWK4Db6Hd-anq7GQ80,3203
pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703
pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132
pip/_internal/commands/index.py,sha256=RAXxmJwFhVb5S1BYzb5ifX3sn9Na8v2CCVYwSMP8pao,4731
pip/_internal/commands/inspect.py,sha256=PGrY9TRTRCM3y5Ml8Bdk8DEOXquWRfscr4DRo1LOTPc,3189
pip/_internal/commands/install.py,sha256=iqesiLIZc6Op9uihMQFYRhAA2DQRZUxbM4z1BwXoFls,29428
pip/_internal/commands/list.py,sha256=oiIzSjLP6__d7dIS3q0Xb5ywsaOThBWRqMyjjKzkPdM,12769
pip/_internal/commands/search.py,sha256=fWkUQVx_gm8ebbFAlCgqtxKXT9rNahpJ-BI__3HNZpg,5626
pip/_internal/commands/show.py,sha256=IG9L5uo8w6UA4tI_IlmaxLCoNKPa5JNJCljj3NWs0OE,7507
pip/_internal/commands/uninstall.py,sha256=7pOR7enK76gimyxQbzxcG1OsyLXL3DvX939xmM8Fvtg,3892
pip/_internal/commands/wheel.py,sha256=eJRhr_qoNNxWAkkdJCNiQM7CXd4E1_YyQhsqJnBPGGg,6414
pip/_internal/configuration.py,sha256=XkAiBS0hpzsM-LF0Qu5hvPWO_Bs67-oQKRYFBuMbESs,14006
pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858
pip/_internal/distributions/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/distributions/__pycache__/base.cpython-313.pyc,,
pip/_internal/distributions/__pycache__/installed.cpython-313.pyc,,
pip/_internal/distributions/__pycache__/sdist.cpython-313.pyc,,
pip/_internal/distributions/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/distributions/base.py,sha256=QeB9qvKXDIjLdPBDE5fMgpfGqMMCr-govnuoQnGuiF8,1783
pip/_internal/distributions/installed.py,sha256=QinHFbWAQ8oE0pbD8MFZWkwlnfU1QYTccA1vnhrlYOU,842
pip/_internal/distributions/sdist.py,sha256=PlcP4a6-R6c98XnOM-b6Lkb3rsvh9iG4ok8shaanrzs,6751
pip/_internal/distributions/wheel.py,sha256=THBYfnv7VVt8mYhMYUtH13S1E7FDwtDyDfmUcl8ai0E,1317
pip/_internal/exceptions.py,sha256=2_byISIv3kSnI_9T-Esfxrt0LnTRgcUHyxu0twsHjQY,26481
pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30
pip/_internal/index/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/index/__pycache__/collector.cpython-313.pyc,,
pip/_internal/index/__pycache__/package_finder.cpython-313.pyc,,
pip/_internal/index/__pycache__/sources.cpython-313.pyc,,
pip/_internal/index/collector.py,sha256=RdPO0JLAlmyBWPAWYHPyRoGjz3GNAeTngCNkbGey_mE,16265
pip/_internal/index/package_finder.py,sha256=yRC4xsyudwKnNoU6IXvNoyqYo5ScT7lB6Wa-z2eh7cs,37666
pip/_internal/index/sources.py,sha256=lPBLK5Xiy8Q6IQMio26Wl7ocfZOKkgGklIBNyUJ23fI,8632
pip/_internal/locations/__init__.py,sha256=UaAxeZ_f93FyouuFf4p7SXYF-4WstXuEvd3LbmPCAno,14925
pip/_internal/locations/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/locations/__pycache__/_distutils.cpython-313.pyc,,
pip/_internal/locations/__pycache__/_sysconfig.cpython-313.pyc,,
pip/_internal/locations/__pycache__/base.cpython-313.pyc,,
pip/_internal/locations/_distutils.py,sha256=x6nyVLj7X11Y4khIdf-mFlxMl2FWadtVEgeb8upc_WI,6013
pip/_internal/locations/_sysconfig.py,sha256=IGzds60qsFneRogC-oeBaY7bEh3lPt_v47kMJChQXsU,7724
pip/_internal/locations/base.py,sha256=RQiPi1d4FVM2Bxk04dQhXZ2PqkeljEL2fZZ9SYqIQ78,2556
pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340
pip/_internal/metadata/__init__.py,sha256=9pU3W3s-6HtjFuYhWcLTYVmSaziklPv7k2x8p7X1GmA,4339
pip/_internal/metadata/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/metadata/__pycache__/_json.cpython-313.pyc,,
pip/_internal/metadata/__pycache__/base.cpython-313.pyc,,
pip/_internal/metadata/__pycache__/pkg_resources.cpython-313.pyc,,
pip/_internal/metadata/_json.py,sha256=P0cAJrH_mtmMZvlZ16ZXm_-izA4lpr5wy08laICuiaA,2644
pip/_internal/metadata/base.py,sha256=ft0K5XNgI4ETqZnRv2-CtvgYiMOMAeGMAzxT-f6VLJA,25298
pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135
pip/_internal/metadata/importlib/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/metadata/importlib/__pycache__/_compat.cpython-313.pyc,,
pip/_internal/metadata/importlib/__pycache__/_dists.cpython-313.pyc,,
pip/_internal/metadata/importlib/__pycache__/_envs.cpython-313.pyc,,
pip/_internal/metadata/importlib/_compat.py,sha256=c6av8sP8BBjAZuFSJow1iWfygUXNM3xRTCn5nqw6B9M,2796
pip/_internal/metadata/importlib/_dists.py,sha256=anh0mLI-FYRPUhAdipd0Va3YJJc6HelCKQ0bFhY10a0,8017
pip/_internal/metadata/importlib/_envs.py,sha256=UUB980XSrDWrMpQ1_G45i0r8Hqlg_tg3IPQ63mEqbNc,7431
pip/_internal/metadata/pkg_resources.py,sha256=U07ETAINSGeSRBfWUG93E4tZZbaW_f7PGzEqZN0hulc,10542
pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63
pip/_internal/models/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/models/__pycache__/candidate.cpython-313.pyc,,
pip/_internal/models/__pycache__/direct_url.cpython-313.pyc,,
pip/_internal/models/__pycache__/format_control.cpython-313.pyc,,
pip/_internal/models/__pycache__/index.cpython-313.pyc,,
pip/_internal/models/__pycache__/installation_report.cpython-313.pyc,,
pip/_internal/models/__pycache__/link.cpython-313.pyc,,
pip/_internal/models/__pycache__/scheme.cpython-313.pyc,,
pip/_internal/models/__pycache__/search_scope.cpython-313.pyc,,
pip/_internal/models/__pycache__/selection_prefs.cpython-313.pyc,,
pip/_internal/models/__pycache__/target_python.cpython-313.pyc,,
pip/_internal/models/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/models/candidate.py,sha256=zzgFRuw_kWPjKpGw7LC0ZUMD2CQ2EberUIYs8izjdCA,753
pip/_internal/models/direct_url.py,sha256=uBtY2HHd3TO9cKQJWh0ThvE5FRr-MWRYChRU4IG9HZE,6578
pip/_internal/models/format_control.py,sha256=wtsQqSK9HaUiNxQEuB-C62eVimw6G4_VQFxV9-_KDBE,2486
pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030
pip/_internal/models/installation_report.py,sha256=zRVZoaz-2vsrezj_H3hLOhMZCK9c7TbzWgC-jOalD00,2818
pip/_internal/models/link.py,sha256=jHax9O-9zlSzEwjBCDkx0OXjKXwBDwOuPwn-PsR8dCs,21034
pip/_internal/models/scheme.py,sha256=PakmHJM3e8OOWSZFtfz1Az7f1meONJnkGuQxFlt3wBE,575
pip/_internal/models/search_scope.py,sha256=67NEnsYY84784S-MM7ekQuo9KXLH-7MzFntXjapvAo0,4531
pip/_internal/models/selection_prefs.py,sha256=qaFfDs3ciqoXPg6xx45N1jPLqccLJw4N0s4P0PyHTQ8,2015
pip/_internal/models/target_python.py,sha256=2XaH2rZ5ZF-K5wcJbEMGEl7SqrTToDDNkrtQ2v_v_-Q,4271
pip/_internal/models/wheel.py,sha256=G7dND_s4ebPkEL7RJ1qCY0QhUUWIIK6AnjWgRATF5no,4539
pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50
pip/_internal/network/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/network/__pycache__/auth.cpython-313.pyc,,
pip/_internal/network/__pycache__/cache.cpython-313.pyc,,
pip/_internal/network/__pycache__/download.cpython-313.pyc,,
pip/_internal/network/__pycache__/lazy_wheel.cpython-313.pyc,,
pip/_internal/network/__pycache__/session.cpython-313.pyc,,
pip/_internal/network/__pycache__/utils.cpython-313.pyc,,
pip/_internal/network/__pycache__/xmlrpc.cpython-313.pyc,,
pip/_internal/network/auth.py,sha256=D4gASjUrqoDFlSt6gQ767KAAjv6PUyJU0puDlhXNVRE,20809
pip/_internal/network/cache.py,sha256=48A971qCzKNFvkb57uGEk7-0xaqPS0HWj2711QNTxkU,3935
pip/_internal/network/download.py,sha256=FLOP29dPYECBiAi7eEjvAbNkyzaKNqbyjOT2m8HPW8U,6048
pip/_internal/network/lazy_wheel.py,sha256=PBdoMoNQQIA84Fhgne38jWF52W4x_KtsHjxgv4dkRKA,7622
pip/_internal/network/session.py,sha256=XmanBKjVwPFmh1iJ58q6TDh9xabH37gREuQJ_feuZGA,18741
pip/_internal/network/utils.py,sha256=Inaxel-NxBu4PQWkjyErdnfewsFCcgHph7dzR1-FboY,4088
pip/_internal/network/xmlrpc.py,sha256=sAxzOacJ-N1NXGPvap9jC3zuYWSnnv3GXtgR2-E2APA,1838
pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/operations/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/operations/__pycache__/check.cpython-313.pyc,,
pip/_internal/operations/__pycache__/freeze.cpython-313.pyc,,
pip/_internal/operations/__pycache__/prepare.cpython-313.pyc,,
pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/operations/build/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/build_tracker.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/metadata.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/metadata_editable.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/wheel_editable.cpython-313.pyc,,
pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-313.pyc,,
pip/_internal/operations/build/build_tracker.py,sha256=-ARW_TcjHCOX7D2NUOGntB4Fgc6b4aolsXkAK6BWL7w,4774
pip/_internal/operations/build/metadata.py,sha256=9S0CUD8U3QqZeXp-Zyt8HxwU90lE4QrnYDgrqZDzBnc,1422
pip/_internal/operations/build/metadata_editable.py,sha256=VLL7LvntKE8qxdhUdEJhcotFzUsOSI8NNS043xULKew,1474
pip/_internal/operations/build/metadata_legacy.py,sha256=8i6i1QZX9m_lKPStEFsHKM0MT4a-CD408JOw99daLmo,2190
pip/_internal/operations/build/wheel.py,sha256=sT12FBLAxDC6wyrDorh8kvcZ1jG5qInCRWzzP-UkJiQ,1075
pip/_internal/operations/build/wheel_editable.py,sha256=yOtoH6zpAkoKYEUtr8FhzrYnkNHQaQBjWQ2HYae1MQg,1417
pip/_internal/operations/build/wheel_legacy.py,sha256=K-6kNhmj-1xDF45ny1yheMerF0ui4EoQCLzEoHh6-tc,3045
pip/_internal/operations/check.py,sha256=L24vRL8VWbyywdoeAhM89WCd8zLTnjIbULlKelUgIec,5912
pip/_internal/operations/freeze.py,sha256=V59yEyCSz_YhZuhH09-6aV_zvYBMrS_IxFFNqn2QzlA,9864
pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51
pip/_internal/operations/install/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/operations/install/__pycache__/editable_legacy.cpython-313.pyc,,
pip/_internal/operations/install/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/operations/install/editable_legacy.py,sha256=PoEsNEPGbIZ2yQphPsmYTKLOCMs4gv5OcCdzW124NcA,1283
pip/_internal/operations/install/wheel.py,sha256=X5Iz9yUg5LlK5VNQ9g2ikc6dcRu8EPi_SUi5iuEDRgo,27615
pip/_internal/operations/prepare.py,sha256=joWJwPkuqGscQgVNImLK71e9hRapwKvRCM8HclysmvU,28118
pip/_internal/pyproject.py,sha256=rw4fwlptDp1hZgYoplwbAGwWA32sWQkp7ysf8Ju6iXc,7287
pip/_internal/req/__init__.py,sha256=HxBFtZy_BbCclLgr26waMtpzYdO5T3vxePvpGAXSt5s,2653
pip/_internal/req/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/req/__pycache__/constructors.cpython-313.pyc,,
pip/_internal/req/__pycache__/req_file.cpython-313.pyc,,
pip/_internal/req/__pycache__/req_install.cpython-313.pyc,,
pip/_internal/req/__pycache__/req_set.cpython-313.pyc,,
pip/_internal/req/__pycache__/req_uninstall.cpython-313.pyc,,
pip/_internal/req/constructors.py,sha256=v1qzCN1mIldwx-nCrPc8JO4lxkm3Fv8M5RWvt8LISjc,18430
pip/_internal/req/req_file.py,sha256=gOOJTzL-mDRPcQhjwqjDrjn4V-3rK9TnEFnU3v8RA4Q,18752
pip/_internal/req/req_install.py,sha256=yhT98NGDoAEk03jznTJnYCznzhiMEEA2ocgsUG_dcNU,35788
pip/_internal/req/req_set.py,sha256=j3esG0s6SzoVReX9rWn4rpYNtyET_fwxbwJPRimvRxo,2858
pip/_internal/req/req_uninstall.py,sha256=qzDIxJo-OETWqGais7tSMCDcWbATYABT-Tid3ityF0s,23853
pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/resolution/__pycache__/base.cpython-313.pyc,,
pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583
pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/legacy/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/resolution/legacy/__pycache__/resolver.cpython-313.pyc,,
pip/_internal/resolution/legacy/resolver.py,sha256=3HZiJBRd1FTN6jQpI4qRO8-TbLYeIbUTS6PFvXnXs2w,24068
pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/base.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-313.pyc,,
pip/_internal/resolution/resolvelib/base.py,sha256=DCf669FsqyQY5uqXeePDHQY1e4QO-pBzWH8O0s9-K94,5023
pip/_internal/resolution/resolvelib/candidates.py,sha256=5UZ1upNnmqsP-nmEZaDYxaBgCoejw_e2WVGmmAvBxXc,20001
pip/_internal/resolution/resolvelib/factory.py,sha256=511CaUR41LqjALuFafLVfx15WRvMhxYTdjQCoSvp4gw,32661
pip/_internal/resolution/resolvelib/found_candidates.py,sha256=9hrTyQqFvl9I7Tji79F1AxHv39Qh1rkJ_7deSHSMfQc,6383
pip/_internal/resolution/resolvelib/provider.py,sha256=bcsFnYvlmtB80cwVdW1fIwgol8ZNr1f1VHyRTkz47SM,9935
pip/_internal/resolution/resolvelib/reporter.py,sha256=00JtoXEkTlw0-rl_sl54d71avwOsJHt9GGHcrj5Sza0,3168
pip/_internal/resolution/resolvelib/requirements.py,sha256=7JG4Z72e5Yk4vU0S5ulGvbqTy4FMQGYhY5zQhX9zTtY,8065
pip/_internal/resolution/resolvelib/resolver.py,sha256=nLJOsVMEVi2gQUVJoUFKMZAeu2f7GRMjGMvNSWyz0Bc,12592
pip/_internal/self_outdated_check.py,sha256=pkjQixuWyQ1vrVxZAaYD6SSHgXuFUnHZybXEWTkh0S0,8145
pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/utils/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/utils/__pycache__/_jaraco_text.cpython-313.pyc,,
pip/_internal/utils/__pycache__/_log.cpython-313.pyc,,
pip/_internal/utils/__pycache__/appdirs.cpython-313.pyc,,
pip/_internal/utils/__pycache__/compat.cpython-313.pyc,,
pip/_internal/utils/__pycache__/compatibility_tags.cpython-313.pyc,,
pip/_internal/utils/__pycache__/datetime.cpython-313.pyc,,
pip/_internal/utils/__pycache__/deprecation.cpython-313.pyc,,
pip/_internal/utils/__pycache__/direct_url_helpers.cpython-313.pyc,,
pip/_internal/utils/__pycache__/egg_link.cpython-313.pyc,,
pip/_internal/utils/__pycache__/encoding.cpython-313.pyc,,
pip/_internal/utils/__pycache__/entrypoints.cpython-313.pyc,,
pip/_internal/utils/__pycache__/filesystem.cpython-313.pyc,,
pip/_internal/utils/__pycache__/filetypes.cpython-313.pyc,,
pip/_internal/utils/__pycache__/glibc.cpython-313.pyc,,
pip/_internal/utils/__pycache__/hashes.cpython-313.pyc,,
pip/_internal/utils/__pycache__/logging.cpython-313.pyc,,
pip/_internal/utils/__pycache__/misc.cpython-313.pyc,,
pip/_internal/utils/__pycache__/packaging.cpython-313.pyc,,
pip/_internal/utils/__pycache__/retry.cpython-313.pyc,,
pip/_internal/utils/__pycache__/setuptools_build.cpython-313.pyc,,
pip/_internal/utils/__pycache__/subprocess.cpython-313.pyc,,
pip/_internal/utils/__pycache__/temp_dir.cpython-313.pyc,,
pip/_internal/utils/__pycache__/unpacking.cpython-313.pyc,,
pip/_internal/utils/__pycache__/urls.cpython-313.pyc,,
pip/_internal/utils/__pycache__/virtualenv.cpython-313.pyc,,
pip/_internal/utils/__pycache__/wheel.cpython-313.pyc,,
pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350
pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015
pip/_internal/utils/appdirs.py,sha256=swgcTKOm3daLeXTW6v5BUS2Ti2RvEnGRQYH_yDXklAo,1665
pip/_internal/utils/compat.py,sha256=ckkFveBiYQjRWjkNsajt_oWPS57tJvE8XxoC4OIYgCY,2399
pip/_internal/utils/compatibility_tags.py,sha256=OWq5axHpW-MEEPztGdvgADrgJPAcV9a88Rxm4Z8VBs8,6272
pip/_internal/utils/datetime.py,sha256=m21Y3wAtQc-ji6Veb6k_M5g6A0ZyFI4egchTdnwh-pQ,242
pip/_internal/utils/deprecation.py,sha256=k7Qg_UBAaaTdyq82YVARA6D7RmcGTXGv7fnfcgigj4Q,3707
pip/_internal/utils/direct_url_helpers.py,sha256=r2MRtkVDACv9AGqYODBUC9CjwgtsUU1s68hmgfCJMtA,3196
pip/_internal/utils/egg_link.py,sha256=0FePZoUYKv4RGQ2t6x7w5Z427wbA_Uo3WZnAkrgsuqo,2463
pip/_internal/utils/encoding.py,sha256=qqsXDtiwMIjXMEiIVSaOjwH5YmirCaK-dIzb6-XJsL0,1169
pip/_internal/utils/entrypoints.py,sha256=YlhLTRl2oHBAuqhc-zmL7USS67TPWVHImjeAQHreZTQ,3064
pip/_internal/utils/filesystem.py,sha256=ajvA-q4ocliW9kPp8Yquh-4vssXbu-UKbo5FV9V4X64,4950
pip/_internal/utils/filetypes.py,sha256=i8XAQ0eFCog26Fw9yV0Yb1ygAqKYB1w9Cz9n0fj8gZU,716
pip/_internal/utils/glibc.py,sha256=vUkWq_1pJuzcYNcGKLlQmABoUiisK8noYY1yc8Wq4w4,3734
pip/_internal/utils/hashes.py,sha256=XGGLL0AG8-RhWnyz87xF6MFZ--BKadHU35D47eApCKI,4972
pip/_internal/utils/logging.py,sha256=7BFKB1uFjdxD5crM-GtwA5T2qjbQ2LPD-gJDuJeDNTg,11606
pip/_internal/utils/misc.py,sha256=NRV0_2fFhzy1jhvInSBv4dqCmTwct8PV7Kp0m-BPRGM,23530
pip/_internal/utils/packaging.py,sha256=iI3LH43lVNR4hWBOqF6lFsZq4aycb2j0UcHlmDmcqUg,2109
pip/_internal/utils/retry.py,sha256=mhFbykXjhTnZfgzeuy-vl9c8nECnYn_CMtwNJX2tYzQ,1392
pip/_internal/utils/setuptools_build.py,sha256=ouXpud-jeS8xPyTPsXJ-m34NPvK5os45otAzdSV_IJE,4435
pip/_internal/utils/subprocess.py,sha256=EsvqSRiSMHF98T8Txmu6NLU3U--MpTTQjtNgKP0P--M,8988
pip/_internal/utils/temp_dir.py,sha256=5qOXe8M4JeY6vaFQM867d5zkp1bSwMZ-KT5jymmP0Zg,9310
pip/_internal/utils/unpacking.py,sha256=eyDkSsk4nW8ZfiSjNzJduCznpHyaGHVv3ak_LMGsiEM,11951
pip/_internal/utils/urls.py,sha256=qceSOZb5lbNDrHNsv7_S4L4Ytszja5NwPKUMnZHbYnM,1599
pip/_internal/utils/virtualenv.py,sha256=S6f7csYorRpiD6cvn3jISZYc3I8PJC43H5iMFpRAEDU,3456
pip/_internal/utils/wheel.py,sha256=b442jkydFHjXzDy6cMR7MpzWBJ1Q82hR5F33cmcHV3g,4494
pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596
pip/_internal/vcs/__pycache__/__init__.cpython-313.pyc,,
pip/_internal/vcs/__pycache__/bazaar.cpython-313.pyc,,
pip/_internal/vcs/__pycache__/git.cpython-313.pyc,,
pip/_internal/vcs/__pycache__/mercurial.cpython-313.pyc,,
pip/_internal/vcs/__pycache__/subversion.cpython-313.pyc,,
pip/_internal/vcs/__pycache__/versioncontrol.cpython-313.pyc,,
pip/_internal/vcs/bazaar.py,sha256=EKStcQaKpNu0NK4p5Q10Oc4xb3DUxFw024XrJy40bFQ,3528
pip/_internal/vcs/git.py,sha256=3tpc9LQA_J4IVW5r5NvWaaSeDzcmJOrSFZN0J8vIKfU,18177
pip/_internal/vcs/mercurial.py,sha256=oULOhzJ2Uie-06d1omkL-_Gc6meGaUkyogvqG9ZCyPs,5249
pip/_internal/vcs/subversion.py,sha256=ddTugHBqHzV3ebKlU5QXHPN4gUqlyXbOx8q8NgXKvs8,11735
pip/_internal/vcs/versioncontrol.py,sha256=cvf_-hnTAjQLXJ3d17FMNhQfcO1AcKWUF10tfrYyP-c,22440
pip/_internal/wheel_builder.py,sha256=DL3A8LKeRj_ACp11WS5wSgASgPFqeyAeXJKdXfmaWXU,11799
pip/_vendor/__init__.py,sha256=JYuAXvClhInxIrA2FTp5p-uuWVL7WV6-vEpTs46-Qh4,4873
pip/_vendor/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/__pycache__/typing_extensions.cpython-313.pyc,,
pip/_vendor/cachecontrol/__init__.py,sha256=GiYoagwPEiJ_xR_lbwWGaoCiPtF_rz4isjfjdDAgHU4,676
pip/_vendor/cachecontrol/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/adapter.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/cache.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/controller.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/serialize.cpython-313.pyc,,
pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-313.pyc,,
pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737
pip/_vendor/cachecontrol/adapter.py,sha256=fByO_Pd_EOemjWbuocvBWdN85xT0q_TBm2lxS6vD4fk,6355
pip/_vendor/cachecontrol/cache.py,sha256=OTQj72tUf8C1uEgczdl3Gc8vkldSzsTITKtDGKMx4z8,1952
pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303
pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-313.pyc,,
pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-313.pyc,,
pip/_vendor/cachecontrol/caches/file_cache.py,sha256=9AlmmTJc6cslb6k5z_6q0sGPHVrMj8zv-uWy-simmfE,5406
pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386
pip/_vendor/cachecontrol/controller.py,sha256=o-ejGJlBmpKK8QQLyTPJj0t7siU8XVHXuV8MCybCxQ8,18575
pip/_vendor/cachecontrol/filewrapper.py,sha256=STttGmIPBvZzt2b51dUOwoWX5crcMCpKZOisM3f5BNc,4292
pip/_vendor/cachecontrol/heuristics.py,sha256=IYe4QmHERWsMvtxNrp920WeaIsaTTyqLB14DSheSbtY,4834
pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163
pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417
pip/_vendor/certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94
pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255
pip/_vendor/certifi/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/certifi/__pycache__/__main__.cpython-313.pyc,,
pip/_vendor/certifi/__pycache__/core.cpython-313.pyc,,
pip/_vendor/certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427
pip/_vendor/certifi/core.py,sha256=2SRT5rIcQChFDbe37BQa-kULxAgJ8qN6l1jfqTp4HIs,4486
pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/distlib/__init__.py,sha256=dcwgYGYGQqAEawBXPDtIx80DO_3cOmFv8HTc8JMzknQ,625
pip/_vendor/distlib/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/compat.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/database.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/index.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/locators.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/manifest.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/markers.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/metadata.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/resources.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/scripts.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/util.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/version.cpython-313.pyc,,
pip/_vendor/distlib/__pycache__/wheel.cpython-313.pyc,,
pip/_vendor/distlib/compat.py,sha256=2jRSjRI4o-vlXeTK2BCGIUhkc6e9ZGhSsacRM5oseTw,41467
pip/_vendor/distlib/database.py,sha256=mHy_LxiXIsIVRb-T0-idBrVLw3Ffij5teHCpbjmJ9YU,51160
pip/_vendor/distlib/index.py,sha256=lTbw268rRhj8dw1sib3VZ_0EhSGgoJO3FKJzSFMOaeA,20797
pip/_vendor/distlib/locators.py,sha256=oBeAZpFuPQSY09MgNnLfQGGAXXvVO96BFpZyKMuK4tM,51026
pip/_vendor/distlib/manifest.py,sha256=3qfmAmVwxRqU1o23AlfXrQGZzh6g_GGzTAP_Hb9C5zQ,14168
pip/_vendor/distlib/markers.py,sha256=X6sDvkFGcYS8gUW8hfsWuKEKAqhQZAJ7iXOMLxRYjYk,5164
pip/_vendor/distlib/metadata.py,sha256=zil3sg2EUfLXVigljY2d_03IJt-JSs7nX-73fECMX2s,38724
pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820
pip/_vendor/distlib/scripts.py,sha256=BJliaDAZaVB7WAkwokgC3HXwLD2iWiHaVI50H7C6eG8,18608
pip/_vendor/distlib/t32.exe,sha256=a0GV5kCoWsMutvliiCKmIgV98eRZ33wXoS-XrqvJQVs,97792
pip/_vendor/distlib/t64-arm.exe,sha256=68TAa32V504xVBnufojh0PcenpR3U4wAqTqf-MZqbPw,182784
pip/_vendor/distlib/t64.exe,sha256=gaYY8hy4fbkHYTTnA4i26ct8IQZzkBG2pRdy0iyuBrc,108032
pip/_vendor/distlib/util.py,sha256=vMPGvsS4j9hF6Y9k3Tyom1aaHLb0rFmZAEyzeAdel9w,66682
pip/_vendor/distlib/version.py,sha256=s5VIs8wBn0fxzGxWM_aA2ZZyx525HcZbMvcTlTyZ3Rg,23727
pip/_vendor/distlib/w32.exe,sha256=R4csx3-OGM9kL4aPIzQKRo5TfmRSHZo6QWyLhDhNBks,91648
pip/_vendor/distlib/w64-arm.exe,sha256=xdyYhKj0WDcVUOCb05blQYvzdYIKMbmJn2SZvzkcey4,168448
pip/_vendor/distlib/w64.exe,sha256=ejGf-rojoBfXseGLpya6bFTFPWRG21X5KvU8J5iU-K0,101888
pip/_vendor/distlib/wheel.py,sha256=DFIVguEQHCdxnSdAO0dfFsgMcvVZitg7bCOuLwZ7A_s,43979
pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981
pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64
pip/_vendor/distro/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/distro/__pycache__/__main__.cpython-313.pyc,,
pip/_vendor/distro/__pycache__/distro.cpython-313.pyc,,
pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430
pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849
pip/_vendor/idna/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/codec.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/compat.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/core.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/idnadata.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/intranges.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/package_data.cpython-313.pyc,,
pip/_vendor/idna/__pycache__/uts46data.cpython-313.pyc,,
pip/_vendor/idna/codec.py,sha256=PS6m-XmdST7Wj7J7ulRMakPDt5EBJyYrT3CPtjh-7t4,3426
pip/_vendor/idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321
pip/_vendor/idna/core.py,sha256=lyhpoe2vulEaB_65xhXmoKgO-xUqFDvcwxu5hpNNO4E,12663
pip/_vendor/idna/idnadata.py,sha256=dqRwytzkjIHMBa2R1lYvHDwACenZPt8eGVu1Y8UBE-E,78320
pip/_vendor/idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881
pip/_vendor/idna/package_data.py,sha256=Tkt0KnIeyIlnHddOaz9WSkkislNgokJAuE-p5GorMqo,21
pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/idna/uts46data.py,sha256=1KuksWqLuccPXm2uyRVkhfiFLNIhM_H2m4azCcnOqEU,206503
pip/_vendor/msgpack/__init__.py,sha256=gsMP7JTECZNUSjvOyIbdhNOkpB9Z8BcGwabVGY2UcdQ,1077
pip/_vendor/msgpack/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/msgpack/__pycache__/exceptions.cpython-313.pyc,,
pip/_vendor/msgpack/__pycache__/ext.cpython-313.pyc,,
pip/_vendor/msgpack/__pycache__/fallback.cpython-313.pyc,,
pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081
pip/_vendor/msgpack/ext.py,sha256=fKp00BqDLjUtZnPd70Llr138zk8JsCuSpJkkZ5S4dt8,5629
pip/_vendor/msgpack/fallback.py,sha256=wdUWJkWX2gzfRW9BBCTOuIE1Wvrf5PtBtR8ZtY7G_EE,33175
pip/_vendor/packaging/__init__.py,sha256=dtw2bNmWCQ9WnMoK3bk_elL1svSlikXtLpZhCFIB9SE,496
pip/_vendor/packaging/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_elffile.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_manylinux.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_musllinux.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_parser.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_structures.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/_tokenizer.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/markers.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/metadata.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/requirements.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/specifiers.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/tags.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/utils.cpython-313.pyc,,
pip/_vendor/packaging/__pycache__/version.cpython-313.pyc,,
pip/_vendor/packaging/_elffile.py,sha256=_LcJW4YNKywYsl4169B2ukKRqwxjxst_8H0FRVQKlz8,3282
pip/_vendor/packaging/_manylinux.py,sha256=Xo4V0PZz8sbuVCbTni0t1CR0AHeir_7ib4lTmV8scD4,9586
pip/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
pip/_vendor/packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236
pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
pip/_vendor/packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273
pip/_vendor/packaging/markers.py,sha256=dWKSqn5Sp-jDmOG-W3GfLHKjwhf1IsznbT71VlBoB5M,10671
pip/_vendor/packaging/metadata.py,sha256=KINuSkJ12u-SyoKNTy_pHNGAfMUtxNvZ53qA1zAKcKI,32349
pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
pip/_vendor/packaging/specifiers.py,sha256=HfGgfNJRvrzC759gnnoojHyiWs_DYmcw5PEh5jHH-YE,39738
pip/_vendor/packaging/tags.py,sha256=Fo6_cit95-7QfcMb16XtI7AUiSMgdwA_hCO_9lV2pz4,21388
pip/_vendor/packaging/utils.py,sha256=NAdYUwnlAOpkat_RthavX8a07YuVxgGL_vwrx73GSDM,5287
pip/_vendor/packaging/version.py,sha256=wE4sSVlF-d1H6HFC1vszEe35CwTig_fh4HHIFg95hFE,16210
pip/_vendor/pkg_resources/__init__.py,sha256=jrhDRbOubP74QuPXxd7U7Po42PH2l-LZ2XfcO7llpZ4,124463
pip/_vendor/pkg_resources/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/platformdirs/__init__.py,sha256=FTA6LGNm40GwNZt3gG3uLAacWvf2E_2HTmH0rAALGR8,22285
pip/_vendor/platformdirs/__main__.py,sha256=jBJ8zb7Mpx5ebcqF83xrpO94MaeCpNGHVf9cvDN2JLg,1505
pip/_vendor/platformdirs/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/__main__.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/android.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/api.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/macos.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/unix.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/version.cpython-313.pyc,,
pip/_vendor/platformdirs/__pycache__/windows.cpython-313.pyc,,
pip/_vendor/platformdirs/android.py,sha256=xZXY9Jd46WOsxT2U6-5HsNtDZ-IQqxcEUrBLl3hYk4o,9016
pip/_vendor/platformdirs/api.py,sha256=QBYdUac2eC521ek_y53uD1Dcq-lJX8IgSRVd4InC6uc,8996
pip/_vendor/platformdirs/macos.py,sha256=wftsbsvq6nZ0WORXSiCrZNkRHz_WKuktl0a6mC7MFkI,5580
pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/platformdirs/unix.py,sha256=Cci9Wqt35dAMsg6HT9nRGHSBW5obb0pR3AE1JJnsCXg,10643
pip/_vendor/platformdirs/version.py,sha256=r7F76tZRjgQKzrpx_I0_ZMQOMU-PS7eGnHD7zEK3KB0,411
pip/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125
pip/_vendor/pygments/__init__.py,sha256=7N1oiaWulw_nCsTY4EEixYLz15pWY5u4uPAFFi-ielU,2983
pip/_vendor/pygments/__main__.py,sha256=isIhBxLg65nLlXukG4VkMuPfNdd7gFzTZ_R_z3Q8diY,353
pip/_vendor/pygments/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/__main__.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/cmdline.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/console.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/filter.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/formatter.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/lexer.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/modeline.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/plugin.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/regexopt.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/scanner.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/sphinxext.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/style.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/token.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/unistring.cpython-313.pyc,,
pip/_vendor/pygments/__pycache__/util.cpython-313.pyc,,
pip/_vendor/pygments/cmdline.py,sha256=LIVzmAunlk9sRJJp54O4KRy9GDIN4Wu13v9p9QzfGPM,23656
pip/_vendor/pygments/console.py,sha256=yhP9UsLAVmWKVQf2446JJewkA7AiXeeTf4Ieg3Oi2fU,1718
pip/_vendor/pygments/filter.py,sha256=_ADNPCskD8_GmodHi6_LoVgPU3Zh336aBCT5cOeTMs0,1910
pip/_vendor/pygments/filters/__init__.py,sha256=RdedK2KWKXlKwR7cvkfr3NUj9YiZQgMgilRMFUg2jPA,40392
pip/_vendor/pygments/filters/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pygments/formatter.py,sha256=jDWBTndlBH2Z5IYZFVDnP0qn1CaTQjTWt7iAGtCnJEg,4390
pip/_vendor/pygments/formatters/__init__.py,sha256=8No-NUs8rBTSSBJIv4hSEQt2M0cFB4hwAT0snVc2QGE,5385
pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/groff.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/html.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/img.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/irc.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/latex.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/other.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/svg.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-313.pyc,,
pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-313.pyc,,
pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176
pip/_vendor/pygments/formatters/bbcode.py,sha256=3JQLI45tcrQ_kRUMjuab6C7Hb0XUsbVWqqbSn9cMjkI,3320
pip/_vendor/pygments/formatters/groff.py,sha256=M39k0PaSSZRnxWjqBSVPkF0mu1-Vr7bm6RsFvs-CNN4,5106
pip/_vendor/pygments/formatters/html.py,sha256=SE2jc3YCqbMS3rZW9EAmDlAUhdVxJ52gA4dileEvCGU,35669
pip/_vendor/pygments/formatters/img.py,sha256=MwA4xWPLOwh6j7Yc6oHzjuqSPt0M1fh5r-5BTIIUfsU,23287
pip/_vendor/pygments/formatters/irc.py,sha256=dp1Z0l_ObJ5NFh9MhqLGg5ptG5hgJqedT2Vkutt9v0M,4981
pip/_vendor/pygments/formatters/latex.py,sha256=XMmhOCqUKDBQtG5mGJNAFYxApqaC5puo5cMmPfK3944,19306
pip/_vendor/pygments/formatters/other.py,sha256=56PMJOliin-rAUdnRM0i1wsV1GdUPd_dvQq0_UPfF9c,5034
pip/_vendor/pygments/formatters/pangomarkup.py,sha256=y16U00aVYYEFpeCfGXlYBSMacG425CbfoG8oKbKegIg,2218
pip/_vendor/pygments/formatters/rtf.py,sha256=ZT90dmcKyJboIB0mArhL7IhE467GXRN0G7QAUgG03To,11957
pip/_vendor/pygments/formatters/svg.py,sha256=KKsiophPupHuxm0So-MsbQEWOT54IAiSF7hZPmxtKXE,7174
pip/_vendor/pygments/formatters/terminal.py,sha256=AojNG4MlKq2L6IsC_VnXHu4AbHCBn9Otog6u45XvxeI,4674
pip/_vendor/pygments/formatters/terminal256.py,sha256=kGkNUVo3FpwjytIDS0if79EuUoroAprcWt3igrcIqT0,11753
pip/_vendor/pygments/lexer.py,sha256=TYHDt___gNW4axTl2zvPZff-VQi8fPaIh5OKRcVSjUM,35349
pip/_vendor/pygments/lexers/__init__.py,sha256=pIlxyQJuu_syh9lE080cq8ceVbEVcKp0osAFU5fawJU,12115
pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-313.pyc,,
pip/_vendor/pygments/lexers/__pycache__/python.cpython-313.pyc,,
pip/_vendor/pygments/lexers/_mapping.py,sha256=61-h3zr103m01OS5BUq_AfUiL9YI06Ves9ipQ7k4vr4,76097
pip/_vendor/pygments/lexers/python.py,sha256=2J_YJrPTr_A6fJY_qKiKv0GpgPwHMrlMSeo59qN3fe4,53687
pip/_vendor/pygments/modeline.py,sha256=gtRYZBS-CKOCDXHhGZqApboHBaZwGH8gznN3O6nuxj4,1005
pip/_vendor/pygments/plugin.py,sha256=ioeJ3QeoJ-UQhZpY9JL7vbxsTVuwwM7BCu-Jb8nN0AU,1891
pip/_vendor/pygments/regexopt.py,sha256=Hky4EB13rIXEHQUNkwmCrYqtIlnXDehNR3MztafZ43w,3072
pip/_vendor/pygments/scanner.py,sha256=NDy3ofK_fHRFK4hIDvxpamG871aewqcsIb6sgTi7Fhk,3092
pip/_vendor/pygments/sphinxext.py,sha256=iOptJBcqOGPwMEJ2p70PvwpZPIGdvdZ8dxvq6kzxDgA,7981
pip/_vendor/pygments/style.py,sha256=rSCZWFpg1_DwFMXDU0nEVmAcBHpuQGf9RxvOPPQvKLQ,6420
pip/_vendor/pygments/styles/__init__.py,sha256=qUk6_1z5KmT8EdJFZYgESmG6P_HJF_2vVrDD7HSCGYY,2042
pip/_vendor/pygments/styles/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-313.pyc,,
pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312
pip/_vendor/pygments/token.py,sha256=qZwT7LSPy5YBY3JgDjut642CCy7JdQzAfmqD9NmT5j0,6226
pip/_vendor/pygments/unistring.py,sha256=p5c1i-HhoIhWemy9CUsaN9o39oomYHNxXll0Xfw6tEA,63208
pip/_vendor/pygments/util.py,sha256=2tj2nS1X9_OpcuSjf8dOET2bDVZhs8cEKd_uT6-Fgg8,10031
pip/_vendor/pyproject_hooks/__init__.py,sha256=kCehmy0UaBa9oVMD7ZIZrnswfnP3LXZ5lvnNJAL5JBM,491
pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-313.pyc,,
pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-313.pyc,,
pip/_vendor/pyproject_hooks/_compat.py,sha256=by6evrYnqkisiM-MQcvOKs5bgDMzlOSgZqRHNqf04zE,138
pip/_vendor/pyproject_hooks/_impl.py,sha256=61GJxzQip0IInhuO69ZI5GbNQ82XEDUB_1Gg5_KtUoc,11920
pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=9gQATptbFkelkIy0OfWFEACzqxXJMQDWCH9rBOAZVwQ,546
pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-313.pyc,,
pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=m2b34c917IW5o-Q_6TYIHlsK9lSUlNiyrITTUH_zwew,10927
pip/_vendor/requests/__init__.py,sha256=HlB_HzhrzGtfD_aaYUwUh1zWXLZ75_YCLyit75d0Vz8,5057
pip/_vendor/requests/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/__version__.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/_internal_utils.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/adapters.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/api.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/auth.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/certs.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/compat.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/cookies.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/exceptions.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/help.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/hooks.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/models.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/packages.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/sessions.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/status_codes.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/structures.cpython-313.pyc,,
pip/_vendor/requests/__pycache__/utils.cpython-313.pyc,,
pip/_vendor/requests/__version__.py,sha256=FVfglgZmNQnmYPXpOohDU58F5EUb_-VnSTaAesS187g,435
pip/_vendor/requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495
pip/_vendor/requests/adapters.py,sha256=J7VeVxKBvawbtlX2DERVo05J9BXTcWYLMHNd1Baa-bk,27607
pip/_vendor/requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449
pip/_vendor/requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186
pip/_vendor/requests/certs.py,sha256=PVPooB0jP5hkZEULSCwC074532UFbR2Ptgu0I5zwmCs,575
pip/_vendor/requests/compat.py,sha256=Mo9f9xZpefod8Zm-n9_StJcVTmwSukXR2p3IQyyVXvU,1485
pip/_vendor/requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590
pip/_vendor/requests/exceptions.py,sha256=D1wqzYWne1mS2rU43tP9CeN1G7QAy7eqL9o1god6Ejw,4272
pip/_vendor/requests/help.py,sha256=hRKaf9u0G7fdwrqMHtF3oG16RKktRf6KiwtSq2Fo1_0,3813
pip/_vendor/requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733
pip/_vendor/requests/models.py,sha256=x4K4CmH-lC0l2Kb-iPfMN4dRXxHEcbOaEWBL_i09AwI,35483
pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057
pip/_vendor/requests/sessions.py,sha256=ykTI8UWGSltOfH07HKollH7kTBGw4WhiBVaQGmckTw4,30495
pip/_vendor/requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322
pip/_vendor/requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912
pip/_vendor/requests/utils.py,sha256=L79vnFbzJ3SFLKtJwpoWe41Tozi3RlZv94pY1TFIyow,33631
pip/_vendor/resolvelib/__init__.py,sha256=h509TdEcpb5-44JonaU3ex2TM15GVBLjM9CNCPwnTTs,537
pip/_vendor/resolvelib/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/resolvelib/__pycache__/providers.cpython-313.pyc,,
pip/_vendor/resolvelib/__pycache__/reporters.cpython-313.pyc,,
pip/_vendor/resolvelib/__pycache__/resolvers.cpython-313.pyc,,
pip/_vendor/resolvelib/__pycache__/structs.cpython-313.pyc,,
pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-313.pyc,,
pip/_vendor/resolvelib/compat/collections_abc.py,sha256=uy8xUZ-NDEw916tugUXm8HgwCGiMO0f-RcdnpkfXfOs,156
pip/_vendor/resolvelib/providers.py,sha256=fuuvVrCetu5gsxPB43ERyjfO8aReS3rFQHpDgiItbs4,5871
pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/resolvelib/reporters.py,sha256=TSbRmWzTc26w0ggsV1bxVpeWDB8QNIre6twYl7GIZBE,1601
pip/_vendor/resolvelib/resolvers.py,sha256=G8rsLZSq64g5VmIq-lB7UcIJ1gjAxIQJmTF4REZleQ0,20511
pip/_vendor/resolvelib/structs.py,sha256=0_1_XO8z_CLhegP3Vpf9VJ3zJcfLm0NOHRM-i0Ykz3o,4963
pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090
pip/_vendor/rich/__main__.py,sha256=eO7Cq8JnrgG8zVoeImiAs92q3hXNMIfp0w5lMsO7Q2Y,8477
pip/_vendor/rich/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/__main__.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_cell_widths.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_emoji_codes.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_emoji_replace.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_export_format.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_extension.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_fileno.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_inspect.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_log_render.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_loop.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_null_file.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_palettes.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_pick.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_ratio.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_spinners.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_stack.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_timer.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_win32_console.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_windows.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_windows_renderer.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/_wrap.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/abc.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/align.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/ansi.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/bar.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/box.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/cells.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/color.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/color_triplet.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/columns.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/console.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/constrain.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/containers.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/control.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/default_styles.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/diagnose.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/emoji.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/errors.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/file_proxy.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/filesize.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/highlighter.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/json.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/jupyter.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/layout.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/live.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/live_render.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/logging.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/markup.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/measure.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/padding.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/pager.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/palette.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/panel.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/pretty.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/progress.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/progress_bar.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/prompt.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/protocol.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/region.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/repr.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/rule.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/scope.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/screen.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/segment.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/spinner.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/status.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/style.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/styled.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/syntax.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/table.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/terminal_theme.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/text.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/theme.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/themes.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/traceback.cpython-313.pyc,,
pip/_vendor/rich/__pycache__/tree.cpython-313.pyc,,
pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209
pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235
pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064
pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128
pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265
pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799
pip/_vendor/rich/_inspect.py,sha256=oZJGw31e64dwXSCmrDnvZbwVb1ZKhWfU8wI3VWohjJk,9695
pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225
pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236
pip/_vendor/rich/_null_file.py,sha256=tGSXk_v-IZmbj1GAzHit8A3kYIQMiCpVsCFfsC-_KJ4,1387
pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063
pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423
pip/_vendor/rich/_ratio.py,sha256=Zt58apszI6hAAcXPpgdWKpu3c31UBWebOeR4mbyptvU,5471
pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919
pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351
pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417
pip/_vendor/rich/_win32_console.py,sha256=P0vxI2fcndym1UU1S37XAzQzQnkyY7YqAKmxm24_gug,22820
pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925
pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783
pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404
pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890
pip/_vendor/rich/align.py,sha256=sCUkisXkQfoq-IQPyBELfJ8l7LihZJX3HbH8K7Cie-M,10368
pip/_vendor/rich/ansi.py,sha256=iD6532QYqnBm6hADulKjrV8l8kFJ-9fEVooHJHH3hMg,6906
pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263
pip/_vendor/rich/box.py,sha256=nr5fYIUghB_iUCEq6y0Z3LlCT8gFPDrzN9u2kn7tJl4,10831
pip/_vendor/rich/cells.py,sha256=aMmGK4BjXhgE6_JF1ZEGmW3O7mKkE8g84vUnj4Et4To,4780
pip/_vendor/rich/color.py,sha256=bCRATVdRe5IClJ6Hl62de2PKQ_U4i2MZ4ugjUEg7Tao,18223
pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054
pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131
pip/_vendor/rich/console.py,sha256=deFZIubq2M9A2MCsKFAsFQlWDvcOMsGuUA07QkOaHIw,99173
pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288
pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502
pip/_vendor/rich/control.py,sha256=DSkHTUQLorfSERAKE_oTAEUFefZnZp4bQb4q8rHbKws,6630
pip/_vendor/rich/default_styles.py,sha256=-Fe318kMVI_IwciK5POpThcO0-9DYJ67TZAN6DlmlmM,8082
pip/_vendor/rich/diagnose.py,sha256=an6uouwhKPAlvQhYpNNpGq9EJysfMIOvvCbO3oSoR24,972
pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501
pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642
pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683
pip/_vendor/rich/filesize.py,sha256=9fTLAPCAwHmBXdRv7KZU194jSgNrRb6Wx7RIoBgqeKY,2508
pip/_vendor/rich/highlighter.py,sha256=6ZAjUcNhBRajBCo9umFUclyi2xL0-55JL7S0vYGUJu4,9585
pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031
pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252
pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004
pip/_vendor/rich/live.py,sha256=vUcnJV2LMSK3sQNaILbm0-_B8BpAeiHfcQMAMLfpRe0,14271
pip/_vendor/rich/live_render.py,sha256=zJtB471jGziBtEwxc54x12wEQtH4BuQr1SA8v9kU82w,3666
pip/_vendor/rich/logging.py,sha256=uB-cB-3Q4bmXDLLpbOWkmFviw-Fde39zyMV6tKJ2WHQ,11903
pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451
pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305
pip/_vendor/rich/padding.py,sha256=kTFGsdGe0os7tXLnHKpwTI90CXEvrceeZGCshmJy5zw,4970
pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828
pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396
pip/_vendor/rich/panel.py,sha256=2Fd1V7e1kHxlPFIusoHY5T7-Cs0RpkrihgVG9ZVqJ4g,10705
pip/_vendor/rich/pretty.py,sha256=5oIHP_CGWnHEnD0zMdW5qfGC5kHqIKn7zH_eC4crULE,35848
pip/_vendor/rich/progress.py,sha256=P02xi7T2Ua3qq17o83bkshe4c0v_45cg8VyTj6US6Vg,59715
pip/_vendor/rich/progress_bar.py,sha256=L4jw8E6Qb_x-jhOrLVhkuMaPmiAhFIl8jHQbWFrKuR8,8164
pip/_vendor/rich/prompt.py,sha256=wdOn2X8XTJKnLnlw6PoMY7xG4iUPp3ezt4O5gqvpV-E,11304
pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391
pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166
pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431
pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602
pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843
pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591
pip/_vendor/rich/segment.py,sha256=hU1ueeXqI6YeFa08K9DAjlF2QLxcJY9pwZx7RsXavlk,24246
pip/_vendor/rich/spinner.py,sha256=15koCmF0DQeD8-k28Lpt6X_zJQUlzEhgo_6A6uy47lc,4339
pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424
pip/_vendor/rich/style.py,sha256=3hiocH_4N8vwRm3-8yFWzM7tSwjjEven69XqWasSQwM,27073
pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258
pip/_vendor/rich/syntax.py,sha256=TnZDuOD4DeHFbkaVEAji1gf8qgAlMU9Boe_GksMGCkk,35475
pip/_vendor/rich/table.py,sha256=nGEvAZHF4dy1vT9h9Gj9O5qhSQO3ODAxJv0RY1vnIB8,39680
pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370
pip/_vendor/rich/text.py,sha256=5rQ3zvNrg5UZKNLecbh7fiw9v3HeFulNVtRY_CBDjjE,47312
pip/_vendor/rich/theme.py,sha256=belFJogzA0W0HysQabKaHOc3RWH2ko3fQAJhoN-AFdo,3777
pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102
pip/_vendor/rich/traceback.py,sha256=CUpxYLjQWIb6vQQ6O72X0hvDV6caryGqU6UweHgOyCY,29601
pip/_vendor/rich/tree.py,sha256=meAOUU6sYnoBEOX2ILrPLY9k5bWrWNQKkaiEFvHinXM,9167
pip/_vendor/tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396
pip/_vendor/tomli/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/tomli/__pycache__/_parser.cpython-313.pyc,,
pip/_vendor/tomli/__pycache__/_re.cpython-313.pyc,,
pip/_vendor/tomli/__pycache__/_types.cpython-313.pyc,,
pip/_vendor/tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633
pip/_vendor/tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943
pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
pip/_vendor/truststore/__init__.py,sha256=WIDeyzWm7EVX44g354M25vpRXbeY1lsPH6EmUJUcq4o,1264
pip/_vendor/truststore/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/truststore/__pycache__/_api.cpython-313.pyc,,
pip/_vendor/truststore/__pycache__/_macos.cpython-313.pyc,,
pip/_vendor/truststore/__pycache__/_openssl.cpython-313.pyc,,
pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-313.pyc,,
pip/_vendor/truststore/__pycache__/_windows.cpython-313.pyc,,
pip/_vendor/truststore/_api.py,sha256=GeXRNTlxPZ3kif4kNoh6JY0oE4QRzTGcgXr6l_X_Gk0,10555
pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503
pip/_vendor/truststore/_openssl.py,sha256=LLUZ7ZGaio-i5dpKKjKCSeSufmn6T8pi9lDcFnvSyq0,2324
pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130
pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993
pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/typing_extensions.py,sha256=78hFl0HpDY-ylHUVCnWdU5nTHxUP2-S-3wEZk6CQmLk,134499
pip/_vendor/urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333
pip/_vendor/urllib3/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/_collections.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/_version.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/connection.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/connectionpool.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/exceptions.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/fields.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/filepost.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/poolmanager.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/request.cpython-313.pyc,,
pip/_vendor/urllib3/__pycache__/response.cpython-313.pyc,,
pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372
pip/_vendor/urllib3/_version.py,sha256=t9wGB6ooOTXXgiY66K1m6BZS1CJyXHAU8EoWDTe6Shk,64
pip/_vendor/urllib3/connection.py,sha256=ttIA909BrbTUzwkqEe_TzZVh4JOOj7g61Ysei2mrwGg,20314
pip/_vendor/urllib3/connectionpool.py,sha256=e2eiAwNbFNCKxj4bwDKNK-w7HIdSz3OmMxU_TIt-evQ,40408
pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957
pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-313.pyc,,
pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632
pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922
pip/_vendor/urllib3/contrib/appengine.py,sha256=VR68eAVE137lxTgjBDwCna5UiBZTOKa01Aj_-5BaCz4,11036
pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528
pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=hDJh4MhyY_p-oKlFcYcQaVQRDv6GMmBGuW9yjxyeejM,17081
pip/_vendor/urllib3/contrib/securetransport.py,sha256=Fef1IIUUFHqpevzXiDPbIGkDKchY2FVKeVeLGR1Qq3g,34446
pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097
pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217
pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579
pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440
pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/packages/__pycache__/six.cpython-313.pyc,,
pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-313.pyc,,
pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-313.pyc,,
pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417
pip/_vendor/urllib3/packages/backports/weakref_finalize.py,sha256=tRCal5OAhNSRyb0DhHp-38AtIlCsRP8BxF3NX-6rqIA,5343
pip/_vendor/urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665
pip/_vendor/urllib3/poolmanager.py,sha256=aWyhXRtNO4JUnCSVVqKTKQd8EXTvUm1VN9pgs2bcONo,19990
pip/_vendor/urllib3/request.py,sha256=YTWFNr7QIwh7E1W9dde9LM77v2VWTJ5V78XuTTw7D1A,6691
pip/_vendor/urllib3/response.py,sha256=fmDJAFkG71uFTn-sVSTh2Iw0WmcXQYqkbRjihvwBjU8,30641
pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155
pip/_vendor/urllib3/util/__pycache__/__init__.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/connection.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/proxy.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/queue.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/request.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/response.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/retry.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/timeout.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/url.cpython-313.pyc,,
pip/_vendor/urllib3/util/__pycache__/wait.cpython-313.pyc,,
pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901
pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605
pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498
pip/_vendor/urllib3/util/request.py,sha256=C0OUt2tcU6LRiQJ7YYNP9GvPrSvl7ziIBekQ-5nlBZk,3997
pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510
pip/_vendor/urllib3/util/retry.py,sha256=6ENvOZ8PBDzh8kgixpql9lIrb2dxH-k7ZmBanJF2Ng4,22050
pip/_vendor/urllib3/util/ssl_.py,sha256=QDuuTxPSCj1rYtZ4xpD7Ux-r20TD50aHyqKyhQ7Bq4A,17460
pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758
pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895
pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168
pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296
pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403
pip/_vendor/vendor.txt,sha256=43152uDtpsunEE29vmLqqKZUosdrbvzIFkzscLB55Cg,332
pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: setuptools (75.2.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1,3 +0,0 @@
[console_scripts]
pip = pip._internal.cli.main:main
pip3 = pip._internal.cli.main:main

View File

@ -1,6 +1,6 @@
from typing import List, Optional from typing import List, Optional
__version__ = "24.3.1" __version__ = "25.0.1"
def main(args: Optional[List[str]] = None) -> int: def main(args: Optional[List[str]] = None) -> int:

View File

@ -11,7 +11,6 @@ from collections import OrderedDict
from types import TracebackType from types import TracebackType
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
from pip._vendor.certifi import where
from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import Version
from pip import __file__ as pip_location from pip import __file__ as pip_location
@ -270,21 +269,25 @@ class BuildEnvironment:
for link in finder.find_links: for link in finder.find_links:
args.extend(["--find-links", link]) args.extend(["--find-links", link])
if finder.proxy:
args.extend(["--proxy", finder.proxy])
for host in finder.trusted_hosts: for host in finder.trusted_hosts:
args.extend(["--trusted-host", host]) args.extend(["--trusted-host", host])
if finder.custom_cert:
args.extend(["--cert", finder.custom_cert])
if finder.client_cert:
args.extend(["--client-cert", finder.client_cert])
if finder.allow_all_prereleases: if finder.allow_all_prereleases:
args.append("--pre") args.append("--pre")
if finder.prefer_binary: if finder.prefer_binary:
args.append("--prefer-binary") args.append("--prefer-binary")
args.append("--") args.append("--")
args.extend(requirements) args.extend(requirements)
extra_environ = {"_PIP_STANDALONE_CERT": where()}
with open_spinner(f"Installing {kind}") as spinner: with open_spinner(f"Installing {kind}") as spinner:
call_subprocess( call_subprocess(
args, args,
command_desc=f"pip subprocess to install {kind}", command_desc=f"pip subprocess to install {kind}",
spinner=spinner, spinner=spinner,
extra_environ=extra_environ,
) )

View File

@ -29,6 +29,7 @@ from pip._internal.exceptions import (
NetworkConnectionError, NetworkConnectionError,
PreviousBuildDirError, PreviousBuildDirError,
) )
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.filesystem import check_path_owner from pip._internal.utils.filesystem import check_path_owner
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
from pip._internal.utils.misc import get_prog, normalize_path from pip._internal.utils.misc import get_prog, normalize_path
@ -228,4 +229,12 @@ class Command(CommandContextMixIn):
) )
options.cache_dir = None options.cache_dir = None
if options.no_python_version_warning:
deprecated(
reason="--no-python-version-warning is deprecated.",
replacement="to remove the flag as it's a no-op",
gone_in="25.1",
issue=13154,
)
return self._run_wrapper(level_number, options, args) return self._run_wrapper(level_number, options, args)

View File

@ -260,8 +260,8 @@ keyring_provider: Callable[..., Option] = partial(
default="auto", default="auto",
help=( help=(
"Enable the credential lookup via the keyring library if user input is allowed." "Enable the credential lookup via the keyring library if user input is allowed."
" Specify which mechanism to use [disabled, import, subprocess]." " Specify which mechanism to use [auto, disabled, import, subprocess]."
" (default: disabled)" " (default: %default)"
), ),
) )

View File

@ -123,6 +123,7 @@ class SessionCommandMixin(CommandContextMixIn):
"https": options.proxy, "https": options.proxy,
} }
session.trust_env = False session.trust_env = False
session.pip_proxy = options.proxy
# Determine if we can prompt the user for authentication or not # Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input session.auth.prompting = not options.no_input

View File

@ -63,7 +63,7 @@ def _raw_progress_bar(
size: Optional[int], size: Optional[int],
) -> Generator[bytes, None, None]: ) -> Generator[bytes, None, None]:
def write_progress(current: int, total: int) -> None: def write_progress(current: int, total: int) -> None:
sys.stdout.write("Progress %d of %d\n" % (current, total)) sys.stdout.write(f"Progress {current} of {total}\n")
sys.stdout.flush() sys.stdout.flush()
current = 0 current = 0

View File

@ -8,6 +8,7 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.exceptions import CommandError, PipError from pip._internal.exceptions import CommandError, PipError
from pip._internal.utils import filesystem from pip._internal.utils import filesystem
from pip._internal.utils.logging import getLogger from pip._internal.utils.logging import getLogger
from pip._internal.utils.misc import format_size
logger = getLogger(__name__) logger = getLogger(__name__)
@ -180,10 +181,12 @@ class CacheCommand(Command):
if not files: if not files:
logger.warning(no_matching_msg) logger.warning(no_matching_msg)
bytes_removed = 0
for filename in files: for filename in files:
bytes_removed += os.stat(filename).st_size
os.unlink(filename) os.unlink(filename)
logger.verbose("Removed %s", filename) logger.verbose("Removed %s", filename)
logger.info("Files removed: %s", len(files)) logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
def purge_cache(self, options: Values, args: List[Any]) -> None: def purge_cache(self, options: Values, args: List[Any]) -> None:
if args: if args:

View File

@ -10,6 +10,13 @@ from typing import List, Optional
from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.rich import print_json from pip._vendor.rich import print_json
# Eagerly import self_outdated_check to avoid crashes. Otherwise,
# this module would be imported *after* pip was replaced, resulting
# in crashes if the new self_outdated_check module was incompatible
# with the rest of pip that's already imported, or allowing a
# wheel to execute arbitrary code on install by replacing
# self_outdated_check.
import pip._internal.self_outdated_check # noqa: F401
from pip._internal.cache import WheelCache from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_target_python from pip._internal.cli.cmdoptions import make_target_python
@ -408,12 +415,6 @@ class InstallCommand(RequirementCommand):
# If we're not replacing an already installed pip, # If we're not replacing an already installed pip,
# we're not modifying it. # we're not modifying it.
modifying_pip = pip_req.satisfied_by is None modifying_pip = pip_req.satisfied_by is None
if modifying_pip:
# Eagerly import this module to avoid crashes. Otherwise, this
# module would be imported *after* pip was replaced, resulting in
# crashes if the new self_outdated_check module was incompatible
# with the rest of pip that's already imported.
import pip._internal.self_outdated_check # noqa: F401
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip) protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
reqs_to_build = [ reqs_to_build = [
@ -432,7 +433,7 @@ class InstallCommand(RequirementCommand):
if build_failures: if build_failures:
raise InstallationError( raise InstallationError(
"ERROR: Failed to build installable wheels for some " "Failed to build installable wheels for some "
"pyproject.toml based projects ({})".format( "pyproject.toml based projects ({})".format(
", ".join(r.name for r in build_failures) # type: ignore ", ".join(r.name for r in build_failures) # type: ignore
) )

View File

@ -66,6 +66,7 @@ class _PackageInfo(NamedTuple):
author: str author: str
author_email: str author_email: str
license: str license: str
license_expression: str
entry_points: List[str] entry_points: List[str]
files: Optional[List[str]] files: Optional[List[str]]
@ -161,6 +162,7 @@ def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None
author=metadata.get("Author", ""), author=metadata.get("Author", ""),
author_email=metadata.get("Author-email", ""), author_email=metadata.get("Author-email", ""),
license=metadata.get("License", ""), license=metadata.get("License", ""),
license_expression=metadata.get("License-Expression", ""),
entry_points=entry_points, entry_points=entry_points,
files=files, files=files,
) )
@ -180,13 +182,18 @@ def print_results(
if i > 0: if i > 0:
write_output("---") write_output("---")
metadata_version_tuple = tuple(map(int, dist.metadata_version.split(".")))
write_output("Name: %s", dist.name) write_output("Name: %s", dist.name)
write_output("Version: %s", dist.version) write_output("Version: %s", dist.version)
write_output("Summary: %s", dist.summary) write_output("Summary: %s", dist.summary)
write_output("Home-page: %s", dist.homepage) write_output("Home-page: %s", dist.homepage)
write_output("Author: %s", dist.author) write_output("Author: %s", dist.author)
write_output("Author-email: %s", dist.author_email) write_output("Author-email: %s", dist.author_email)
write_output("License: %s", dist.license) if metadata_version_tuple >= (2, 4) and dist.license_expression:
write_output("License-Expression: %s", dist.license_expression)
else:
write_output("License: %s", dist.license)
write_output("Location: %s", dist.location) write_output("Location: %s", dist.location)
if dist.editable_project_location is not None: if dist.editable_project_location is not None:
write_output( write_output(

View File

@ -330,7 +330,7 @@ class Configuration:
This should be treated like items of a dictionary. The order This should be treated like items of a dictionary. The order
here doesn't affect what gets overridden. That is controlled here doesn't affect what gets overridden. That is controlled
by OVERRIDE_ORDER. However this does control the order they are by OVERRIDE_ORDER. However this does control the order they are
displayed to the user. It's probably most ergononmic to display displayed to the user. It's probably most ergonomic to display
things in the same order as OVERRIDE_ORDER things in the same order as OVERRIDE_ORDER
""" """
# SMELL: Move the conditions out of this function # SMELL: Move the conditions out of this function

View File

@ -334,44 +334,30 @@ class CandidatePreferences:
allow_all_prereleases: bool = False allow_all_prereleases: bool = False
@dataclass(frozen=True)
class BestCandidateResult: class BestCandidateResult:
"""A collection of candidates, returned by `PackageFinder.find_best_candidate`. """A collection of candidates, returned by `PackageFinder.find_best_candidate`.
This class is only intended to be instantiated by CandidateEvaluator's This class is only intended to be instantiated by CandidateEvaluator's
`compute_best_candidate()` method. `compute_best_candidate()` method.
:param all_candidates: A sequence of all available candidates found.
:param applicable_candidates: The applicable candidates.
:param best_candidate: The most preferred candidate found, or None
if no applicable candidates were found.
""" """
def __init__( all_candidates: List[InstallationCandidate]
self, applicable_candidates: List[InstallationCandidate]
candidates: List[InstallationCandidate], best_candidate: Optional[InstallationCandidate]
applicable_candidates: List[InstallationCandidate],
best_candidate: Optional[InstallationCandidate],
) -> None:
"""
:param candidates: A sequence of all available candidates found.
:param applicable_candidates: The applicable candidates.
:param best_candidate: The most preferred candidate found, or None
if no applicable candidates were found.
"""
assert set(applicable_candidates) <= set(candidates)
if best_candidate is None: def __post_init__(self) -> None:
assert not applicable_candidates assert set(self.applicable_candidates) <= set(self.all_candidates)
if self.best_candidate is None:
assert not self.applicable_candidates
else: else:
assert best_candidate in applicable_candidates assert self.best_candidate in self.applicable_candidates
self._applicable_candidates = applicable_candidates
self._candidates = candidates
self.best_candidate = best_candidate
def iter_all(self) -> Iterable[InstallationCandidate]:
"""Iterate through all candidates."""
return iter(self._candidates)
def iter_applicable(self) -> Iterable[InstallationCandidate]:
"""Iterate through the applicable candidates."""
return iter(self._applicable_candidates)
class CandidateEvaluator: class CandidateEvaluator:
@ -675,11 +661,29 @@ class PackageFinder:
def index_urls(self) -> List[str]: def index_urls(self) -> List[str]:
return self.search_scope.index_urls return self.search_scope.index_urls
@property
def proxy(self) -> Optional[str]:
return self._link_collector.session.pip_proxy
@property @property
def trusted_hosts(self) -> Iterable[str]: def trusted_hosts(self) -> Iterable[str]:
for host_port in self._link_collector.session.pip_trusted_origins: for host_port in self._link_collector.session.pip_trusted_origins:
yield build_netloc(*host_port) yield build_netloc(*host_port)
@property
def custom_cert(self) -> Optional[str]:
# session.verify is either a boolean (use default bundle/no SSL
# verification) or a string path to a custom CA bundle to use. We only
# care about the latter.
verify = self._link_collector.session.verify
return verify if isinstance(verify, str) else None
@property
def client_cert(self) -> Optional[str]:
cert = self._link_collector.session.cert
assert not isinstance(cert, tuple), "pip only supports PEM client certs"
return cert
@property @property
def allow_all_prereleases(self) -> bool: def allow_all_prereleases(self) -> bool:
return self._candidate_prefs.allow_all_prereleases return self._candidate_prefs.allow_all_prereleases
@ -732,6 +736,11 @@ class PackageFinder:
return no_eggs + eggs return no_eggs + eggs
def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None: def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None:
# This is a hot method so don't waste time hashing links unless we're
# actually going to log 'em.
if not logger.isEnabledFor(logging.DEBUG):
return
entry = (link, result, detail) entry = (link, result, detail)
if entry not in self._logged_links: if entry not in self._logged_links:
# Put the link at the end so the reason is more visible and because # Put the link at the end so the reason is more visible and because
@ -929,7 +938,7 @@ class PackageFinder:
"Could not find a version that satisfies the requirement %s " "Could not find a version that satisfies the requirement %s "
"(from versions: %s)", "(from versions: %s)",
req, req,
_format_versions(best_candidate_result.iter_all()), _format_versions(best_candidate_result.all_candidates),
) )
raise DistributionNotFound(f"No matching distribution found for {req}") raise DistributionNotFound(f"No matching distribution found for {req}")
@ -963,7 +972,7 @@ class PackageFinder:
logger.debug( logger.debug(
"Using version %s (newest of versions: %s)", "Using version %s (newest of versions: %s)",
best_candidate.version, best_candidate.version,
_format_versions(best_candidate_result.iter_applicable()), _format_versions(best_candidate_result.applicable_candidates),
) )
return best_candidate return best_candidate
@ -971,7 +980,7 @@ class PackageFinder:
logger.debug( logger.debug(
"Installed version (%s) is most up-to-date (past versions: %s)", "Installed version (%s) is most up-to-date (past versions: %s)",
installed_version, installed_version,
_format_versions(best_candidate_result.iter_applicable()), _format_versions(best_candidate_result.applicable_candidates),
) )
raise BestVersionAlreadyInstalled raise BestVersionAlreadyInstalled

View File

@ -30,7 +30,7 @@ def _should_use_importlib_metadata() -> bool:
"""Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend. """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
By default, pip uses ``importlib.metadata`` on Python 3.11+, and By default, pip uses ``importlib.metadata`` on Python 3.11+, and
``pkg_resourcess`` otherwise. This can be overridden by a couple of ways: ``pkg_resources`` otherwise. This can be overridden by a couple of ways:
* If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
dictates whether ``importlib.metadata`` is used, regardless of Python dictates whether ``importlib.metadata`` is used, regardless of Python
@ -71,7 +71,7 @@ def get_default_environment() -> BaseEnvironment:
This returns an Environment instance from the chosen backend. The default This returns an Environment instance from the chosen backend. The default
Environment instance should be built from ``sys.path`` and may use caching Environment instance should be built from ``sys.path`` and may use caching
to share instance state accorss calls. to share instance state across calls.
""" """
return select_backend().Environment.default() return select_backend().Environment.default()

View File

@ -23,6 +23,8 @@ METADATA_FIELDS = [
("Maintainer", False), ("Maintainer", False),
("Maintainer-email", False), ("Maintainer-email", False),
("License", False), ("License", False),
("License-Expression", False),
("License-File", True),
("Classifier", True), ("Classifier", True),
("Requires-Dist", True), ("Requires-Dist", True),
("Requires-Python", False), ("Requires-Python", False),

View File

@ -2,6 +2,7 @@ import email.message
import importlib.metadata import importlib.metadata
import pathlib import pathlib
import zipfile import zipfile
from os import PathLike
from typing import ( from typing import (
Collection, Collection,
Dict, Dict,
@ -10,6 +11,7 @@ from typing import (
Mapping, Mapping,
Optional, Optional,
Sequence, Sequence,
Union,
cast, cast,
) )
@ -95,6 +97,11 @@ class WheelDistribution(importlib.metadata.Distribution):
raise UnsupportedWheel(error) raise UnsupportedWheel(error)
return text return text
def locate_file(self, path: Union[str, "PathLike[str]"]) -> pathlib.Path:
# This method doesn't make sense for our in-memory wheel, but the API
# requires us to define it.
raise NotImplementedError
class Distribution(BaseDistribution): class Distribution(BaseDistribution):
def __init__( def __init__(
@ -190,7 +197,7 @@ class Distribution(BaseDistribution):
return content return content
def iter_entry_points(self) -> Iterable[BaseEntryPoint]: def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
# importlib.metadata's EntryPoint structure sasitfies BaseEntryPoint. # importlib.metadata's EntryPoint structure satisfies BaseEntryPoint.
return self._dist.entry_points return self._dist.entry_points
def _metadata_impl(self) -> email.message.Message: def _metadata_impl(self) -> email.message.Message:

View File

@ -170,12 +170,23 @@ def _ensure_quoted_url(url: str) -> str:
and without double-quoting other characters. and without double-quoting other characters.
""" """
# Split the URL into parts according to the general structure # Split the URL into parts according to the general structure
# `scheme://netloc/path;parameters?query#fragment`. # `scheme://netloc/path?query#fragment`.
result = urllib.parse.urlparse(url) result = urllib.parse.urlsplit(url)
# If the netloc is empty, then the URL refers to a local filesystem path. # If the netloc is empty, then the URL refers to a local filesystem path.
is_local_path = not result.netloc is_local_path = not result.netloc
path = _clean_url_path(result.path, is_local_path=is_local_path) path = _clean_url_path(result.path, is_local_path=is_local_path)
return urllib.parse.urlunparse(result._replace(path=path)) return urllib.parse.urlunsplit(result._replace(path=path))
def _absolute_link_url(base_url: str, url: str) -> str:
"""
A faster implementation of urllib.parse.urljoin with a shortcut
for absolute http/https URLs.
"""
if url.startswith(("https://", "http://")):
return url
else:
return urllib.parse.urljoin(base_url, url)
@functools.total_ordering @functools.total_ordering
@ -185,6 +196,7 @@ class Link:
__slots__ = [ __slots__ = [
"_parsed_url", "_parsed_url",
"_url", "_url",
"_path",
"_hashes", "_hashes",
"comes_from", "comes_from",
"requires_python", "requires_python",
@ -241,6 +253,8 @@ class Link:
# Store the url as a private attribute to prevent accidentally # Store the url as a private attribute to prevent accidentally
# trying to set a new value. # trying to set a new value.
self._url = url self._url = url
# The .path property is hot, so calculate its value ahead of time.
self._path = urllib.parse.unquote(self._parsed_url.path)
link_hash = LinkHash.find_hash_url_fragment(url) link_hash = LinkHash.find_hash_url_fragment(url)
hashes_from_link = {} if link_hash is None else link_hash.as_dict() hashes_from_link = {} if link_hash is None else link_hash.as_dict()
@ -270,7 +284,7 @@ class Link:
if file_url is None: if file_url is None:
return None return None
url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url)) url = _ensure_quoted_url(_absolute_link_url(page_url, file_url))
pyrequire = file_data.get("requires-python") pyrequire = file_data.get("requires-python")
yanked_reason = file_data.get("yanked") yanked_reason = file_data.get("yanked")
hashes = file_data.get("hashes", {}) hashes = file_data.get("hashes", {})
@ -322,7 +336,7 @@ class Link:
if not href: if not href:
return None return None
url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href)) url = _ensure_quoted_url(_absolute_link_url(base_url, href))
pyrequire = anchor_attribs.get("data-requires-python") pyrequire = anchor_attribs.get("data-requires-python")
yanked_reason = anchor_attribs.get("data-yanked") yanked_reason = anchor_attribs.get("data-yanked")
@ -421,7 +435,7 @@ class Link:
@property @property
def path(self) -> str: def path(self) -> str:
return urllib.parse.unquote(self._parsed_url.path) return self._path
def splitext(self) -> Tuple[str, str]: def splitext(self) -> Tuple[str, str]:
return splitext(posixpath.basename(self.path.rstrip("/"))) return splitext(posixpath.basename(self.path.rstrip("/")))
@ -452,10 +466,10 @@ class Link:
project_name = match.group(1) project_name = match.group(1)
if not self._project_name_re.match(project_name): if not self._project_name_re.match(project_name):
deprecated( deprecated(
reason=f"{self} contains an egg fragment with a non-PEP 508 name", reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
replacement="to use the req @ url syntax, and remove the egg fragment", replacement="to use the req @ url syntax, and remove the egg fragment",
gone_in="25.0", gone_in="25.1",
issue=11617, issue=13157,
) )
return project_name return project_name

View File

@ -76,6 +76,18 @@ class SafeFileCache(SeparateBodyBaseCache):
with adjacent_tmp_file(path) as f: with adjacent_tmp_file(path) as f:
f.write(data) f.write(data)
# Inherit the read/write permissions of the cache directory
# to enable multi-user cache use-cases.
mode = (
os.stat(self.directory).st_mode
& 0o666 # select read/write permissions of cache directory
| 0o600 # set owner read/write permissions
)
# Change permissions only if there is no risk of following a symlink.
if os.chmod in os.supports_fd:
os.chmod(f.fileno(), mode)
elif os.chmod in os.supports_follow_symlinks:
os.chmod(f.name, mode, follow_symlinks=False)
replace(f.name, path) replace(f.name, path)

View File

@ -339,6 +339,7 @@ class PipSession(requests.Session):
# Namespace the attribute with "pip_" just in case to prevent # Namespace the attribute with "pip_" just in case to prevent
# possible conflicts with the base class. # possible conflicts with the base class.
self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = [] self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
self.pip_proxy = None
# Attach our User Agent to the request # Attach our User Agent to the request
self.headers["User-Agent"] = user_agent() self.headers["User-Agent"] = user_agent()

View File

@ -38,4 +38,5 @@ def generate_editable_metadata(
except InstallationSubprocessError as error: except InstallationSubprocessError as error:
raise MetadataGenerationFailed(package_details=details) from error raise MetadataGenerationFailed(package_details=details) from error
assert distinfo_dir is not None
return os.path.join(metadata_dir, distinfo_dir) return os.path.join(metadata_dir, distinfo_dir)

View File

@ -1,9 +1,10 @@
import collections import collections
import logging import logging
import os import os
from dataclasses import dataclass, field
from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import InvalidVersion from pip._vendor.packaging.version import InvalidVersion
from pip._internal.exceptions import BadCommand, InstallationError from pip._internal.exceptions import BadCommand, InstallationError
@ -220,19 +221,16 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
) )
@dataclass(frozen=True)
class FrozenRequirement: class FrozenRequirement:
def __init__( name: str
self, req: str
name: str, editable: bool
req: str, comments: Iterable[str] = field(default_factory=tuple)
editable: bool,
comments: Iterable[str] = (), @property
) -> None: def canonical_name(self) -> NormalizedName:
self.name = name return canonicalize_name(self.name)
self.canonical_name = canonicalize_name(name)
self.req = req
self.editable = editable
self.comments = comments
@classmethod @classmethod
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement": def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":

View File

@ -73,7 +73,7 @@ def load_pyproject_toml(
build_system = None build_system = None
# The following cases must use PEP 517 # The following cases must use PEP 517
# We check for use_pep517 being non-None and falsey because that means # We check for use_pep517 being non-None and falsy because that means
# the user explicitly requested --no-use-pep517. The value 0 as # the user explicitly requested --no-use-pep517. The value 0 as
# opposed to False can occur when the value is provided via an # opposed to False can occur when the value is provided via an
# environment variable or config file option (due to the quirk of # environment variable or config file option (due to the quirk of

View File

@ -2,12 +2,16 @@
Requirements file parsing Requirements file parsing
""" """
import codecs
import locale
import logging import logging
import optparse import optparse
import os import os
import re import re
import shlex import shlex
import sys
import urllib.parse import urllib.parse
from dataclasses import dataclass
from optparse import Values from optparse import Values
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
@ -25,7 +29,6 @@ from typing import (
from pip._internal.cli import cmdoptions from pip._internal.cli import cmdoptions
from pip._internal.exceptions import InstallationError, RequirementsFileParseError from pip._internal.exceptions import InstallationError, RequirementsFileParseError
from pip._internal.models.search_scope import SearchScope from pip._internal.models.search_scope import SearchScope
from pip._internal.utils.encoding import auto_decode
if TYPE_CHECKING: if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder from pip._internal.index.package_finder import PackageFinder
@ -81,52 +84,66 @@ SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
] ]
# order of BOMS is important: codecs.BOM_UTF16_LE is a prefix of codecs.BOM_UTF32_LE
# so data.startswith(BOM_UTF16_LE) would be true for UTF32_LE data
BOMS: List[Tuple[bytes, str]] = [
(codecs.BOM_UTF8, "utf-8"),
(codecs.BOM_UTF32, "utf-32"),
(codecs.BOM_UTF32_BE, "utf-32-be"),
(codecs.BOM_UTF32_LE, "utf-32-le"),
(codecs.BOM_UTF16, "utf-16"),
(codecs.BOM_UTF16_BE, "utf-16-be"),
(codecs.BOM_UTF16_LE, "utf-16-le"),
]
PEP263_ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
DEFAULT_ENCODING = "utf-8"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class ParsedRequirement: class ParsedRequirement:
def __init__( # TODO: replace this with slots=True when dropping Python 3.9 support.
self, __slots__ = (
requirement: str, "requirement",
is_editable: bool, "is_editable",
comes_from: str, "comes_from",
constraint: bool, "constraint",
options: Optional[Dict[str, Any]] = None, "options",
line_source: Optional[str] = None, "line_source",
) -> None: )
self.requirement = requirement
self.is_editable = is_editable requirement: str
self.comes_from = comes_from is_editable: bool
self.options = options comes_from: str
self.constraint = constraint constraint: bool
self.line_source = line_source options: Optional[Dict[str, Any]]
line_source: Optional[str]
@dataclass(frozen=True)
class ParsedLine: class ParsedLine:
def __init__( __slots__ = ("filename", "lineno", "args", "opts", "constraint")
self,
filename: str,
lineno: int,
args: str,
opts: Values,
constraint: bool,
) -> None:
self.filename = filename
self.lineno = lineno
self.opts = opts
self.constraint = constraint
if args: filename: str
self.is_requirement = True lineno: int
self.is_editable = False args: str
self.requirement = args opts: Values
elif opts.editables: constraint: bool
self.is_requirement = True
self.is_editable = True @property
def is_editable(self) -> bool:
return bool(self.opts.editables)
@property
def requirement(self) -> Optional[str]:
if self.args:
return self.args
elif self.is_editable:
# We don't support multiple -e on one line # We don't support multiple -e on one line
self.requirement = opts.editables[0] return self.opts.editables[0]
else: return None
self.is_requirement = False
def parse_requirements( def parse_requirements(
@ -179,7 +196,7 @@ def handle_requirement_line(
line.lineno, line.lineno,
) )
assert line.is_requirement assert line.requirement is not None
# get the options that apply to requirements # get the options that apply to requirements
if line.is_editable: if line.is_editable:
@ -301,7 +318,7 @@ def handle_line(
affect the finder. affect the finder.
""" """
if line.is_requirement: if line.requirement is not None:
parsed_req = handle_requirement_line(line, options) parsed_req = handle_requirement_line(line, options)
return parsed_req return parsed_req
else: else:
@ -340,7 +357,7 @@ class RequirementsFileParser:
parsed_files_stack: List[Dict[str, Optional[str]]], parsed_files_stack: List[Dict[str, Optional[str]]],
) -> Generator[ParsedLine, None, None]: ) -> Generator[ParsedLine, None, None]:
for line in self._parse_file(filename, constraint): for line in self._parse_file(filename, constraint):
if not line.is_requirement and ( if line.requirement is None and (
line.opts.requirements or line.opts.constraints line.opts.requirements or line.opts.constraints
): ):
# parse a nested requirements file # parse a nested requirements file
@ -568,7 +585,39 @@ def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]:
# Assume this is a bare path. # Assume this is a bare path.
try: try:
with open(url, "rb") as f: with open(url, "rb") as f:
content = auto_decode(f.read()) raw_content = f.read()
except OSError as exc: except OSError as exc:
raise InstallationError(f"Could not open requirements file: {exc}") raise InstallationError(f"Could not open requirements file: {exc}")
content = _decode_req_file(raw_content, url)
return url, content return url, content
def _decode_req_file(data: bytes, url: str) -> str:
for bom, encoding in BOMS:
if data.startswith(bom):
return data[len(bom) :].decode(encoding)
for line in data.split(b"\n")[:2]:
if line[0:1] == b"#":
result = PEP263_ENCODING_RE.search(line)
if result is not None:
encoding = result.groups()[0].decode("ascii")
return data.decode(encoding)
try:
return data.decode(DEFAULT_ENCODING)
except UnicodeDecodeError:
locale_encoding = locale.getpreferredencoding(False) or sys.getdefaultencoding()
logging.warning(
"unable to decode data from %s with default encoding %s, "
"falling back to encoding from locale: %s. "
"If this is intentional you should specify the encoding with a "
"PEP-263 style comment, e.g. '# -*- coding: %s -*-'",
url,
DEFAULT_ENCODING,
locale_encoding,
locale_encoding,
)
return data.decode(locale_encoding)

View File

@ -837,7 +837,7 @@ class InstallRequirement:
"try using --config-settings editable_mode=compat. " "try using --config-settings editable_mode=compat. "
"Please consult the setuptools documentation for more information" "Please consult the setuptools documentation for more information"
), ),
gone_in="25.0", gone_in="25.1",
issue=11457, issue=11457,
) )
if self.config_settings: if self.config_settings:
@ -925,7 +925,7 @@ def check_legacy_setup_py_options(
reason="--build-option and --global-option are deprecated.", reason="--build-option and --global-option are deprecated.",
issue=11859, issue=11859,
replacement="to use --config-settings", replacement="to use --config-settings",
gone_in="25.0", gone_in=None,
) )
logger.warning( logger.warning(
"Implying --no-binary=:all: due to the presence of " "Implying --no-binary=:all: due to the presence of "

View File

@ -309,7 +309,7 @@ class Factory:
specifier=specifier, specifier=specifier,
hashes=hashes, hashes=hashes,
) )
icans = list(result.iter_applicable()) icans = result.applicable_candidates
# PEP 592: Yanked releases are ignored unless the specifier # PEP 592: Yanked releases are ignored unless the specifier
# explicitly pins a version (via '==' or '===') that can be # explicitly pins a version (via '==' or '===') that can be

View File

@ -26,7 +26,11 @@ from pip._internal.utils.entrypoints import (
get_best_invocation_for_this_python, get_best_invocation_for_this_python,
) )
from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace
from pip._internal.utils.misc import ensure_dir from pip._internal.utils.misc import (
ExternallyManagedEnvironment,
check_externally_managed,
ensure_dir,
)
_WEEK = datetime.timedelta(days=7) _WEEK = datetime.timedelta(days=7)
@ -231,6 +235,10 @@ def pip_self_version_check(session: PipSession, options: optparse.Values) -> Non
installed_dist = get_default_environment().get_distribution("pip") installed_dist = get_default_environment().get_distribution("pip")
if not installed_dist: if not installed_dist:
return return
try:
check_externally_managed()
except ExternallyManagedEnvironment:
return
upgrade_prompt = _self_version_check_logic( upgrade_prompt = _self_version_check_logic(
state=SelfCheckState(cache_dir=options.cache_dir), state=SelfCheckState(cache_dir=options.cache_dir),

View File

@ -1,36 +0,0 @@
import codecs
import locale
import re
import sys
from typing import List, Tuple
BOMS: List[Tuple[bytes, str]] = [
(codecs.BOM_UTF8, "utf-8"),
(codecs.BOM_UTF16, "utf-16"),
(codecs.BOM_UTF16_BE, "utf-16-be"),
(codecs.BOM_UTF16_LE, "utf-16-le"),
(codecs.BOM_UTF32, "utf-32"),
(codecs.BOM_UTF32_BE, "utf-32-be"),
(codecs.BOM_UTF32_LE, "utf-32-le"),
]
ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
def auto_decode(data: bytes) -> str:
"""Check a bytes string for a BOM to correctly detect the encoding
Fallback to locale.getpreferredencoding(False) like open() on Python3"""
for bom, encoding in BOMS:
if data.startswith(bom):
return data[len(bom) :].decode(encoding)
# Lets check the first two lines as in PEP263
for line in data.split(b"\n")[:2]:
if line[0:1] == b"#" and ENCODING_RE.search(line):
result = ENCODING_RE.search(line)
assert result is not None
encoding = result.groups()[0].decode("ascii")
return data.decode(encoding)
return data.decode(
locale.getpreferredencoding(False) or sys.getdefaultencoding(),
)

View File

@ -137,12 +137,19 @@ class IndentedRenderable:
yield Segment("\n") yield Segment("\n")
class PipConsole(Console):
def on_broken_pipe(self) -> None:
# Reraise the original exception, rich 13.8.0+ exits by default
# instead, preventing our handler from firing.
raise BrokenPipeError() from None
class RichPipStreamHandler(RichHandler): class RichPipStreamHandler(RichHandler):
KEYWORDS: ClassVar[Optional[List[str]]] = [] KEYWORDS: ClassVar[Optional[List[str]]] = []
def __init__(self, stream: Optional[TextIO], no_color: bool) -> None: def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
super().__init__( super().__init__(
console=Console(file=stream, no_color=no_color, soft_wrap=True), console=PipConsole(file=stream, no_color=no_color, soft_wrap=True),
show_time=False, show_time=False,
show_level=False, show_level=False,
show_path=False, show_path=False,

View File

@ -19,12 +19,13 @@ from typing import (
Any, Any,
BinaryIO, BinaryIO,
Callable, Callable,
Dict,
Generator, Generator,
Iterable, Iterable,
Iterator, Iterator,
List, List,
Mapping,
Optional, Optional,
Sequence,
TextIO, TextIO,
Tuple, Tuple,
Type, Type,
@ -667,7 +668,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_wheel( def build_wheel(
self, self,
wheel_directory: str, wheel_directory: str,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None, metadata_directory: Optional[str] = None,
) -> str: ) -> str:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
@ -678,7 +679,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_sdist( def build_sdist(
self, self,
sdist_directory: str, sdist_directory: str,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, config_settings: Optional[Mapping[str, Any]] = None,
) -> str: ) -> str:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
return super().build_sdist(sdist_directory, config_settings=cs) return super().build_sdist(sdist_directory, config_settings=cs)
@ -686,7 +687,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_editable( def build_editable(
self, self,
wheel_directory: str, wheel_directory: str,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None, metadata_directory: Optional[str] = None,
) -> str: ) -> str:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
@ -695,27 +696,27 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
) )
def get_requires_for_build_wheel( def get_requires_for_build_wheel(
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None self, config_settings: Optional[Mapping[str, Any]] = None
) -> List[str]: ) -> Sequence[str]:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
return super().get_requires_for_build_wheel(config_settings=cs) return super().get_requires_for_build_wheel(config_settings=cs)
def get_requires_for_build_sdist( def get_requires_for_build_sdist(
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None self, config_settings: Optional[Mapping[str, Any]] = None
) -> List[str]: ) -> Sequence[str]:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
return super().get_requires_for_build_sdist(config_settings=cs) return super().get_requires_for_build_sdist(config_settings=cs)
def get_requires_for_build_editable( def get_requires_for_build_editable(
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None self, config_settings: Optional[Mapping[str, Any]] = None
) -> List[str]: ) -> Sequence[str]:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
return super().get_requires_for_build_editable(config_settings=cs) return super().get_requires_for_build_editable(config_settings=cs)
def prepare_metadata_for_build_wheel( def prepare_metadata_for_build_wheel(
self, self,
metadata_directory: str, metadata_directory: str,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True, _allow_fallback: bool = True,
) -> str: ) -> str:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
@ -728,9 +729,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def prepare_metadata_for_build_editable( def prepare_metadata_for_build_editable(
self, self,
metadata_directory: str, metadata_directory: str,
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True, _allow_fallback: bool = True,
) -> str: ) -> Optional[str]:
cs = self.config_holder.config_settings cs = self.config_holder.config_settings
return super().prepare_metadata_for_build_editable( return super().prepare_metadata_for_build_editable(
metadata_directory=metadata_directory, metadata_directory=metadata_directory,
@ -764,7 +765,7 @@ def warn_if_run_as_root() -> None:
logger.warning( logger.warning(
"Running pip as the 'root' user can result in broken permissions and " "Running pip as the 'root' user can result in broken permissions and "
"conflicting behaviour with the system package manager, possibly " "conflicting behaviour with the system package manager, possibly "
"rendering your system unusable." "rendering your system unusable. "
"It is recommended to use a virtual environment instead: " "It is recommended to use a virtual environment instead: "
"https://pip.pypa.io/warnings/venv. " "https://pip.pypa.io/warnings/venv. "
"Use the --root-user-action option if you know what you are doing and " "Use the --root-user-action option if you know what you are doing and "

View File

@ -11,6 +11,7 @@ NormalizedExtra = NewType("NormalizedExtra", str)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@functools.lru_cache(maxsize=32)
def check_requires_python( def check_requires_python(
requires_python: Optional[str], version_info: Tuple[int, ...] requires_python: Optional[str], version_info: Tuple[int, ...]
) -> bool: ) -> bool:

View File

@ -176,7 +176,7 @@ def untar_file(filename: str, location: str) -> None:
) )
mode = "r:*" mode = "r:*"
tar = tarfile.open(filename, mode, encoding="utf-8") tar = tarfile.open(filename, mode, encoding="utf-8") # type: ignore
try: try:
leading = has_leading_dir([member.name for member in tar.getmembers()]) leading = has_leading_dir([member.name for member in tar.getmembers()])

View File

@ -6,9 +6,10 @@
Make it easy to import from cachecontrol without long namespaces. Make it easy to import from cachecontrol without long namespaces.
""" """
__author__ = "Eric Larson" __author__ = "Eric Larson"
__email__ = "eric@ionrock.org" __email__ = "eric@ionrock.org"
__version__ = "0.14.0" __version__ = "0.14.1"
from pip._vendor.cachecontrol.adapter import CacheControlAdapter from pip._vendor.cachecontrol.adapter import CacheControlAdapter
from pip._vendor.cachecontrol.controller import CacheController from pip._vendor.cachecontrol.controller import CacheController

View File

@ -77,7 +77,7 @@ class CacheControlAdapter(HTTPAdapter):
return resp return resp
def build_response( def build_response( # type: ignore[override]
self, self,
request: PreparedRequest, request: PreparedRequest,
response: HTTPResponse, response: HTTPResponse,
@ -143,7 +143,7 @@ class CacheControlAdapter(HTTPAdapter):
_update_chunk_length, response _update_chunk_length, response
) )
resp: Response = super().build_response(request, response) # type: ignore[no-untyped-call] resp: Response = super().build_response(request, response)
# See if we should invalidate the cache. # See if we should invalidate the cache.
if request.method in self.invalidating_methods and resp.ok: if request.method in self.invalidating_methods and resp.ok:

View File

@ -6,6 +6,7 @@
The cache object API for implementing caches. The default is a thread The cache object API for implementing caches. The default is a thread
safe in-memory dictionary. safe in-memory dictionary.
""" """
from __future__ import annotations from __future__ import annotations
from threading import Lock from threading import Lock

View File

@ -6,7 +6,7 @@ from __future__ import annotations
import hashlib import hashlib
import os import os
from textwrap import dedent from textwrap import dedent
from typing import IO, TYPE_CHECKING, Union from typing import IO, TYPE_CHECKING
from pathlib import Path from pathlib import Path
from pip._vendor.cachecontrol.cache import BaseCache, SeparateBodyBaseCache from pip._vendor.cachecontrol.cache import BaseCache, SeparateBodyBaseCache

View File

@ -5,6 +5,7 @@
""" """
The httplib2 algorithms ported for use with requests. The httplib2 algorithms ported for use with requests.
""" """
from __future__ import annotations from __future__ import annotations
import calendar import calendar

View File

@ -38,10 +38,10 @@ class CallbackFileWrapper:
self.__callback = callback self.__callback = callback
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
# The vaguaries of garbage collection means that self.__fp is # The vagaries of garbage collection means that self.__fp is
# not always set. By using __getattribute__ and the private # not always set. By using __getattribute__ and the private
# name[0] allows looking up the attribute value and raising an # name[0] allows looking up the attribute value and raising an
# AttributeError when it doesn't exist. This stop thigns from # AttributeError when it doesn't exist. This stop things from
# infinitely recursing calls to getattr in the case where # infinitely recursing calls to getattr in the case where
# self.__fp hasn't been set. # self.__fp hasn't been set.
# #

View File

@ -68,7 +68,10 @@ class OneDayCache(BaseHeuristic):
if "expires" not in response.headers: if "expires" not in response.headers:
date = parsedate(response.headers["date"]) date = parsedate(response.headers["date"])
expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) # type: ignore[index,misc] expires = expire_after(
timedelta(days=1),
date=datetime(*date[:6], tzinfo=timezone.utc), # type: ignore[index,misc]
)
headers["expires"] = datetime_to_header(expires) headers["expires"] = datetime_to_header(expires)
headers["cache-control"] = "public" headers["cache-control"] = "public"
return headers return headers

View File

@ -1,4 +1,3 @@
from .package_data import __version__
from .core import ( from .core import (
IDNABidiError, IDNABidiError,
IDNAError, IDNAError,
@ -20,8 +19,10 @@ from .core import (
valid_string_length, valid_string_length,
) )
from .intranges import intranges_contain from .intranges import intranges_contain
from .package_data import __version__
__all__ = [ __all__ = [
"__version__",
"IDNABidiError", "IDNABidiError",
"IDNAError", "IDNAError",
"InvalidCodepoint", "InvalidCodepoint",

View File

@ -1,49 +1,51 @@
from .core import encode, decode, alabel, ulabel, IDNAError
import codecs import codecs
import re import re
from typing import Any, Tuple, Optional from typing import Any, Optional, Tuple
from .core import IDNAError, alabel, decode, encode, ulabel
_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]')
class Codec(codecs.Codec): class Codec(codecs.Codec):
def encode(self, data: str, errors: str = "strict") -> Tuple[bytes, int]:
def encode(self, data: str, errors: str = 'strict') -> Tuple[bytes, int]: if errors != "strict":
if errors != 'strict': raise IDNAError('Unsupported error handling "{}"'.format(errors))
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
if not data: if not data:
return b"", 0 return b"", 0
return encode(data), len(data) return encode(data), len(data)
def decode(self, data: bytes, errors: str = 'strict') -> Tuple[str, int]: def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]:
if errors != 'strict': if errors != "strict":
raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) raise IDNAError('Unsupported error handling "{}"'.format(errors))
if not data: if not data:
return '', 0 return "", 0
return decode(data), len(data) return decode(data), len(data)
class IncrementalEncoder(codecs.BufferedIncrementalEncoder): class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]:
if errors != 'strict': if errors != "strict":
raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) raise IDNAError('Unsupported error handling "{}"'.format(errors))
if not data: if not data:
return b'', 0 return b"", 0
labels = _unicode_dots_re.split(data) labels = _unicode_dots_re.split(data)
trailing_dot = b'' trailing_dot = b""
if labels: if labels:
if not labels[-1]: if not labels[-1]:
trailing_dot = b'.' trailing_dot = b"."
del labels[-1] del labels[-1]
elif not final: elif not final:
# Keep potentially unfinished label until the next call # Keep potentially unfinished label until the next call
del labels[-1] del labels[-1]
if labels: if labels:
trailing_dot = b'.' trailing_dot = b"."
result = [] result = []
size = 0 size = 0
@ -54,32 +56,33 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
size += len(label) size += len(label)
# Join with U+002E # Join with U+002E
result_bytes = b'.'.join(result) + trailing_dot result_bytes = b".".join(result) + trailing_dot
size += len(trailing_dot) size += len(trailing_dot)
return result_bytes, size return result_bytes, size
class IncrementalDecoder(codecs.BufferedIncrementalDecoder): class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]:
if errors != 'strict': if errors != "strict":
raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) raise IDNAError('Unsupported error handling "{}"'.format(errors))
if not data: if not data:
return ('', 0) return ("", 0)
if not isinstance(data, str): if not isinstance(data, str):
data = str(data, 'ascii') data = str(data, "ascii")
labels = _unicode_dots_re.split(data) labels = _unicode_dots_re.split(data)
trailing_dot = '' trailing_dot = ""
if labels: if labels:
if not labels[-1]: if not labels[-1]:
trailing_dot = '.' trailing_dot = "."
del labels[-1] del labels[-1]
elif not final: elif not final:
# Keep potentially unfinished label until the next call # Keep potentially unfinished label until the next call
del labels[-1] del labels[-1]
if labels: if labels:
trailing_dot = '.' trailing_dot = "."
result = [] result = []
size = 0 size = 0
@ -89,7 +92,7 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
size += 1 size += 1
size += len(label) size += len(label)
result_str = '.'.join(result) + trailing_dot result_str = ".".join(result) + trailing_dot
size += len(trailing_dot) size += len(trailing_dot)
return (result_str, size) return (result_str, size)
@ -103,7 +106,7 @@ class StreamReader(Codec, codecs.StreamReader):
def search_function(name: str) -> Optional[codecs.CodecInfo]: def search_function(name: str) -> Optional[codecs.CodecInfo]:
if name != 'idna2008': if name != "idna2008":
return None return None
return codecs.CodecInfo( return codecs.CodecInfo(
name=name, name=name,
@ -115,4 +118,5 @@ def search_function(name: str) -> Optional[codecs.CodecInfo]:
streamreader=StreamReader, streamreader=StreamReader,
) )
codecs.register(search_function) codecs.register(search_function)

View File

@ -1,13 +1,15 @@
from .core import *
from .codec import *
from typing import Any, Union from typing import Any, Union
from .core import decode, encode
def ToASCII(label: str) -> bytes: def ToASCII(label: str) -> bytes:
return encode(label) return encode(label)
def ToUnicode(label: Union[bytes, bytearray]) -> str: def ToUnicode(label: Union[bytes, bytearray]) -> str:
return decode(label) return decode(label)
def nameprep(s: Any) -> None:
raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol')
def nameprep(s: Any) -> None:
raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol")

View File

@ -1,31 +1,37 @@
from . import idnadata
import bisect import bisect
import unicodedata
import re import re
from typing import Union, Optional import unicodedata
from typing import Optional, Union
from . import idnadata
from .intranges import intranges_contain from .intranges import intranges_contain
_virama_combining_class = 9 _virama_combining_class = 9
_alabel_prefix = b'xn--' _alabel_prefix = b"xn--"
_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') _unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
class IDNAError(UnicodeError): class IDNAError(UnicodeError):
""" Base exception for all IDNA-encoding related problems """ """Base exception for all IDNA-encoding related problems"""
pass pass
class IDNABidiError(IDNAError): class IDNABidiError(IDNAError):
""" Exception when bidirectional requirements are not satisfied """ """Exception when bidirectional requirements are not satisfied"""
pass pass
class InvalidCodepoint(IDNAError): class InvalidCodepoint(IDNAError):
""" Exception when a disallowed or unallocated codepoint is used """ """Exception when a disallowed or unallocated codepoint is used"""
pass pass
class InvalidCodepointContext(IDNAError): class InvalidCodepointContext(IDNAError):
""" Exception when the codepoint is not valid in the context it is used """ """Exception when the codepoint is not valid in the context it is used"""
pass pass
@ -33,17 +39,20 @@ def _combining_class(cp: int) -> int:
v = unicodedata.combining(chr(cp)) v = unicodedata.combining(chr(cp))
if v == 0: if v == 0:
if not unicodedata.name(chr(cp)): if not unicodedata.name(chr(cp)):
raise ValueError('Unknown character in unicodedata') raise ValueError("Unknown character in unicodedata")
return v return v
def _is_script(cp: str, script: str) -> bool: def _is_script(cp: str, script: str) -> bool:
return intranges_contain(ord(cp), idnadata.scripts[script]) return intranges_contain(ord(cp), idnadata.scripts[script])
def _punycode(s: str) -> bytes: def _punycode(s: str) -> bytes:
return s.encode('punycode') return s.encode("punycode")
def _unot(s: int) -> str: def _unot(s: int) -> str:
return 'U+{:04X}'.format(s) return "U+{:04X}".format(s)
def valid_label_length(label: Union[bytes, str]) -> bool: def valid_label_length(label: Union[bytes, str]) -> bool:
@ -61,96 +70,106 @@ def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool:
def check_bidi(label: str, check_ltr: bool = False) -> bool: def check_bidi(label: str, check_ltr: bool = False) -> bool:
# Bidi rules should only be applied if string contains RTL characters # Bidi rules should only be applied if string contains RTL characters
bidi_label = False bidi_label = False
for (idx, cp) in enumerate(label, 1): for idx, cp in enumerate(label, 1):
direction = unicodedata.bidirectional(cp) direction = unicodedata.bidirectional(cp)
if direction == '': if direction == "":
# String likely comes from a newer version of Unicode # String likely comes from a newer version of Unicode
raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx)) raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx))
if direction in ['R', 'AL', 'AN']: if direction in ["R", "AL", "AN"]:
bidi_label = True bidi_label = True
if not bidi_label and not check_ltr: if not bidi_label and not check_ltr:
return True return True
# Bidi rule 1 # Bidi rule 1
direction = unicodedata.bidirectional(label[0]) direction = unicodedata.bidirectional(label[0])
if direction in ['R', 'AL']: if direction in ["R", "AL"]:
rtl = True rtl = True
elif direction == 'L': elif direction == "L":
rtl = False rtl = False
else: else:
raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label))) raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label)))
valid_ending = False valid_ending = False
number_type = None # type: Optional[str] number_type: Optional[str] = None
for (idx, cp) in enumerate(label, 1): for idx, cp in enumerate(label, 1):
direction = unicodedata.bidirectional(cp) direction = unicodedata.bidirectional(cp)
if rtl: if rtl:
# Bidi rule 2 # Bidi rule 2
if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: if direction not in [
raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx)) "R",
"AL",
"AN",
"EN",
"ES",
"CS",
"ET",
"ON",
"BN",
"NSM",
]:
raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx))
# Bidi rule 3 # Bidi rule 3
if direction in ['R', 'AL', 'EN', 'AN']: if direction in ["R", "AL", "EN", "AN"]:
valid_ending = True valid_ending = True
elif direction != 'NSM': elif direction != "NSM":
valid_ending = False valid_ending = False
# Bidi rule 4 # Bidi rule 4
if direction in ['AN', 'EN']: if direction in ["AN", "EN"]:
if not number_type: if not number_type:
number_type = direction number_type = direction
else: else:
if number_type != direction: if number_type != direction:
raise IDNABidiError('Can not mix numeral types in a right-to-left label') raise IDNABidiError("Can not mix numeral types in a right-to-left label")
else: else:
# Bidi rule 5 # Bidi rule 5
if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]:
raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx)) raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx))
# Bidi rule 6 # Bidi rule 6
if direction in ['L', 'EN']: if direction in ["L", "EN"]:
valid_ending = True valid_ending = True
elif direction != 'NSM': elif direction != "NSM":
valid_ending = False valid_ending = False
if not valid_ending: if not valid_ending:
raise IDNABidiError('Label ends with illegal codepoint directionality') raise IDNABidiError("Label ends with illegal codepoint directionality")
return True return True
def check_initial_combiner(label: str) -> bool: def check_initial_combiner(label: str) -> bool:
if unicodedata.category(label[0])[0] == 'M': if unicodedata.category(label[0])[0] == "M":
raise IDNAError('Label begins with an illegal combining character') raise IDNAError("Label begins with an illegal combining character")
return True return True
def check_hyphen_ok(label: str) -> bool: def check_hyphen_ok(label: str) -> bool:
if label[2:4] == '--': if label[2:4] == "--":
raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') raise IDNAError("Label has disallowed hyphens in 3rd and 4th position")
if label[0] == '-' or label[-1] == '-': if label[0] == "-" or label[-1] == "-":
raise IDNAError('Label must not start or end with a hyphen') raise IDNAError("Label must not start or end with a hyphen")
return True return True
def check_nfc(label: str) -> None: def check_nfc(label: str) -> None:
if unicodedata.normalize('NFC', label) != label: if unicodedata.normalize("NFC", label) != label:
raise IDNAError('Label must be in Normalization Form C') raise IDNAError("Label must be in Normalization Form C")
def valid_contextj(label: str, pos: int) -> bool: def valid_contextj(label: str, pos: int) -> bool:
cp_value = ord(label[pos]) cp_value = ord(label[pos])
if cp_value == 0x200c: if cp_value == 0x200C:
if pos > 0: if pos > 0:
if _combining_class(ord(label[pos - 1])) == _virama_combining_class: if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
return True return True
ok = False ok = False
for i in range(pos-1, -1, -1): for i in range(pos - 1, -1, -1):
joining_type = idnadata.joining_types.get(ord(label[i])) joining_type = idnadata.joining_types.get(ord(label[i]))
if joining_type == ord('T'): if joining_type == ord("T"):
continue continue
elif joining_type in [ord('L'), ord('D')]: elif joining_type in [ord("L"), ord("D")]:
ok = True ok = True
break break
else: else:
@ -160,63 +179,61 @@ def valid_contextj(label: str, pos: int) -> bool:
return False return False
ok = False ok = False
for i in range(pos+1, len(label)): for i in range(pos + 1, len(label)):
joining_type = idnadata.joining_types.get(ord(label[i])) joining_type = idnadata.joining_types.get(ord(label[i]))
if joining_type == ord('T'): if joining_type == ord("T"):
continue continue
elif joining_type in [ord('R'), ord('D')]: elif joining_type in [ord("R"), ord("D")]:
ok = True ok = True
break break
else: else:
break break
return ok return ok
if cp_value == 0x200d: if cp_value == 0x200D:
if pos > 0: if pos > 0:
if _combining_class(ord(label[pos - 1])) == _virama_combining_class: if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
return True return True
return False return False
else: else:
return False return False
def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
cp_value = ord(label[pos]) cp_value = ord(label[pos])
if cp_value == 0x00b7: if cp_value == 0x00B7:
if 0 < pos < len(label)-1: if 0 < pos < len(label) - 1:
if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C:
return True return True
return False return False
elif cp_value == 0x0375: elif cp_value == 0x0375:
if pos < len(label)-1 and len(label) > 1: if pos < len(label) - 1 and len(label) > 1:
return _is_script(label[pos + 1], 'Greek') return _is_script(label[pos + 1], "Greek")
return False return False
elif cp_value == 0x05f3 or cp_value == 0x05f4: elif cp_value == 0x05F3 or cp_value == 0x05F4:
if pos > 0: if pos > 0:
return _is_script(label[pos - 1], 'Hebrew') return _is_script(label[pos - 1], "Hebrew")
return False return False
elif cp_value == 0x30fb: elif cp_value == 0x30FB:
for cp in label: for cp in label:
if cp == '\u30fb': if cp == "\u30fb":
continue continue
if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"):
return True return True
return False return False
elif 0x660 <= cp_value <= 0x669: elif 0x660 <= cp_value <= 0x669:
for cp in label: for cp in label:
if 0x6f0 <= ord(cp) <= 0x06f9: if 0x6F0 <= ord(cp) <= 0x06F9:
return False return False
return True return True
elif 0x6f0 <= cp_value <= 0x6f9: elif 0x6F0 <= cp_value <= 0x6F9:
for cp in label: for cp in label:
if 0x660 <= ord(cp) <= 0x0669: if 0x660 <= ord(cp) <= 0x0669:
return False return False
@ -227,37 +244,49 @@ def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
def check_label(label: Union[str, bytes, bytearray]) -> None: def check_label(label: Union[str, bytes, bytearray]) -> None:
if isinstance(label, (bytes, bytearray)): if isinstance(label, (bytes, bytearray)):
label = label.decode('utf-8') label = label.decode("utf-8")
if len(label) == 0: if len(label) == 0:
raise IDNAError('Empty Label') raise IDNAError("Empty Label")
check_nfc(label) check_nfc(label)
check_hyphen_ok(label) check_hyphen_ok(label)
check_initial_combiner(label) check_initial_combiner(label)
for (pos, cp) in enumerate(label): for pos, cp in enumerate(label):
cp_value = ord(cp) cp_value = ord(cp)
if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]):
continue continue
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]):
if not valid_contextj(label, pos): try:
raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format( if not valid_contextj(label, pos):
_unot(cp_value), pos+1, repr(label))) raise InvalidCodepointContext(
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
)
except ValueError:
raise IDNAError(
"Unknown codepoint adjacent to joiner {} at position {} in {}".format(
_unot(cp_value), pos + 1, repr(label)
)
)
elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]):
if not valid_contexto(label, pos): if not valid_contexto(label, pos):
raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label))) raise InvalidCodepointContext(
"Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
)
else: else:
raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label))) raise InvalidCodepoint(
"Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label))
)
check_bidi(label) check_bidi(label)
def alabel(label: str) -> bytes: def alabel(label: str) -> bytes:
try: try:
label_bytes = label.encode('ascii') label_bytes = label.encode("ascii")
ulabel(label_bytes) ulabel(label_bytes)
if not valid_label_length(label_bytes): if not valid_label_length(label_bytes):
raise IDNAError('Label too long') raise IDNAError("Label too long")
return label_bytes return label_bytes
except UnicodeEncodeError: except UnicodeEncodeError:
pass pass
@ -266,7 +295,7 @@ def alabel(label: str) -> bytes:
label_bytes = _alabel_prefix + _punycode(label) label_bytes = _alabel_prefix + _punycode(label)
if not valid_label_length(label_bytes): if not valid_label_length(label_bytes):
raise IDNAError('Label too long') raise IDNAError("Label too long")
return label_bytes return label_bytes
@ -274,7 +303,7 @@ def alabel(label: str) -> bytes:
def ulabel(label: Union[str, bytes, bytearray]) -> str: def ulabel(label: Union[str, bytes, bytearray]) -> str:
if not isinstance(label, (bytes, bytearray)): if not isinstance(label, (bytes, bytearray)):
try: try:
label_bytes = label.encode('ascii') label_bytes = label.encode("ascii")
except UnicodeEncodeError: except UnicodeEncodeError:
check_label(label) check_label(label)
return label return label
@ -283,19 +312,19 @@ def ulabel(label: Union[str, bytes, bytearray]) -> str:
label_bytes = label_bytes.lower() label_bytes = label_bytes.lower()
if label_bytes.startswith(_alabel_prefix): if label_bytes.startswith(_alabel_prefix):
label_bytes = label_bytes[len(_alabel_prefix):] label_bytes = label_bytes[len(_alabel_prefix) :]
if not label_bytes: if not label_bytes:
raise IDNAError('Malformed A-label, no Punycode eligible content found') raise IDNAError("Malformed A-label, no Punycode eligible content found")
if label_bytes.decode('ascii')[-1] == '-': if label_bytes.decode("ascii")[-1] == "-":
raise IDNAError('A-label must not end with a hyphen') raise IDNAError("A-label must not end with a hyphen")
else: else:
check_label(label_bytes) check_label(label_bytes)
return label_bytes.decode('ascii') return label_bytes.decode("ascii")
try: try:
label = label_bytes.decode('punycode') label = label_bytes.decode("punycode")
except UnicodeError: except UnicodeError:
raise IDNAError('Invalid A-label') raise IDNAError("Invalid A-label")
check_label(label) check_label(label)
return label return label
@ -303,52 +332,60 @@ def ulabel(label: Union[str, bytes, bytearray]) -> str:
def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
"""Re-map the characters in the string according to UTS46 processing.""" """Re-map the characters in the string according to UTS46 processing."""
from .uts46data import uts46data from .uts46data import uts46data
output = ''
output = ""
for pos, char in enumerate(domain): for pos, char in enumerate(domain):
code_point = ord(char) code_point = ord(char)
try: try:
uts46row = uts46data[code_point if code_point < 256 else uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1]
bisect.bisect_left(uts46data, (code_point, 'Z')) - 1]
status = uts46row[1] status = uts46row[1]
replacement = None # type: Optional[str] replacement: Optional[str] = None
if len(uts46row) == 3: if len(uts46row) == 3:
replacement = uts46row[2] replacement = uts46row[2]
if (status == 'V' or if (
(status == 'D' and not transitional) or status == "V"
(status == '3' and not std3_rules and replacement is None)): or (status == "D" and not transitional)
or (status == "3" and not std3_rules and replacement is None)
):
output += char output += char
elif replacement is not None and (status == 'M' or elif replacement is not None and (
(status == '3' and not std3_rules) or status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional)
(status == 'D' and transitional)): ):
output += replacement output += replacement
elif status != 'I': elif status != "I":
raise IndexError() raise IndexError()
except IndexError: except IndexError:
raise InvalidCodepoint( raise InvalidCodepoint(
'Codepoint {} not allowed at position {} in {}'.format( "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain))
_unot(code_point), pos + 1, repr(domain))) )
return unicodedata.normalize('NFC', output) return unicodedata.normalize("NFC", output)
def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes: def encode(
s: Union[str, bytes, bytearray],
strict: bool = False,
uts46: bool = False,
std3_rules: bool = False,
transitional: bool = False,
) -> bytes:
if not isinstance(s, str): if not isinstance(s, str):
try: try:
s = str(s, 'ascii') s = str(s, "ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
raise IDNAError('should pass a unicode string to the function rather than a byte string.') raise IDNAError("should pass a unicode string to the function rather than a byte string.")
if uts46: if uts46:
s = uts46_remap(s, std3_rules, transitional) s = uts46_remap(s, std3_rules, transitional)
trailing_dot = False trailing_dot = False
result = [] result = []
if strict: if strict:
labels = s.split('.') labels = s.split(".")
else: else:
labels = _unicode_dots_re.split(s) labels = _unicode_dots_re.split(s)
if not labels or labels == ['']: if not labels or labels == [""]:
raise IDNAError('Empty domain') raise IDNAError("Empty domain")
if labels[-1] == '': if labels[-1] == "":
del labels[-1] del labels[-1]
trailing_dot = True trailing_dot = True
for label in labels: for label in labels:
@ -356,21 +393,26 @@ def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool =
if s: if s:
result.append(s) result.append(s)
else: else:
raise IDNAError('Empty label') raise IDNAError("Empty label")
if trailing_dot: if trailing_dot:
result.append(b'') result.append(b"")
s = b'.'.join(result) s = b".".join(result)
if not valid_string_length(s, trailing_dot): if not valid_string_length(s, trailing_dot):
raise IDNAError('Domain too long') raise IDNAError("Domain too long")
return s return s
def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str: def decode(
s: Union[str, bytes, bytearray],
strict: bool = False,
uts46: bool = False,
std3_rules: bool = False,
) -> str:
try: try:
if not isinstance(s, str): if not isinstance(s, str):
s = str(s, 'ascii') s = str(s, "ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
raise IDNAError('Invalid ASCII in A-label') raise IDNAError("Invalid ASCII in A-label")
if uts46: if uts46:
s = uts46_remap(s, std3_rules, False) s = uts46_remap(s, std3_rules, False)
trailing_dot = False trailing_dot = False
@ -378,9 +420,9 @@ def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool =
if not strict: if not strict:
labels = _unicode_dots_re.split(s) labels = _unicode_dots_re.split(s)
else: else:
labels = s.split('.') labels = s.split(".")
if not labels or labels == ['']: if not labels or labels == [""]:
raise IDNAError('Empty domain') raise IDNAError("Empty domain")
if not labels[-1]: if not labels[-1]:
del labels[-1] del labels[-1]
trailing_dot = True trailing_dot = True
@ -389,7 +431,7 @@ def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool =
if s: if s:
result.append(s) result.append(s)
else: else:
raise IDNAError('Empty label') raise IDNAError("Empty label")
if trailing_dot: if trailing_dot:
result.append('') result.append("")
return '.'.join(result) return ".".join(result)

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ in the original list?" in time O(log(# runs)).
import bisect import bisect
from typing import List, Tuple from typing import List, Tuple
def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
"""Represent a list of integers as a sequence of ranges: """Represent a list of integers as a sequence of ranges:
((start_0, end_0), (start_1, end_1), ...), such that the original ((start_0, end_0), (start_1, end_1), ...), such that the original
@ -20,18 +21,20 @@ def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
ranges = [] ranges = []
last_write = -1 last_write = -1
for i in range(len(sorted_list)): for i in range(len(sorted_list)):
if i+1 < len(sorted_list): if i + 1 < len(sorted_list):
if sorted_list[i] == sorted_list[i+1]-1: if sorted_list[i] == sorted_list[i + 1] - 1:
continue continue
current_range = sorted_list[last_write+1:i+1] current_range = sorted_list[last_write + 1 : i + 1]
ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
last_write = i last_write = i
return tuple(ranges) return tuple(ranges)
def _encode_range(start: int, end: int) -> int: def _encode_range(start: int, end: int) -> int:
return (start << 32) | end return (start << 32) | end
def _decode_range(r: int) -> Tuple[int, int]: def _decode_range(r: int) -> Tuple[int, int]:
return (r >> 32), (r & ((1 << 32) - 1)) return (r >> 32), (r & ((1 << 32) - 1))
@ -43,7 +46,7 @@ def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool:
# we could be immediately ahead of a tuple (start, end) # we could be immediately ahead of a tuple (start, end)
# with start < int_ <= end # with start < int_ <= end
if pos > 0: if pos > 0:
left, right = _decode_range(ranges[pos-1]) left, right = _decode_range(ranges[pos - 1])
if left <= int_ < right: if left <= int_ < right:
return True return True
# or we could be immediately behind a tuple (int_, end) # or we could be immediately behind a tuple (int_, end)

View File

@ -1,2 +1 @@
__version__ = '3.7' __version__ = "3.10"

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,20 @@
from .exceptions import * # ruff: noqa: F401
from .ext import ExtType, Timestamp
import os import os
from .exceptions import * # noqa: F403
from .ext import ExtType, Timestamp
version = (1, 0, 8) version = (1, 1, 0)
__version__ = "1.0.8" __version__ = "1.1.0"
if os.environ.get("MSGPACK_PUREPYTHON"): if os.environ.get("MSGPACK_PUREPYTHON"):
from .fallback import Packer, unpackb, Unpacker from .fallback import Packer, Unpacker, unpackb
else: else:
try: try:
from ._cmsgpack import Packer, unpackb, Unpacker from ._cmsgpack import Packer, Unpacker, unpackb
except ImportError: except ImportError:
from .fallback import Packer, unpackb, Unpacker from .fallback import Packer, Unpacker, unpackb
def pack(o, stream, **kwargs): def pack(o, stream, **kwargs):

View File

@ -1,6 +1,6 @@
from collections import namedtuple
import datetime import datetime
import struct import struct
from collections import namedtuple
class ExtType(namedtuple("ExtType", "code data")): class ExtType(namedtuple("ExtType", "code data")):
@ -157,7 +157,9 @@ class Timestamp:
:rtype: `datetime.datetime` :rtype: `datetime.datetime`
""" """
utc = datetime.timezone.utc utc = datetime.timezone.utc
return datetime.datetime.fromtimestamp(0, utc) + datetime.timedelta(seconds=self.to_unix()) return datetime.datetime.fromtimestamp(0, utc) + datetime.timedelta(
seconds=self.seconds, microseconds=self.nanoseconds // 1000
)
@staticmethod @staticmethod
def from_datetime(dt): def from_datetime(dt):
@ -165,4 +167,4 @@ class Timestamp:
:rtype: Timestamp :rtype: Timestamp
""" """
return Timestamp.from_unix(dt.timestamp()) return Timestamp(seconds=int(dt.timestamp()), nanoseconds=dt.microsecond * 1000)

View File

@ -1,27 +1,22 @@
"""Fallback pure Python implementation of msgpack""" """Fallback pure Python implementation of msgpack"""
from datetime import datetime as _DateTime
import sys
import struct
import struct
import sys
from datetime import datetime as _DateTime
if hasattr(sys, "pypy_version_info"): if hasattr(sys, "pypy_version_info"):
# StringIO is slow on PyPy, StringIO is faster. However: PyPy's own
# StringBuilder is fastest.
from __pypy__ import newlist_hint from __pypy__ import newlist_hint
from __pypy__.builders import BytesBuilder
try: _USING_STRINGBUILDER = True
from __pypy__.builders import BytesBuilder as StringBuilder
except ImportError:
from __pypy__.builders import StringBuilder
USING_STRINGBUILDER = True
class StringIO: class BytesIO:
def __init__(self, s=b""): def __init__(self, s=b""):
if s: if s:
self.builder = StringBuilder(len(s)) self.builder = BytesBuilder(len(s))
self.builder.append(s) self.builder.append(s)
else: else:
self.builder = StringBuilder() self.builder = BytesBuilder()
def write(self, s): def write(self, s):
if isinstance(s, memoryview): if isinstance(s, memoryview):
@ -34,17 +29,17 @@ if hasattr(sys, "pypy_version_info"):
return self.builder.build() return self.builder.build()
else: else:
USING_STRINGBUILDER = False from io import BytesIO
from io import BytesIO as StringIO
newlist_hint = lambda size: [] _USING_STRINGBUILDER = False
def newlist_hint(size):
return []
from .exceptions import BufferFull, OutOfData, ExtraData, FormatError, StackError from .exceptions import BufferFull, ExtraData, FormatError, OutOfData, StackError
from .ext import ExtType, Timestamp from .ext import ExtType, Timestamp
EX_SKIP = 0 EX_SKIP = 0
EX_CONSTRUCT = 1 EX_CONSTRUCT = 1
EX_READ_ARRAY_HEADER = 2 EX_READ_ARRAY_HEADER = 2
@ -231,6 +226,7 @@ class Unpacker:
def __init__( def __init__(
self, self,
file_like=None, file_like=None,
*,
read_size=0, read_size=0,
use_list=True, use_list=True,
raw=False, raw=False,
@ -333,6 +329,7 @@ class Unpacker:
# Use extend here: INPLACE_ADD += doesn't reliably typecast memoryview in jython # Use extend here: INPLACE_ADD += doesn't reliably typecast memoryview in jython
self._buffer.extend(view) self._buffer.extend(view)
view.release()
def _consume(self): def _consume(self):
"""Gets rid of the used parts of the buffer.""" """Gets rid of the used parts of the buffer."""
@ -649,32 +646,13 @@ class Packer:
The error handler for encoding unicode. (default: 'strict') The error handler for encoding unicode. (default: 'strict')
DO NOT USE THIS!! This option is kept for very specific usage. DO NOT USE THIS!! This option is kept for very specific usage.
Example of streaming deserialize from file-like object:: :param int buf_size:
Internal buffer size. This option is used only for C implementation.
unpacker = Unpacker(file_like)
for o in unpacker:
process(o)
Example of streaming deserialize from socket::
unpacker = Unpacker()
while True:
buf = sock.recv(1024**2)
if not buf:
break
unpacker.feed(buf)
for o in unpacker:
process(o)
Raises ``ExtraData`` when *packed* contains extra bytes.
Raises ``OutOfData`` when *packed* is incomplete.
Raises ``FormatError`` when *packed* is not valid msgpack.
Raises ``StackError`` when *packed* contains too nested.
Other exceptions can be raised during unpacking.
""" """
def __init__( def __init__(
self, self,
*,
default=None, default=None,
use_single_float=False, use_single_float=False,
autoreset=True, autoreset=True,
@ -682,17 +660,17 @@ class Packer:
strict_types=False, strict_types=False,
datetime=False, datetime=False,
unicode_errors=None, unicode_errors=None,
buf_size=None,
): ):
self._strict_types = strict_types self._strict_types = strict_types
self._use_float = use_single_float self._use_float = use_single_float
self._autoreset = autoreset self._autoreset = autoreset
self._use_bin_type = use_bin_type self._use_bin_type = use_bin_type
self._buffer = StringIO() self._buffer = BytesIO()
self._datetime = bool(datetime) self._datetime = bool(datetime)
self._unicode_errors = unicode_errors or "strict" self._unicode_errors = unicode_errors or "strict"
if default is not None: if default is not None and not callable(default):
if not callable(default): raise TypeError("default must be callable")
raise TypeError("default must be callable")
self._default = default self._default = default
def _pack( def _pack(
@ -823,18 +801,18 @@ class Packer:
try: try:
self._pack(obj) self._pack(obj)
except: except:
self._buffer = StringIO() # force reset self._buffer = BytesIO() # force reset
raise raise
if self._autoreset: if self._autoreset:
ret = self._buffer.getvalue() ret = self._buffer.getvalue()
self._buffer = StringIO() self._buffer = BytesIO()
return ret return ret
def pack_map_pairs(self, pairs): def pack_map_pairs(self, pairs):
self._pack_map_pairs(len(pairs), pairs) self._pack_map_pairs(len(pairs), pairs)
if self._autoreset: if self._autoreset:
ret = self._buffer.getvalue() ret = self._buffer.getvalue()
self._buffer = StringIO() self._buffer = BytesIO()
return ret return ret
def pack_array_header(self, n): def pack_array_header(self, n):
@ -843,7 +821,7 @@ class Packer:
self._pack_array_header(n) self._pack_array_header(n)
if self._autoreset: if self._autoreset:
ret = self._buffer.getvalue() ret = self._buffer.getvalue()
self._buffer = StringIO() self._buffer = BytesIO()
return ret return ret
def pack_map_header(self, n): def pack_map_header(self, n):
@ -852,7 +830,7 @@ class Packer:
self._pack_map_header(n) self._pack_map_header(n)
if self._autoreset: if self._autoreset:
ret = self._buffer.getvalue() ret = self._buffer.getvalue()
self._buffer = StringIO() self._buffer = BytesIO()
return ret return ret
def pack_ext_type(self, typecode, data): def pack_ext_type(self, typecode, data):
@ -941,11 +919,11 @@ class Packer:
This method is useful only when autoreset=False. This method is useful only when autoreset=False.
""" """
self._buffer = StringIO() self._buffer = BytesIO()
def getbuffer(self): def getbuffer(self):
"""Return view of internal buffer.""" """Return view of internal buffer."""
if USING_STRINGBUILDER: if _USING_STRINGBUILDER:
return memoryview(self.bytes()) return memoryview(self.bytes())
else: else:
return self._buffer.getbuffer() return self._buffer.getbuffer()

View File

@ -6,10 +6,10 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages" __summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging" __uri__ = "https://github.com/pypa/packaging"
__version__ = "24.1" __version__ = "24.2"
__author__ = "Donald Stufft and individual contributors" __author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io" __email__ = "donald@stufft.io"
__license__ = "BSD-2-Clause or Apache-2.0" __license__ = "BSD-2-Clause or Apache-2.0"
__copyright__ = "2014 %s" % __author__ __copyright__ = f"2014 {__author__}"

View File

@ -48,8 +48,8 @@ class ELFFile:
try: try:
ident = self._read("16B") ident = self._read("16B")
except struct.error: except struct.error as e:
raise ELFInvalid("unable to parse identification") raise ELFInvalid("unable to parse identification") from e
magic = bytes(ident[:4]) magic = bytes(ident[:4])
if magic != b"\x7fELF": if magic != b"\x7fELF":
raise ELFInvalid(f"invalid magic: {magic!r}") raise ELFInvalid(f"invalid magic: {magic!r}")
@ -67,11 +67,11 @@ class ELFFile:
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB. (2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. (2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
}[(self.capacity, self.encoding)] }[(self.capacity, self.encoding)]
except KeyError: except KeyError as e:
raise ELFInvalid( raise ELFInvalid(
f"unrecognized capacity ({self.capacity}) or " f"unrecognized capacity ({self.capacity}) or "
f"encoding ({self.encoding})" f"encoding ({self.encoding})"
) ) from e
try: try:
( (

View File

@ -164,6 +164,7 @@ def _parse_glibc_version(version_str: str) -> tuple[int, int]:
f"Expected glibc version with 2 components major.minor," f"Expected glibc version with 2 components major.minor,"
f" got: {version_str}", f" got: {version_str}",
RuntimeWarning, RuntimeWarning,
stacklevel=2,
) )
return -1, -1 return -1, -1
return int(m.group("major")), int(m.group("minor")) return int(m.group("major")), int(m.group("minor"))

View File

@ -18,9 +18,9 @@ from .utils import canonicalize_name
__all__ = [ __all__ = [
"InvalidMarker", "InvalidMarker",
"Marker",
"UndefinedComparison", "UndefinedComparison",
"UndefinedEnvironmentName", "UndefinedEnvironmentName",
"Marker",
"default_environment", "default_environment",
] ]
@ -232,7 +232,7 @@ def _evaluate_markers(markers: MarkerList, environment: dict[str, str]) -> bool:
def format_full_version(info: sys._version_info) -> str: def format_full_version(info: sys._version_info) -> str:
version = "{0.major}.{0.minor}.{0.micro}".format(info) version = f"{info.major}.{info.minor}.{info.micro}"
kind = info.releaselevel kind = info.releaselevel
if kind != "final": if kind != "final":
version += kind[0] + str(info.serial) version += kind[0] + str(info.serial)
@ -309,12 +309,6 @@ class Marker:
""" """
current_environment = cast("dict[str, str]", default_environment()) current_environment = cast("dict[str, str]", default_environment())
current_environment["extra"] = "" current_environment["extra"] = ""
# Work around platform.python_version() returning something that is not PEP 440
# compliant for non-tagged Python builds. We preserve default_environment()'s
# behavior of returning platform.python_version() verbatim, and leave it to the
# caller to provide a syntactically valid version if they want to override it.
if current_environment["python_full_version"].endswith("+"):
current_environment["python_full_version"] += "local"
if environment is not None: if environment is not None:
current_environment.update(environment) current_environment.update(environment)
# The API used to allow setting extra to None. We need to handle this # The API used to allow setting extra to None. We need to handle this
@ -322,4 +316,16 @@ class Marker:
if current_environment["extra"] is None: if current_environment["extra"] is None:
current_environment["extra"] = "" current_environment["extra"] = ""
return _evaluate_markers(self._markers, current_environment) return _evaluate_markers(
self._markers, _repair_python_full_version(current_environment)
)
def _repair_python_full_version(env: dict[str, str]) -> dict[str, str]:
"""
Work around platform.python_version() returning something that is not PEP 440
compliant for non-tagged Python builds.
"""
if env["python_full_version"].endswith("+"):
env["python_full_version"] += "local"
return env

View File

@ -5,6 +5,8 @@ import email.header
import email.message import email.message
import email.parser import email.parser
import email.policy import email.policy
import pathlib
import sys
import typing import typing
from typing import ( from typing import (
Any, Any,
@ -15,15 +17,16 @@ from typing import (
cast, cast,
) )
from . import requirements, specifiers, utils from . import licenses, requirements, specifiers, utils
from . import version as version_module from . import version as version_module
from .licenses import NormalizedLicenseExpression
T = typing.TypeVar("T") T = typing.TypeVar("T")
try: if sys.version_info >= (3, 11): # pragma: no cover
ExceptionGroup ExceptionGroup = ExceptionGroup
except NameError: # pragma: no cover else: # pragma: no cover
class ExceptionGroup(Exception): class ExceptionGroup(Exception):
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11. """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
@ -42,9 +45,6 @@ except NameError: # pragma: no cover
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})" return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
else: # pragma: no cover
ExceptionGroup = ExceptionGroup
class InvalidMetadata(ValueError): class InvalidMetadata(ValueError):
"""A metadata field contains invalid data.""" """A metadata field contains invalid data."""
@ -128,6 +128,10 @@ class RawMetadata(TypedDict, total=False):
# No new fields were added in PEP 685, just some edge case were # No new fields were added in PEP 685, just some edge case were
# tightened up to provide better interoptability. # tightened up to provide better interoptability.
# Metadata 2.4 - PEP 639
license_expression: str
license_files: list[str]
_STRING_FIELDS = { _STRING_FIELDS = {
"author", "author",
@ -137,6 +141,7 @@ _STRING_FIELDS = {
"download_url", "download_url",
"home_page", "home_page",
"license", "license",
"license_expression",
"maintainer", "maintainer",
"maintainer_email", "maintainer_email",
"metadata_version", "metadata_version",
@ -149,6 +154,7 @@ _STRING_FIELDS = {
_LIST_FIELDS = { _LIST_FIELDS = {
"classifiers", "classifiers",
"dynamic", "dynamic",
"license_files",
"obsoletes", "obsoletes",
"obsoletes_dist", "obsoletes_dist",
"platforms", "platforms",
@ -167,7 +173,7 @@ _DICT_FIELDS = {
def _parse_keywords(data: str) -> list[str]: def _parse_keywords(data: str) -> list[str]:
"""Split a string of comma-separate keyboards into a list of keywords.""" """Split a string of comma-separated keywords into a list of keywords."""
return [k.strip() for k in data.split(",")] return [k.strip() for k in data.split(",")]
@ -216,16 +222,18 @@ def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
# If our source is a str, then our caller has managed encodings for us, # If our source is a str, then our caller has managed encodings for us,
# and we don't need to deal with it. # and we don't need to deal with it.
if isinstance(source, str): if isinstance(source, str):
payload: str = msg.get_payload() payload = msg.get_payload()
assert isinstance(payload, str)
return payload return payload
# If our source is a bytes, then we're managing the encoding and we need # If our source is a bytes, then we're managing the encoding and we need
# to deal with it. # to deal with it.
else: else:
bpayload: bytes = msg.get_payload(decode=True) bpayload = msg.get_payload(decode=True)
assert isinstance(bpayload, bytes)
try: try:
return bpayload.decode("utf8", "strict") return bpayload.decode("utf8", "strict")
except UnicodeDecodeError: except UnicodeDecodeError as exc:
raise ValueError("payload in an invalid encoding") raise ValueError("payload in an invalid encoding") from exc
# The various parse_FORMAT functions here are intended to be as lenient as # The various parse_FORMAT functions here are intended to be as lenient as
@ -251,6 +259,8 @@ _EMAIL_TO_RAW_MAPPING = {
"home-page": "home_page", "home-page": "home_page",
"keywords": "keywords", "keywords": "keywords",
"license": "license", "license": "license",
"license-expression": "license_expression",
"license-file": "license_files",
"maintainer": "maintainer", "maintainer": "maintainer",
"maintainer-email": "maintainer_email", "maintainer-email": "maintainer_email",
"metadata-version": "metadata_version", "metadata-version": "metadata_version",
@ -426,7 +436,7 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
payload = _get_payload(parsed, data) payload = _get_payload(parsed, data)
except ValueError: except ValueError:
unparsed.setdefault("description", []).append( unparsed.setdefault("description", []).append(
parsed.get_payload(decode=isinstance(data, bytes)) parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload]
) )
else: else:
if payload: if payload:
@ -453,8 +463,8 @@ _NOT_FOUND = object()
# Keep the two values in sync. # Keep the two values in sync.
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"] _VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"] _MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"]) _REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
@ -535,7 +545,7 @@ class _Validator(Generic[T]):
except utils.InvalidName as exc: except utils.InvalidName as exc:
raise self._invalid_metadata( raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc f"{value!r} is invalid for {{field}}", cause=exc
) ) from exc
else: else:
return value return value
@ -547,7 +557,7 @@ class _Validator(Generic[T]):
except version_module.InvalidVersion as exc: except version_module.InvalidVersion as exc:
raise self._invalid_metadata( raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc f"{value!r} is invalid for {{field}}", cause=exc
) ) from exc
def _process_summary(self, value: str) -> str: def _process_summary(self, value: str) -> str:
"""Check the field contains no newlines.""" """Check the field contains no newlines."""
@ -591,10 +601,12 @@ class _Validator(Generic[T]):
for dynamic_field in map(str.lower, value): for dynamic_field in map(str.lower, value):
if dynamic_field in {"name", "version", "metadata-version"}: if dynamic_field in {"name", "version", "metadata-version"}:
raise self._invalid_metadata( raise self._invalid_metadata(
f"{value!r} is not allowed as a dynamic field" f"{dynamic_field!r} is not allowed as a dynamic field"
) )
elif dynamic_field not in _EMAIL_TO_RAW_MAPPING: elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
raise self._invalid_metadata(f"{value!r} is not a valid dynamic field") raise self._invalid_metadata(
f"{dynamic_field!r} is not a valid dynamic field"
)
return list(map(str.lower, value)) return list(map(str.lower, value))
def _process_provides_extra( def _process_provides_extra(
@ -608,7 +620,7 @@ class _Validator(Generic[T]):
except utils.InvalidName as exc: except utils.InvalidName as exc:
raise self._invalid_metadata( raise self._invalid_metadata(
f"{name!r} is invalid for {{field}}", cause=exc f"{name!r} is invalid for {{field}}", cause=exc
) ) from exc
else: else:
return normalized_names return normalized_names
@ -618,7 +630,7 @@ class _Validator(Generic[T]):
except specifiers.InvalidSpecifier as exc: except specifiers.InvalidSpecifier as exc:
raise self._invalid_metadata( raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc f"{value!r} is invalid for {{field}}", cause=exc
) ) from exc
def _process_requires_dist( def _process_requires_dist(
self, self,
@ -629,10 +641,49 @@ class _Validator(Generic[T]):
for req in value: for req in value:
reqs.append(requirements.Requirement(req)) reqs.append(requirements.Requirement(req))
except requirements.InvalidRequirement as exc: except requirements.InvalidRequirement as exc:
raise self._invalid_metadata(f"{req!r} is invalid for {{field}}", cause=exc) raise self._invalid_metadata(
f"{req!r} is invalid for {{field}}", cause=exc
) from exc
else: else:
return reqs return reqs
def _process_license_expression(
self, value: str
) -> NormalizedLicenseExpression | None:
try:
return licenses.canonicalize_license_expression(value)
except ValueError as exc:
raise self._invalid_metadata(
f"{value!r} is invalid for {{field}}", cause=exc
) from exc
def _process_license_files(self, value: list[str]) -> list[str]:
paths = []
for path in value:
if ".." in path:
raise self._invalid_metadata(
f"{path!r} is invalid for {{field}}, "
"parent directory indicators are not allowed"
)
if "*" in path:
raise self._invalid_metadata(
f"{path!r} is invalid for {{field}}, paths must be resolved"
)
if (
pathlib.PurePosixPath(path).is_absolute()
or pathlib.PureWindowsPath(path).is_absolute()
):
raise self._invalid_metadata(
f"{path!r} is invalid for {{field}}, paths must be relative"
)
if pathlib.PureWindowsPath(path).as_posix() != path:
raise self._invalid_metadata(
f"{path!r} is invalid for {{field}}, "
"paths must use '/' delimiter"
)
paths.append(path)
return paths
class Metadata: class Metadata:
"""Representation of distribution metadata. """Representation of distribution metadata.
@ -688,8 +739,8 @@ class Metadata:
field = _RAW_TO_EMAIL_MAPPING[key] field = _RAW_TO_EMAIL_MAPPING[key]
exc = InvalidMetadata( exc = InvalidMetadata(
field, field,
"{field} introduced in metadata version " f"{field} introduced in metadata version "
"{field_metadata_version}, not {metadata_version}", f"{field_metadata_version}, not {metadata_version}",
) )
exceptions.append(exc) exceptions.append(exc)
continue continue
@ -733,6 +784,8 @@ class Metadata:
metadata_version: _Validator[_MetadataVersion] = _Validator() metadata_version: _Validator[_MetadataVersion] = _Validator()
""":external:ref:`core-metadata-metadata-version` """:external:ref:`core-metadata-metadata-version`
(required; validated to be a valid metadata version)""" (required; validated to be a valid metadata version)"""
# `name` is not normalized/typed to NormalizedName so as to provide access to
# the original/raw name.
name: _Validator[str] = _Validator() name: _Validator[str] = _Validator()
""":external:ref:`core-metadata-name` """:external:ref:`core-metadata-name`
(required; validated using :func:`~packaging.utils.canonicalize_name` and its (required; validated using :func:`~packaging.utils.canonicalize_name` and its
@ -770,6 +823,12 @@ class Metadata:
""":external:ref:`core-metadata-maintainer-email`""" """:external:ref:`core-metadata-maintainer-email`"""
license: _Validator[str | None] = _Validator() license: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-license`""" """:external:ref:`core-metadata-license`"""
license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator(
added="2.4"
)
""":external:ref:`core-metadata-license-expression`"""
license_files: _Validator[list[str] | None] = _Validator(added="2.4")
""":external:ref:`core-metadata-license-file`"""
classifiers: _Validator[list[str] | None] = _Validator(added="1.1") classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
""":external:ref:`core-metadata-classifier`""" """:external:ref:`core-metadata-classifier`"""
requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator( requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(

View File

@ -234,7 +234,7 @@ class Specifier(BaseSpecifier):
""" """
match = self._regex.search(spec) match = self._regex.search(spec)
if not match: if not match:
raise InvalidSpecifier(f"Invalid specifier: '{spec}'") raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
self._spec: tuple[str, str] = ( self._spec: tuple[str, str] = (
match.group("operator").strip(), match.group("operator").strip(),
@ -256,7 +256,7 @@ class Specifier(BaseSpecifier):
# operators, and if they are if they are including an explicit # operators, and if they are if they are including an explicit
# prerelease. # prerelease.
operator, version = self._spec operator, version = self._spec
if operator in ["==", ">=", "<=", "~=", "==="]: if operator in ["==", ">=", "<=", "~=", "===", ">", "<"]:
# The == specifier can include a trailing .*, if it does we # The == specifier can include a trailing .*, if it does we
# want to remove before parsing. # want to remove before parsing.
if operator == "==" and version.endswith(".*"): if operator == "==" and version.endswith(".*"):
@ -694,12 +694,18 @@ class SpecifierSet(BaseSpecifier):
specifiers (``>=3.0,!=3.1``), or no specifier at all. specifiers (``>=3.0,!=3.1``), or no specifier at all.
""" """
def __init__(self, specifiers: str = "", prereleases: bool | None = None) -> None: def __init__(
self,
specifiers: str | Iterable[Specifier] = "",
prereleases: bool | None = None,
) -> None:
"""Initialize a SpecifierSet instance. """Initialize a SpecifierSet instance.
:param specifiers: :param specifiers:
The string representation of a specifier or a comma-separated list of The string representation of a specifier or a comma-separated list of
specifiers which will be parsed and normalized before use. specifiers which will be parsed and normalized before use.
May also be an iterable of ``Specifier`` instances, which will be used
as is.
:param prereleases: :param prereleases:
This tells the SpecifierSet if it should accept prerelease versions if This tells the SpecifierSet if it should accept prerelease versions if
applicable or not. The default of ``None`` will autodetect it from the applicable or not. The default of ``None`` will autodetect it from the
@ -710,12 +716,17 @@ class SpecifierSet(BaseSpecifier):
raised. raised.
""" """
# Split on `,` to break each individual specifier into it's own item, and if isinstance(specifiers, str):
# strip each item to remove leading/trailing whitespace. # Split on `,` to break each individual specifier into its own item, and
split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] # strip each item to remove leading/trailing whitespace.
split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
# Make each individual specifier a Specifier and save in a frozen set for later. # Make each individual specifier a Specifier and save in a frozen set
self._specs = frozenset(map(Specifier, split_specifiers)) # for later.
self._specs = frozenset(map(Specifier, split_specifiers))
else:
# Save the supplied specifiers in a frozen set.
self._specs = frozenset(specifiers)
# Store our prereleases value so we can use it later to determine if # Store our prereleases value so we can use it later to determine if
# we accept prereleases or not. # we accept prereleases or not.

View File

@ -47,7 +47,7 @@ class Tag:
is also supported. is also supported.
""" """
__slots__ = ["_interpreter", "_abi", "_platform", "_hash"] __slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
def __init__(self, interpreter: str, abi: str, platform: str) -> None: def __init__(self, interpreter: str, abi: str, platform: str) -> None:
self._interpreter = interpreter.lower() self._interpreter = interpreter.lower()
@ -235,9 +235,8 @@ def cpython_tags(
if use_abi3: if use_abi3:
for minor_version in range(python_version[1] - 1, 1, -1): for minor_version in range(python_version[1] - 1, 1, -1):
for platform_ in platforms: for platform_ in platforms:
interpreter = "cp{version}".format( version = _version_nodot((python_version[0], minor_version))
version=_version_nodot((python_version[0], minor_version)) interpreter = f"cp{version}"
)
yield Tag(interpreter, "abi3", platform_) yield Tag(interpreter, "abi3", platform_)
@ -435,24 +434,22 @@ def mac_platforms(
if (10, 0) <= version and version < (11, 0): if (10, 0) <= version and version < (11, 0):
# Prior to Mac OS 11, each yearly release of Mac OS bumped the # Prior to Mac OS 11, each yearly release of Mac OS bumped the
# "minor" version number. The major version was always 10. # "minor" version number. The major version was always 10.
major_version = 10
for minor_version in range(version[1], -1, -1): for minor_version in range(version[1], -1, -1):
compat_version = 10, minor_version compat_version = major_version, minor_version
binary_formats = _mac_binary_formats(compat_version, arch) binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats: for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format( yield f"macosx_{major_version}_{minor_version}_{binary_format}"
major=10, minor=minor_version, binary_format=binary_format
)
if version >= (11, 0): if version >= (11, 0):
# Starting with Mac OS 11, each yearly release bumps the major version # Starting with Mac OS 11, each yearly release bumps the major version
# number. The minor versions are now the midyear updates. # number. The minor versions are now the midyear updates.
minor_version = 0
for major_version in range(version[0], 10, -1): for major_version in range(version[0], 10, -1):
compat_version = major_version, 0 compat_version = major_version, minor_version
binary_formats = _mac_binary_formats(compat_version, arch) binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats: for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format( yield f"macosx_{major_version}_{minor_version}_{binary_format}"
major=major_version, minor=0, binary_format=binary_format
)
if version >= (11, 0): if version >= (11, 0):
# Mac OS 11 on x86_64 is compatible with binaries from previous releases. # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
@ -462,25 +459,18 @@ def mac_platforms(
# However, the "universal2" binary format can have a # However, the "universal2" binary format can have a
# macOS version earlier than 11.0 when the x86_64 part of the binary supports # macOS version earlier than 11.0 when the x86_64 part of the binary supports
# that version of macOS. # that version of macOS.
major_version = 10
if arch == "x86_64": if arch == "x86_64":
for minor_version in range(16, 3, -1): for minor_version in range(16, 3, -1):
compat_version = 10, minor_version compat_version = major_version, minor_version
binary_formats = _mac_binary_formats(compat_version, arch) binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats: for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format( yield f"macosx_{major_version}_{minor_version}_{binary_format}"
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
)
else: else:
for minor_version in range(16, 3, -1): for minor_version in range(16, 3, -1):
compat_version = 10, minor_version compat_version = major_version, minor_version
binary_format = "universal2" binary_format = "universal2"
yield "macosx_{major}_{minor}_{binary_format}".format( yield f"macosx_{major_version}_{minor_version}_{binary_format}"
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
)
def ios_platforms( def ios_platforms(
@ -500,7 +490,7 @@ def ios_platforms(
# if iOS is the current platform, ios_ver *must* be defined. However, # if iOS is the current platform, ios_ver *must* be defined. However,
# it won't exist for CPython versions before 3.13, which causes a mypy # it won't exist for CPython versions before 3.13, which causes a mypy
# error. # error.
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined] _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2]))) version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
if multiarch is None: if multiarch is None:

View File

@ -4,11 +4,12 @@
from __future__ import annotations from __future__ import annotations
import functools
import re import re
from typing import NewType, Tuple, Union, cast from typing import NewType, Tuple, Union, cast
from .tags import Tag, parse_tag from .tags import Tag, parse_tag
from .version import InvalidVersion, Version from .version import InvalidVersion, Version, _TrimmedRelease
BuildTag = Union[Tuple[()], Tuple[int, str]] BuildTag = Union[Tuple[()], Tuple[int, str]]
NormalizedName = NewType("NormalizedName", str) NormalizedName = NewType("NormalizedName", str)
@ -54,52 +55,40 @@ def is_normalized_name(name: str) -> bool:
return _normalized_regex.match(name) is not None return _normalized_regex.match(name) is not None
@functools.singledispatch
def canonicalize_version( def canonicalize_version(
version: Version | str, *, strip_trailing_zero: bool = True version: Version | str, *, strip_trailing_zero: bool = True
) -> str: ) -> str:
""" """
This is very similar to Version.__str__, but has one subtle difference Return a canonical form of a version as a string.
with the way it handles the release segment.
>>> canonicalize_version('1.0.1')
'1.0.1'
Per PEP 625, versions may have multiple canonical forms, differing
only by trailing zeros.
>>> canonicalize_version('1.0.0')
'1'
>>> canonicalize_version('1.0.0', strip_trailing_zero=False)
'1.0.0'
Invalid versions are returned unaltered.
>>> canonicalize_version('foo bar baz')
'foo bar baz'
""" """
if isinstance(version, str): return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version)
try:
parsed = Version(version)
except InvalidVersion:
# Legacy versions cannot be normalized
return version
else:
parsed = version
parts = []
# Epoch @canonicalize_version.register
if parsed.epoch != 0: def _(version: str, *, strip_trailing_zero: bool = True) -> str:
parts.append(f"{parsed.epoch}!") try:
parsed = Version(version)
# Release segment except InvalidVersion:
release_segment = ".".join(str(x) for x in parsed.release) # Legacy versions cannot be normalized
if strip_trailing_zero: return version
# NB: This strips trailing '.0's to normalize return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero)
release_segment = re.sub(r"(\.0)+$", "", release_segment)
parts.append(release_segment)
# Pre-release
if parsed.pre is not None:
parts.append("".join(str(x) for x in parsed.pre))
# Post-release
if parsed.post is not None:
parts.append(f".post{parsed.post}")
# Development release
if parsed.dev is not None:
parts.append(f".dev{parsed.dev}")
# Local version segment
if parsed.local is not None:
parts.append(f"+{parsed.local}")
return "".join(parts)
def parse_wheel_filename( def parse_wheel_filename(
@ -107,28 +96,28 @@ def parse_wheel_filename(
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]: ) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
if not filename.endswith(".whl"): if not filename.endswith(".whl"):
raise InvalidWheelFilename( raise InvalidWheelFilename(
f"Invalid wheel filename (extension must be '.whl'): {filename}" f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
) )
filename = filename[:-4] filename = filename[:-4]
dashes = filename.count("-") dashes = filename.count("-")
if dashes not in (4, 5): if dashes not in (4, 5):
raise InvalidWheelFilename( raise InvalidWheelFilename(
f"Invalid wheel filename (wrong number of parts): {filename}" f"Invalid wheel filename (wrong number of parts): {filename!r}"
) )
parts = filename.split("-", dashes - 2) parts = filename.split("-", dashes - 2)
name_part = parts[0] name_part = parts[0]
# See PEP 427 for the rules on escaping the project name. # See PEP 427 for the rules on escaping the project name.
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
raise InvalidWheelFilename(f"Invalid project name: {filename}") raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
name = canonicalize_name(name_part) name = canonicalize_name(name_part)
try: try:
version = Version(parts[1]) version = Version(parts[1])
except InvalidVersion as e: except InvalidVersion as e:
raise InvalidWheelFilename( raise InvalidWheelFilename(
f"Invalid wheel filename (invalid version): {filename}" f"Invalid wheel filename (invalid version): {filename!r}"
) from e ) from e
if dashes == 5: if dashes == 5:
@ -136,7 +125,7 @@ def parse_wheel_filename(
build_match = _build_tag_regex.match(build_part) build_match = _build_tag_regex.match(build_part)
if build_match is None: if build_match is None:
raise InvalidWheelFilename( raise InvalidWheelFilename(
f"Invalid build number: {build_part} in '{filename}'" f"Invalid build number: {build_part} in {filename!r}"
) )
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
else: else:
@ -153,14 +142,14 @@ def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
else: else:
raise InvalidSdistFilename( raise InvalidSdistFilename(
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
f" {filename}" f" {filename!r}"
) )
# We are requiring a PEP 440 version, which cannot contain dashes, # We are requiring a PEP 440 version, which cannot contain dashes,
# so we split on the last dash. # so we split on the last dash.
name_part, sep, version_part = file_stem.rpartition("-") name_part, sep, version_part = file_stem.rpartition("-")
if not sep: if not sep:
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
name = canonicalize_name(name_part) name = canonicalize_name(name_part)
@ -168,7 +157,7 @@ def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
version = Version(version_part) version = Version(version_part)
except InvalidVersion as e: except InvalidVersion as e:
raise InvalidSdistFilename( raise InvalidSdistFilename(
f"Invalid sdist filename (invalid version): {filename}" f"Invalid sdist filename (invalid version): {filename!r}"
) from e ) from e
return (name, version) return (name, version)

View File

@ -15,7 +15,7 @@ from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"] __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
LocalType = Tuple[Union[int, str], ...] LocalType = Tuple[Union[int, str], ...]
@ -199,7 +199,7 @@ class Version(_BaseVersion):
# Validate the version and parse it into pieces # Validate the version and parse it into pieces
match = self._regex.search(version) match = self._regex.search(version)
if not match: if not match:
raise InvalidVersion(f"Invalid version: '{version}'") raise InvalidVersion(f"Invalid version: {version!r}")
# Store the parsed out pieces of the version # Store the parsed out pieces of the version
self._version = _Version( self._version = _Version(
@ -232,7 +232,7 @@ class Version(_BaseVersion):
return f"<Version('{self}')>" return f"<Version('{self}')>"
def __str__(self) -> str: def __str__(self) -> str:
"""A string representation of the version that can be rounded-tripped. """A string representation of the version that can be round-tripped.
>>> str(Version("1.0a5")) >>> str(Version("1.0a5"))
'1.0a5' '1.0a5'
@ -350,8 +350,8 @@ class Version(_BaseVersion):
'1.2.3' '1.2.3'
>>> Version("1.2.3+abc").public >>> Version("1.2.3+abc").public
'1.2.3' '1.2.3'
>>> Version("1.2.3+abc.dev1").public >>> Version("1!1.2.3dev1+abc").public
'1.2.3' '1!1.2.3.dev1'
""" """
return str(self).split("+", 1)[0] return str(self).split("+", 1)[0]
@ -363,7 +363,7 @@ class Version(_BaseVersion):
'1.2.3' '1.2.3'
>>> Version("1.2.3+abc").base_version >>> Version("1.2.3+abc").base_version
'1.2.3' '1.2.3'
>>> Version("1!1.2.3+abc.dev1").base_version >>> Version("1!1.2.3dev1+abc").base_version
'1!1.2.3' '1!1.2.3'
The "base version" is the public version of the project without any pre or post The "base version" is the public version of the project without any pre or post
@ -451,6 +451,23 @@ class Version(_BaseVersion):
return self.release[2] if len(self.release) >= 3 else 0 return self.release[2] if len(self.release) >= 3 else 0
class _TrimmedRelease(Version):
@property
def release(self) -> tuple[int, ...]:
"""
Release segment without any trailing zeros.
>>> _TrimmedRelease('1.0.0').release
(1,)
>>> _TrimmedRelease('0.0').release
(0,)
"""
rel = super().release
nonzeros = (index for index, val in enumerate(rel) if val)
last_nonzero = max(nonzeros, default=0)
return rel[: last_nonzero + 1]
def _parse_letter_version( def _parse_letter_version(
letter: str | None, number: str | bytes | SupportsInt | None letter: str | None, number: str | bytes | SupportsInt | None
) -> tuple[str, int] | None: ) -> tuple[str, int] | None:
@ -476,7 +493,9 @@ def _parse_letter_version(
letter = "post" letter = "post"
return letter, int(number) return letter, int(number)
if not letter and number:
assert not letter
if number:
# We assume if we are given a number, but we are not given a letter # We assume if we are given a number, but we are not given a letter
# then this is using the implicit post release syntax (e.g. 1.0-1) # then this is using the implicit post release syntax (e.g. 1.0-1)
letter = "post" letter = "post"

View File

@ -19,18 +19,18 @@ if TYPE_CHECKING:
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
if sys.platform == "win32":
from pip._vendor.platformdirs.windows import Windows as _Result
elif sys.platform == "darwin":
from pip._vendor.platformdirs.macos import MacOS as _Result
else:
from pip._vendor.platformdirs.unix import Unix as _Result
def _set_platform_dir_class() -> type[PlatformDirsABC]: def _set_platform_dir_class() -> type[PlatformDirsABC]:
if sys.platform == "win32":
from pip._vendor.platformdirs.windows import Windows as Result # noqa: PLC0415
elif sys.platform == "darwin":
from pip._vendor.platformdirs.macos import MacOS as Result # noqa: PLC0415
else:
from pip._vendor.platformdirs.unix import Unix as Result # noqa: PLC0415
if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
if os.getenv("SHELL") or os.getenv("PREFIX"): if os.getenv("SHELL") or os.getenv("PREFIX"):
return Result return _Result
from pip._vendor.platformdirs.android import _android_folder # noqa: PLC0415 from pip._vendor.platformdirs.android import _android_folder # noqa: PLC0415
@ -39,10 +39,14 @@ def _set_platform_dir_class() -> type[PlatformDirsABC]:
return Android # return to avoid redefinition of a result return Android # return to avoid redefinition of a result
return Result return _Result
PlatformDirs = _set_platform_dir_class() #: Currently active platform if TYPE_CHECKING:
# Work around mypy issue: https://github.com/python/mypy/issues/10962
PlatformDirs = _Result
else:
PlatformDirs = _set_platform_dir_class() #: Currently active platform
AppDirs = PlatformDirs #: Backwards compatibility with appdirs AppDirs = PlatformDirs #: Backwards compatibility with appdirs

View File

@ -117,7 +117,7 @@ class Android(PlatformDirsABC):
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def _android_folder() -> str | None: # noqa: C901, PLR0912 def _android_folder() -> str | None: # noqa: C901
""":return: base folder for the Android OS or None if it cannot be found""" """:return: base folder for the Android OS or None if it cannot be found"""
result: str | None = None result: str | None = None
# type checker isn't happy with our "import android", just don't do this when type checking see # type checker isn't happy with our "import android", just don't do this when type checking see

View File

@ -91,6 +91,12 @@ class PlatformDirsABC(ABC): # noqa: PLR0904
if self.ensure_exists: if self.ensure_exists:
Path(path).mkdir(parents=True, exist_ok=True) Path(path).mkdir(parents=True, exist_ok=True)
def _first_item_as_path_if_multipath(self, directory: str) -> Path:
if self.multipath:
# If multipath is True, the first path is returned.
directory = directory.split(os.pathsep)[0]
return Path(directory)
@property @property
@abstractmethod @abstractmethod
def user_data_dir(self) -> str: def user_data_dir(self) -> str:

View File

@ -4,9 +4,13 @@ from __future__ import annotations
import os.path import os.path
import sys import sys
from typing import TYPE_CHECKING
from .api import PlatformDirsABC from .api import PlatformDirsABC
if TYPE_CHECKING:
from pathlib import Path
class MacOS(PlatformDirsABC): class MacOS(PlatformDirsABC):
""" """
@ -42,6 +46,11 @@ class MacOS(PlatformDirsABC):
return os.pathsep.join(path_list) return os.pathsep.join(path_list)
return path_list[0] return path_list[0]
@property
def site_data_path(self) -> Path:
""":return: data path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
return self._first_item_as_path_if_multipath(self.site_data_dir)
@property @property
def user_config_dir(self) -> str: def user_config_dir(self) -> str:
""":return: config directory tied to the user, same as `user_data_dir`""" """:return: config directory tied to the user, same as `user_data_dir`"""
@ -74,6 +83,11 @@ class MacOS(PlatformDirsABC):
return os.pathsep.join(path_list) return os.pathsep.join(path_list)
return path_list[0] return path_list[0]
@property
def site_cache_path(self) -> Path:
""":return: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
return self._first_item_as_path_if_multipath(self.site_cache_dir)
@property @property
def user_state_dir(self) -> str: def user_state_dir(self) -> str:
""":return: state directory tied to the user, same as `user_data_dir`""" """:return: state directory tied to the user, same as `user_data_dir`"""

View File

@ -218,12 +218,6 @@ class Unix(PlatformDirsABC): # noqa: PLR0904
""":return: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``""" """:return: cache path shared by users. Only return the first item, even if ``multipath`` is set to ``True``"""
return self._first_item_as_path_if_multipath(self.site_cache_dir) return self._first_item_as_path_if_multipath(self.site_cache_dir)
def _first_item_as_path_if_multipath(self, directory: str) -> Path:
if self.multipath:
# If multipath is True, the first path is returned.
directory = directory.split(os.pathsep)[0]
return Path(directory)
def iter_config_dirs(self) -> Iterator[str]: def iter_config_dirs(self) -> Iterator[str]:
""":yield: all user and site configuration directories.""" """:yield: all user and site configuration directories."""
yield self.user_config_dir yield self.user_config_dir

View File

@ -12,5 +12,5 @@ __version__: str
__version_tuple__: VERSION_TUPLE __version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE version_tuple: VERSION_TUPLE
__version__ = version = '4.2.2' __version__ = version = '4.3.6'
__version_tuple__ = version_tuple = (4, 2, 2) __version_tuple__ = version_tuple = (4, 3, 6)

View File

@ -1,8 +1,9 @@
"""Wrappers to call pyproject.toml-based build backend hooks. """Wrappers to call pyproject.toml-based build backend hooks.
""" """
from typing import TYPE_CHECKING
from ._impl import ( from ._impl import (
BackendInvalid,
BackendUnavailable, BackendUnavailable,
BuildBackendHookCaller, BuildBackendHookCaller,
HookMissing, HookMissing,
@ -11,13 +12,20 @@ from ._impl import (
quiet_subprocess_runner, quiet_subprocess_runner,
) )
__version__ = '1.0.0' __version__ = "1.2.0"
__all__ = [ __all__ = [
'BackendUnavailable', "BackendUnavailable",
'BackendInvalid', "BackendInvalid",
'HookMissing', "HookMissing",
'UnsupportedOperation', "UnsupportedOperation",
'default_subprocess_runner', "default_subprocess_runner",
'quiet_subprocess_runner', "quiet_subprocess_runner",
'BuildBackendHookCaller', "BuildBackendHookCaller",
] ]
BackendInvalid = BackendUnavailable # Deprecated alias, previously a separate exception
if TYPE_CHECKING:
from ._impl import SubprocessRunner
__all__ += ["SubprocessRunner"]

View File

@ -1,8 +0,0 @@
__all__ = ("tomllib",)
import sys
if sys.version_info >= (3, 11):
import tomllib
else:
from pip._vendor import tomli as tomllib

View File

@ -6,48 +6,72 @@ from contextlib import contextmanager
from os.path import abspath from os.path import abspath
from os.path import join as pjoin from os.path import join as pjoin
from subprocess import STDOUT, check_call, check_output from subprocess import STDOUT, check_call, check_output
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence
from ._in_process import _in_proc_script_path from ._in_process import _in_proc_script_path
if TYPE_CHECKING:
from typing import Protocol
def write_json(obj, path, **kwargs): class SubprocessRunner(Protocol):
with open(path, 'w', encoding='utf-8') as f: """A protocol for the subprocess runner."""
def __call__(
self,
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
...
def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, **kwargs) json.dump(obj, f, **kwargs)
def read_json(path): def read_json(path: str) -> Mapping[str, Any]:
with open(path, encoding='utf-8') as f: with open(path, encoding="utf-8") as f:
return json.load(f) return json.load(f)
class BackendUnavailable(Exception): class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process.""" """Will be raised if the backend cannot be imported in the hook process."""
def __init__(self, traceback):
self.traceback = traceback
def __init__(
class BackendInvalid(Exception): self,
"""Will be raised if the backend is invalid.""" traceback: str,
def __init__(self, backend_name, backend_path, message): message: Optional[str] = None,
super().__init__(message) backend_name: Optional[str] = None,
backend_path: Optional[Sequence[str]] = None,
) -> None:
# Preserving arg order for the sake of API backward compatibility.
self.backend_name = backend_name self.backend_name = backend_name
self.backend_path = backend_path self.backend_path = backend_path
self.traceback = traceback
super().__init__(message or "Error while importing backend")
class HookMissing(Exception): class HookMissing(Exception):
"""Will be raised on missing hooks (if a fallback can't be used).""" """Will be raised on missing hooks (if a fallback can't be used)."""
def __init__(self, hook_name):
def __init__(self, hook_name: str) -> None:
super().__init__(hook_name) super().__init__(hook_name)
self.hook_name = hook_name self.hook_name = hook_name
class UnsupportedOperation(Exception): class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't.""" """May be raised by build_sdist if the backend indicates that it can't."""
def __init__(self, traceback):
def __init__(self, traceback: str) -> None:
self.traceback = traceback self.traceback = traceback
def default_subprocess_runner(cmd, cwd=None, extra_environ=None): def default_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
"""The default method of calling the wrapper subprocess. """The default method of calling the wrapper subprocess.
This uses :func:`subprocess.check_call` under the hood. This uses :func:`subprocess.check_call` under the hood.
@ -59,7 +83,11 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
check_call(cmd, cwd=cwd, env=env) check_call(cmd, cwd=cwd, env=env)
def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): def quiet_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
"""Call the subprocess while suppressing output. """Call the subprocess while suppressing output.
This uses :func:`subprocess.check_output` under the hood. This uses :func:`subprocess.check_output` under the hood.
@ -71,7 +99,7 @@ def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None):
check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)
def norm_and_check(source_tree, requested): def norm_and_check(source_tree: str, requested: str) -> str:
"""Normalise and check a backend path. """Normalise and check a backend path.
Ensure that the requested backend path is specified as a relative path, Ensure that the requested backend path is specified as a relative path,
@ -96,17 +124,16 @@ def norm_and_check(source_tree, requested):
class BuildBackendHookCaller: class BuildBackendHookCaller:
"""A wrapper to call the build backend hooks for a source directory. """A wrapper to call the build backend hooks for a source directory."""
"""
def __init__( def __init__(
self, self,
source_dir, source_dir: str,
build_backend, build_backend: str,
backend_path=None, backend_path: Optional[Sequence[str]] = None,
runner=None, runner: Optional["SubprocessRunner"] = None,
python_executable=None, python_executable: Optional[str] = None,
): ) -> None:
""" """
:param source_dir: The source directory to invoke the build backend for :param source_dir: The source directory to invoke the build backend for
:param build_backend: The build backend spec :param build_backend: The build backend spec
@ -121,9 +148,7 @@ class BuildBackendHookCaller:
self.source_dir = abspath(source_dir) self.source_dir = abspath(source_dir)
self.build_backend = build_backend self.build_backend = build_backend
if backend_path: if backend_path:
backend_path = [ backend_path = [norm_and_check(self.source_dir, p) for p in backend_path]
norm_and_check(self.source_dir, p) for p in backend_path
]
self.backend_path = backend_path self.backend_path = backend_path
self._subprocess_runner = runner self._subprocess_runner = runner
if not python_executable: if not python_executable:
@ -131,10 +156,12 @@ class BuildBackendHookCaller:
self.python_executable = python_executable self.python_executable = python_executable
@contextmanager @contextmanager
def subprocess_runner(self, runner): def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]:
"""A context manager for temporarily overriding the default """A context manager for temporarily overriding the default
:ref:`subprocess runner <Subprocess Runners>`. :ref:`subprocess runner <Subprocess Runners>`.
:param runner: The new subprocess runner to use within the context.
.. code-block:: python .. code-block:: python
hook_caller = BuildBackendHookCaller(...) hook_caller = BuildBackendHookCaller(...)
@ -148,33 +175,44 @@ class BuildBackendHookCaller:
finally: finally:
self._subprocess_runner = prev self._subprocess_runner = prev
def _supported_features(self): def _supported_features(self) -> Sequence[str]:
"""Return the list of optional features supported by the backend.""" """Return the list of optional features supported by the backend."""
return self._call_hook('_supported_features', {}) return self._call_hook("_supported_features", {})
def get_requires_for_build_wheel(self, config_settings=None): def get_requires_for_build_wheel(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""Get additional dependencies required for building a wheel. """Get additional dependencies required for building a wheel.
:param config_settings: The configuration settings for the build backend
:returns: A list of :pep:`dependency specifiers <508>`. :returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]
.. admonition:: Fallback .. admonition:: Fallback
If the build backend does not defined a hook with this name, an If the build backend does not defined a hook with this name, an
empty list will be returned. empty list will be returned.
""" """
return self._call_hook('get_requires_for_build_wheel', { return self._call_hook(
'config_settings': config_settings "get_requires_for_build_wheel", {"config_settings": config_settings}
}) )
def prepare_metadata_for_build_wheel( def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None, self,
_allow_fallback=True): metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True,
) -> str:
"""Prepare a ``*.dist-info`` folder with metadata for this project. """Prepare a ``*.dist-info`` folder with metadata for this project.
:param metadata_directory: The directory to write the metadata to
:param config_settings: The configuration settings for the build backend
:param _allow_fallback:
Whether to allow the fallback to building a wheel and extracting
the metadata from it. Should be passed as a keyword argument only.
:returns: Name of the newly created subfolder within :returns: Name of the newly created subfolder within
``metadata_directory``, containing the metadata. ``metadata_directory``, containing the metadata.
:rtype: str
.. admonition:: Fallback .. admonition:: Fallback
@ -183,17 +221,26 @@ class BuildBackendHookCaller:
wheel via the ``build_wheel`` hook and the dist-info extracted from wheel via the ``build_wheel`` hook and the dist-info extracted from
that will be returned. that will be returned.
""" """
return self._call_hook('prepare_metadata_for_build_wheel', { return self._call_hook(
'metadata_directory': abspath(metadata_directory), "prepare_metadata_for_build_wheel",
'config_settings': config_settings, {
'_allow_fallback': _allow_fallback, "metadata_directory": abspath(metadata_directory),
}) "config_settings": config_settings,
"_allow_fallback": _allow_fallback,
},
)
def build_wheel( def build_wheel(
self, wheel_directory, config_settings=None, self,
metadata_directory=None): wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None,
) -> str:
"""Build a wheel from this project. """Build a wheel from this project.
:param wheel_directory: The directory to write the wheel to
:param config_settings: The configuration settings for the build backend
:param metadata_directory: The directory to reuse existing metadata from
:returns: :returns:
The name of the newly created wheel within ``wheel_directory``. The name of the newly created wheel within ``wheel_directory``.
@ -206,35 +253,48 @@ class BuildBackendHookCaller:
""" """
if metadata_directory is not None: if metadata_directory is not None:
metadata_directory = abspath(metadata_directory) metadata_directory = abspath(metadata_directory)
return self._call_hook('build_wheel', { return self._call_hook(
'wheel_directory': abspath(wheel_directory), "build_wheel",
'config_settings': config_settings, {
'metadata_directory': metadata_directory, "wheel_directory": abspath(wheel_directory),
}) "config_settings": config_settings,
"metadata_directory": metadata_directory,
},
)
def get_requires_for_build_editable(self, config_settings=None): def get_requires_for_build_editable(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""Get additional dependencies required for building an editable wheel. """Get additional dependencies required for building an editable wheel.
:param config_settings: The configuration settings for the build backend
:returns: A list of :pep:`dependency specifiers <508>`. :returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]
.. admonition:: Fallback .. admonition:: Fallback
If the build backend does not defined a hook with this name, an If the build backend does not defined a hook with this name, an
empty list will be returned. empty list will be returned.
""" """
return self._call_hook('get_requires_for_build_editable', { return self._call_hook(
'config_settings': config_settings "get_requires_for_build_editable", {"config_settings": config_settings}
}) )
def prepare_metadata_for_build_editable( def prepare_metadata_for_build_editable(
self, metadata_directory, config_settings=None, self,
_allow_fallback=True): metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True,
) -> Optional[str]:
"""Prepare a ``*.dist-info`` folder with metadata for this project. """Prepare a ``*.dist-info`` folder with metadata for this project.
:param metadata_directory: The directory to write the metadata to
:param config_settings: The configuration settings for the build backend
:param _allow_fallback:
Whether to allow the fallback to building a wheel and extracting
the metadata from it. Should be passed as a keyword argument only.
:returns: Name of the newly created subfolder within :returns: Name of the newly created subfolder within
``metadata_directory``, containing the metadata. ``metadata_directory``, containing the metadata.
:rtype: str
.. admonition:: Fallback .. admonition:: Fallback
@ -243,17 +303,26 @@ class BuildBackendHookCaller:
wheel via the ``build_editable`` hook and the dist-info wheel via the ``build_editable`` hook and the dist-info
extracted from that will be returned. extracted from that will be returned.
""" """
return self._call_hook('prepare_metadata_for_build_editable', { return self._call_hook(
'metadata_directory': abspath(metadata_directory), "prepare_metadata_for_build_editable",
'config_settings': config_settings, {
'_allow_fallback': _allow_fallback, "metadata_directory": abspath(metadata_directory),
}) "config_settings": config_settings,
"_allow_fallback": _allow_fallback,
},
)
def build_editable( def build_editable(
self, wheel_directory, config_settings=None, self,
metadata_directory=None): wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None,
) -> str:
"""Build an editable wheel from this project. """Build an editable wheel from this project.
:param wheel_directory: The directory to write the wheel to
:param config_settings: The configuration settings for the build backend
:param metadata_directory: The directory to reuse existing metadata from
:returns: :returns:
The name of the newly created wheel within ``wheel_directory``. The name of the newly created wheel within ``wheel_directory``.
@ -267,43 +336,55 @@ class BuildBackendHookCaller:
""" """
if metadata_directory is not None: if metadata_directory is not None:
metadata_directory = abspath(metadata_directory) metadata_directory = abspath(metadata_directory)
return self._call_hook('build_editable', { return self._call_hook(
'wheel_directory': abspath(wheel_directory), "build_editable",
'config_settings': config_settings, {
'metadata_directory': metadata_directory, "wheel_directory": abspath(wheel_directory),
}) "config_settings": config_settings,
"metadata_directory": metadata_directory,
},
)
def get_requires_for_build_sdist(self, config_settings=None): def get_requires_for_build_sdist(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""Get additional dependencies required for building an sdist. """Get additional dependencies required for building an sdist.
:returns: A list of :pep:`dependency specifiers <508>`. :returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]
""" """
return self._call_hook('get_requires_for_build_sdist', { return self._call_hook(
'config_settings': config_settings "get_requires_for_build_sdist", {"config_settings": config_settings}
}) )
def build_sdist(self, sdist_directory, config_settings=None): def build_sdist(
self,
sdist_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
) -> str:
"""Build an sdist from this project. """Build an sdist from this project.
:returns: :returns:
The name of the newly created sdist within ``wheel_directory``. The name of the newly created sdist within ``wheel_directory``.
""" """
return self._call_hook('build_sdist', { return self._call_hook(
'sdist_directory': abspath(sdist_directory), "build_sdist",
'config_settings': config_settings, {
}) "sdist_directory": abspath(sdist_directory),
"config_settings": config_settings,
},
)
def _call_hook(self, hook_name, kwargs): def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any:
extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend} extra_environ = {"_PYPROJECT_HOOKS_BUILD_BACKEND": self.build_backend}
if self.backend_path: if self.backend_path:
backend_path = os.pathsep.join(self.backend_path) backend_path = os.pathsep.join(self.backend_path)
extra_environ['PEP517_BACKEND_PATH'] = backend_path extra_environ["_PYPROJECT_HOOKS_BACKEND_PATH"] = backend_path
with tempfile.TemporaryDirectory() as td: with tempfile.TemporaryDirectory() as td:
hook_input = {'kwargs': kwargs} hook_input = {"kwargs": kwargs}
write_json(hook_input, pjoin(td, 'input.json'), indent=2) write_json(hook_input, pjoin(td, "input.json"), indent=2)
# Run the hook in a subprocess # Run the hook in a subprocess
with _in_proc_script_path() as script: with _in_proc_script_path() as script:
@ -311,20 +392,19 @@ class BuildBackendHookCaller:
self._subprocess_runner( self._subprocess_runner(
[python, abspath(str(script)), hook_name, td], [python, abspath(str(script)), hook_name, td],
cwd=self.source_dir, cwd=self.source_dir,
extra_environ=extra_environ extra_environ=extra_environ,
) )
data = read_json(pjoin(td, 'output.json')) data = read_json(pjoin(td, "output.json"))
if data.get('unsupported'): if data.get("unsupported"):
raise UnsupportedOperation(data.get('traceback', '')) raise UnsupportedOperation(data.get("traceback", ""))
if data.get('no_backend'): if data.get("no_backend"):
raise BackendUnavailable(data.get('traceback', '')) raise BackendUnavailable(
if data.get('backend_invalid'): data.get("traceback", ""),
raise BackendInvalid( message=data.get("backend_error", ""),
backend_name=self.build_backend, backend_name=self.build_backend,
backend_path=self.backend_path, backend_path=self.backend_path,
message=data.get('backend_error', '')
) )
if data.get('hook_missing'): if data.get("hook_missing"):
raise HookMissing(data.get('missing_hook_name') or hook_name) raise HookMissing(data.get("missing_hook_name") or hook_name)
return data['return_val'] return data["return_val"]

View File

@ -11,8 +11,11 @@ try:
except AttributeError: except AttributeError:
# Python 3.8 compatibility # Python 3.8 compatibility
def _in_proc_script_path(): def _in_proc_script_path():
return resources.path(__package__, '_in_process.py') return resources.path(__package__, "_in_process.py")
else: else:
def _in_proc_script_path(): def _in_proc_script_path():
return resources.as_file( return resources.as_file(
resources.files(__package__).joinpath('_in_process.py')) resources.files(__package__).joinpath("_in_process.py")
)

View File

@ -3,8 +3,8 @@
It expects: It expects:
- Command line args: hook_name, control_dir - Command line args: hook_name, control_dir
- Environment variables: - Environment variables:
PEP517_BUILD_BACKEND=entry.point:spec _PYPROJECT_HOOKS_BUILD_BACKEND=entry.point:spec
PEP517_BACKEND_PATH=paths (separated with os.pathsep) _PYPROJECT_HOOKS_BACKEND_PATH=paths (separated with os.pathsep)
- control_dir/input.json: - control_dir/input.json:
- {"kwargs": {...}} - {"kwargs": {...}}
@ -21,6 +21,7 @@ import sys
import traceback import traceback
from glob import glob from glob import glob
from importlib import import_module from importlib import import_module
from importlib.machinery import PathFinder
from os.path import join as pjoin from os.path import join as pjoin
# This file is run as a script, and `import wrappers` is not zip-safe, so we # This file is run as a script, and `import wrappers` is not zip-safe, so we
@ -28,69 +29,93 @@ from os.path import join as pjoin
def write_json(obj, path, **kwargs): def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f: with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, **kwargs) json.dump(obj, f, **kwargs)
def read_json(path): def read_json(path):
with open(path, encoding='utf-8') as f: with open(path, encoding="utf-8") as f:
return json.load(f) return json.load(f)
class BackendUnavailable(Exception): class BackendUnavailable(Exception):
"""Raised if we cannot import the backend""" """Raised if we cannot import the backend"""
def __init__(self, traceback):
self.traceback = traceback
def __init__(self, message, traceback=None):
class BackendInvalid(Exception): super().__init__(message)
"""Raised if the backend is invalid"""
def __init__(self, message):
self.message = message self.message = message
self.traceback = traceback
class HookMissing(Exception): class HookMissing(Exception):
"""Raised if a hook is missing and we are not executing the fallback""" """Raised if a hook is missing and we are not executing the fallback"""
def __init__(self, hook_name=None): def __init__(self, hook_name=None):
super().__init__(hook_name) super().__init__(hook_name)
self.hook_name = hook_name self.hook_name = hook_name
def contained_in(filename, directory):
"""Test if a file is located within the given directory."""
filename = os.path.normcase(os.path.abspath(filename))
directory = os.path.normcase(os.path.abspath(directory))
return os.path.commonprefix([filename, directory]) == directory
def _build_backend(): def _build_backend():
"""Find and load the build backend""" """Find and load the build backend"""
# Add in-tree backend directories to the front of sys.path. backend_path = os.environ.get("_PYPROJECT_HOOKS_BACKEND_PATH")
backend_path = os.environ.get('PEP517_BACKEND_PATH') ep = os.environ["_PYPROJECT_HOOKS_BUILD_BACKEND"]
if backend_path: mod_path, _, obj_path = ep.partition(":")
extra_pathitems = backend_path.split(os.pathsep)
sys.path[:0] = extra_pathitems if backend_path:
# Ensure in-tree backend directories have the highest priority when importing.
extra_pathitems = backend_path.split(os.pathsep)
sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path))
ep = os.environ['PEP517_BUILD_BACKEND']
mod_path, _, obj_path = ep.partition(':')
try: try:
obj = import_module(mod_path) obj = import_module(mod_path)
except ImportError: except ImportError:
raise BackendUnavailable(traceback.format_exc()) msg = f"Cannot import {mod_path!r}"
raise BackendUnavailable(msg, traceback.format_exc())
if backend_path:
if not any(
contained_in(obj.__file__, path)
for path in extra_pathitems
):
raise BackendInvalid("Backend was not loaded from backend-path")
if obj_path: if obj_path:
for path_part in obj_path.split('.'): for path_part in obj_path.split("."):
obj = getattr(obj, path_part) obj = getattr(obj, path_part)
return obj return obj
class _BackendPathFinder:
"""Implements the MetaPathFinder interface to locate modules in ``backend-path``.
Since the environment provided by the frontend can contain all sorts of
MetaPathFinders, the only way to ensure the backend is loaded from the
right place is to prepend our own.
"""
def __init__(self, backend_path, backend_module):
self.backend_path = backend_path
self.backend_module = backend_module
self.backend_parent, _, _ = backend_module.partition(".")
def find_spec(self, fullname, _path, _target=None):
if "." in fullname:
# Rely on importlib to find nested modules based on parent's path
return None
# Ignore other items in _path or sys.path and use backend_path instead:
spec = PathFinder.find_spec(fullname, path=self.backend_path)
if spec is None and fullname == self.backend_parent:
# According to the spec, the backend MUST be loaded from backend-path.
# Therefore, we can halt the import machinery and raise a clean error.
msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}"
raise BackendUnavailable(msg)
return spec
if sys.version_info >= (3, 8):
def find_distributions(self, context=None):
# Delayed import: Python 3.7 does not contain importlib.metadata
from importlib.metadata import DistributionFinder, MetadataPathFinder
context = DistributionFinder.Context(path=self.backend_path)
return MetadataPathFinder.find_distributions(context=context)
def _supported_features(): def _supported_features():
"""Return the list of options features supported by the backend. """Return the list of options features supported by the backend.
@ -133,7 +158,8 @@ def get_requires_for_build_editable(config_settings):
def prepare_metadata_for_build_wheel( def prepare_metadata_for_build_wheel(
metadata_directory, config_settings, _allow_fallback): metadata_directory, config_settings, _allow_fallback
):
"""Invoke optional prepare_metadata_for_build_wheel """Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined, Implements a fallback by building a wheel if the hook isn't defined,
@ -150,12 +176,14 @@ def prepare_metadata_for_build_wheel(
# fallback to build_wheel outside the try block to avoid exception chaining # fallback to build_wheel outside the try block to avoid exception chaining
# which can be confusing to users and is not relevant # which can be confusing to users and is not relevant
whl_basename = backend.build_wheel(metadata_directory, config_settings) whl_basename = backend.build_wheel(metadata_directory, config_settings)
return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, return _get_wheel_metadata_from_wheel(
config_settings) whl_basename, metadata_directory, config_settings
)
def prepare_metadata_for_build_editable( def prepare_metadata_for_build_editable(
metadata_directory, config_settings, _allow_fallback): metadata_directory, config_settings, _allow_fallback
):
"""Invoke optional prepare_metadata_for_build_editable """Invoke optional prepare_metadata_for_build_editable
Implements a fallback by building an editable wheel if the hook isn't Implements a fallback by building an editable wheel if the hook isn't
@ -171,24 +199,24 @@ def prepare_metadata_for_build_editable(
try: try:
build_hook = backend.build_editable build_hook = backend.build_editable
except AttributeError: except AttributeError:
raise HookMissing(hook_name='build_editable') raise HookMissing(hook_name="build_editable")
else: else:
whl_basename = build_hook(metadata_directory, config_settings) whl_basename = build_hook(metadata_directory, config_settings)
return _get_wheel_metadata_from_wheel(whl_basename, return _get_wheel_metadata_from_wheel(
metadata_directory, whl_basename, metadata_directory, config_settings
config_settings) )
else: else:
return hook(metadata_directory, config_settings) return hook(metadata_directory, config_settings)
WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' WHEEL_BUILT_MARKER = "PYPROJECT_HOOKS_ALREADY_BUILT_WHEEL"
def _dist_info_files(whl_zip): def _dist_info_files(whl_zip):
"""Identify the .dist-info folder inside a wheel ZipFile.""" """Identify the .dist-info folder inside a wheel ZipFile."""
res = [] res = []
for path in whl_zip.namelist(): for path in whl_zip.namelist():
m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) m = re.match(r"[^/\\]+-[^/\\]+\.dist-info/", path)
if m: if m:
res.append(path) res.append(path)
if res: if res:
@ -196,40 +224,41 @@ def _dist_info_files(whl_zip):
raise Exception("No .dist-info folder found in wheel") raise Exception("No .dist-info folder found in wheel")
def _get_wheel_metadata_from_wheel( def _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, config_settings):
whl_basename, metadata_directory, config_settings):
"""Extract the metadata from a wheel. """Extract the metadata from a wheel.
Fallback for when the build backend does not Fallback for when the build backend does not
define the 'get_wheel_metadata' hook. define the 'get_wheel_metadata' hook.
""" """
from zipfile import ZipFile from zipfile import ZipFile
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'):
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), "wb"):
pass # Touch marker file pass # Touch marker file
whl_file = os.path.join(metadata_directory, whl_basename) whl_file = os.path.join(metadata_directory, whl_basename)
with ZipFile(whl_file) as zipf: with ZipFile(whl_file) as zipf:
dist_info = _dist_info_files(zipf) dist_info = _dist_info_files(zipf)
zipf.extractall(path=metadata_directory, members=dist_info) zipf.extractall(path=metadata_directory, members=dist_info)
return dist_info[0].split('/')[0] return dist_info[0].split("/")[0]
def _find_already_built_wheel(metadata_directory): def _find_already_built_wheel(metadata_directory):
"""Check for a wheel already built during the get_wheel_metadata hook. """Check for a wheel already built during the get_wheel_metadata hook."""
"""
if not metadata_directory: if not metadata_directory:
return None return None
metadata_parent = os.path.dirname(metadata_directory) metadata_parent = os.path.dirname(metadata_directory)
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
return None return None
whl_files = glob(os.path.join(metadata_parent, '*.whl')) whl_files = glob(os.path.join(metadata_parent, "*.whl"))
if not whl_files: if not whl_files:
print('Found wheel built marker, but no .whl files') print("Found wheel built marker, but no .whl files")
return None return None
if len(whl_files) > 1: if len(whl_files) > 1:
print('Found multiple .whl files; unspecified behaviour. ' print(
'Will call build_wheel.') "Found multiple .whl files; unspecified behaviour. "
"Will call build_wheel."
)
return None return None
# Exactly one .whl file # Exactly one .whl file
@ -248,8 +277,9 @@ def build_wheel(wheel_directory, config_settings, metadata_directory=None):
shutil.copy2(prebuilt_whl, wheel_directory) shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl) return os.path.basename(prebuilt_whl)
return _build_backend().build_wheel(wheel_directory, config_settings, return _build_backend().build_wheel(
metadata_directory) wheel_directory, config_settings, metadata_directory
)
def build_editable(wheel_directory, config_settings, metadata_directory=None): def build_editable(wheel_directory, config_settings, metadata_directory=None):
@ -293,6 +323,7 @@ class _DummyException(Exception):
class GotUnsupportedOperation(Exception): class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation""" """For internal use when backend raises UnsupportedOperation"""
def __init__(self, traceback): def __init__(self, traceback):
self.traceback = traceback self.traceback = traceback
@ -302,20 +333,20 @@ def build_sdist(sdist_directory, config_settings):
backend = _build_backend() backend = _build_backend()
try: try:
return backend.build_sdist(sdist_directory, config_settings) return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, 'UnsupportedOperation', _DummyException): except getattr(backend, "UnsupportedOperation", _DummyException):
raise GotUnsupportedOperation(traceback.format_exc()) raise GotUnsupportedOperation(traceback.format_exc())
HOOK_NAMES = { HOOK_NAMES = {
'get_requires_for_build_wheel', "get_requires_for_build_wheel",
'prepare_metadata_for_build_wheel', "prepare_metadata_for_build_wheel",
'build_wheel', "build_wheel",
'get_requires_for_build_editable', "get_requires_for_build_editable",
'prepare_metadata_for_build_editable', "prepare_metadata_for_build_editable",
'build_editable', "build_editable",
'get_requires_for_build_sdist', "get_requires_for_build_sdist",
'build_sdist', "build_sdist",
'_supported_features', "_supported_features",
} }
@ -326,28 +357,33 @@ def main():
control_dir = sys.argv[2] control_dir = sys.argv[2]
if hook_name not in HOOK_NAMES: if hook_name not in HOOK_NAMES:
sys.exit("Unknown hook: %s" % hook_name) sys.exit("Unknown hook: %s" % hook_name)
# Remove the parent directory from sys.path to avoid polluting the backend
# import namespace with this directory.
here = os.path.dirname(__file__)
if here in sys.path:
sys.path.remove(here)
hook = globals()[hook_name] hook = globals()[hook_name]
hook_input = read_json(pjoin(control_dir, 'input.json')) hook_input = read_json(pjoin(control_dir, "input.json"))
json_out = {'unsupported': False, 'return_val': None} json_out = {"unsupported": False, "return_val": None}
try: try:
json_out['return_val'] = hook(**hook_input['kwargs']) json_out["return_val"] = hook(**hook_input["kwargs"])
except BackendUnavailable as e: except BackendUnavailable as e:
json_out['no_backend'] = True json_out["no_backend"] = True
json_out['traceback'] = e.traceback json_out["traceback"] = e.traceback
except BackendInvalid as e: json_out["backend_error"] = e.message
json_out['backend_invalid'] = True
json_out['backend_error'] = e.message
except GotUnsupportedOperation as e: except GotUnsupportedOperation as e:
json_out['unsupported'] = True json_out["unsupported"] = True
json_out['traceback'] = e.traceback json_out["traceback"] = e.traceback
except HookMissing as e: except HookMissing as e:
json_out['hook_missing'] = True json_out["hook_missing"] = True
json_out['missing_hook_name'] = e.hook_name or hook_name json_out["missing_hook_name"] = e.hook_name or hook_name
write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) write_json(json_out, pjoin(control_dir, "output.json"), indent=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -11,14 +11,7 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately environment, you can change the definition of where() to return a separately
packaged CA bundle. packaged CA bundle.
""" """
from pip._vendor.certifi import where
import os
if "_PIP_STANDALONE_CERT" not in os.environ:
from pip._vendor.certifi import where
else:
def where():
return os.environ["_PIP_STANDALONE_CERT"]
if __name__ == "__main__": if __name__ == "__main__":
print(where()) print(where())

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
import inspect import inspect
from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union

View File

@ -46,7 +46,7 @@ class NullFile(IO[str]):
return iter([""]) return iter([""])
def __enter__(self) -> IO[str]: def __enter__(self) -> IO[str]:
pass return self
def __exit__( def __exit__(
self, self,

View File

@ -2,6 +2,7 @@
The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
""" """
import ctypes import ctypes
import sys import sys
from typing import Any from typing import Any
@ -380,7 +381,7 @@ class LegacyWindowsTerm:
WindowsCoordinates: The current cursor position. WindowsCoordinates: The current cursor position.
""" """
coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X)) return WindowsCoordinates(row=coord.Y, col=coord.X)
@property @property
def screen_size(self) -> WindowsCoordinates: def screen_size(self) -> WindowsCoordinates:
@ -390,9 +391,7 @@ class LegacyWindowsTerm:
WindowsCoordinates: The width and height of the screen as WindowsCoordinates. WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
""" """
screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
return WindowsCoordinates( return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)
row=cast(int, screen_size.Y), col=cast(int, screen_size.X)
)
def write_text(self, text: str) -> None: def write_text(self, text: str) -> None:
"""Write text directly to the terminal without any modification of styles """Write text directly to the terminal without any modification of styles

View File

@ -240,6 +240,7 @@ class VerticalCenter(JupyterMixin):
Args: Args:
renderable (RenderableType): A renderable object. renderable (RenderableType): A renderable object.
style (StyleType, optional): An optional style to apply to the background. Defaults to None.
""" """
def __init__( def __init__(

View File

@ -9,6 +9,7 @@ from .text import Text
re_ansi = re.compile( re_ansi = re.compile(
r""" r"""
(?:\x1b[0-?])|
(?:\x1b\](.*?)\x1b\\)| (?:\x1b\](.*?)\x1b\\)|
(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~])) (?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
""", """,

View File

@ -1,13 +1,33 @@
from __future__ import annotations from __future__ import annotations
import re
from functools import lru_cache from functools import lru_cache
from typing import Callable from typing import Callable
from ._cell_widths import CELL_WIDTHS from ._cell_widths import CELL_WIDTHS
# Regex to match sequence of the most common character ranges # Ranges of unicode ordinals that produce a 1-cell wide character
_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match # This is non-exhaustive, but covers most common Western characters
_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [
(0x20, 0x7E), # Latin (excluding non-printable)
(0xA0, 0xAC),
(0xAE, 0x002FF),
(0x00370, 0x00482), # Greek / Cyrillic
(0x02500, 0x025FC), # Box drawing, box elements, geometric shapes
(0x02800, 0x028FF), # Braille
]
# A set of characters that are a single cell wide
_SINGLE_CELLS = frozenset(
[
character
for _start, _end in _SINGLE_CELL_UNICODE_RANGES
for character in map(chr, range(_start, _end + 1))
]
)
# When called with a string this will return True if all
# characters are single-cell, otherwise False
_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset
@lru_cache(4096) @lru_cache(4096)
@ -23,9 +43,9 @@ def cached_cell_len(text: str) -> int:
Returns: Returns:
int: Get the number of cells required to display text. int: Get the number of cells required to display text.
""" """
_get_size = get_character_cell_size if _is_single_cell_widths(text):
total_size = sum(_get_size(character) for character in text) return len(text)
return total_size return sum(map(get_character_cell_size, text))
def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int:
@ -39,9 +59,9 @@ def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> in
""" """
if len(text) < 512: if len(text) < 512:
return _cell_len(text) return _cell_len(text)
_get_size = get_character_cell_size if _is_single_cell_widths(text):
total_size = sum(_get_size(character) for character in text) return len(text)
return total_size return sum(map(get_character_cell_size, text))
@lru_cache(maxsize=4096) @lru_cache(maxsize=4096)
@ -54,20 +74,7 @@ def get_character_cell_size(character: str) -> int:
Returns: Returns:
int: Number of cells (0, 1 or 2) occupied by that character. int: Number of cells (0, 1 or 2) occupied by that character.
""" """
return _get_codepoint_cell_size(ord(character)) codepoint = ord(character)
@lru_cache(maxsize=4096)
def _get_codepoint_cell_size(codepoint: int) -> int:
"""Get the cell size of a character.
Args:
codepoint (int): Codepoint of a character.
Returns:
int: Number of cells (0, 1 or 2) occupied by that character.
"""
_table = CELL_WIDTHS _table = CELL_WIDTHS
lower_bound = 0 lower_bound = 0
upper_bound = len(_table) - 1 upper_bound = len(_table) - 1

View File

@ -1,5 +1,5 @@
import platform
import re import re
import sys
from colorsys import rgb_to_hls from colorsys import rgb_to_hls
from enum import IntEnum from enum import IntEnum
from functools import lru_cache from functools import lru_cache
@ -15,7 +15,7 @@ if TYPE_CHECKING: # pragma: no cover
from .text import Text from .text import Text
WINDOWS = platform.system() == "Windows" WINDOWS = sys.platform == "win32"
class ColorSystem(IntEnum): class ColorSystem(IntEnum):

View File

@ -1,6 +1,5 @@
import inspect import inspect
import os import os
import platform
import sys import sys
import threading import threading
import zlib import zlib
@ -76,7 +75,7 @@ if TYPE_CHECKING:
JUPYTER_DEFAULT_COLUMNS = 115 JUPYTER_DEFAULT_COLUMNS = 115
JUPYTER_DEFAULT_LINES = 100 JUPYTER_DEFAULT_LINES = 100
WINDOWS = platform.system() == "Windows" WINDOWS = sys.platform == "win32"
HighlighterType = Callable[[Union[str, "Text"]], "Text"] HighlighterType = Callable[[Union[str, "Text"]], "Text"]
JustifyMethod = Literal["default", "left", "center", "right", "full"] JustifyMethod = Literal["default", "left", "center", "right", "full"]
@ -90,15 +89,15 @@ class NoChange:
NO_CHANGE = NoChange() NO_CHANGE = NoChange()
try: try:
_STDIN_FILENO = sys.__stdin__.fileno() _STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr]
except Exception: except Exception:
_STDIN_FILENO = 0 _STDIN_FILENO = 0
try: try:
_STDOUT_FILENO = sys.__stdout__.fileno() _STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr]
except Exception: except Exception:
_STDOUT_FILENO = 1 _STDOUT_FILENO = 1
try: try:
_STDERR_FILENO = sys.__stderr__.fileno() _STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr]
except Exception: except Exception:
_STDERR_FILENO = 2 _STDERR_FILENO = 2
@ -1006,19 +1005,14 @@ class Console:
width: Optional[int] = None width: Optional[int] = None
height: Optional[int] = None height: Optional[int] = None
if WINDOWS: # pragma: no cover streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS
for file_descriptor in streams:
try: try:
width, height = os.get_terminal_size() width, height = os.get_terminal_size(file_descriptor)
except (AttributeError, ValueError, OSError): # Probably not a terminal except (AttributeError, ValueError, OSError): # Probably not a terminal
pass pass
else: else:
for file_descriptor in _STD_STREAMS: break
try:
width, height = os.get_terminal_size(file_descriptor)
except (AttributeError, ValueError, OSError):
pass
else:
break
columns = self._environ.get("COLUMNS") columns = self._environ.get("COLUMNS")
if columns is not None and columns.isdigit(): if columns is not None and columns.isdigit():
@ -1309,7 +1303,7 @@ class Console:
renderable = rich_cast(renderable) renderable = rich_cast(renderable)
if hasattr(renderable, "__rich_console__") and not isclass(renderable): if hasattr(renderable, "__rich_console__") and not isclass(renderable):
render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr] render_iterable = renderable.__rich_console__(self, _options)
elif isinstance(renderable, str): elif isinstance(renderable, str):
text_renderable = self.render_str( text_renderable = self.render_str(
renderable, highlight=_options.highlight, markup=_options.markup renderable, highlight=_options.highlight, markup=_options.markup
@ -1386,9 +1380,14 @@ class Console:
extra_lines = render_options.height - len(lines) extra_lines = render_options.height - len(lines)
if extra_lines > 0: if extra_lines > 0:
pad_line = [ pad_line = [
[Segment(" " * render_options.max_width, style), Segment("\n")] (
if new_lines [
else [Segment(" " * render_options.max_width, style)] Segment(" " * render_options.max_width, style),
Segment("\n"),
]
if new_lines
else [Segment(" " * render_options.max_width, style)]
)
] ]
lines.extend(pad_line * extra_lines) lines.extend(pad_line * extra_lines)
@ -1437,9 +1436,11 @@ class Console:
rich_text.overflow = overflow rich_text.overflow = overflow
else: else:
rich_text = Text( rich_text = Text(
_emoji_replace(text, default_variant=self._emoji_variant) (
if emoji_enabled _emoji_replace(text, default_variant=self._emoji_variant)
else text, if emoji_enabled
else text
),
justify=justify, justify=justify,
overflow=overflow, overflow=overflow,
style=style, style=style,
@ -1536,7 +1537,11 @@ class Console:
if isinstance(renderable, str): if isinstance(renderable, str):
append_text( append_text(
self.render_str( self.render_str(
renderable, emoji=emoji, markup=markup, highlighter=_highlighter renderable,
emoji=emoji,
markup=markup,
highlight=highlight,
highlighter=_highlighter,
) )
) )
elif isinstance(renderable, Text): elif isinstance(renderable, Text):
@ -1986,6 +1991,20 @@ class Console:
): ):
buffer_extend(line) buffer_extend(line)
def on_broken_pipe(self) -> None:
"""This function is called when a `BrokenPipeError` is raised.
This can occur when piping Textual output in Linux and macOS.
The default implementation is to exit the app, but you could implement
this method in a subclass to change the behavior.
See https://docs.python.org/3/library/signal.html#note-on-sigpipe for details.
"""
self.quiet = True
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
raise SystemExit(1)
def _check_buffer(self) -> None: def _check_buffer(self) -> None:
"""Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
Rendering is supported on Windows, Unix and Jupyter environments. For Rendering is supported on Windows, Unix and Jupyter environments. For
@ -1995,8 +2014,17 @@ class Console:
if self.quiet: if self.quiet:
del self._buffer[:] del self._buffer[:]
return return
try:
self._write_buffer()
except BrokenPipeError:
self.on_broken_pipe()
def _write_buffer(self) -> None:
"""Write the buffer to the output file."""
with self._lock: with self._lock:
if self.record: if self.record and not self._buffer_index:
with self._record_buffer_lock: with self._record_buffer_lock:
self._record_buffer.extend(self._buffer[:]) self._record_buffer.extend(self._buffer[:])
@ -2166,7 +2194,7 @@ class Console:
""" """
text = self.export_text(clear=clear, styles=styles) text = self.export_text(clear=clear, styles=styles)
with open(path, "wt", encoding="utf-8") as write_file: with open(path, "w", encoding="utf-8") as write_file:
write_file.write(text) write_file.write(text)
def export_html( def export_html(
@ -2272,7 +2300,7 @@ class Console:
code_format=code_format, code_format=code_format,
inline_styles=inline_styles, inline_styles=inline_styles,
) )
with open(path, "wt", encoding="utf-8") as write_file: with open(path, "w", encoding="utf-8") as write_file:
write_file.write(html) write_file.write(html)
def export_svg( def export_svg(
@ -2561,7 +2589,7 @@ class Console:
font_aspect_ratio=font_aspect_ratio, font_aspect_ratio=font_aspect_ratio,
unique_id=unique_id, unique_id=unique_id,
) )
with open(path, "wt", encoding="utf-8") as write_file: with open(path, "w", encoding="utf-8") as write_file:
write_file.write(svg) write_file.write(svg)

View File

@ -54,7 +54,7 @@ DEFAULT_STYLES: Dict[str, Style] = {
"logging.level.notset": Style(dim=True), "logging.level.notset": Style(dim=True),
"logging.level.debug": Style(color="green"), "logging.level.debug": Style(color="green"),
"logging.level.info": Style(color="blue"), "logging.level.info": Style(color="blue"),
"logging.level.warning": Style(color="red"), "logging.level.warning": Style(color="yellow"),
"logging.level.error": Style(color="red", bold=True), "logging.level.error": Style(color="red", bold=True),
"logging.level.critical": Style(color="red", bold=True, reverse=True), "logging.level.critical": Style(color="red", bold=True, reverse=True),
"log.level": Style.null(), "log.level": Style.null(),
@ -120,6 +120,7 @@ DEFAULT_STYLES: Dict[str, Style] = {
"traceback.exc_type": Style(color="bright_red", bold=True), "traceback.exc_type": Style(color="bright_red", bold=True),
"traceback.exc_value": Style.null(), "traceback.exc_value": Style.null(),
"traceback.offset": Style(color="bright_red", bold=True), "traceback.offset": Style(color="bright_red", bold=True),
"traceback.error_range": Style(underline=True, bold=True, dim=False),
"bar.back": Style(color="grey23"), "bar.back": Style(color="grey23"),
"bar.complete": Style(color="rgb(249,38,114)"), "bar.complete": Style(color="rgb(249,38,114)"),
"bar.finished": Style(color="rgb(114,156,31)"), "bar.finished": Style(color="rgb(114,156,31)"),

View File

@ -1,4 +1,3 @@
# coding: utf-8
"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2 """Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2
The functions declared in this module should cover the different The functions declared in this module should cover the different
@ -27,7 +26,7 @@ def _to_str(
if size == 1: if size == 1:
return "1 byte" return "1 byte"
elif size < base: elif size < base:
return "{:,} bytes".format(size) return f"{size:,} bytes"
for i, suffix in enumerate(suffixes, 2): # noqa: B007 for i, suffix in enumerate(suffixes, 2): # noqa: B007
unit = base**i unit = base**i

View File

@ -98,7 +98,7 @@ class ReprHighlighter(RegexHighlighter):
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)", r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?", r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")", r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)", r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)",
), ),
] ]

View File

@ -37,7 +37,7 @@ class Live(JupyterMixin, RenderHook):
Args: Args:
renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing. renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing.
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout. console (Console, optional): Optional Console instance. Defaults to an internal Console instance writing to stdout.
screen (bool, optional): Enable alternate screen mode. Defaults to False. screen (bool, optional): Enable alternate screen mode. Defaults to False.
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4. refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4.

View File

@ -36,11 +36,13 @@ class RichHandler(Handler):
markup (bool, optional): Enable console markup in log messages. Defaults to False. markup (bool, optional): Enable console markup in log messages. Defaults to False.
rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False. rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False.
tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None. tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None.
tracebacks_code_width (int, optional): Number of code characters used to render tracebacks, or None for full width. Defaults to 88.
tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None. tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None.
tracebacks_theme (str, optional): Override pygments theme used in traceback. tracebacks_theme (str, optional): Override pygments theme used in traceback.
tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True. tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True.
tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False. tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
tracebacks_max_frames (int, optional): Optional maximum number of frames returned by traceback.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10. Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
@ -74,11 +76,13 @@ class RichHandler(Handler):
markup: bool = False, markup: bool = False,
rich_tracebacks: bool = False, rich_tracebacks: bool = False,
tracebacks_width: Optional[int] = None, tracebacks_width: Optional[int] = None,
tracebacks_code_width: int = 88,
tracebacks_extra_lines: int = 3, tracebacks_extra_lines: int = 3,
tracebacks_theme: Optional[str] = None, tracebacks_theme: Optional[str] = None,
tracebacks_word_wrap: bool = True, tracebacks_word_wrap: bool = True,
tracebacks_show_locals: bool = False, tracebacks_show_locals: bool = False,
tracebacks_suppress: Iterable[Union[str, ModuleType]] = (), tracebacks_suppress: Iterable[Union[str, ModuleType]] = (),
tracebacks_max_frames: int = 100,
locals_max_length: int = 10, locals_max_length: int = 10,
locals_max_string: int = 80, locals_max_string: int = 80,
log_time_format: Union[str, FormatTimeCallable] = "[%x %X]", log_time_format: Union[str, FormatTimeCallable] = "[%x %X]",
@ -104,6 +108,8 @@ class RichHandler(Handler):
self.tracebacks_word_wrap = tracebacks_word_wrap self.tracebacks_word_wrap = tracebacks_word_wrap
self.tracebacks_show_locals = tracebacks_show_locals self.tracebacks_show_locals = tracebacks_show_locals
self.tracebacks_suppress = tracebacks_suppress self.tracebacks_suppress = tracebacks_suppress
self.tracebacks_max_frames = tracebacks_max_frames
self.tracebacks_code_width = tracebacks_code_width
self.locals_max_length = locals_max_length self.locals_max_length = locals_max_length
self.locals_max_string = locals_max_string self.locals_max_string = locals_max_string
self.keywords = keywords self.keywords = keywords
@ -140,6 +146,7 @@ class RichHandler(Handler):
exc_value, exc_value,
exc_traceback, exc_traceback,
width=self.tracebacks_width, width=self.tracebacks_width,
code_width=self.tracebacks_code_width,
extra_lines=self.tracebacks_extra_lines, extra_lines=self.tracebacks_extra_lines,
theme=self.tracebacks_theme, theme=self.tracebacks_theme,
word_wrap=self.tracebacks_word_wrap, word_wrap=self.tracebacks_word_wrap,
@ -147,6 +154,7 @@ class RichHandler(Handler):
locals_max_length=self.locals_max_length, locals_max_length=self.locals_max_length,
locals_max_string=self.locals_max_string, locals_max_string=self.locals_max_string,
suppress=self.tracebacks_suppress, suppress=self.tracebacks_suppress,
max_frames=self.tracebacks_max_frames,
) )
message = record.getMessage() message = record.getMessage()
if self.formatter: if self.formatter:

View File

@ -1,4 +1,4 @@
from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union from typing import TYPE_CHECKING, List, Optional, Tuple, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from .console import ( from .console import (
@ -7,11 +7,11 @@ if TYPE_CHECKING:
RenderableType, RenderableType,
RenderResult, RenderResult,
) )
from .jupyter import JupyterMixin from .jupyter import JupyterMixin
from .measure import Measurement from .measure import Measurement
from .style import Style
from .segment import Segment from .segment import Segment
from .style import Style
PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]] PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
@ -66,10 +66,10 @@ class Padding(JupyterMixin):
_pad = pad[0] _pad = pad[0]
return (_pad, _pad, _pad, _pad) return (_pad, _pad, _pad, _pad)
if len(pad) == 2: if len(pad) == 2:
pad_top, pad_right = cast(Tuple[int, int], pad) pad_top, pad_right = pad
return (pad_top, pad_right, pad_top, pad_right) return (pad_top, pad_right, pad_top, pad_right)
if len(pad) == 4: if len(pad) == 4:
top, right, bottom, left = cast(Tuple[int, int, int, int], pad) top, right, bottom, left = pad
return (top, right, bottom, left) return (top, right, bottom, left)
raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given") raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")

View File

@ -22,11 +22,13 @@ class Panel(JupyterMixin):
Args: Args:
renderable (RenderableType): A console renderable object. renderable (RenderableType): A console renderable object.
box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
Defaults to box.ROUNDED. title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None.
title_align (AlignMethod, optional): Alignment of title. Defaults to "center".
subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None.
subtitle_align (AlignMethod, optional): Alignment of subtitle. Defaults to "center".
safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
expand (bool, optional): If True the panel will stretch to fill the console expand (bool, optional): If True the panel will stretch to fill the console width, otherwise it will be sized to fit the contents. Defaults to True.
width, otherwise it will be sized to fit the contents. Defaults to True.
style (str, optional): The style of the panel (border and contents). Defaults to "none". style (str, optional): The style of the panel (border and contents). Defaults to "none".
border_style (str, optional): The style of the border. Defaults to "none". border_style (str, optional): The style of the border. Defaults to "none".
width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect. width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
@ -144,7 +146,8 @@ class Panel(JupyterMixin):
Padding(self.renderable, _padding) if any(_padding) else self.renderable Padding(self.renderable, _padding) if any(_padding) else self.renderable
) )
style = console.get_style(self.style) style = console.get_style(self.style)
border_style = style + console.get_style(self.border_style) partial_border_style = console.get_style(self.border_style)
border_style = style + partial_border_style
width = ( width = (
options.max_width options.max_width
if self.width is None if self.width is None
@ -172,6 +175,9 @@ class Panel(JupyterMixin):
text = text.copy() text = text.copy()
text.truncate(width) text.truncate(width)
excess_space = width - cell_len(text.plain) excess_space = width - cell_len(text.plain)
if text.style:
text.stylize(console.get_style(text.style))
if excess_space: if excess_space:
if align == "left": if align == "left":
return Text.assemble( return Text.assemble(
@ -200,7 +206,7 @@ class Panel(JupyterMixin):
title_text = self._title title_text = self._title
if title_text is not None: if title_text is not None:
title_text.stylize_before(border_style) title_text.stylize_before(partial_border_style)
child_width = ( child_width = (
width - 2 width - 2
@ -249,7 +255,7 @@ class Panel(JupyterMixin):
subtitle_text = self._subtitle subtitle_text = self._subtitle
if subtitle_text is not None: if subtitle_text is not None:
subtitle_text.stylize_before(border_style) subtitle_text.stylize_before(partial_border_style)
if subtitle_text is None or width <= 4: if subtitle_text is None or width <= 4:
yield Segment(box.get_bottom([width - 2]), border_style) yield Segment(box.get_bottom([width - 2]), border_style)

View File

@ -3,6 +3,7 @@ import collections
import dataclasses import dataclasses
import inspect import inspect
import os import os
import reprlib
import sys import sys
from array import array from array import array
from collections import Counter, UserDict, UserList, defaultdict, deque from collections import Counter, UserDict, UserList, defaultdict, deque
@ -15,6 +16,7 @@ from typing import (
Any, Any,
Callable, Callable,
DefaultDict, DefaultDict,
Deque,
Dict, Dict,
Iterable, Iterable,
List, List,
@ -77,7 +79,10 @@ def _is_dataclass_repr(obj: object) -> bool:
# Digging in to a lot of internals here # Digging in to a lot of internals here
# Catching all exceptions in case something is missing on a non CPython implementation # Catching all exceptions in case something is missing on a non CPython implementation
try: try:
return obj.__repr__.__code__.co_filename == dataclasses.__file__ return obj.__repr__.__code__.co_filename in (
dataclasses.__file__,
reprlib.__file__,
)
except Exception: # pragma: no coverage except Exception: # pragma: no coverage
return False return False
@ -130,17 +135,19 @@ def _ipy_display_hook(
if _safe_isinstance(value, ConsoleRenderable): if _safe_isinstance(value, ConsoleRenderable):
console.line() console.line()
console.print( console.print(
value (
if _safe_isinstance(value, RichRenderable) value
else Pretty( if _safe_isinstance(value, RichRenderable)
value, else Pretty(
overflow=overflow, value,
indent_guides=indent_guides, overflow=overflow,
max_length=max_length, indent_guides=indent_guides,
max_string=max_string, max_length=max_length,
max_depth=max_depth, max_string=max_string,
expand_all=expand_all, max_depth=max_depth,
margin=12, expand_all=expand_all,
margin=12,
)
), ),
crop=crop, crop=crop,
new_line_start=True, new_line_start=True,
@ -196,16 +203,18 @@ def install(
assert console is not None assert console is not None
builtins._ = None # type: ignore[attr-defined] builtins._ = None # type: ignore[attr-defined]
console.print( console.print(
value (
if _safe_isinstance(value, RichRenderable) value
else Pretty( if _safe_isinstance(value, RichRenderable)
value, else Pretty(
overflow=overflow, value,
indent_guides=indent_guides, overflow=overflow,
max_length=max_length, indent_guides=indent_guides,
max_string=max_string, max_length=max_length,
max_depth=max_depth, max_string=max_string,
expand_all=expand_all, max_depth=max_depth,
expand_all=expand_all,
)
), ),
crop=crop, crop=crop,
) )
@ -353,6 +362,16 @@ def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, st
) )
def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]:
if _object.maxlen is None:
return ("deque([", "])", "deque()")
return (
"deque([",
f"], maxlen={_object.maxlen})",
f"deque(maxlen={_object.maxlen})",
)
def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]: def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})") return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
@ -362,7 +381,7 @@ _BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
array: _get_braces_for_array, array: _get_braces_for_array,
defaultdict: _get_braces_for_defaultdict, defaultdict: _get_braces_for_defaultdict,
Counter: lambda _object: ("Counter({", "})", "Counter()"), Counter: lambda _object: ("Counter({", "})", "Counter()"),
deque: lambda _object: ("deque([", "])", "deque()"), deque: _get_braces_for_deque,
dict: lambda _object: ("{", "}", "{}"), dict: lambda _object: ("{", "}", "{}"),
UserDict: lambda _object: ("{", "}", "{}"), UserDict: lambda _object: ("{", "}", "{}"),
frozenset: lambda _object: ("frozenset({", "})", "frozenset()"), frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
@ -762,7 +781,9 @@ def traverse(
) )
for last, field in loop_last( for last, field in loop_last(
field for field in fields(obj) if field.repr field
for field in fields(obj)
if field.repr and hasattr(obj, field.name)
): ):
child_node = _traverse(getattr(obj, field.name), depth=depth + 1) child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
child_node.key_repr = field.name child_node.key_repr = field.name
@ -846,7 +867,7 @@ def traverse(
pop_visited(obj_id) pop_visited(obj_id)
else: else:
node = Node(value_repr=to_repr(obj), last=root) node = Node(value_repr=to_repr(obj), last=root)
node.is_tuple = _safe_isinstance(obj, tuple) node.is_tuple = type(obj) == tuple
node.is_namedtuple = _is_namedtuple(obj) node.is_namedtuple = _is_namedtuple(obj)
return node return node

View File

@ -39,6 +39,11 @@ if sys.version_info >= (3, 8):
else: else:
from pip._vendor.typing_extensions import Literal # pragma: no cover from pip._vendor.typing_extensions import Literal # pragma: no cover
if sys.version_info >= (3, 11):
from typing import Self
else:
from pip._vendor.typing_extensions import Self # pragma: no cover
from . import filesize, get_console from . import filesize, get_console
from .console import Console, Group, JustifyMethod, RenderableType from .console import Console, Group, JustifyMethod, RenderableType
from .highlighter import Highlighter from .highlighter import Highlighter
@ -70,7 +75,7 @@ class _TrackThread(Thread):
self.done = Event() self.done = Event()
self.completed = 0 self.completed = 0
super().__init__() super().__init__(daemon=True)
def run(self) -> None: def run(self) -> None:
task_id = self.task_id task_id = self.task_id
@ -78,7 +83,7 @@ class _TrackThread(Thread):
update_period = self.update_period update_period = self.update_period
last_completed = 0 last_completed = 0
wait = self.done.wait wait = self.done.wait
while not wait(update_period): while not wait(update_period) and self.progress.live.is_started:
completed = self.completed completed = self.completed
if last_completed != completed: if last_completed != completed:
advance(task_id, completed - last_completed) advance(task_id, completed - last_completed)
@ -104,6 +109,7 @@ def track(
sequence: Union[Sequence[ProgressType], Iterable[ProgressType]], sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
description: str = "Working...", description: str = "Working...",
total: Optional[float] = None, total: Optional[float] = None,
completed: int = 0,
auto_refresh: bool = True, auto_refresh: bool = True,
console: Optional[Console] = None, console: Optional[Console] = None,
transient: bool = False, transient: bool = False,
@ -123,6 +129,7 @@ def track(
sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over. sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over.
description (str, optional): Description of task show next to progress bar. Defaults to "Working". description (str, optional): Description of task show next to progress bar. Defaults to "Working".
total: (float, optional): Total number of steps. Default is len(sequence). total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True. auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
transient: (bool, optional): Clear the progress on exit. Defaults to False. transient: (bool, optional): Clear the progress on exit. Defaults to False.
console (Console, optional): Console to write to. Default creates internal Console instance. console (Console, optional): Console to write to. Default creates internal Console instance.
@ -166,7 +173,11 @@ def track(
with progress: with progress:
yield from progress.track( yield from progress.track(
sequence, total=total, description=description, update_period=update_period sequence,
total=total,
completed=completed,
description=description,
update_period=update_period,
) )
@ -269,6 +280,9 @@ class _Reader(RawIOBase, BinaryIO):
def write(self, s: Any) -> int: def write(self, s: Any) -> int:
raise UnsupportedOperation("write") raise UnsupportedOperation("write")
def writelines(self, lines: Iterable[Any]) -> None:
raise UnsupportedOperation("writelines")
class _ReadContext(ContextManager[_I], Generic[_I]): class _ReadContext(ContextManager[_I], Generic[_I]):
"""A utility class to handle a context for both a reader and a progress.""" """A utility class to handle a context for both a reader and a progress."""
@ -1050,7 +1064,7 @@ class Progress(JupyterMixin):
"""Renders an auto-updating progress bar(s). """Renders an auto-updating progress bar(s).
Args: Args:
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout. console (Console, optional): Optional Console instance. Defaults to an internal Console instance writing to stdout.
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`. auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`.
refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None. refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None.
speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30. speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30.
@ -1161,10 +1175,10 @@ class Progress(JupyterMixin):
def stop(self) -> None: def stop(self) -> None:
"""Stop the progress display.""" """Stop the progress display."""
self.live.stop() self.live.stop()
if not self.console.is_interactive: if not self.console.is_interactive and not self.console.is_jupyter:
self.console.print() self.console.print()
def __enter__(self) -> "Progress": def __enter__(self) -> Self:
self.start() self.start()
return self return self
@ -1180,6 +1194,7 @@ class Progress(JupyterMixin):
self, self,
sequence: Union[Iterable[ProgressType], Sequence[ProgressType]], sequence: Union[Iterable[ProgressType], Sequence[ProgressType]],
total: Optional[float] = None, total: Optional[float] = None,
completed: int = 0,
task_id: Optional[TaskID] = None, task_id: Optional[TaskID] = None,
description: str = "Working...", description: str = "Working...",
update_period: float = 0.1, update_period: float = 0.1,
@ -1189,6 +1204,7 @@ class Progress(JupyterMixin):
Args: Args:
sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress. sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress.
total: (float, optional): Total number of steps. Default is len(sequence). total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
task_id: (TaskID): Task to track. Default is new task. task_id: (TaskID): Task to track. Default is new task.
description: (str, optional): Description of task, if new task is created. description: (str, optional): Description of task, if new task is created.
update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1. update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
@ -1200,9 +1216,9 @@ class Progress(JupyterMixin):
total = float(length_hint(sequence)) or None total = float(length_hint(sequence)) or None
if task_id is None: if task_id is None:
task_id = self.add_task(description, total=total) task_id = self.add_task(description, total=total, completed=completed)
else: else:
self.update(task_id, total=total) self.update(task_id, total=total, completed=completed)
if self.live.auto_refresh: if self.live.auto_refresh:
with _TrackThread(self, task_id, update_period) as track_thread: with _TrackThread(self, task_id, update_period) as track_thread:
@ -1326,7 +1342,7 @@ class Progress(JupyterMixin):
# normalize the mode (always rb, rt) # normalize the mode (always rb, rt)
_mode = "".join(sorted(mode, reverse=False)) _mode = "".join(sorted(mode, reverse=False))
if _mode not in ("br", "rt", "r"): if _mode not in ("br", "rt", "r"):
raise ValueError("invalid mode {!r}".format(mode)) raise ValueError(f"invalid mode {mode!r}")
# patch buffering to provide the same behaviour as the builtin `open` # patch buffering to provide the same behaviour as the builtin `open`
line_buffering = buffering == 1 line_buffering = buffering == 1

View File

@ -108,7 +108,7 @@ class ProgressBar(JupyterMixin):
for index in range(PULSE_SIZE): for index in range(PULSE_SIZE):
position = index / PULSE_SIZE position = index / PULSE_SIZE
fade = 0.5 + cos((position * pi * 2)) / 2.0 fade = 0.5 + cos(position * pi * 2) / 2.0
color = blend_rgb(fore_color, back_color, cross_fade=fade) color = blend_rgb(fore_color, back_color, cross_fade=fade)
append(_Segment(bar, _Style(color=from_triplet(color)))) append(_Segment(bar, _Style(color=from_triplet(color))))
return segments return segments

View File

@ -36,6 +36,7 @@ class PromptBase(Generic[PromptType]):
console (Console, optional): A Console instance or None to use global console. Defaults to None. console (Console, optional): A Console instance or None to use global console. Defaults to None.
password (bool, optional): Enable password input. Defaults to False. password (bool, optional): Enable password input. Defaults to False.
choices (List[str], optional): A list of valid choices. Defaults to None. choices (List[str], optional): A list of valid choices. Defaults to None.
case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.
show_default (bool, optional): Show default in prompt. Defaults to True. show_default (bool, optional): Show default in prompt. Defaults to True.
show_choices (bool, optional): Show choices in prompt. Defaults to True. show_choices (bool, optional): Show choices in prompt. Defaults to True.
""" """
@ -57,6 +58,7 @@ class PromptBase(Generic[PromptType]):
console: Optional[Console] = None, console: Optional[Console] = None,
password: bool = False, password: bool = False,
choices: Optional[List[str]] = None, choices: Optional[List[str]] = None,
case_sensitive: bool = True,
show_default: bool = True, show_default: bool = True,
show_choices: bool = True, show_choices: bool = True,
) -> None: ) -> None:
@ -69,6 +71,7 @@ class PromptBase(Generic[PromptType]):
self.password = password self.password = password
if choices is not None: if choices is not None:
self.choices = choices self.choices = choices
self.case_sensitive = case_sensitive
self.show_default = show_default self.show_default = show_default
self.show_choices = show_choices self.show_choices = show_choices
@ -81,6 +84,7 @@ class PromptBase(Generic[PromptType]):
console: Optional[Console] = None, console: Optional[Console] = None,
password: bool = False, password: bool = False,
choices: Optional[List[str]] = None, choices: Optional[List[str]] = None,
case_sensitive: bool = True,
show_default: bool = True, show_default: bool = True,
show_choices: bool = True, show_choices: bool = True,
default: DefaultType, default: DefaultType,
@ -97,6 +101,7 @@ class PromptBase(Generic[PromptType]):
console: Optional[Console] = None, console: Optional[Console] = None,
password: bool = False, password: bool = False,
choices: Optional[List[str]] = None, choices: Optional[List[str]] = None,
case_sensitive: bool = True,
show_default: bool = True, show_default: bool = True,
show_choices: bool = True, show_choices: bool = True,
stream: Optional[TextIO] = None, stream: Optional[TextIO] = None,
@ -111,6 +116,7 @@ class PromptBase(Generic[PromptType]):
console: Optional[Console] = None, console: Optional[Console] = None,
password: bool = False, password: bool = False,
choices: Optional[List[str]] = None, choices: Optional[List[str]] = None,
case_sensitive: bool = True,
show_default: bool = True, show_default: bool = True,
show_choices: bool = True, show_choices: bool = True,
default: Any = ..., default: Any = ...,
@ -126,6 +132,7 @@ class PromptBase(Generic[PromptType]):
console (Console, optional): A Console instance or None to use global console. Defaults to None. console (Console, optional): A Console instance or None to use global console. Defaults to None.
password (bool, optional): Enable password input. Defaults to False. password (bool, optional): Enable password input. Defaults to False.
choices (List[str], optional): A list of valid choices. Defaults to None. choices (List[str], optional): A list of valid choices. Defaults to None.
case_sensitive (bool, optional): Matching of choices should be case-sensitive. Defaults to True.
show_default (bool, optional): Show default in prompt. Defaults to True. show_default (bool, optional): Show default in prompt. Defaults to True.
show_choices (bool, optional): Show choices in prompt. Defaults to True. show_choices (bool, optional): Show choices in prompt. Defaults to True.
stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None. stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.
@ -135,6 +142,7 @@ class PromptBase(Generic[PromptType]):
console=console, console=console,
password=password, password=password,
choices=choices, choices=choices,
case_sensitive=case_sensitive,
show_default=show_default, show_default=show_default,
show_choices=show_choices, show_choices=show_choices,
) )
@ -212,7 +220,9 @@ class PromptBase(Generic[PromptType]):
bool: True if choice was valid, otherwise False. bool: True if choice was valid, otherwise False.
""" """
assert self.choices is not None assert self.choices is not None
return value.strip() in self.choices if self.case_sensitive:
return value.strip() in self.choices
return value.strip().lower() in [choice.lower() for choice in self.choices]
def process_response(self, value: str) -> PromptType: def process_response(self, value: str) -> PromptType:
"""Process response from user, convert to prompt type. """Process response from user, convert to prompt type.
@ -232,9 +242,17 @@ class PromptBase(Generic[PromptType]):
except ValueError: except ValueError:
raise InvalidResponse(self.validate_error_message) raise InvalidResponse(self.validate_error_message)
if self.choices is not None and not self.check_choice(value): if self.choices is not None:
raise InvalidResponse(self.illegal_choice_message) if not self.check_choice(value):
raise InvalidResponse(self.illegal_choice_message)
if not self.case_sensitive:
# return the original choice, not the lower case version
return_value = self.response_type(
self.choices[
[choice.lower() for choice in self.choices].index(value.lower())
]
)
return return_value return return_value
def on_validate_error(self, value: str, error: InvalidResponse) -> None: def on_validate_error(self, value: str, error: InvalidResponse) -> None:
@ -371,5 +389,12 @@ if __name__ == "__main__": # pragma: no cover
fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"]) fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"])
print(f"fruit={fruit!r}") print(f"fruit={fruit!r}")
doggie = Prompt.ask(
"What's the best Dog? (Case INSENSITIVE)",
choices=["Border Terrier", "Collie", "Labradoodle"],
case_sensitive=False,
)
print(f"doggie={doggie!r}")
else: else:
print("[b]OK :loudly_crying_face:") print("[b]OK :loudly_crying_face:")

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