Coverage for src/gitlabracadabra/containers/registry_session.py: 98%
31 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 22:55 +0200
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 22:55 +0200
1#
2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
17from __future__ import annotations
19from typing import TYPE_CHECKING
21from gitlabracadabra.containers.authenticated_session import AuthenticatedSession
22from gitlabracadabra.containers.const import DOCKER_HOSTNAME, DOCKER_REGISTRY
24if TYPE_CHECKING:
25 from collections.abc import Callable
27 from requests import Response
28 from requests.auth import AuthBase
30 from gitlabracadabra.containers.authenticated_session import Data, Params
31 from gitlabracadabra.containers.scope import Scope
34class RegistrySession:
35 """Container registry HTTP methods."""
37 def __init__(
38 self,
39 hostname: str,
40 session_callback: Callable[[AuthenticatedSession], None] | None = None,
41 ) -> None:
42 """Instantiate a registry connection.
44 Args:
45 hostname: fqdn of a registry.
46 session_callback: Callback applied to requests Session.
47 """
48 self._session = AuthenticatedSession()
49 self._hostname = hostname
50 if hostname == DOCKER_HOSTNAME:
51 self._session.connection_hostname = DOCKER_REGISTRY
52 else:
53 self._session.connection_hostname = hostname
54 if session_callback is not None:
55 session_callback(self._session)
56 # Cache where blobs are present
57 # dict key is digest, value is a list of manifest names
58 # Used in WithBlobs
59 self._blobs: dict[str, list[str]] = {}
60 self._sizes: dict[str, int] = {}
62 def __del__(self) -> None:
63 """Destroy a registry connection."""
64 self._session.close()
66 @property
67 def hostname(self) -> str:
68 """Get hostname.
70 Returns:
71 The registry hostname.
72 """
73 return self._hostname
75 def request(
76 self,
77 method: str,
78 url: str,
79 *,
80 scopes: set[Scope] | None = None,
81 params: Params = None,
82 data: Data | None = None,
83 headers: dict[str, str] | None = None,
84 content_type: str | None = None,
85 accept: tuple[str, ...] | None = None,
86 auth: AuthBase | None = None,
87 stream: bool | None = None,
88 raise_for_status: bool = True,
89 ) -> Response:
90 """Send an HTTP request.
92 Args:
93 method: HTTP method.
94 url: Either a path or a full url.
95 scopes: An optional set of scopes.
96 params: query string params.
97 data: Request body stream.
98 headers: Request headers.
99 content_type: Uploaded MIME type.
100 accept: An optional list of accepted mime-types.
101 auth: HTTPBasicAuth.
102 stream: Stream the response.
103 raise_for_status: Raises `requests.HTTPError`, if one occurred.
105 Returns:
106 A Response.
107 """
108 headers = headers.copy() if headers else {}
109 if accept:
110 headers["Accept"] = ", ".join(accept)
111 if content_type:
112 headers["Content-Type"] = content_type
114 self._session.connect(scopes)
115 response = self._session.authenticated_request(
116 method,
117 url,
118 params=params,
119 data=data,
120 headers=headers,
121 auth=auth,
122 stream=stream,
123 )
124 if raise_for_status: 124 ↛ 126line 124 didn't jump to line 126 because the condition on line 124 was always true
125 response.raise_for_status()
126 return response