python-gitlab 4.10.0__py3-none-any.whl → 4.11.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gitlab/__init__.py CHANGED
@@ -27,7 +27,7 @@ from gitlab._version import ( # noqa: F401
27
27
  __title__,
28
28
  __version__,
29
29
  )
30
- from gitlab.client import Gitlab, GitlabList # noqa: F401
30
+ from gitlab.client import Gitlab, GitlabList, GraphQL # noqa: F401
31
31
  from gitlab.exceptions import * # noqa: F401,F403
32
32
 
33
33
  warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab")
@@ -42,5 +42,6 @@ __all__ = [
42
42
  "__version__",
43
43
  "Gitlab",
44
44
  "GitlabList",
45
+ "GraphQL",
45
46
  ]
46
47
  __all__.extend(gitlab.exceptions.__all__)
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+
3
+ import httpx
4
+ from gql.transport.httpx import HTTPXTransport
5
+
6
+
7
+ class GitlabTransport(HTTPXTransport):
8
+ """A gql httpx transport that reuses an existing httpx.Client.
9
+ By default, gql's transports do not have a keep-alive session
10
+ and do not enable providing your own session that's kept open.
11
+ This transport lets us provide and close our session on our own
12
+ and provide additional auth.
13
+ For details, see https://github.com/graphql-python/gql/issues/91.
14
+ """
15
+
16
+ def __init__(self, *args: Any, client: httpx.Client, **kwargs: Any):
17
+ super().__init__(*args, **kwargs)
18
+ self.client = client
19
+
20
+ def connect(self) -> None:
21
+ pass
22
+
23
+ def close(self) -> None:
24
+ pass
gitlab/_version.py CHANGED
@@ -3,4 +3,4 @@ __copyright__ = "Copyright 2013-2019 Gauvain Pocentek, 2019-2023 python-gitlab t
3
3
  __email__ = "gauvainpocentek@gmail.com"
4
4
  __license__ = "LGPL3"
5
5
  __title__ = "python-gitlab"
6
- __version__ = "4.10.0"
6
+ __version__ = "4.11.1"
gitlab/client.py CHANGED
@@ -1,8 +1,9 @@
1
1
  """Wrapper for the GitLab API."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  import re
5
- import time
6
7
  from typing import (
7
8
  Any,
8
9
  BinaryIO,
@@ -25,6 +26,19 @@ import gitlab.const
25
26
  import gitlab.exceptions
26
27
  from gitlab import _backends, utils
27
28
 
29
+ try:
30
+ import gql
31
+ import gql.transport.exceptions
32
+ import graphql
33
+ import httpx
34
+
35
+ from ._backends.graphql import GitlabTransport
36
+
37
+ _GQL_INSTALLED = True
38
+ except ImportError: # pragma: no cover
39
+ _GQL_INSTALLED = False
40
+
41
+
28
42
  REDIRECT_MSG = (
29
43
  "python-gitlab detected a {status_code} ({reason!r}) redirection. You must update "
30
44
  "your GitLab URL to the correct URL to avoid issues. The redirection was from: "
@@ -89,7 +103,7 @@ class Gitlab:
89
103
  self._api_version = str(api_version)
90
104
  self._server_version: Optional[str] = None
91
105
  self._server_revision: Optional[str] = None
92
- self._base_url = self._get_base_url(url)
106
+ self._base_url = utils.get_base_url(url)
93
107
  self._url = f"{self._base_url}/api/v{api_version}"
94
108
  #: Timeout to use for requests to gitlab server
95
109
  self.timeout = timeout
@@ -557,18 +571,6 @@ class Gitlab:
557
571
  "verify": self.ssl_verify,
558
572
  }
559
573
 
560
- @staticmethod
561
- def _get_base_url(url: Optional[str] = None) -> str:
562
- """Return the base URL with the trailing slash stripped.
563
- If the URL is a Falsy value, return the default URL.
564
- Returns:
565
- The base URL
566
- """
567
- if not url:
568
- return gitlab.const.DEFAULT_URL
569
-
570
- return url.rstrip("/")
571
-
572
574
  def _build_url(self, path: str) -> str:
573
575
  """Returns the full url from path.
574
576
 
@@ -718,7 +720,12 @@ class Gitlab:
718
720
  send_data = self._backend.prepare_send_data(files, post_data, raw)
719
721
  opts["headers"]["Content-type"] = send_data.content_type
720
722
 
721
- cur_retries = 0
723
+ retry = utils.Retry(
724
+ max_retries=max_retries,
725
+ obey_rate_limit=obey_rate_limit,
726
+ retry_transient_errors=retry_transient_errors,
727
+ )
728
+
722
729
  while True:
723
730
  try:
724
731
  result = self._backend.http_request(
@@ -733,14 +740,8 @@ class Gitlab:
733
740
  **opts,
734
741
  )
735
742
  except (requests.ConnectionError, requests.exceptions.ChunkedEncodingError):
736
- if retry_transient_errors and (
737
- max_retries == -1 or cur_retries < max_retries
738
- ):
739
- wait_time = 2**cur_retries * 0.1
740
- cur_retries += 1
741
- time.sleep(wait_time)
743
+ if retry.handle_retry():
742
744
  continue
743
-
744
745
  raise
745
746
 
746
747
  self._check_redirects(result.response)
@@ -748,31 +749,10 @@ class Gitlab:
748
749
  if 200 <= result.status_code < 300:
749
750
  return result.response
750
751
 
751
- def should_retry() -> bool:
752
- if result.status_code == 429 and obey_rate_limit:
753
- return True
754
-
755
- if not retry_transient_errors:
756
- return False
757
- if result.status_code in gitlab.const.RETRYABLE_TRANSIENT_ERROR_CODES:
758
- return True
759
- if result.status_code == 409 and "Resource lock" in result.reason:
760
- return True
761
-
762
- return False
763
-
764
- if should_retry():
765
- # Response headers documentation:
766
- # https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers
767
- if max_retries == -1 or cur_retries < max_retries:
768
- wait_time = 2**cur_retries * 0.1
769
- if "Retry-After" in result.headers:
770
- wait_time = int(result.headers["Retry-After"])
771
- elif "RateLimit-Reset" in result.headers:
772
- wait_time = int(result.headers["RateLimit-Reset"]) - time.time()
773
- cur_retries += 1
774
- time.sleep(wait_time)
775
- continue
752
+ if retry.handle_retry_on_status(
753
+ result.status_code, result.headers, result.reason
754
+ ):
755
+ continue
776
756
 
777
757
  error_message = result.content
778
758
  try:
@@ -1296,3 +1276,94 @@ class GitlabList:
1296
1276
  return self.next()
1297
1277
 
1298
1278
  raise StopIteration
1279
+
1280
+
1281
+ class GraphQL:
1282
+ def __init__(
1283
+ self,
1284
+ url: Optional[str] = None,
1285
+ *,
1286
+ token: Optional[str] = None,
1287
+ ssl_verify: Union[bool, str] = True,
1288
+ client: Optional[httpx.Client] = None,
1289
+ timeout: Optional[float] = None,
1290
+ user_agent: str = gitlab.const.USER_AGENT,
1291
+ fetch_schema_from_transport: bool = False,
1292
+ max_retries: int = 10,
1293
+ obey_rate_limit: bool = True,
1294
+ retry_transient_errors: bool = False,
1295
+ ) -> None:
1296
+ if not _GQL_INSTALLED:
1297
+ raise ImportError(
1298
+ "The GraphQL client could not be initialized because "
1299
+ "the gql dependencies are not installed. "
1300
+ "Install them with 'pip install python-gitlab[graphql]'"
1301
+ )
1302
+ self._base_url = utils.get_base_url(url)
1303
+ self._timeout = timeout
1304
+ self._token = token
1305
+ self._url = f"{self._base_url}/api/graphql"
1306
+ self._user_agent = user_agent
1307
+ self._ssl_verify = ssl_verify
1308
+ self._max_retries = max_retries
1309
+ self._obey_rate_limit = obey_rate_limit
1310
+ self._retry_transient_errors = retry_transient_errors
1311
+
1312
+ opts = self._get_client_opts()
1313
+ self._http_client = client or httpx.Client(**opts)
1314
+ self._transport = GitlabTransport(self._url, client=self._http_client)
1315
+ self._client = gql.Client(
1316
+ transport=self._transport,
1317
+ fetch_schema_from_transport=fetch_schema_from_transport,
1318
+ )
1319
+ self._gql = gql.gql
1320
+
1321
+ def __enter__(self) -> "GraphQL":
1322
+ return self
1323
+
1324
+ def __exit__(self, *args: Any) -> None:
1325
+ self._http_client.close()
1326
+
1327
+ def _get_client_opts(self) -> Dict[str, Any]:
1328
+ headers = {"User-Agent": self._user_agent}
1329
+
1330
+ if self._token:
1331
+ headers["Authorization"] = f"Bearer {self._token}"
1332
+
1333
+ return {
1334
+ "headers": headers,
1335
+ "timeout": self._timeout,
1336
+ "verify": self._ssl_verify,
1337
+ }
1338
+
1339
+ def execute(
1340
+ self, request: Union[str, graphql.Source], *args: Any, **kwargs: Any
1341
+ ) -> Any:
1342
+ parsed_document = self._gql(request)
1343
+ retry = utils.Retry(
1344
+ max_retries=self._max_retries,
1345
+ obey_rate_limit=self._obey_rate_limit,
1346
+ retry_transient_errors=self._retry_transient_errors,
1347
+ )
1348
+
1349
+ while True:
1350
+ try:
1351
+ result = self._client.execute(parsed_document, *args, **kwargs)
1352
+ except gql.transport.exceptions.TransportServerError as e:
1353
+ if retry.handle_retry_on_status(
1354
+ status_code=e.code, headers=self._transport.response_headers
1355
+ ):
1356
+ continue
1357
+
1358
+ if e.code == 401:
1359
+ raise gitlab.exceptions.GitlabAuthenticationError(
1360
+ response_code=e.code,
1361
+ error_message=str(e),
1362
+ )
1363
+
1364
+ raise gitlab.exceptions.GitlabHttpError(
1365
+ response_code=e.code,
1366
+ error_message=str(e),
1367
+ )
1368
+
1369
+ return result
gitlab/utils.py CHANGED
@@ -2,14 +2,26 @@ import dataclasses
2
2
  import email.message
3
3
  import logging
4
4
  import pathlib
5
+ import time
5
6
  import traceback
6
7
  import urllib.parse
7
8
  import warnings
8
- from typing import Any, Callable, Dict, Iterator, Literal, Optional, Tuple, Type, Union
9
+ from typing import (
10
+ Any,
11
+ Callable,
12
+ Dict,
13
+ Iterator,
14
+ Literal,
15
+ MutableMapping,
16
+ Optional,
17
+ Tuple,
18
+ Type,
19
+ Union,
20
+ )
9
21
 
10
22
  import requests
11
23
 
12
- from gitlab import types
24
+ from gitlab import const, types
13
25
 
14
26
 
15
27
  class _StdoutStream:
@@ -17,6 +29,18 @@ class _StdoutStream:
17
29
  print(chunk)
18
30
 
19
31
 
32
+ def get_base_url(url: Optional[str] = None) -> str:
33
+ """Return the base URL with the trailing slash stripped.
34
+ If the URL is a Falsy value, return the default URL.
35
+ Returns:
36
+ The base URL
37
+ """
38
+ if not url:
39
+ return const.DEFAULT_URL
40
+
41
+ return url.rstrip("/")
42
+
43
+
20
44
  def get_content_type(content_type: Optional[str]) -> str:
21
45
  message = email.message.Message()
22
46
  if content_type is not None:
@@ -73,6 +97,71 @@ def response_content(
73
97
  return None
74
98
 
75
99
 
100
+ class Retry:
101
+ def __init__(
102
+ self,
103
+ max_retries: int,
104
+ obey_rate_limit: Optional[bool] = True,
105
+ retry_transient_errors: Optional[bool] = False,
106
+ ) -> None:
107
+ self.cur_retries = 0
108
+ self.max_retries = max_retries
109
+ self.obey_rate_limit = obey_rate_limit
110
+ self.retry_transient_errors = retry_transient_errors
111
+
112
+ def _retryable_status_code(
113
+ self, status_code: Optional[int], reason: str = ""
114
+ ) -> bool:
115
+ if status_code == 429 and self.obey_rate_limit:
116
+ return True
117
+
118
+ if not self.retry_transient_errors:
119
+ return False
120
+ if status_code in const.RETRYABLE_TRANSIENT_ERROR_CODES:
121
+ return True
122
+ if status_code == 409 and "Resource lock" in reason:
123
+ return True
124
+
125
+ return False
126
+
127
+ def handle_retry_on_status(
128
+ self,
129
+ status_code: Optional[int],
130
+ headers: Optional[MutableMapping[str, str]] = None,
131
+ reason: str = "",
132
+ ) -> bool:
133
+ if not self._retryable_status_code(status_code, reason):
134
+ return False
135
+
136
+ if headers is None:
137
+ headers = {}
138
+
139
+ # Response headers documentation:
140
+ # https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers
141
+ if self.max_retries == -1 or self.cur_retries < self.max_retries:
142
+ wait_time = 2**self.cur_retries * 0.1
143
+ if "Retry-After" in headers:
144
+ wait_time = int(headers["Retry-After"])
145
+ elif "RateLimit-Reset" in headers:
146
+ wait_time = int(headers["RateLimit-Reset"]) - time.time()
147
+ self.cur_retries += 1
148
+ time.sleep(wait_time)
149
+ return True
150
+
151
+ return False
152
+
153
+ def handle_retry(self) -> bool:
154
+ if self.retry_transient_errors and (
155
+ self.max_retries == -1 or self.cur_retries < self.max_retries
156
+ ):
157
+ wait_time = 2**self.cur_retries * 0.1
158
+ self.cur_retries += 1
159
+ time.sleep(wait_time)
160
+ return True
161
+
162
+ return False
163
+
164
+
76
165
  def _transform_types(
77
166
  data: Dict[str, Any],
78
167
  custom_types: Dict[str, Any],
@@ -37,8 +37,9 @@ class GroupMemberManager(CRUDMixin, RESTManager):
37
37
  _obj_cls = GroupMember
38
38
  _from_parent_attrs = {"group_id": "id"}
39
39
  _create_attrs = RequiredOptional(
40
- required=("access_level", "user_id"),
40
+ required=("access_level",),
41
41
  optional=("expires_at", "tasks_to_be_done"),
42
+ exclusive=("username", "user_id"),
42
43
  )
43
44
  _update_attrs = RequiredOptional(
44
45
  required=("access_level",), optional=("expires_at",)
@@ -101,8 +102,9 @@ class ProjectMemberManager(CRUDMixin, RESTManager):
101
102
  _obj_cls = ProjectMember
102
103
  _from_parent_attrs = {"project_id": "id"}
103
104
  _create_attrs = RequiredOptional(
104
- required=("access_level", "user_id"),
105
+ required=("access_level",),
105
106
  optional=("expires_at", "tasks_to_be_done"),
107
+ exclusive=("username", "user_id"),
106
108
  )
107
109
  _update_attrs = RequiredOptional(
108
110
  required=("access_level",), optional=("expires_at",)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-gitlab
3
- Version: 4.10.0
3
+ Version: 4.11.1
4
4
  Summary: A python wrapper for the GitLab API
5
5
  Author-email: Gauvain Pocentek <gauvain@pocentek.net>
6
6
  Maintainer-email: John Villalovos <john@sodarock.com>, Max Wittig <max.wittig@siemens.com>, Nejc Habjan <nejc.habjan@siemens.com>, Roger Meier <r.meier@siemens.com>
@@ -32,6 +32,8 @@ Requires-Dist: requests >=2.32.0
32
32
  Requires-Dist: requests-toolbelt >=1.0.0
33
33
  Provides-Extra: autocompletion
34
34
  Requires-Dist: argcomplete <3,>=1.10.0 ; extra == 'autocompletion'
35
+ Provides-Extra: graphql
36
+ Requires-Dist: gql[httpx] <4,>=3.5.0 ; extra == 'graphql'
35
37
  Provides-Extra: yaml
36
38
  Requires-Dist: PyYaml >=6.0.1 ; extra == 'yaml'
37
39
 
@@ -1,17 +1,18 @@
1
- gitlab/__init__.py,sha256=bd8BSLyUUjtHMKtzmf-T5855W6FUHcuhIwx2hNu0w2o,1382
1
+ gitlab/__init__.py,sha256=DlY_IEbIbeTJnMfkcl4866XbJ_50UiaE4yI0PwOq36E,1406
2
2
  gitlab/__main__.py,sha256=HTesNl0UAU6mPb9EXWkTKMy6Q6pAUxGi3iPnDHTE2uE,68
3
- gitlab/_version.py,sha256=C2if8HKzMGs_l0KkoB0yUyEdcIgWXwepCAiMYKCptLo,250
3
+ gitlab/_version.py,sha256=3A0MvnWrTCInQYk_lrq1rViL2qGa7xznDd8PugfxA9Y,250
4
4
  gitlab/base.py,sha256=5cotawlHD01Vw88aN4o7wNIhDyk_bmcwubX4mbOpnVo,13780
5
5
  gitlab/cli.py,sha256=d3-LtZuA1Fgon5wZWn4c3E70fTIu4mM4Juyhh3F8EBs,12416
6
- gitlab/client.py,sha256=fPezDHNi4kJxzGxGeDWOWsKmKy76wVR4-fteCgDrY4I,49296
6
+ gitlab/client.py,sha256=WK13AV69dJg-5dWIcYV51_ylExuZolrCGXSLHT1vDkU,51217
7
7
  gitlab/config.py,sha256=T1DgUXD0-MN2qNszrv-SO5d4uy0FITnNN0vWJgOt2yo,9088
8
8
  gitlab/const.py,sha256=rtPU-fxVSOvgpueoQVTvZGQp6iAZ-aa3nsY4RcSs_M4,5352
9
9
  gitlab/exceptions.py,sha256=VOQftPzEq5mpVj6vke7z6Xe4S7Yf_rDTab0lNHqf3AY,8390
10
10
  gitlab/mixins.py,sha256=7iPlzqGmd5Ew2RLzRzRWsJ4r8Bn6wteUj791BJrjtXc,36645
11
11
  gitlab/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  gitlab/types.py,sha256=lepiiI_YOr94B4koqIHuY70tszZC_X3YW4lDvbadbI8,3312
13
- gitlab/utils.py,sha256=9WwqvrpYkKZCXFlLZmbEiVvbGWv3ubmi05HiABhwGSA,6569
13
+ gitlab/utils.py,sha256=3OngV45Gb4UO2GR-6-kXax1Ghdw6bpyRdUC2cHpyCSw,9027
14
14
  gitlab/_backends/__init__.py,sha256=WalQZRIDzw19FuNxraG7fvck6ddg4cdNd3bi53QKvZM,392
15
+ gitlab/_backends/graphql.py,sha256=SiGEfqqBqRey_EhozhF1pWFO81c_VVKdM50XvCQ4PZc,737
15
16
  gitlab/_backends/protocol.py,sha256=m5qSz1o3i0H4XJCWnqx0wIFilOIU9cKxzFsYxLL6Big,842
16
17
  gitlab/_backends/requests_backend.py,sha256=CrSDTfkvi17dT4kTU8R3qQFBNCPJqEfBJq4gJ2GXleA,5534
17
18
  gitlab/v4/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -58,7 +59,7 @@ gitlab/v4/objects/jobs.py,sha256=g7l5dA6-99tyLDoohjJ_xZvGyMbeytn4L9T-h78NQaE,914
58
59
  gitlab/v4/objects/keys.py,sha256=IclYGSzcVEZPIhDdIz-p16rvb68FnBTgAf1cWCXWjkY,882
59
60
  gitlab/v4/objects/labels.py,sha256=JvOciJ6V76pF9HuJp5OT_Ykq8oqaa6ItxvpKf3hiEzs,4736
60
61
  gitlab/v4/objects/ldap.py,sha256=adpkdfk7VBjshuh8SpCsc77Pax4QgqCx1N12CuzitDE,1662
61
- gitlab/v4/objects/members.py,sha256=knzhMYLqaKWAUbTX4QAowMmtMirU2Kizt85zZRcpgmA,3836
62
+ gitlab/v4/objects/members.py,sha256=YJO9MaqlCSUnozHIlI7MfSlcWTju4xRmW8QIlEiBmok,3902
62
63
  gitlab/v4/objects/merge_request_approvals.py,sha256=oPZFd4AUtrAVhBTa0iM4krNkk2UTNOTw_MWlEWo2HAQ,6400
63
64
  gitlab/v4/objects/merge_requests.py,sha256=tpFCMmTVWyL9X7HtUoZuHJP4MVZUz1kk9-Bv-SbnwfU,17422
64
65
  gitlab/v4/objects/merge_trains.py,sha256=e0Gp2Ri75elcG_r9w8qxdrcWW_YiebPRwUYIH5od8kc,422
@@ -94,10 +95,10 @@ gitlab/v4/objects/triggers.py,sha256=UAERq_C-QdPBbBQPHLh5IfhpkdDeIxdnVGPHfu9Qy5Y
94
95
  gitlab/v4/objects/users.py,sha256=_gGrTwcE17jeoXIPgfFSv54jtF1_9C1R0Y0hhssTvXY,21381
95
96
  gitlab/v4/objects/variables.py,sha256=S0Vz32jEpUbo4J2js8gMPPTVpcy1ge5FYVHLiPz9c-A,2627
96
97
  gitlab/v4/objects/wikis.py,sha256=JtI1cQqZV1_PRfKVlQRMh4LZjdxEfi9T2VuFYv6PrV8,1775
97
- python_gitlab-4.10.0.dist-info/AUTHORS,sha256=Z0P61GJSVnp7iFbRcMezhx3f4zMyPkVmG--TWaRo768,526
98
- python_gitlab-4.10.0.dist-info/COPYING,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651
99
- python_gitlab-4.10.0.dist-info/METADATA,sha256=11-PatLY4p9_VyEBKOAU4MWJcYVPn3YjnifSnxeLZlg,8229
100
- python_gitlab-4.10.0.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
101
- python_gitlab-4.10.0.dist-info/entry_points.txt,sha256=nhpKLLP_uQPFByn8UtE9zsvQQwa402t52o_Cw9IFXMo,43
102
- python_gitlab-4.10.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
103
- python_gitlab-4.10.0.dist-info/RECORD,,
98
+ python_gitlab-4.11.1.dist-info/AUTHORS,sha256=Z0P61GJSVnp7iFbRcMezhx3f4zMyPkVmG--TWaRo768,526
99
+ python_gitlab-4.11.1.dist-info/COPYING,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651
100
+ python_gitlab-4.11.1.dist-info/METADATA,sha256=wCr5WsVuHR7d3SeMIdAu3HuzRXXjniEYb8LoBJGxyoI,8311
101
+ python_gitlab-4.11.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
102
+ python_gitlab-4.11.1.dist-info/entry_points.txt,sha256=nhpKLLP_uQPFByn8UtE9zsvQQwa402t52o_Cw9IFXMo,43
103
+ python_gitlab-4.11.1.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
104
+ python_gitlab-4.11.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5