From 3dd73287b78b1a25c1c37a90b0101bf3c5cdc58f Mon Sep 17 00:00:00 2001 From: TAHRI Ahmed R Date: Sun, 3 Dec 2023 21:03:54 +0100 Subject: [PATCH] :bookmark: Release 3.3.4 (#51) **Fixed** - Overall static typing experience have been improved. - Code HTTP 425 is now registered as `too_early` in addition to the legacy `unordered_collection`. **Removed** - Private module `niquests._internal_utils` has been removed as it no longer serve its purposes. --- AUTHORS.rst | 187 +---------------------------- HISTORY.md | 10 ++ README.md | 3 +- docs/api.rst | 170 ++++++-------------------- docs/community/faq.rst | 32 ----- docs/community/release-process.rst | 8 -- docs/community/vulnerabilities.rst | 66 +--------- docs/user/advanced.rst | 10 +- docs/user/authentication.rst | 7 ++ src/niquests/__version__.py | 4 +- src/niquests/_compat.py | 3 + src/niquests/_internal_utils.py | 21 ---- src/niquests/_typing.py | 2 + src/niquests/auth.py | 5 +- src/niquests/cookies.py | 6 +- src/niquests/help.py | 2 +- src/niquests/models.py | 54 +++++++-- src/niquests/sessions.py | 28 +++-- src/niquests/status_codes.py | 2 +- src/niquests/utils.py | 3 - tests/test_utils.py | 13 -- 21 files changed, 137 insertions(+), 499 deletions(-) delete mode 100644 src/niquests/_internal_utils.py diff --git a/AUTHORS.rst b/AUTHORS.rst index 7d745c2fda..2f5355e5c8 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,12 +1,11 @@ Requests was lovingly created by Kenneth Reitz. -Keepers of the Crystals -``````````````````````` - +Core maintainers +```````````````` - Ahmed Tahri `@Ousret `_. -Previous Keepers of Crystals -```````````````````````````` +Previous from Requests +`````````````````````` - Kenneth Reitz `@ken-reitz `_, reluctant Keeper of the Master Crystal. - Cory Benfield `@lukasa `_ - Ian Cordasco `@sigmavirus24 `_. @@ -15,180 +14,4 @@ Previous Keepers of Crystals Patches and Suggestions ``````````````````````` - -- Various Pocoo Members -- Chris Adams -- Flavio Percoco Premoli -- Dj Gilcrease -- Justin Murphy -- Rob Madole -- Aram Dulyan -- Johannes Gorset -- 村山めがね (Megane Murayama) -- James Rowe -- Daniel Schauenberg -- Zbigniew Siciarz -- Daniele Tricoli 'Eriol' -- Richard Boulton -- Miguel Olivares -- Alberto Paro -- Jérémy Bethmont -- 潘旭 (Xu Pan) -- Tamás Gulácsi -- Rubén Abad -- Peter Manser -- Jeremy Selier -- Jens Diemer -- Alex (`@alopatin `_) -- Tom Hogans -- Armin Ronacher -- Shrikant Sharat Kandula -- Mikko Ohtamaa -- Den Shabalin -- Daniel Miller -- Alejandro Giacometti -- Rick Mak -- Johan Bergström -- Josselin Jacquard -- Travis N. Vaught -- Fredrik Möllerstrand -- Daniel Hengeveld -- Dan Head -- Bruno Renié -- David Fischer -- Joseph McCullough -- Juergen Brendel -- Juan Riaza -- Ryan Kelly -- Rolando Espinoza La fuente -- Robert Gieseke -- Idan Gazit -- Ed Summers -- Chris Van Horne -- Christopher Davis -- Ori Livneh -- Jason Emerick -- Bryan Helmig -- Jonas Obrist -- Lucian Ursu -- Tom Moertel -- Frank Kumro Jr -- Chase Sterling -- Marty Alchin -- takluyver -- Ben Toews (`@mastahyeti `_) -- David Kemp -- Brendon Crawford -- Denis (`@Telofy `_) -- Matt Giuca -- Adam Tauber -- Honza Javorek -- Brendan Maguire -- Chris Dary -- Danver Braganza -- Max Countryman -- Nick Chadwick -- Jonathan Drosdeck -- Jiri Machalek -- Steve Pulec -- Michael Kelly -- Michael Newman -- Jonty Wareing -- Shivaram Lingamneni -- Miguel Turner -- Rohan Jain (`@crodjer `_) -- Justin Barber -- Roman Haritonov (`@reclosedev `_) -- Josh Imhoff -- Arup Malakar -- Danilo Bargen (`@dbrgn `_) -- Torsten Landschoff -- Michael Holler (`@apotheos `_) -- Timnit Gebru -- Sarah Gonzalez -- Victoria Mo -- Leila Muhtasib -- Matthias Rahlf -- Jakub Roztocil -- Rhys Elsmore -- André Graf (`@dergraf `_) -- Stephen Zhuang (`@everbird `_) -- Martijn Pieters -- Jonatan Heyman -- David Bonner (`@rascalking `_) -- Vinod Chandru -- Johnny Goodnow -- Denis Ryzhkov -- Wilfred Hughes -- Dmitry Medvinsky -- Bryce Boe (`@bboe `_) -- Colin Dunklau (`@cdunklau `_) -- Bob Carroll (`@rcarz `_) -- Hugo Osvaldo Barrera (`@hobarrera `_) -- Łukasz Langa -- Dave Shawley -- James Clarke (`@jam `_) -- Kevin Burke -- Flavio Curella -- David Pursehouse (`@dpursehouse `_) -- Jon Parise (`@jparise `_) -- Alexander Karpinsky (`@homm86 `_) -- Marc Schlaich (`@schlamar `_) -- Park Ilsu (`@daftshady `_) -- Matt Spitz (`@mattspitz `_) -- Vikram Oberoi (`@voberoi `_) -- Can Ibanoglu (`@canibanoglu `_) -- Thomas Weißschuh (`@t-8ch `_) -- Jayson Vantuyl -- Pengfei.X -- Kamil Madac -- Michael Becker (`@beckerfuffle `_) -- Erik Wickstrom (`@erikwickstrom `_) -- Константин Подшумок (`@podshumok `_) -- Ben Bass (`@codedstructure `_) -- Jonathan Wong (`@ContinuousFunction `_) -- Martin Jul (`@mjul `_) -- Joe Alcorn (`@buttscicles `_) -- Syed Suhail Ahmed (`@syedsuhail `_) -- Scott Sadler (`@ssadler `_) -- Arthur Darcet (`@arthurdarcet `_) -- Ulrich Petri (`@ulope `_) -- Muhammad Yasoob Ullah Khalid (`@yasoob `_) -- Paul van der Linden (`@pvanderlinden `_) -- Colin Dickson (`@colindickson `_) -- Smiley Barry (`@smiley `_) -- Shagun Sodhani (`@shagunsodhani `_) -- Robin Linderborg (`@vienno `_) -- Brian Samek (`@bsamek `_) -- Dmitry Dygalo (`@Stranger6667 `_) -- piotrjurkiewicz -- Jesse Shapiro (`@haikuginger `_) -- Nate Prewitt (`@nateprewitt `_) -- Maik Himstedt -- Michael Hunsinger -- Brian Bamsch (`@bbamsch `_) -- Om Prakash Kumar (`@iamprakashom `_) -- Philipp Konrad (`@gardiac2002 `_) -- Hussain Tamboli (`@hussaintamboli `_) -- Casey Davidson (`@davidsoncasey `_) -- Andrii Soldatenko (`@a_soldatenko `_) -- Moinuddin Quadri (`@moin18 `_) -- Matt Kohl (`@mattkohl `_) -- Jonathan Vanasco (`@jvanasco `_) -- David Fontenot (`@davidfontenot `_) -- Shmuel Amar (`@shmuelamar `_) -- Gary Wu (`@garywu `_) -- Ryan Pineo (`@ryanpineo `_) -- Ed Morley (`@edmorley `_) -- Matt Liu (`@mlcrazy `_) -- Taylor Hoff (`@PrimordialHelios `_) -- Arthur Vigil (`@ahvigil `_) -- Nehal J Wani (`@nehaljwani `_) -- Demetrios Bairaktaris (`@DemetriosBairaktaris `_) -- Darren Dormer (`@ddormer `_) -- Rajiv Mayani (`@mayani `_) -- Antti Kaihola (`@akaihola `_) -- "Dull Bananas" (`@dullbananas `_) -- Alessio Izzo (`@aless10 `_) -- Sylvain Marié (`@smarie `_) -- Hod Bin Noon (`@hodbn `_) +See https://github.com/jawah/niquests/graphs/contributors diff --git a/HISTORY.md b/HISTORY.md index a595af09b6..272a9711d8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,16 @@ Release History =============== +3.3.4 (2023-12-03) +------------------ + +**Fixed** +- Overall static typing experience have been improved. +- Code HTTP 425 is now registered as `too_early` in addition to the legacy `unordered_collection`. + +**Removed** +- Private module `niquests._internal_utils` has been removed as it no longer serve its purposes. + 3.3.3 (2023-11-26) ------------------ diff --git a/README.md b/README.md index 4203612175..4ed4c3c0ed 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **Niquests** is a simple, yet elegant, HTTP library. It is a drop-in replacement for **Requests** that is no longer under feature freeze. -Niquests, is the “**Safest**, **Fastest***, **Easiest**, and **Most advanced**” Python HTTP Client. +Niquests, is the “**Safest**, **Fastest***, **Easiest**, and **Most advanced**” Python HTTP Client. Production Ready! ✔️ **Try before you switch:** [See Multiplexed in Action](https://replit.com/@ahmedtahri4/Python#main.py)
📖 **See why you should switch:** [Read about 10 reasons why](https://medium.com/dev-genius/10-reasons-you-should-quit-your-http-client-98fd4c94bef3) @@ -51,6 +51,7 @@ Niquests is ready for the demands of building robust and reliable HTTP–speakin - Automatic Content Decompression and Decoding - OS truststore by default, no more certifi! - OCSP Certificate Revocation Verification +- In-memory certificates (CAs, and mTLS) - Browser-style TLS/SSL Verification - Sessions with Cookie Persistence - Keep-Alive & Connection Pooling diff --git a/docs/api.rst b/docs/api.rst index 8e7ea1f3e5..a3e0ae1010 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -103,149 +103,47 @@ Status Code Lookup .. automodule:: niquests.status_codes -Migrating to 1.x +Migrating to 3.x ---------------- -This section details the main differences between 0.x and 1.x and is meant -to ease the pain of upgrading. - - -API Changes -~~~~~~~~~~~ - -* ``Response.json`` is now a callable and not a property of a response. - - :: - - import requests - r = niquests.get('https://api.github.com/events') - r.json() # This *call* raises an exception if JSON decoding fails - -* The ``Session`` API has changed. Sessions objects no longer take parameters. - ``Session`` is also now capitalized, but it can still be - instantiated with a lowercase ``session`` for backwards compatibility. - - :: - - s = niquests.Session() # formerly, session took parameters - s.auth = auth - s.headers.update(headers) - r = s.get('https://httpbin.org/headers') - -* All request hooks have been removed except 'response'. - -* Authentication helpers have been broken out into separate modules. See - requests-oauthlib_ and requests-kerberos_. - -.. _requests-oauthlib: https://github.com/requests/requests-oauthlib -.. _requests-kerberos: https://github.com/requests/requests-kerberos - -* The parameter for streaming requests was changed from ``prefetch`` to - ``stream`` and the logic was inverted. In addition, ``stream`` is now - required for raw response reading. - - :: - - # in 0.x, passing prefetch=False would accomplish the same thing - r = niquests.get('https://api.github.com/events', stream=True) - for chunk in r.iter_content(8192): - ... - -* The ``config`` parameter to the requests method has been removed. Some of - these options are now configured on a ``Session`` such as keep-alive and - maximum number of redirects. The verbosity option should be handled by - configuring logging. - - :: - - import requests - import logging - - # Enabling debugging at http.client level (requests->urllib3->http.client) - # you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. - # the only thing missing will be the response.body which is not logged. - try: # for Python 3 - from http.client import HTTPConnection - except ImportError: - from httplib import HTTPConnection - HTTPConnection.debuglevel = 1 - - logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True - - niquests.get('https://httpbin.org/headers') - - - -Licensing -~~~~~~~~~ - -One key difference that has nothing to do with the API is a change in the -license from the ISC_ license to the `Apache 2.0`_ license. The Apache 2.0 -license ensures that contributions to Niquests are also covered by the Apache -2.0 license. - -.. _ISC: https://opensource.org/licenses/ISC -.. _Apache 2.0: https://opensource.org/licenses/Apache-2.0 - - -Migrating to 2.x ----------------- - - -Compared with the 1.0 release, there were relatively few backwards +Compared with the 2.0 release, there were relatively few backwards incompatible changes, but there are still a few issues to be aware of with this major release. -For more details on the changes in this release including new APIs, links -to the relevant GitHub issues and some of the bug fixes, read Cory's blog_ -on the subject. - -.. _blog: https://lukasa.co.uk/2013/09/Requests_20/ - - -API Changes -~~~~~~~~~~~ - -* There were a couple changes to how Niquests handles exceptions. - ``RequestException`` is now a subclass of ``IOError`` rather than - ``RuntimeError`` as that more accurately categorizes the type of error. - In addition, an invalid URL escape sequence now raises a subclass of - ``RequestException`` rather than a ``ValueError``. - - :: - - niquests.get('http://%zz/') # raises niquests.exceptions.InvalidURL - - Lastly, ``httplib.IncompleteRead`` exceptions caused by incorrect chunked - encoding will now raise a Requests ``ChunkedEncodingError`` instead. - -* The proxy API has changed slightly. The scheme for a proxy URL is now - required. - - :: - - proxies = { - "http": "10.10.1.10:3128", # use http://10.10.1.10:3128 instead - } - - # In requests 1.x, this was legal, in requests 2.x, - # this raises niquests.exceptions.MissingSchema - niquests.get("http://example.org", proxies=proxies) +Removed +~~~~~~~ + +* Property ``apparent_encoding`` in favor of a discrete internal inference. +* Support for the legacy ``chardet`` detector in case it was present in environment. + Extra ``chardet_on_py3`` is now unavailable. +* **requests.compat** no longer hold reference to _chardet_. +* Deprecated ``requests.packages`` that was meant to avoid breakage from people importing ``urllib3`` or ``chardet`` within this package. + They were _vendored_ in early versions of Requests. A long time ago. +* Deprecated function ``get_encodings_from_content`` from utils. +* Deprecated function ``get_unicode_from_response`` from utils. +* BasicAuth middleware no-longer support anything else than ``bytes`` or ``str`` for username and password. +* ``requests.compat`` is stripped of every reference that no longer vary between supported interpreter version. +* Charset fall back **ISO-8859-1** when content-type is text and no charset was specified. +* Main function ``get``, ``post``, ``put``, ``patch``, ``delete``, and ``head`` no longer accept **kwargs**. They have a fixed list of typed argument. + It is no longer possible to specify non-supported additional keyword argument from a ``Session`` instance or directly through ``requests.api`` functions. + e.g. function ``delete`` no-longer accept ``json``, or ``files`` arguments. as per RFCs specifications. You can still override this behavior through the ``request`` function. +* Mixin classes ``RequestEncodingMixin``, and ``RequestHooksMixin`` due to OOP violations. Now deported directly into child classes. +* Function ``unicode_is_ascii`` as it is part of the stable ``str`` stdlib on Python 3 or greater. +* Alias function ``session`` for ``Session`` context manager that was kept for BC reasons since the v1. +* pyOpenSSL/urllib3 injection in case built-in ssl module does not have SNI support as it is not the case anymore for every supported interpreters. +* Constant ``DEFAULT_CA_BUNDLE_PATH``, and submodule ``certs`` due to dropping ``certifi``. +* Function ``extract_zipped_paths`` because rendered useless as it was made to handle an edge case where ``certifi`` is "zipped". +* Extra ``security`` when installing this package. It was previously emptied in the previous major. +* Warning emitted when passing a file opened in text-mode instead of binary. urllib3.future can overrule + the content-length if it detects an error. You should not encounter broken request being sent. +* Support for ``simplejson`` if was present in environment. +* Submodule ``compat``. +* Dependency check at runtime for ``urllib3``. There's no more check and warnings at runtime for that subject. Ever. Behavioural Changes ~~~~~~~~~~~~~~~~~~~~~~~ -* Keys in the ``headers`` dictionary are now native strings on all Python - versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the - keys are not native strings (unicode on Python 2 or bytestrings on Python 3) - they will be converted to the native string type assuming UTF-8 encoding. - -* Values in the ``headers`` dictionary should always be strings. This has - been the project's position since before 1.0 but a recent change - (since version 2.11.0) enforces this more strictly. It's advised to avoid - passing header values as unicode when possible. +* Niquests negotiate for a HTTP/2 connection by default, fallback to HTTP/1.1 if not available. +* Support for HTTP/3 can be present by default if your platform support the pre-built wheel for qh3. +* Server capability for HTTP/3 is remembered automatically (in-memory) for subsequent requests. diff --git a/docs/community/faq.rst b/docs/community/faq.rst index a6544bc1b0..2d04630b0a 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -25,38 +25,6 @@ Niquests allows you to easily override User-Agent strings, along with any other HTTP Header. See `documentation about headers `_. - -Why not Httplib2? ------------------ - -Chris Adams gave an excellent summary on -`Hacker News `_: - - httplib2 is part of why you should use requests: it's far more respectable - as a client but not as well documented and it still takes way too much code - for basic operations. I appreciate what httplib2 is trying to do, that - there's a ton of hard low-level annoyances in building a modern HTTP - client, but really, just use requests instead. Kenneth Reitz is very - motivated and he gets the degree to which simple things should be simple - whereas httplib2 feels more like an academic exercise than something - people should use to build production systems[1]. - - Disclosure: I'm listed in the requests AUTHORS file but can claim credit - for, oh, about 0.0001% of the awesomeness. - - 1. http://code.google.com/p/httplib2/issues/detail?id=96 is a good example: - an annoying bug which affect many people, there was a fix available for - months, which worked great when I applied it in a fork and pounded a couple - TB of data through it, but it took over a year to make it into trunk and - even longer to make it onto PyPI where any other project which required " - httplib2" would get the working version. - - -Python 3 Support? ------------------ - -Yes! Niquests officially supports Python 3.7+ and PyPy. - What are "hostname doesn't match" errors? ----------------------------------------- diff --git a/docs/community/release-process.rst b/docs/community/release-process.rst index 430ae31eb2..c91bb1bcbd 100644 --- a/docs/community/release-process.rst +++ b/docs/community/release-process.rst @@ -43,11 +43,3 @@ released the previous version. If the previous version of Niquests released Hotfixes will **not** include upgrades to vendored dependencies after ``v2.6.2`` - -Reasoning ---------- - -In the 2.5 and 2.6 release series, the Requests core team upgraded vendored -dependencies and caused a great deal of headaches for both users and the core -team. To reduce this pain, we're forming a concrete set of procedures so -expectations will be properly set. diff --git a/docs/community/vulnerabilities.rst b/docs/community/vulnerabilities.rst index a9335b53a6..88d0f6d045 100644 --- a/docs/community/vulnerabilities.rst +++ b/docs/community/vulnerabilities.rst @@ -2,7 +2,7 @@ Vulnerability Disclosure ======================== If you think you have found a potential security vulnerability in requests, -please use Github vulnerability disclosure form within the repository. +please use Tidelift vulnerability disclosure contact within the repository. If English is not your first language, please try to describe the problem and its impact to the best of your ability. For greater detail, please use your @@ -19,70 +19,6 @@ release. We will respect your privacy and will only publicize your involvement if you grant us permission. -Process -------- - -This following information discusses the process the requests project follows -in response to vulnerability disclosures. If you are disclosing a -vulnerability, this section of the documentation lets you know how we will -respond to your disclosure. - -Timeline -~~~~~~~~ - -When you report an issue, one of the project members will respond to you within -two days *at the outside*. In most cases responses will be faster, usually -within 24 hours. This initial response will at the very least confirm receipt -of the report. - -If we were able to rapidly reproduce the issue, the initial response will also -contain confirmation of the issue. If we are not, we will often ask for more -information about the reproduction scenario. - -Our goal is to have a fix for any vulnerability released within two weeks of -the initial disclosure. This may potentially involve shipping an interim -release that simply disables function while a more mature fix can be prepared, -but will in the vast majority of cases mean shipping a complete release as soon -as possible. - -Throughout the fix process we will keep you up to speed with how the fix is -progressing. Once the fix is prepared, we will notify you that we believe we -have a fix. Often we will ask you to confirm the fix resolves the problem in -your environment, especially if we are not confident of our reproduction -scenario. - -At this point, we will prepare for the release. We will obtain a CVE number -if one is required, providing you with full credit for the discovery. We will -also decide on a planned release date, and let you know when it is. This -release date will *always* be on a weekday. - -At this point we will reach out to our major downstream packagers to notify -them of an impending security-related patch so they can make arrangements. In -addition, these packagers will be provided with the intended patch ahead of -time, to ensure that they are able to promptly release their downstream -packages. Currently the list of people we actively contact *ahead of a public -release* is: - -- Python Maintenance Team, Red Hat (python-maint@redhat.com) -- Daniele Tricoli, Debian (@eriol) - -We will notify these individuals at least a week ahead of our planned release -date to ensure that they have sufficient time to prepare. If you believe you -should be on this list, please let one of the maintainers know at one of the -email addresses at the top of this article. - -On release day, we will push the patch to our public repository, along with an -updated changelog that describes the issue and credits you. We will then issue -a PyPI release containing the patch. - -At this point, we will publicise the release. This will involve mails to -mailing lists, Tweets, and all other communication mechanisms available to the -core team. - -We will also explicitly mention which commits contain the fix to make it easier -for other distributors and users to easily patch their own versions of requests -if upgrading is not an option. - Previous CVEs ------------- diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index c81baeaa87..a8bcf7fa21 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -1284,10 +1284,10 @@ Having a session without HTTP/2 enabled should be done that way:: session = niquests.Session(disable_http2=True) -Passing a bearer token ----------------------- +Thread Safety +------------- -You may use ``auth=my_token`` as a shortcut to passing ``headers={"Authorization": f"Bearer {my_token}"}`` in -get, post, request, etc... +Niquests is meant to be thread-safe. Any error or unattended behaviors are covered by our support for bug policy. +Both main scenarios are eligible, meaning Thread and Async, with Thread and Sync. -.. note:: If you pass a token with its custom prefix, it will be taken and passed as-is. e.g. ``auth="NotBearer eyDdx.."`` +Support include notable performance issues like abusive lock. \ No newline at end of file diff --git a/docs/user/authentication.rst b/docs/user/authentication.rst index c56340b03c..f8bfa42ffc 100644 --- a/docs/user/authentication.rst +++ b/docs/user/authentication.rst @@ -32,6 +32,13 @@ for using it:: Providing the credentials in a tuple like this is exactly the same as the ``HTTPBasicAuth`` example above. +Passing a bearer token +---------------------- + +You may use ``auth=my_token`` as a shortcut to passing ``headers={"Authorization": f"Bearer {my_token}"}`` in +get, post, request, etc... + +.. note:: If you pass a token with its custom prefix, it will be taken and passed as-is. e.g. ``auth="NotBearer eyDdx.."`` netrc Authentication ~~~~~~~~~~~~~~~~~~~~ diff --git a/src/niquests/__version__.py b/src/niquests/__version__.py index 4d3ef31913..3ae6112d4a 100644 --- a/src/niquests/__version__.py +++ b/src/niquests/__version__.py @@ -9,9 +9,9 @@ __url__: str = "https://niquests.readthedocs.io" __version__: str -__version__ = "3.3.3" +__version__ = "3.3.4" -__build__: int = 0x030303 +__build__: int = 0x030304 __author__: str = "Kenneth Reitz" __author_email__: str = "me@kennethreitz.org" __license__: str = "Apache-2.0" diff --git a/src/niquests/_compat.py b/src/niquests/_compat.py index 68562d2e81..1bee261e26 100644 --- a/src/niquests/_compat.py +++ b/src/niquests/_compat.py @@ -5,4 +5,7 @@ HAS_LEGACY_URLLIB3: bool = int(__version__.split(".")[-1]) < 900 except (ValueError, ImportError): + # Means one of the two cases: + # 1) urllib3 does not exist -> fallback to urllib3_future + # 2) urllib3 exist but not fork -> fallback to urllib3_future HAS_LEGACY_URLLIB3 = True diff --git a/src/niquests/_internal_utils.py b/src/niquests/_internal_utils.py deleted file mode 100644 index 01889b864c..0000000000 --- a/src/niquests/_internal_utils.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -requests._internal_utils -~~~~~~~~~~~~~~ - -Provides utility functions that are consumed internally by Requests -which depend on extremely few external helpers (such as compat) -""" -from __future__ import annotations - - -def to_native_string(string: str | bytes, encoding: str = "ascii") -> str: - """Given a string object, regardless of type, returns a representation of - that string in the native string type, encoding and decoding where - necessary. This assumes ASCII unless told otherwise. - """ - if isinstance(string, str): - out = string - else: - out = string.decode(encoding) - - return out diff --git a/src/niquests/_typing.py b/src/niquests/_typing.py index 91b82b56e8..b8c0d5cb03 100644 --- a/src/niquests/_typing.py +++ b/src/niquests/_typing.py @@ -45,6 +45,8 @@ #: HTTP Headers can be represented through three ways. 1) typical dict, 2) internal insensitive dict, and 3) list of tuple. HeadersType: typing.TypeAlias = typing.Union[ typing.MutableMapping[typing.Union[str, bytes], typing.Union[str, bytes]], + typing.MutableMapping[str, str], + typing.MutableMapping[bytes, bytes], CaseInsensitiveDict, typing.List[typing.Tuple[typing.Union[str, bytes], typing.Union[str, bytes]]], Headers, diff --git a/src/niquests/auth.py b/src/niquests/auth.py index 66aa1123d3..eb450f6654 100644 --- a/src/niquests/auth.py +++ b/src/niquests/auth.py @@ -15,7 +15,6 @@ from base64 import b64encode from urllib.parse import urlparse -from ._internal_utils import to_native_string from .cookies import extract_cookies_to_jar from .utils import parse_dict_header @@ -32,9 +31,7 @@ def _basic_auth_str(username: str | bytes, password: str | bytes) -> str: if isinstance(password, str): password = password.encode("utf-8") - authstr = "Basic " + to_native_string( - b64encode(b":".join((username, password))).strip() - ) + authstr = "Basic " + b64encode(b":".join((username, password))).strip().decode() return authstr diff --git a/src/niquests/cookies.py b/src/niquests/cookies.py index f153f6cabd..feb7d8e1f4 100644 --- a/src/niquests/cookies.py +++ b/src/niquests/cookies.py @@ -26,8 +26,6 @@ else: from urllib3_future import BaseHTTPResponse # type: ignore[assignment] -from ._internal_utils import to_native_string - if typing.TYPE_CHECKING: from .models import PreparedRequest, Request @@ -64,7 +62,9 @@ def get_full_url(self): if not self._r.headers.get("Host"): return self._r.url # If they did set it, retrieve it and reconstruct the expected domain - host = to_native_string(self._r.headers["Host"], encoding="utf-8") + host = self._r.headers["Host"] + if isinstance(host, bytes): + host = host.decode("utf-8") parsed = urlparse(self._r.url) # Reconstruct the URL as we expect it return urlunparse( diff --git a/src/niquests/help.py b/src/niquests/help.py index a37cbcb6e7..b85af1e8a0 100644 --- a/src/niquests/help.py +++ b/src/niquests/help.py @@ -118,7 +118,7 @@ def info(): "charset_normalizer": charset_normalizer_info, "cryptography": cryptography_info, "idna": idna_info, - "requests": { + "niquests": { "version": niquests_version, }, "http3": { diff --git a/src/niquests/models.py b/src/niquests/models.py index 4283a1041c..1e1966d172 100644 --- a/src/niquests/models.py +++ b/src/niquests/models.py @@ -15,6 +15,10 @@ import encodings.idna # noqa: F401 import json as _json import typing + +if typing.TYPE_CHECKING: + from typing_extensions import Literal + from collections.abc import Mapping from http import cookiejar as cookielib from http.cookiejar import CookieJar @@ -51,7 +55,6 @@ from urllib3_future.filepost import choose_boundary, encode_multipart_formdata # type: ignore[assignment] from urllib3_future.util import parse_url # type: ignore[assignment] -from ._internal_utils import to_native_string from ._typing import ( BodyFormType, BodyType, @@ -184,7 +187,7 @@ def __init__( # Default empty dicts for dict params. data = [] if data is None else data files = [] if files is None else files - headers = {} if headers is None else headers + headers = CaseInsensitiveDict() if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks @@ -424,8 +427,8 @@ def prepare_url(self, url: str | None, params: QueryParameterType | None) -> Non path = "/" if params: - if isinstance(params, (str, bytes)): - params = to_native_string(params) + if isinstance(params, bytes): + params = params.decode("utf-8") enc_params = self._encode_params(params) if enc_params: @@ -450,7 +453,9 @@ def prepare_headers(self, headers: HeadersType | None) -> None: else: for header in headers.items(): name, value = header - self.headers[to_native_string(name)] = value + if isinstance(name, bytes): + name = name.decode() + self.headers[name] = value def prepare_body( self, @@ -725,8 +730,8 @@ def _encode_params( result = [] for k, vs in to_key_val_list(data): iterable_vs: typing.Iterable[str | bytes] - if isinstance(vs, (str, bytes, int, float)): - # not officially supported, but some people maybe passing ints or float. + if isinstance(vs, (str, bytes, int, float, bool)): + # not officially supported, but some people maybe passing ints, float or bool. if isinstance(vs, (str, bytes)) is False: iterable_vs = [str(vs)] else: @@ -922,7 +927,7 @@ class Response: ] def __init__(self) -> None: - self._content: typing.Literal[False] | bytes | None = False + self._content: Literal[False] | bytes | None = False self._content_consumed: bool = False self._next: PreparedRequest | None = None @@ -1084,6 +1089,18 @@ def ocsp_verified(self) -> bool | None: return self.request.ocsp_verified return None + @typing.overload + def iter_content( + self, chunk_size: int = ..., decode_unicode: Literal[False] = ... + ) -> typing.Generator[bytes, None, None]: + ... + + @typing.overload + def iter_content( + self, chunk_size: int = ..., *, decode_unicode: Literal[True] + ) -> typing.Generator[str, None, None]: + ... + def iter_content( self, chunk_size: int = 1, decode_unicode: bool = False ) -> typing.Generator[bytes | str, None, None]: @@ -1145,6 +1162,25 @@ def generate() -> typing.Generator[bytes, None, None]: return chunks + @typing.overload + def iter_lines( + self, + chunk_size: int = ..., + decode_unicode: Literal[False] = ..., + delimiter: str | bytes | None = ..., + ) -> typing.Generator[bytes, None, None]: + ... + + @typing.overload + def iter_lines( + self, + chunk_size: int = ..., + *, + decode_unicode: Literal[True], + delimiter: str | bytes | None = ..., + ) -> typing.Generator[str, None, None]: + ... + def iter_lines( self, chunk_size: int = ITER_CHUNK_SIZE, @@ -1168,7 +1204,7 @@ def iter_lines( pending = None - for chunk in self.iter_content( + for chunk in self.iter_content( # type: ignore[call-overload] chunk_size=chunk_size, decode_unicode=decode_unicode ): if pending is not None: diff --git a/src/niquests/sessions.py b/src/niquests/sessions.py index 04ccfa3fc5..ba44229f50 100644 --- a/src/niquests/sessions.py +++ b/src/niquests/sessions.py @@ -26,7 +26,6 @@ from urllib3_future import ConnectionInfo # type: ignore[assignment] from ._constant import DEFAULT_RETRIES, READ_DEFAULT_TIMEOUT, WRITE_DEFAULT_TIMEOUT -from ._internal_utils import to_native_string from ._typing import ( BodyType, CacheLayerAltSvcType, @@ -1220,20 +1219,16 @@ def get_redirect_target(self, resp: Response) -> str | None: # This causes incorrect handling of UTF8 encoded location headers. # To solve this, we re-encode the location in latin1. try: - return to_native_string( - location.encode("latin1") - if isinstance(location, str) - else location, - "utf8", - ) + return ( + location.encode("latin1") if isinstance(location, str) else location + ).decode("utf-8") except UnicodeDecodeError: try: - return to_native_string( + return ( location.encode("utf-8") if isinstance(location, str) - else location, - "utf8", - ) + else location + ).decode("utf-8") except (UnicodeDecodeError, UnicodeEncodeError) as e: raise HTTPError( "Response specify a Location header but is unreadable. This is a violation." @@ -1317,7 +1312,10 @@ def resolve_redirects( # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith("//"): parsed_rurl = urlparse(resp.url) - url = ":".join([to_native_string(parsed_rurl.scheme), url]) + target_scheme = parsed_rurl.scheme + if isinstance(target_scheme, bytes): + target_scheme = target_scheme.decode() + url = ":".join([target_scheme, url]) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) parsed = urlparse(url) @@ -1342,7 +1340,11 @@ def resolve_redirects( else: url = requote_uri(url) - prepared_request.url = to_native_string(url) + # this shouldn't happen, but kept in extreme case of being nice with BC. + if isinstance(url, bytes): + url = url.decode("utf-8") + + prepared_request.url = url assert prepared_request.headers is not None self.rebuild_method(prepared_request, resp) diff --git a/src/niquests/status_codes.py b/src/niquests/status_codes.py index 1082ae6182..3e3e7d640b 100644 --- a/src/niquests/status_codes.py +++ b/src/niquests/status_codes.py @@ -81,7 +81,7 @@ 422: ("unprocessable_entity", "unprocessable"), 423: ("locked",), 424: ("failed_dependency", "dependency"), - 425: ("unordered_collection", "unordered"), + 425: ("unordered_collection", "unordered", "too_early"), 426: ("upgrade_required", "upgrade"), 428: ("precondition_required", "precondition"), 429: ("too_many_requests", "too_many"), diff --git a/src/niquests/utils.py b/src/niquests/utils.py index 2bf5afc553..d57f2ab724 100644 --- a/src/niquests/utils.py +++ b/src/niquests/utils.py @@ -39,9 +39,6 @@ from urllib3_future.util import make_headers, parse_url # type: ignore[assignment] from .__version__ import __version__ - -# to_native_string is unused here, but imported here for backwards compatibility -from ._internal_utils import to_native_string # noqa: F401 from .cookies import cookiejar_from_dict from .exceptions import InvalidURL, UnrewindableBodyError from .structures import CaseInsensitiveDict diff --git a/tests/test_utils.py b/tests/test_utils.py index 086ba0dcff..3b1457bac5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -38,7 +38,6 @@ should_bypass_proxies, super_len, to_key_val_list, - to_native_string, unquote_header_value, unquote_unreserved, urldefragauth, @@ -589,18 +588,6 @@ def test_prepend_scheme_if_needed(value, expected): assert prepend_scheme_if_needed(value, "http") == expected -@pytest.mark.parametrize( - "value, expected", - ( - ("T", "T"), - (b"T", "T"), - ("T", "T"), - ), -) -def test_to_native_string(value, expected): - assert to_native_string(value) == expected - - @pytest.mark.parametrize( "url, expected", (