From 75783bcb6d3706a8495f77ab242a6ef0cdb51809 Mon Sep 17 00:00:00 2001 From: yashbudhia Date: Sat, 5 Sep 2026 14:52:14 +0530 Subject: [PATCH] Fix UnboundLocalError in _get_version when package metadata is missing _get_version() imported DistributionNotFound only inside the pkg_resources fallback branch, but named it in the except clause that also guards the importlib.metadata path. When importlib.metadata.version() raised PackageNotFoundError, evaluating that except tuple hit the unbound name and the original exception was replaced by UnboundLocalError, which propagated out of _update_user_agent_header() and so out of every request. That is exactly the situation the fallback was written for, e.g. running the SDK from a source checkout or a vendored copy that was never pip-installed. The exception class is now resolved before the lookup, so the fallback version and its warning are actually used. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011ZJQzxtgWvkWobxsDcuLoW --- CHANGELOG.md | 1 + razorpay/client.py | 45 ++++++++++++++++++------------------ tests/test_client_version.py | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 tests/test_client_version.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2b7f71..e11b65ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +fix: `Client._get_version()` raised `UnboundLocalError` instead of falling back when the package metadata was missing, which broke every request from an uninstalled source checkout ## [2.0.1][2.0.1] - 2026-03-09 feat: Added Support for cancel token diff --git a/razorpay/client.py b/razorpay/client.py index 6d80e526..2071686d 100644 --- a/razorpay/client.py +++ b/razorpay/client.py @@ -97,33 +97,32 @@ def _update_user_agent_header(self, options): return options def _get_version(self): - version = "" - try: # nosemgrep : gitlab.bandit.B110 - # Try importlib.metadata first (modern approach) - try: - import importlib.metadata - from importlib.metadata import PackageNotFoundError - version = importlib.metadata.version("razorpay") - except ImportError: - # Fall back to pkg_resources + """ + Version of the installed razorpay package, used in the User-Agent. + Falls back to a fixed value, with a warning, when the package + metadata cannot be found (for example when running from a source + checkout that was never installed). + """ + try: + from importlib.metadata import PackageNotFoundError + from importlib.metadata import version as package_version + except ImportError: # pragma: no cover - Python < 3.8 + from pkg_resources import DistributionNotFound as PackageNotFoundError + + def package_version(name): import pkg_resources - from pkg_resources import DistributionNotFound - version = pkg_resources.require("razorpay")[0].version - except (PackageNotFoundError, DistributionNotFound, NameError): # pragma: no cover - # PackageNotFoundError: importlib.metadata couldn't find the package - # DistributionNotFound: pkg_resources couldn't find the package - # NameError: in case the exception classes aren't defined due to import issues - - # If all else fails, use the hardcoded version from the package - version = "1.4.3" + return pkg_resources.require(name)[0].version + try: + return package_version("razorpay") + except PackageNotFoundError: warnings.warn( - "Could not detect razorpay package version. Using fallback version." - "This may indicate an installation issue.", - UserWarning, - stacklevel=4 + "Could not detect razorpay package version. Using fallback version. " + "This may indicate an installation issue.", + UserWarning, + stacklevel=4 ) - return version + return "1.4.3" def _get_app_details_ua(self): app_details_ua = "" diff --git a/tests/test_client_version.py b/tests/test_client_version.py new file mode 100644 index 00000000..040ac51e --- /dev/null +++ b/tests/test_client_version.py @@ -0,0 +1,40 @@ +import importlib.metadata +import unittest +import warnings +from unittest import mock + +import razorpay + + +class TestClientVersion(unittest.TestCase): + + def setUp(self): + self.client = razorpay.Client(auth=('key_id', 'key_secret')) + + def test_version_comes_from_package_metadata(self): + self.assertEqual(self.client._get_version(), + importlib.metadata.version('razorpay')) + + def test_missing_metadata_falls_back_with_a_warning(self): + def missing(name): + raise importlib.metadata.PackageNotFoundError(name) + + with mock.patch('importlib.metadata.version', missing): + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + version = self.client._get_version() + + self.assertTrue(version) + self.assertTrue(any(issubclass(w.category, UserWarning) for w in caught), + 'a UserWarning should explain the fallback') + + def test_requests_still_work_without_package_metadata(self): + def missing(name): + raise importlib.metadata.PackageNotFoundError(name) + + with mock.patch('importlib.metadata.version', missing): + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + options = self.client._update_user_agent_header({}) + + self.assertIn('Razorpay-Python/', options['headers']['User-Agent'])