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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 22 additions & 23 deletions razorpay/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
40 changes: 40 additions & 0 deletions tests/test_client_version.py
Original file line number Diff line number Diff line change
@@ -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'])