Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-proton-core for openSUSE:Factory checked in at 2025-11-06 18:15:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-proton-core (Old) and /work/SRC/openSUSE:Factory/.python-proton-core.new.1980 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-proton-core" Thu Nov 6 18:15:36 2025 rev:5 rq:1316025 version:0.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-proton-core/python-proton-core.changes 2025-04-02 17:12:20.574780793 +0200 +++ /work/SRC/openSUSE:Factory/.python-proton-core.new.1980/python-proton-core.changes 2025-11-06 18:19:04.994355017 +0100 @@ -1,0 +2,6 @@ +Sun Nov 2 15:24:54 UTC 2025 - Yunhe Guo <[email protected]> + +- Update to 0.7.0: + * No upstream changelog + +------------------------------------------------------------------- Old: ---- v0.4.0.tar.gz New: ---- v0.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-proton-core.spec ++++++ --- /var/tmp/diff_new_pack.sxFJeu/_old 2025-11-06 18:19:05.578379711 +0100 +++ /var/tmp/diff_new_pack.sxFJeu/_new 2025-11-06 18:19:05.582379880 +0100 @@ -19,7 +19,7 @@ %define skip_python2 1 %{?sle15_python_module_pythons} Name: python-proton-core -Version: 0.4.0 +Version: 0.7.0 Release: 0 Summary: Proton VPN core library License: GPL-3.0-or-later ++++++ v0.4.0.tar.gz -> v0.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/.gitlab-ci.yml new/python-proton-core-0.7.0/.gitlab-ci.yml --- old/python-proton-core-0.4.0/.gitlab-ci.yml 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/.gitlab-ci.yml 2025-09-11 15:37:17.000000000 +0200 @@ -2,6 +2,9 @@ ALLOW_LINTING_FAILURE: "true" include: - - project: 'ProtonVPN/Linux/integration/ci-libraries' - ref: develop - file: 'develop-pipeline.yml' + - component: $CI_SERVER_FQDN/proton/devops/cicd-components/kits/devsecops/[email protected] + inputs: + stage: scan + - project: 'ProtonVPN/Linux/integration/ci-libraries' + ref: develop + file: 'develop-pipeline.yml' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/CODEOWNERS new/python-proton-core-0.7.0/CODEOWNERS --- old/python-proton-core-0.4.0/CODEOWNERS 1970-01-01 01:00:00.000000000 +0100 +++ new/python-proton-core-0.7.0/CODEOWNERS 2025-09-11 15:37:17.000000000 +0200 @@ -0,0 +1,2 @@ +# ownership: tight +* @ProtonVPN/groups/rnd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/debian/changelog new/python-proton-core-0.7.0/debian/changelog --- old/python-proton-core-0.4.0/debian/changelog 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/debian/changelog 2025-09-11 15:37:17.000000000 +0200 @@ -1,3 +1,21 @@ +proton-core (0.7.0) unstable; urgency=medium + + * Add fido2 support for 2fa authorization. + + -- Luke Titley <[email protected]> Wed, Sep 10 2025 17:08:07 +0200 + +proton-core (0.6.0) unstable; urgency=medium + + * Ensure CI passed soc tests. + + -- Alexandru Cheltuitor <[email protected]> Tue, 17 Jun 2025 15:00:00 +0000 + +proton-core (0.5.0) unstable; urgency=medium + + * Provide access to the binary response when requesting with 'return_raw'. + + -- Luke Titley <[email protected]> Wed, 4 Jun 2025 15:00:00 +0000 + proton-core (0.4.0) unstable; urgency=medium * Require python >= 3.9 to allow libraries using newer language features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/keyring/textfile.py new/python-proton-core-0.7.0/proton/keyring/textfile.py --- old/python-proton-core-0.4.0/proton/keyring/textfile.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/keyring/textfile.py 2025-09-11 15:37:17.000000000 +0200 @@ -39,7 +39,7 @@ raise KeyError(key) try: - with open(filepath, 'r') as f: + with open(filepath, "r", encoding="utf-8") as f: return json.load(f) except json.JSONDecodeError as e: self._del_item(key) @@ -54,7 +54,7 @@ def _set_item(self, key, value): try: - with open(self.__get_filename_for_key(key), 'w') as f: + with open(self.__get_filename_for_key(key), "w", encoding="utf-8") as f: json.dump(value, f) except TypeError as e: # The value we got is not serializable, thus a type error is thrown, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/api.py new/python-proton-core-0.7.0/proton/session/api.py --- old/python-proton-core-0.4.0/proton/session/api.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/api.py 2025-09-11 15:37:17.000000000 +0200 @@ -18,8 +18,10 @@ """ from __future__ import annotations +from dataclasses import dataclass from pathlib import Path from typing import * +import logging from .exceptions import ProtonCryptoError, ProtonAPIError, ProtonAPIAuthenticationNeeded, ProtonAPI2FANeeded, ProtonAPIMissingScopeError, ProtonAPIHumanVerificationNeeded from .srp import User as PmsrpUser @@ -32,6 +34,8 @@ from ..utils import ExecutionEnvironment +logger = logging.getLogger(__name__) + SRP_MODULUS_KEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- xjMEXAHLgxYJKwYBBAHaRw8BAQdAFurWXXwjTemqjD7CXjXVyKf0of7n9Ctm @@ -45,7 +49,31 @@ =Y4Mw -----END PGP PUBLIC KEY BLOCK-----""" -SRP_MODULUS_KEY_FINGERPRINT = "248097092b458509c508dac0350585c4e9518f26" +SRP_MODULUS_KEY_FINGERPRINT = "248097092b458509c508dac0350585c4e9518f26" # nosemgrep: gitleak-detect-ignore,generic.secrets.gitleaks.generic-api-key.generic-api-key # pylint: disable=line-too-long + + +@dataclass +class Fido2AssertionParameters: + """ + Parameters needed to create a FIDO2 assertion for 2FA. + These can be obtained from :attr:`Session.supports_fido2`. + """ + challenge: bytes + rp_id: str + allow_credentials: list[dict] + user_verification: str + + +@dataclass +class Fido2Assertion: + """ + FIDO2 assertion obtained from the security key. + These are passed to :meth:`Session.async_validate_2fa_fido2`. + """ + client_data: bytes + authenticator_data: bytes + signature: bytes + credential_id: bytes def sync_wrapper(f): @@ -279,10 +307,11 @@ finally: self._requests_unlock(no_condition_check) - - async def async_provide_2fa(self, code : str, no_condition_check=False, additional_headers=None) -> bool: """Provide Two Factor Authentication Code to the API. + + This method is deprecated, please use :meth:`async_validate_2fa_code` + or :meth:`async_validate_2fa_fido2` instead. :param code: 2FA code :type code: str @@ -292,11 +321,33 @@ :rtype: bool :raises ProtonAPIAuthenticationNeeded: if 2FA failed, and the session was reset by the API backend (this is normally the case) """ + + logger.warning("async_provide_2fa is deprecated, please use async_validate_2fa_code or async_validate_2fa_fido2 instead") + + return await self.async_validate_2fa_code(code, no_condition_check, + additional_headers) + + async def _async_validate_2fa(self, answer: dict, no_condition_check=False, additional_headers=None) -> bool: + """ + Internal function to validate 2FA, either via code or FIDO2. + + Do not use this method directly, use :meth:`async_validate_2fa_code` + or :meth:`async_validate_2fa_fido2` instead. + + :param answer: dict containing either the TwoFactorCode or FIDO2 information + :type answer: dict + :param no_condition_check: Internal flag to disable locking, defaults to False + :type no_condition_check: bool, optional + :param additional_headers: additional headers to send + :type additional_headers: dict, optional + :return: True if 2FA succeeded, False otherwise. + """ self._requests_lock(no_condition_check) try: - ret = await self.__async_api_request_internal('/auth/2fa', { - "TwoFactorCode": code - }, no_condition_check=True, additional_headers=additional_headers) + ret = await self.__async_api_request_internal( + '/auth/2fa', answer, no_condition_check=True, + additional_headers=additional_headers) + self.__Scopes = ret['Scopes'] if ret.get('Code') == 1000: self.__2FA = None @@ -315,6 +366,60 @@ finally: self._requests_unlock(no_condition_check) + async def async_validate_2fa_code(self, + code: str, + no_condition_check=False, + additional_headers=None) -> bool: + """ + Validate a 2FA code against the API. + :param code: 2FA code + :type code: str + :param no_condition_check: Internal flag to disable locking, defaults to False + :type no_condition_check: bool, optional + :param additional_headers: additional headers to send + :type additional_headers: dict, optional + :return: True if 2FA succeeded, False otherwise. + :rtype: bool + """ + + return await self._async_validate_2fa( + {"TwoFactorCode": code}, + no_condition_check, + additional_headers) + + async def async_validate_2fa_fido2(self, + assertion: Fido2Assertion, + no_condition_check=False, + additional_headers=None) -> bool: + """ + Validate a FIDO2 assertion against the API. + :param assertion: FIDO2 assertion obtained from the security key + :type assertion: Fido2Assertion + :param no_condition_check: Internal flag to disable locking, defaults to False + :type no_condition_check: bool, optional + :param additional_headers: additional headers to send + :type additional_headers: dict, optional + :return: True if 2FA succeeded, False otherwise. + :rtype: bool + """ + + fido2 = self.__2FA["FIDO2"] + + def to_b64(x): return base64.b64encode(x).decode('ascii') + + answer = { + "AuthenticationOptions": fido2["AuthenticationOptions"], + "ClientData": to_b64(assertion.client_data), + "AuthenticatorData": to_b64(assertion.authenticator_data), + "Signature": to_b64(assertion.signature), + "CredentialID": list(assertion.credential_id), + } + return await self._async_validate_2fa( + {"FIDO2": answer}, + no_condition_check, + additional_headers + ) + async def async_refresh(self, only_when_refresh_revision_is=None, no_condition_check=False, additional_headers=None): """Refresh tokens. @@ -424,7 +529,9 @@ async def async_human_verif_request_code(self, address=None, phone=None, additional_headers=None): """Request a verification code. Either address (email address) or phone (phone number) should be specified.""" - assert address is not None ^ phone is not None # nosec (we use email validation by default if both are provided, but it's not super clean if the dev doesn't know about it) + # We use email validation by default if both are provided, + # but it's not super clean if the dev doesn't know about it + assert address is not None ^ phone is not None # nosec B101 # nosemgrep: gitlab.bandit.B101 if address is not None: data = {'Type': 'email', 'Destination': {'Address': address}} @@ -498,6 +605,8 @@ human_verif_provide_token = sync_wrapper(async_human_verif_provide_token) fork = sync_wrapper(async_fork) import_fork = sync_wrapper(async_import_fork) + validate_2fa_code = sync_wrapper(async_validate_2fa_code) + validate_2fa_fido2 = sync_wrapper(async_validate_2fa_fido2) def register_persistence_observer(self, observer: object): """Register an observer that will be notified of any persistent state change of the session @@ -602,6 +711,34 @@ return 'twofactor' in self.Scopes @property + def supports_fido2(self) -> Optional[Fido2AssertionParameters]: + """ + :return: If 2FA is needed, and FIDO2 is supported, return the + parameters needed to create a FIDO2 assertion. + None otherwise (either 2FA is not needed, or FIDO2 is not supported). + :rtype: Fido2AssertionParameters, optional + """ + if not self.__2FA: + return None + + # When FIDO2 is not supported, the __2FA property contains: + # {'AuthenticationOptions': None, 'RegisteredKeys': []} + options = self.__2FA.get('FIDO2', {}).get("AuthenticationOptions") + if not options: + return None + + public_key = options.get("publicKey") + if not public_key: + return None + + return Fido2AssertionParameters( + challenge=public_key["challenge"], + rp_id=public_key["rpId"], + allow_credentials=public_key["allowCredentials"], + user_verification=public_key["userVerification"] + ) + + @property def environment(self): """Get/set the environment in use for that session. It can be only set once at the beginning of the session's object lifetime, as changing the environment can lead to security hole. @@ -758,7 +895,8 @@ if e.http_headers.get('retry-after','-').isnumeric(): await asyncio.sleep(int(e.http_headers.get('retry-after'))) else: - await asyncio.sleep(3+random.random()*5) # nosec (no crypto risk here of using an unsafe generator) + # No crypto risk here of using an unsafe generator + await asyncio.sleep(3+random.random()*5) # nosec B311 # nosemgrep: gitlab.bandit.B311 async def __async_api_request_internal( self, endpoint, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/environments.py new/python-proton-core-0.7.0/proton/session/environments.py --- old/python-proton-core-0.4.0/proton/session/environments.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/environments.py 2025-09-11 15:37:17.000000000 +0200 @@ -23,7 +23,8 @@ @property def name(cls): cls_name = cls.__class__.__name__ - assert cls_name.endswith('Environment'), "Incorrectly named class" # nosec (dev should ensure that to avoid issues) + # dev should ensure that to avoid issues + assert cls_name.endswith('Environment'), "Incorrectly named class" # nosec B101 # nosemgrep: gitlab.bandit.B101 # noqa: E501 # pylint: disable=line-too-long return cls_name[:-11].lower() @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/transports/aiohttp.py new/python-proton-core-0.7.0/proton/session/transports/aiohttp.py --- old/python-proton-core-0.4.0/proton/session/transports/aiohttp.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/transports/aiohttp.py 2025-09-11 15:37:17.000000000 +0200 @@ -129,8 +129,7 @@ json=jsondata, data=form_data, params=params, ssl=ssl_specs ) as ret: if return_raw: - return RawResponse(ret.status, tuple(ret.headers.items()), - await self._parse_json(ret, allow_unmodified=True)) + return await self._build_raw(ret) ret_json = await self._parse_json(ret) @@ -146,10 +145,21 @@ except Exception as e: raise ProtonAPIUnexpectedError(e) - async def _parse_json(self, ret, allow_unmodified=False): - if allow_unmodified and ret.status == NOT_MODIFIED: - return None + async def _build_raw(self, ret): + response = RawResponse(ret.status, + tuple(ret.headers.items()), None, None) + + if ret.status == NOT_MODIFIED: + return response + + response.data = await ret.read() + if ret.headers['content-type'] != 'application/octet-stream': + response.json = await self._parse_json(ret, allow_unmodified=True) + + return response + + async def _parse_json(self, ret, allow_unmodified=False): if ret.headers['content-type'] != 'application/json': raise ProtonAPINotReachable("API returned non-json results") try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/transports/base.py new/python-proton-core-0.7.0/proton/session/transports/base.py --- old/python-proton-core-0.4.0/proton/session/transports/base.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/transports/base.py 2025-09-11 15:37:17.000000000 +0200 @@ -30,11 +30,13 @@ :param status_code: The status code of the response :param headers: The headers in the response - :param json: The body the response parsed as json + :param json: The response parsed as json + :param data: The response as a binary blob of bytes """ status_code: int headers: Tuple[Tuple[str, Any]] json: Optional[dict] + data: Optional[bytes] def find_first_header(self, key, default=None): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/transports/requests.py new/python-proton-core-0.7.0/proton/session/transports/requests.py --- old/python-proton-core-0.4.0/proton/session/transports/requests.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/transports/requests.py 2025-09-11 15:37:17.000000000 +0200 @@ -42,6 +42,20 @@ except ImportError: return None + def _build_raw(self, ret): + response = RawResponse(ret.status_code, + tuple(ret.headers.items()), None, None) + + if ret.status_code == NOT_MODIFIED: + return response + + response.data = ret.content + + if ret.headers['content-type'] != 'application/octet-stream': + response.json = self._parse_json(ret, allow_unmodified=True) + + return response + def _parse_json(self, ret, allow_unmodified=False): if allow_unmodified and ret.status_code == NOT_MODIFIED: return None @@ -103,8 +117,7 @@ raise ProtonAPIUnexpectedError(e) if return_raw: - return RawResponse(ret.status_code, tuple(ret.headers.items()), - self._parse_json(ret, allow_unmodified=True)) + return self._build_raw(ret) ret_json = self._parse_json(ret) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/session/transports/utils/dns.py new/python-proton-core-0.7.0/proton/session/transports/utils/dns.py --- old/python-proton-core-0.4.0/proton/session/transports/utils/dns.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/session/transports/utils/dns.py 2025-09-11 15:37:17.000000000 +0200 @@ -168,7 +168,7 @@ @classmethod def _build_simple_query(cls, domain: bytes, qtype: int, qclass: int): """internal utility to build the simplest DNS request we need""" - id: bytes = struct.pack('!H', random.randint(0, 65535)) + id: bytes = struct.pack('!H', random.randint(0, 65535)) # nosemgrep: gitlab.bandit.B311 # nosec B311 # noqa: E501 # pylint: disable=line-too-long qtype: bytes = struct.pack('!H', qtype) qclass: bytes = struct.pack('!H', qclass) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/sso/sso.py new/python-proton-core-0.7.0/proton/sso/sso.py --- old/python-proton-core-0.4.0/proton/sso/sso.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/sso/sso.py 2025-09-11 15:37:17.000000000 +0200 @@ -71,7 +71,7 @@ self._session_data_cache = {} # This is a global lock, we use it when we modify the indexes - self._global_adv_lock = open(os.path.join(self._adv_locks_path, f'proton-sso.lock'), 'w') + self._global_adv_lock = open(os.path.join(self._adv_locks_path, "proton-sso.lock"), "w", encoding="utf-8") # nosemgrep: python.lang.best-practice.open-never-closed.open-never-closed self.__keyring_backend = None self.__keyring_backend_name = keyring_backend_name @@ -262,7 +262,10 @@ # Don't do anything, we don't know the account yet! return - self._adv_locks[account_name] = open(os.path.join(self._adv_locks_path, f'proton-sso-{self.__encode_name(account_name)}.lock'), 'w') + self._adv_locks[account_name] = open( # nosemgrep: python.lang.best-practice.open-never-closed.open-never-closed # pylint: disable=line-too-long + os.path.join(self._adv_locks_path, f'proton-sso-{self.__encode_name(account_name)}.lock'), + "w", encoding="utf-8" + ) # This is a blocking call. # FIXME: this is Linux specific fcntl.flock(self._adv_locks[account_name], fcntl.LOCK_EX) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/proton/views/basiccli.py new/python-proton-core-0.7.0/proton/views/basiccli.py --- old/python-proton-core-0.4.0/proton/views/basiccli.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/proton/views/basiccli.py 2025-09-11 15:37:17.000000000 +0200 @@ -92,8 +92,8 @@ login = None if ask_password: password = getpass.getpass() - if password == '': - password = None # nosec B105 + if password == '': # nosec B105 + password = None # nosec B105 if ask_2fa: twofa = input("Please enter your 2FA code: ") # nosec (Python 3 only code) if twofa == '' or not twofa.isnumeric(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/rpmbuild/SPECS/package.spec new/python-proton-core-0.7.0/rpmbuild/SPECS/package.spec --- old/python-proton-core-0.4.0/rpmbuild/SPECS/package.spec 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/rpmbuild/SPECS/package.spec 2025-09-11 15:37:17.000000000 +0200 @@ -1,5 +1,5 @@ %define unmangled_name proton-core -%define version 0.4.0 +%define version 0.7.0 %define release 1 Prefix: %{_prefix} @@ -56,6 +56,15 @@ %defattr(-,root,root) %changelog +* Wed Sep 10 2025 Luke Titley <[email protected]> 0.7.0 +- Add fido2 support for 2fa authorization. + +* Tue Jun 17 2025 Alexandru Cheltuitor <[email protected]> 0.6.0 +- Ensure CI passed soc tests. + +* Wed Jun 4 2025 Luke Titley <[email protected]> 0.5.0 +- Provide access to the binary response when requesting with 'return_raw'. + * Tue Nov 19 2024 Alexandru Cheltuitor <[email protected]> 0.4.0 - Require python >= 3.9 to allow libraries using newer language features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/setup.py new/python-proton-core-0.7.0/setup.py --- old/python-proton-core-0.4.0/setup.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/setup.py 2025-09-11 15:37:17.000000000 +0200 @@ -4,7 +4,7 @@ setup( name="proton-core", - version="0.4.0", + version="0.7.0", description="Proton Technologies API wrapper", author="Proton Technologies", author_email="[email protected]", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/tests/test_aiohttp_transport.py new/python-proton-core-0.7.0/tests/test_aiohttp_transport.py --- old/python-proton-core-0.4.0/tests/test_aiohttp_transport.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/tests/test_aiohttp_transport.py 2025-09-11 15:37:17.000000000 +0200 @@ -19,6 +19,7 @@ import unittest from io import StringIO from unittest.mock import patch, AsyncMock, Mock +import json from proton.session import Session from proton.session.formdata import FormData, FormField @@ -101,13 +102,17 @@ class TestAiohttpTransportRawResult(unittest.IsolatedAsyncioTestCase): - def _setup(self, get_mock, status, headers, json): + def _setup(self, get_mock, status, headers, json_value): # Mock the GET response get_mock.return_value.__aenter__.return_value.status = status get_mock.return_value.__aenter__.return_value.headers = headers - get_mock.return_value.__aenter__.return_value.json = AsyncMock( - return_value=json - ) + get_mock.return_value.__aenter__.return_value.json.return_value =\ + json_value + + if json_value is not None: + byte_value = json.dumps(json_value).encode('utf-8') + get_mock.return_value.__aenter__.return_value.read.return_value =\ + byte_value session = Session() aiohttp_transport = AiohttpTransport(session) @@ -120,7 +125,7 @@ session, aiohttp_transport = self._setup(get_mock, status=HTTP_STATUS_OK, headers={"content-type": "application/json"}, - json={"Code": CODE_SUCCESS}) + json_value={"Code": CODE_SUCCESS}) # Test response = await aiohttp_transport.async_api_request("/endpoint", return_raw=True) @@ -129,6 +134,7 @@ assert isinstance(response, RawResponse), "The response should be a RawResponse object." assert response.status_code == HTTP_STATUS_OK assert response.find_first_header("content-type") == "application/json" + assert response.data == b'{"Code": 1000}', "The response data should not be None." assert response.json == {"Code": CODE_SUCCESS} get_mock.assert_called_once() @@ -138,7 +144,7 @@ session, aiohttp_transport = self._setup(get_mock, status=HTTP_STATUS_NOT_MODIFIED, headers={}, - json=None) + json_value=None) # Test response = await aiohttp_transport.async_api_request("/endpoint", return_raw=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/tests/test_requests_transport.py new/python-proton-core-0.7.0/tests/test_requests_transport.py --- old/python-proton-core-0.4.0/tests/test_requests_transport.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/tests/test_requests_transport.py 2025-09-11 15:37:17.000000000 +0200 @@ -19,6 +19,7 @@ import unittest from io import StringIO from unittest.mock import Mock +import json import requests @@ -72,12 +73,15 @@ class TestRequestsTransportRawResult(unittest.IsolatedAsyncioTestCase): - def _setup(self, status, headers, json): + def _setup(self, status, headers, json_value): # Mock requests get call. req_session = Mock(spec=requests.Session) req_session.headers = headers req_session.get.return_value.headers = headers - req_session.get.return_value.json.return_value = json + req_session.get.return_value.json.return_value = json_value + if json_value is not None: + req_session.get.return_value.content =\ + json.dumps(json_value).encode('utf-8') req_session.get.return_value.status_code = status session = Session() @@ -96,6 +100,7 @@ # Checks assert isinstance(response, RawResponse), "The response should be a RawResponse object." + assert response.data == b'{"Code": 1000}', "The response data should not be None." assert response.status_code == HTTP_STATUS_OK assert response.find_first_header("content-type") == "application/json" assert response.json == {"Code": CODE_SUCCESS} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-core-0.4.0/tests/test_session.py new/python-proton-core-0.7.0/tests/test_session.py --- old/python-proton-core-0.4.0/tests/test_session.py 2024-11-20 10:24:53.000000000 +0100 +++ new/python-proton-core-0.7.0/tests/test_session.py 2025-09-11 15:37:17.000000000 +0200 @@ -32,6 +32,12 @@ s = Session() assert await s.async_api_request('/tests/ping') == {'Code': 1000} + async def test_raw_ping(self): + s = Session() + response = await s.async_api_request('/tests/ping', return_raw=True) + assert response.json == {'Code': 1000} + assert response.data == b'{"Code":1000}' + async def test_session_refresh(self): session_state = { "UID": "7pqrddjjxmbqpmxcqzg3utlscjgw74xq",
