Skip to content

Commit

Permalink
Session optimizations
Browse files Browse the repository at this point in the history
* Use stored cookie from `osc`, if it exists
* Dropped support for `CacheControl`
* Added support for Python 3.12
* Updated meta info (due to repo move)
  • Loading branch information
crazyscientist committed Sep 1, 2023
1 parent 281119d commit 6e7f135
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 85 deletions.
1 change: 1 addition & 0 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
- python_version: "3.9"
- python_version: "3.10"
- python_version: "3.11"
- python_version: "3.12"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
12 changes: 0 additions & 12 deletions doc/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,6 @@ E.g. on Ubuntu you can use:
.. _OpenBuildService: https://openbuildservice.org/

Caching
^^^^^^^

If you want to use caching, make sure to also install `CacheControl`_. It's a
purely optional feature so it is not listed in the requirements file. Use:

.. code-block:: bash
pip install CacheControl
.. _CacheControl: https://cachecontrol.readthedocs.io/en/latest/

Configuration
^^^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion osctiny/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

__all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages',
'projects', 'search', 'users']
__version__ = "0.7.12"
__version__ = "0.8.0"
2 changes: 1 addition & 1 deletion osctiny/extensions/buildresults.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def get_log(self, project, repo, arch, package):
:return: The package build log file
:rtype: str
.. versionadded:: {{ NEXT_RELEASE }}
.. versionadded:: 0.8.0
"""

response = self.osc.request(
Expand Down
64 changes: 20 additions & 44 deletions osctiny/osc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from base64 import b64encode
import typing
import errno
from http.cookiejar import CookieJar
from io import BufferedReader, BytesIO, StringIO
import gc
import logging
Expand Down Expand Up @@ -38,13 +39,9 @@
from .extensions.users import Group, Person
from .utils.auth import HttpSignatureAuth
from .utils.backports import cached_property
from .utils.conf import BOOLEAN_PARAMS, get_credentials
from .utils.conf import BOOLEAN_PARAMS, get_credentials, get_cookie_jar
from .utils.errors import OscError

try:
from cachecontrol import CacheControl
except ImportError:
CacheControl = None

THREAD_LOCAL = threading.local()

Expand Down Expand Up @@ -94,7 +91,6 @@ class Osc:
:param password: Password; this is either the user password (``ssh_key_file`` is ``None``) or
the SSH passphrase, if ``ssh_key_file`` is defined
:param verify: See `SSL Cert Verification`_ for more details
:param cache: Store API responses in a cache
:param ssh_key_file: Path to SSH private key file
:raises osctiny.errors.OscError: if no credentials are provided
Expand All @@ -120,6 +116,9 @@ class Osc:
Support for 2FA authentication (i.e. added the ``ssh_key_file`` parameter and changed the
meaning of the ``password`` parameter
.. versionchanged:: 0.8.0
Removed the ``cache`` parameter
.. _SSL Cert Verification:
http://docs.python-requests.org/en/master/user/advanced/
#ssl-cert-verification
Expand All @@ -133,14 +132,12 @@ class Osc:

def __init__(self, url: typing.Optional[str] = None, username: typing.Optional[str] = None,
password: typing.Optional[str] = None, verify: typing.Optional[str] = None,
cache: bool = False,
ssh_key_file: typing.Optional[typing.Union[Path, str]] = None):
# Basic URL and authentication settings
self.url = url or self.url
self.username = username or self.username
self.password = password or self.password
self.verify = verify
self.cache = cache
self.ssh_key = ssh_key_file
if self.ssh_key is not None and not isinstance(self.ssh_key, Path):
self.ssh_key = Path(self.ssh_key)
Expand Down Expand Up @@ -174,7 +171,7 @@ def _session_id(self) -> str:
return f"session_{session_hash}_{os.getpid()}_{threading.get_ident()}"

@property
def _session(self) -> Session:
def session(self) -> Session:
"""
Session object
"""
Expand All @@ -183,6 +180,11 @@ def _session(self) -> Session:
session = Session()
session.verify = self.verify or get_default_verify_paths().capath

cookies = get_cookie_jar()
if cookies is not None:
cookies.load()
session.cookies = cookies

if self.ssh_key is not None:
session.auth = HttpSignatureAuth(username=self.username, password=self.password,
ssh_key_file=self.ssh_key)
Expand All @@ -193,40 +195,22 @@ def _session(self) -> Session:

return session

@property
def session(self) -> typing.Union[CacheControl, Session]:
"""
Session object
Possibly wrapped in CacheControl, if installed.
"""
if not self.cache or CacheControl is None:
return self._session

key = f"cached_{self._session_id}"
session = getattr(THREAD_LOCAL, key, None)
if not session:
session = CacheControl(self._session)
setattr(THREAD_LOCAL, key, session)

return session

@property
def cookies(self) -> RequestsCookieJar:
"""
Access session cookies
"""
return self._session.cookies
return self.session.cookies

@cookies.setter
def cookies(self, value: RequestsCookieJar):
if not isinstance(value, (RequestsCookieJar, dict)):
def cookies(self, value: typing.Union[CookieJar, dict]):
if not isinstance(value, (CookieJar, dict)):
raise TypeError(f"Expected a cookie jar or dict. Got instead: {type(value)}")

if isinstance(value, RequestsCookieJar):
self._session.cookies = value
if isinstance(value, CookieJar):
self.session.cookies = value
else:
self._session.cookies = cookiejar_from_dict(value)
self.session.cookies = cookiejar_from_dict(value)

@property
def parser(self):
Expand All @@ -247,9 +231,6 @@ def request(self, url, method="GET", stream=False, data=None, params=None,
a dictionary and contains a key ``comment``, this value is passed on as
a POST parameter.
If ``stream`` is True, the server response does not get cached because
the returned file might be large or huge.
if ``raise_for_status`` is True, the used ``requests`` framework will
raise an exception for occured errors.
Expand Down Expand Up @@ -292,21 +273,16 @@ def request(self, url, method="GET", stream=False, data=None, params=None,
"""
timeout = timeout or self.default_timeout

if stream:
session = self._session
else:
session = self.session

req = Request(
method,
url.replace("#", quote("#")).replace("?", quote("?")),
data=self.handle_params(url=url, method=method, params=data),
params=self.handle_params(url=url, method=method, params=params)
)
prepped_req = session.prepare_request(req)
prepped_req = self.session.prepare_request(req)
prepped_req.headers['Content-Type'] = "application/octet-stream"
prepped_req.headers['Accept'] = "application/xml"
settings = session.merge_environment_settings(
settings = self.session.merge_environment_settings(
prepped_req.url, {}, None, None, None
)
settings["stream"] = stream
Expand All @@ -327,7 +303,7 @@ def request(self, url, method="GET", stream=False, data=None, params=None,
else parse_qs(req.params, keep_blank_values=True)
).items()))
try:
response = session.send(prepped_req, **settings)
response = self.session.send(prepped_req, **settings)
except _ConnectionError as error:
warnings.warn("Problem connecting to server: {}".format(error))
log_method = logger.error if i < 1 else logger.warning
Expand Down
24 changes: 0 additions & 24 deletions osctiny/tests/test_cache.py

This file was deleted.

23 changes: 23 additions & 0 deletions osctiny/utils/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from base64 import b64decode
from bz2 import decompress
from configparser import ConfigParser, NoSectionError
from http.cookiejar import LWPCookieJar
import os
from pathlib import Path

Expand Down Expand Up @@ -189,3 +190,25 @@ def get_credentials(url: typing.Optional[str] = None) \
raise ValueError(f"`osc` config provides no password or SSH key for URL {url}")

return username, password if sshkey is None else None, sshkey


def get_cookie_jar() -> typing.Optional[LWPCookieJar]:
"""
Get cookies from a persistent osc cookiejar
.. versionadded:: 0.8.0
"""
if _conf is not None:
path = _conf._identify_osccookiejar()
if os.path.isfile(path):
return LWPCookieJar(filename=path)

path_suffix = Path("osc", "cookiejar")
paths = [Path(os.getenv("XDG_STATE_HOME", "/tmp")).joinpath(path_suffix),
Path.home().joinpath(".local", "state").joinpath(path_suffix)]

for path in paths:
if path.is_file():
return LWPCookieJar(filename=str(path)) # compatibility for Python < 3.8

return None
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ def _filter(requires):

setup(
name='osc-tiny',
version='0.7.12',
version='0.8.0',
description='Client API for openSUSE BuildService',
long_description=long_description,
long_description_content_type="text/markdown",
author='Andreas Hasenkopf',
author_email='[email protected]',
url='http://github.com/crazyscientist/osc-tiny',
download_url='http://github.com/crazyscientist/osc-tiny/tarball/master',
maintainer='SUSE Maintenance Automation Engineering team',
maintainer_email='[email protected]',
url='https://github.com/SUSE/osc-tiny',
packages=find_packages(),
license='MIT',
install_requires=get_requires(),
Expand All @@ -49,5 +50,6 @@ def _filter(requires):
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
)

0 comments on commit 6e7f135

Please sign in to comment.