Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/sync-repo-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ branchProtectionRules:
- 'unit (3.10)'
- 'unit (3.11)'
- 'unit (3.12)'
- 'unit (3.13)'
- 'unit (3.14)'
- 'cover'
2 changes: 1 addition & 1 deletion .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
7 changes: 6 additions & 1 deletion .kokoro/presubmit/presubmit.cfg
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# Format: //devtools/kokoro/config/proto/build.proto
# Format: //devtools/kokoro/config/proto/build.proto

env_vars: {
key: "NOX_SESSION"
value: "system-3.12 blacken mypy format"
}
40 changes: 40 additions & 0 deletions .kokoro/samples/python3.14/common.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Format: //devtools/kokoro/config/proto/build.proto

# Build logs will be here
action {
define_artifacts {
regex: "**/*sponge_log.xml"
}
}

# Specify which tests to run
env_vars: {
key: "RUN_TESTS_SESSION"
value: "py-3.14"
}

# Declare build specific Cloud project.
env_vars: {
key: "BUILD_SPECIFIC_GCLOUD_PROJECT"
value: "python-docs-samples-tests-314"
}

env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-pubsub/.kokoro/test-samples.sh"
}

# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker"
}

# Download secrets for samples
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples"

# Download trampoline resources.
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"

# Use the trampoline script to run in docker.
build_file: "python-pubsub/.kokoro/trampoline_v2.sh"
6 changes: 6 additions & 0 deletions .kokoro/samples/python3.14/continuous.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Format: //devtools/kokoro/config/proto/build.proto

env_vars: {
key: "INSTALL_LIBRARY_FROM_SOURCE"
value: "True"
}
11 changes: 11 additions & 0 deletions .kokoro/samples/python3.14/periodic-head.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Format: //devtools/kokoro/config/proto/build.proto

env_vars: {
key: "INSTALL_LIBRARY_FROM_SOURCE"
value: "True"
}

env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-pubsub/.kokoro/test-samples-against-head.sh"
}
6 changes: 6 additions & 0 deletions .kokoro/samples/python3.14/periodic.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Format: //devtools/kokoro/config/proto/build.proto

env_vars: {
key: "INSTALL_LIBRARY_FROM_SOURCE"
value: "False"
}
6 changes: 6 additions & 0 deletions .kokoro/samples/python3.14/presubmit.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Format: //devtools/kokoro/config/proto/build.proto

env_vars: {
key: "INSTALL_LIBRARY_FROM_SOURCE"
value: "True"
}
4 changes: 3 additions & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ In order to add a feature:
documentation.

- The feature must work fully on the following CPython versions:
3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 on both UNIX and Windows.
3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows.

- The feature must not add unnecessary dependencies (where
"unnecessary" is of course subjective, but new dependencies should
Expand Down Expand Up @@ -228,6 +228,7 @@ We support:
- `Python 3.11`_
- `Python 3.12`_
- `Python 3.13`_
- `Python 3.14`_

.. _Python 3.7: https://docs.python.org/3.7/
.. _Python 3.8: https://docs.python.org/3.8/
Expand All @@ -236,6 +237,7 @@ We support:
.. _Python 3.11: https://docs.python.org/3.11/
.. _Python 3.12: https://docs.python.org/3.12/
.. _Python 3.13: https://docs.python.org/3.13/
.. _Python 3.14: https://docs.python.org/3.14/


Supported versions can be found in our ``noxfile.py`` `config`_.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def dispatcher(self) -> Optional[dispatcher.Dispatcher]:
return self._dispatcher

@property
def leaser(self) -> Optional[leaser.Leaser]:
def leaser(self) -> Optional["leaser.Leaser"]:
"""The leaser helper."""
return self._leaser

Expand Down
23 changes: 21 additions & 2 deletions google/cloud/pubsub_v1/subscriber/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import abc
import concurrent.futures
import queue
import sys
import typing
from typing import Callable, List, Optional
import warnings
Expand All @@ -37,7 +38,7 @@ class Scheduler(metaclass=abc.ABCMeta):

@property
@abc.abstractmethod
def queue(self) -> queue.Queue: # pragma: NO COVER
def queue(self) -> "queue.Queue": # pragma: NO COVER
"""Queue: A concurrency-safe queue specific to the underlying
concurrency implementation.

Expand Down Expand Up @@ -162,7 +163,25 @@ def shutdown(
work_item = self._executor._work_queue.get(block=False)
if work_item is None: # Exceutor in shutdown mode.
continue
dropped_messages.append(work_item.args[0]) # type: ignore[index]

dropped_message = None
if sys.version_info < (3, 14):
# For Python < 3.14, work_item.args is a tuple of positional arguments.
# The message is expected to be the first argument.
if hasattr(work_item, "args") and work_item.args:
dropped_message = work_item.args[0] # type: ignore[index]
else:
# For Python >= 3.14, work_item.task is (fn, args, kwargs).
# The message is expected to be the first item in the args tuple (task[1]).
if (
hasattr(work_item, "task")
and len(work_item.task) == 3
and work_item.task[1]
):
dropped_message = work_item.task[1][0]

if dropped_message is not None:
dropped_messages.append(dropped_message)
except queue.Empty:
pass

Expand Down
23 changes: 17 additions & 6 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"3.11",
"3.12",
"3.13",
"3.14",
]
UNIT_TEST_STANDARD_DEPENDENCIES = [
"mock",
Expand Down Expand Up @@ -234,7 +235,12 @@ def install_unittest_dependencies(session, *constraints):
def unit(session, protobuf_implementation):
# Install all test dependencies, then install this package in-place.

if protobuf_implementation == "cpp" and session.python in ("3.11", "3.12", "3.13"):
if protobuf_implementation == "cpp" and session.python in (
"3.11",
"3.12",
"3.13",
"3.14",
):
session.skip("cpp implementation is not supported in python 3.11+")

constraints_path = str(
Expand Down Expand Up @@ -325,15 +331,15 @@ def system(session):
if system_test_exists:
session.run(
"py.test",
"--quiet",
"--verbose",
f"--junitxml=system_{session.python}_sponge_log.xml",
system_test_path,
*session.posargs,
)
if system_test_folder_exists:
if os.path.exists(system_test_folder_path):
session.run(
"py.test",
"--quiet",
"--verbose",
f"--junitxml=system_{session.python}_sponge_log.xml",
system_test_folder_path,
*session.posargs,
Expand Down Expand Up @@ -436,15 +442,20 @@ def docfx(session):
)


@nox.session(python="3.13")
@nox.session(python="3.14")
@nox.parametrize(
"protobuf_implementation",
["python", "upb", "cpp"],
)
def prerelease_deps(session, protobuf_implementation):
"""Run all tests with prerelease versions of dependencies installed."""

if protobuf_implementation == "cpp" and session.python in ("3.11", "3.12", "3.13"):
if protobuf_implementation == "cpp" and session.python in (
"3.11",
"3.12",
"3.13",
"3.14",
):
session.skip("cpp implementation is not supported in python 3.11+")

# Install all dependencies
Expand Down
2 changes: 1 addition & 1 deletion owlbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@
samples=True,
cov_level=99,
versions=gcp.common.detect_versions(path="./google", default_first=True),
unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"],
unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"],
unit_test_dependencies=["flaky"],
system_test_python_versions=["3.12"],
system_test_external_dependencies=["psutil","flaky"],
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
release_status = "Development Status :: 5 - Production/Stable"

dependencies = [
"grpcio >= 1.51.3, < 2.0.0", # https://github.com/googleapis/python-pubsub/issues/609
"grpcio >= 1.51.3, < 2.0.0; python_version < '3.14'", # https://github.com/googleapis/python-pubsub/issues/609
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
# google-api-core >= 1.34.0 is allowed in order to support google-api-core 1.x
"google-auth >= 2.14.1, <3.0.0",
"google-api-core[grpc] >= 1.34.0, <3.0.0,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*",
Expand Down Expand Up @@ -88,6 +89,7 @@
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Topic :: Internet",
],
Expand Down
13 changes: 13 additions & 0 deletions testing/constraints-3.14.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# We use the constraints file for the latest Python version
# (currently this file) to check that the latest
# major versions of dependencies are supported in setup.py.
# List all library dependencies and extras in this file.
# Require the latest major version be installed for each dependency.
# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0",
# Then this file should have google-cloud-foo>=1
google-api-core>=2
google-auth>=2
proto-plus>=1
protobuf>=6
grpc-google-iam-v1>=0
grpcio >= 1.75.1
45 changes: 25 additions & 20 deletions tests/unit/pubsub_v1/publisher/test_publisher_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,33 +308,38 @@ def test_opentelemetry_flow_control_exception(creds, span_exporter):
future2.result()

spans = span_exporter.get_finished_spans()
# Span 1 = Publisher Flow Control Span of first publish
# Span 2 = Publisher Batching Span of first publish
# Span 3 = Publisher Flow Control Span of second publish(raises FlowControlLimitError)
# Span 4 = Publish Create Span of second publish(raises FlowControlLimitError)
assert len(spans) == 4

failed_flow_control_span = spans[2]
finished_publish_create_span = spans[3]
# Find the spans related to the second, failing publish call
failed_create_span = None
failed_fc_span = None
for span in spans:
if span.name == "topicID create":
if span.status.status_code == trace.StatusCode.ERROR:
failed_create_span = span
elif span.name == "publisher flow control":
if span.status.status_code == trace.StatusCode.ERROR:
failed_fc_span = span

assert failed_create_span is not None, "Failed 'topicID create' span not found"
assert failed_fc_span is not None, "Failed 'publisher flow control' span not found"

# Verify failed flow control span values.
assert failed_flow_control_span.name == "publisher flow control"
assert failed_flow_control_span.kind == trace.SpanKind.INTERNAL
assert failed_fc_span.kind == trace.SpanKind.INTERNAL
assert (
failed_flow_control_span.parent.span_id
== finished_publish_create_span.get_span_context().span_id
failed_fc_span.parent.span_id == failed_create_span.get_span_context().span_id
)
assert failed_flow_control_span.status.status_code == trace.StatusCode.ERROR

assert len(failed_flow_control_span.events) == 1
assert failed_flow_control_span.events[0].name == "exception"
assert len(failed_fc_span.events) == 1
assert failed_fc_span.events[0].name == "exception"

# Verify finished publish create span values
assert finished_publish_create_span.name == "topicID create"
assert finished_publish_create_span.status.status_code == trace.StatusCode.ERROR
assert len(finished_publish_create_span.events) == 2
assert finished_publish_create_span.events[0].name == "publish start"
assert finished_publish_create_span.events[1].name == "exception"
assert failed_create_span.status.status_code == trace.StatusCode.ERROR
assert len(failed_create_span.events) >= 1 # Should have at least 'publish start'
assert failed_create_span.events[0].name == "publish start"
# Check for exception event
has_exception_event = any(
event.name == "exception" for event in failed_create_span.events
)
assert has_exception_event, "Exception event not found in failed create span"


@pytest.mark.skipif(
Expand Down