Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: python
version: 10.6.3
version: 10.7.0
schema: 1
scm: github.com/pubnub/python
sdks:
Expand All @@ -18,7 +18,7 @@ sdks:
distributions:
- distribution-type: library
distribution-repository: package
package-name: pubnub-10.6.3
package-name: pubnub-10.7.0
location: https://pypi.org/project/pubnub/
supported-platforms:
supported-operating-systems:
Expand Down Expand Up @@ -94,8 +94,8 @@ sdks:
-
distribution-type: library
distribution-repository: git release
package-name: pubnub-10.6.3
location: https://github.com/pubnub/python/releases/download/10.6.3/pubnub-10.6.3.tar.gz
package-name: pubnub-10.7.0
location: https://github.com/pubnub/python/releases/download/10.7.0/pubnub-10.7.0.tar.gz
supported-platforms:
supported-operating-systems:
Linux:
Expand Down Expand Up @@ -169,6 +169,15 @@ sdks:
license-url: https://github.com/encode/httpx/blob/master/LICENSE.md
is-required: Required
changelog:
- date: 2026-06-08
version: 10.7.0
changes:
- type: feature
text: "Enable HTTP/2 negotiation on the synchronous `httpx` handler."
- type: feature
text: "Add `http_version` field to `ResponseInfo`."
- type: feature
text: "Log negotiated protocol version at DEBUG level across all request handlers."
- date: 2026-04-20
version: 10.6.3
changes:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 10.7.0
June 08 2026

#### Added
- Enable HTTP/2 negotiation on the synchronous `httpx` handler.
- Add `http_version` field to `ResponseInfo`.
- Log negotiated protocol version at DEBUG level across all request handlers.

## 10.6.3
April 20 2026

Expand Down
5 changes: 3 additions & 2 deletions pubnub/request_handlers/async_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ async def async_request(self, options_func, cancellation_event):
uuid=uuid,
auth_key=auth_key,
client_request=None,
client_response=response
client_response=response,
http_version=f"HTTP/{response.version.major}.{response.version.minor}" if response.version else None
Comment thread
parfeon marked this conversation as resolved.
)

# if body is not None and len(body) > 0 and not options.non_json_response:
Expand Down Expand Up @@ -172,7 +173,7 @@ async def async_request(self, options_func, cancellation_event):
else:
data = "N/A"

logger.debug(data)
logger.debug("[%s %s] %s" % (options.operation_type, response_info.http_version, data))

if response.status not in (200, 307, 204):

Expand Down
8 changes: 5 additions & 3 deletions pubnub/request_handlers/async_httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def __init__(self, pubnub):
async def create_session(self):
self._session = httpx.AsyncClient(
timeout=httpx.Timeout(self.pubnub.config.connect_timeout),
transport=self._connector
transport=self._connector,
http2=True
)

async def close_session(self):
Expand Down Expand Up @@ -193,7 +194,8 @@ async def async_request(self, options_func, cancellation_event):
uuid=uuid,
auth_key=auth_key,
client_request=None,
client_response=response
client_response=response,
http_version=response.http_version
)

# if body is not None and len(body) > 0 and not options.non_json_response:
Expand Down Expand Up @@ -224,7 +226,7 @@ async def async_request(self, options_func, cancellation_event):
else:
data = "N/A"

logger.debug(data)
logger.debug("[%s %s] %s" % (options.operation_type, response_info.http_version, data))

if response.status_code not in (200, 307, 204):

Expand Down
17 changes: 9 additions & 8 deletions pubnub/request_handlers/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _ensure_session(self):
with self._session_lock:
if self._session is None or self._session.is_closed:
logger.debug("Creating new HTTP session")
self._session = httpx.Client()
self._session = httpx.Client(http2=True)
return self._session

def close(self):
Expand Down Expand Up @@ -318,7 +318,8 @@ def _build_envelope(self, p_options, e_options):
origin=res.url.host,
uuid=uuid,
auth_key=auth_key,
client_request=res.request
client_request=res.request,
http_version=res.http_version
)

if res.status_code not in [200, 204, 307]:
Expand Down Expand Up @@ -433,15 +434,15 @@ def _invoke_request(self, p_options, e_options, base_origin):

try:
res = session.request(**args)
# Safely access response text - read content first for streaming responses

try:
logger.debug("GOT %s" % res.text)
logger.debug("[%s %s] %s" % (e_options.operation_type, res.http_version, res.text))
except httpx.ResponseNotRead:
# For streaming responses, we need to read first
logger.debug("GOT %s" % res.content.decode('utf-8', errors='ignore'))
content = res.content.decode('utf-8', errors='ignore')
logger.debug("[%s %s] %s" % (e_options.operation_type, res.http_version, content))
except Exception as e:
# Fallback logging in case of any response reading issues
logger.debug("GOT response (content access failed: %s)" % str(e))
msg = "(content access failed: %s)" % str(e)
logger.debug("[%s %s] %s" % (e_options.operation_type, res.http_version, msg))

except httpx.ConnectError as e:
if use_watchdog and self._watchdog.triggered:
Expand Down
13 changes: 11 additions & 2 deletions pubnub/request_handlers/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ def _build_envelope(self, p_options, e_options):
origin=url.hostname,
uuid=uuid,
auth_key=auth_key,
client_request=res.request
client_request=res.request,
http_version=(
Comment thread
parfeon marked this conversation as resolved.
f"HTTP/{res.raw.version // 10}.{res.raw.version % 10}"
if res.raw and res.raw.version else None
)
)

if not res.ok:
Expand Down Expand Up @@ -268,7 +272,12 @@ def _invoke_request(self, p_options, e_options, base_origin):

try:
res = self.session.request(**args)
logger.debug("GOT %s" % res.text)
http_ver = (
f"HTTP/{res.raw.version // 10}.{res.raw.version % 10}"
if res.raw and res.raw.version else "unknown"
)
logger.debug("[%s %s] %s" % (e_options.operation_type, http_ver, res.text))

except requests.exceptions.ConnectionError as e:
raise PubNubException(
pn_error=PNERR_CONNECTION_ERROR,
Expand Down
4 changes: 3 additions & 1 deletion pubnub/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ def __init__(self, headers, pn_config):


class ResponseInfo(object):
def __init__(self, status_code, tls_enabled, origin, uuid, auth_key, client_request, client_response=None):
def __init__(self, status_code, tls_enabled, origin, uuid, auth_key, client_request,
client_response=None, http_version=None):
self.status_code = status_code
self.tls_enabled = tls_enabled
self.origin = origin
self.uuid = uuid
self.auth_key = auth_key
self.client_request = client_request
self.client_response = client_response
self.http_version = http_version


class Envelope(object):
Expand Down
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ flake8>=7.1.2
pytest>=8.3.5
pytest-asyncio>=1.0.0
httpx>=0.28
h2>=4.1
h2>=4.3
Comment thread
parfeon marked this conversation as resolved.
requests>=2.32.2
aiohttp>=3.10.11
aiohttp>=3.10.11,<3.11
cbor2>=5.6
behave>=1.2.6
vcrpy>=6.0.2
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='pubnub',
version='10.6.3',
version='10.7.0',
description='PubNub Real-time push service in the cloud',
author='PubNub',
author_email='support@pubnub.com',
Expand Down Expand Up @@ -32,7 +32,7 @@
install_requires=[
'pycryptodomex>=3.3',
'httpx>=0.28,<1.0',
'h2>=4.1',
'h2>=4.3',
'requests>=2.32.2',
'aiohttp>3.10.11',
'cbor2>=5.6'
Expand Down
15 changes: 15 additions & 0 deletions tests/integrational/asyncio/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import asyncio
import pytest


@pytest.fixture(autouse=True)
def event_loop_for_sync_tests():
try:
asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
yield
loop.close()
else:
yield
Loading