qase-python-commons 5.1.4__tar.gz → 5.1.5__tar.gz

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.
Files changed (53) hide show
  1. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/PKG-INFO +7 -1
  2. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/README.md +6 -0
  3. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/pyproject.toml +1 -1
  4. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/client/api_v1_client.py +59 -16
  5. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase_python_commons.egg-info/PKG-INFO +7 -1
  6. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/setup.cfg +0 -0
  7. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/__init__.py +0 -0
  8. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/__init__.py +0 -0
  9. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/client/api_v2_client.py +0 -0
  10. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/client/base_api_client.py +0 -0
  11. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/config.py +0 -0
  12. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/exceptions/reporter.py +0 -0
  13. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/loader.py +0 -0
  14. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/logger.py +0 -0
  15. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/__init__.py +0 -0
  16. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/attachment.py +0 -0
  17. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/basemodel.py +0 -0
  18. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/api.py +0 -0
  19. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/batch.py +0 -0
  20. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/connection.py +0 -0
  21. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/framework.py +0 -0
  22. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/plan.py +0 -0
  23. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/qaseconfig.py +0 -0
  24. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/report.py +0 -0
  25. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/run.py +0 -0
  26. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/config/testops.py +0 -0
  27. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/external_link.py +0 -0
  28. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/relation.py +0 -0
  29. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/result.py +0 -0
  30. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/run.py +0 -0
  31. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/runtime.py +0 -0
  32. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/models/step.py +0 -0
  33. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/profilers/__init__.py +0 -0
  34. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/profilers/db.py +0 -0
  35. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/profilers/network.py +0 -0
  36. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/profilers/sleep.py +0 -0
  37. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/reporters/__init__.py +0 -0
  38. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/reporters/core.py +0 -0
  39. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/reporters/report.py +0 -0
  40. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/reporters/testops.py +0 -0
  41. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/reporters/testops_multi.py +0 -0
  42. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/retry.py +0 -0
  43. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/status_mapping/__init__.py +0 -0
  44. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/status_mapping/status_mapping.py +0 -0
  45. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/util/__init__.py +0 -0
  46. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/util/host_data.py +0 -0
  47. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/util/token_masker.py +0 -0
  48. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/utils.py +0 -0
  49. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase/commons/validators/base.py +0 -0
  50. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase_python_commons.egg-info/SOURCES.txt +0 -0
  51. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase_python_commons.egg-info/dependency_links.txt +0 -0
  52. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase_python_commons.egg-info/requires.txt +0 -0
  53. {qase_python_commons-5.1.4 → qase_python_commons-5.1.5}/src/qase_python_commons.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qase-python-commons
3
- Version: 5.1.4
3
+ Version: 5.1.5
4
4
  Summary: A library for Qase TestOps and Qase Report
5
5
  Author-email: Qase Team <support@qase.io>
6
6
  Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/main/qase-python-commons
@@ -435,6 +435,12 @@ When a 429 carries a `Retry-After` header, that value replaces the computed
435
435
  backoff. Qase sends roughly 60 seconds, so a run that hits the rate limit takes
436
436
  longer to finish rather than losing the batch.
437
437
 
438
+ The same timeout and retry settings apply to attachment uploads, which go
439
+ through the API v1 client in batches of up to 20 files. **If an attachment batch
440
+ cannot be uploaded after all attempts**, the reporter logs an error, drops those
441
+ attachments and still submits the results they belong to — a failed attachment
442
+ never costs you the test results.
443
+
438
444
  **If a batch cannot be delivered after all attempts**, the reporter logs an
439
445
  error naming how many results were lost and **does not mark the run complete**.
440
446
  An open run is the signal that its data is incomplete; a completed run over
@@ -400,6 +400,12 @@ When a 429 carries a `Retry-After` header, that value replaces the computed
400
400
  backoff. Qase sends roughly 60 seconds, so a run that hits the rate limit takes
401
401
  longer to finish rather than losing the batch.
402
402
 
403
+ The same timeout and retry settings apply to attachment uploads, which go
404
+ through the API v1 client in batches of up to 20 files. **If an attachment batch
405
+ cannot be uploaded after all attempts**, the reporter logs an error, drops those
406
+ attachments and still submits the results they belong to — a failed attachment
407
+ never costs you the test results.
408
+
403
409
  **If a batch cannot be delivered after all attempts**, the reporter logs an
404
410
  error naming how many results were lost and **does not mark the run complete**.
405
411
  An open run is the signal that its data is incomplete; a completed run over
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qase-python-commons"
7
- version = "5.1.4"
7
+ version = "5.1.5"
8
8
  description = "A library for Qase TestOps and Qase Report"
9
9
  readme = "README.md"
10
10
  authors = [{name = "Qase Team", email = "support@qase.io"}]
@@ -8,6 +8,7 @@ from qase.api_client_v1.models.attachmentupload import Attachmentupload
8
8
  from qase.api_client_v1.configuration import Configuration
9
9
  from .. import Logger
10
10
  from .base_api_client import BaseApiClient
11
+ from ..retry import send_with_retry
11
12
  from ..exceptions.reporter import ReporterException
12
13
  from ..models import Attachment
13
14
  from ..models.config.framework import Video, Trace
@@ -46,7 +47,9 @@ class ApiV1Client(BaseApiClient):
46
47
  def get_project(self, project_code: str) -> Union[Project, None]:
47
48
  try:
48
49
  self.logger.log_debug(f"Getting project {project_code}")
49
- response = ProjectsApi(self.client).get_project(code=project_code)
50
+ response = ProjectsApi(self.client).get_project(
51
+ code=project_code, _request_timeout=self.config.testops.api.timeout
52
+ )
50
53
  if hasattr(response, 'result'):
51
54
  self.logger.log_debug(f"Project {project_code} found: {response.result.to_json()}")
52
55
  return response.result
@@ -59,7 +62,9 @@ class ApiV1Client(BaseApiClient):
59
62
  try:
60
63
  self.logger.log_debug(f"Getting environment {environment}")
61
64
  api_instance = EnvironmentsApi(self.client)
62
- response = api_instance.get_environments(code=project_code)
65
+ response = api_instance.get_environments(
66
+ code=project_code, _request_timeout=self.config.testops.api.timeout
67
+ )
63
68
  if hasattr(response, 'result') and hasattr(response.result, 'entities'):
64
69
  for env in response.result.entities:
65
70
  if env.slug == environment:
@@ -76,7 +81,9 @@ class ApiV1Client(BaseApiClient):
76
81
  try:
77
82
  self.logger.log_debug(f"Getting configurations for project {project_code}")
78
83
  api_instance = ConfigurationsApi(self.client)
79
- response = api_instance.get_configurations(code=project_code)
84
+ response = api_instance.get_configurations(
85
+ code=project_code, _request_timeout=self.config.testops.api.timeout
86
+ )
80
87
  if hasattr(response, 'result') and hasattr(response.result, 'entities'):
81
88
  return response.result.entities
82
89
  return []
@@ -117,8 +124,9 @@ class ApiV1Client(BaseApiClient):
117
124
  # Create new group
118
125
  group_create = ConfigurationGroupCreate(title=config_value.name)
119
126
  group_response = ConfigurationsApi(self.client).create_configuration_group(
120
- code=project_code,
121
- configuration_group_create=group_create
127
+ code=project_code,
128
+ configuration_group_create=group_create,
129
+ _request_timeout=self.config.testops.api.timeout
122
130
  )
123
131
  group_id = group_response.result.id
124
132
 
@@ -129,7 +137,8 @@ class ApiV1Client(BaseApiClient):
129
137
  )
130
138
  config_response = ConfigurationsApi(self.client).create_configuration(
131
139
  code=project_code,
132
- configuration_create=config_create
140
+ configuration_create=config_create,
141
+ _request_timeout=self.config.testops.api.timeout
133
142
  )
134
143
  config_id = config_response.result.id
135
144
  return config_id
@@ -141,12 +150,16 @@ class ApiV1Client(BaseApiClient):
141
150
  def complete_run(self, project_code: str, run_id: int) -> None:
142
151
  api_runs = RunsApi(self.client)
143
152
  self.logger.log_debug(f"Completing run {run_id}")
144
- res = api_runs.get_run(project_code, run_id).result
153
+ res = api_runs.get_run(
154
+ project_code, run_id, _request_timeout=self.config.testops.api.timeout
155
+ ).result
145
156
  if res.status == 1:
146
157
  self.logger.log_debug(f"Run {run_id} already completed")
147
158
  return
148
159
  try:
149
- api_runs.complete_run(project_code, run_id)
160
+ api_runs.complete_run(
161
+ project_code, run_id, _request_timeout=self.config.testops.api.timeout
162
+ )
150
163
  self.logger.log(f"Test run link: {self.web}/run/{project_code}/dashboard/{run_id}", "info")
151
164
  except Exception as e:
152
165
  self.logger.log(f"Error at completing run {run_id}: {e}", "error")
@@ -240,10 +253,33 @@ class ApiV1Client(BaseApiClient):
240
253
 
241
254
  # Prepare files for upload
242
255
  files_for_upload = [att.get_for_upload() for att in batch]
243
-
244
- # Upload batch
245
- response = attach_api.upload_attachment(project_code, file=files_for_upload)
246
-
256
+
257
+ # The timeout is what keeps a stalled connection from blocking
258
+ # the reporter thread forever: without it urllib3 waits
259
+ # indefinitely, no exception is ever raised, and the retry below
260
+ # never gets a chance to run.
261
+ #
262
+ # Unlike results, an attachment carries no idempotency key, so a
263
+ # retry whose first response was lost can leave an unreferenced
264
+ # file behind. That is cheaper than the alternative: the whole
265
+ # batch of results these attachments belong to going nowhere.
266
+ response = None
267
+
268
+ def upload_batch():
269
+ nonlocal response
270
+ response = attach_api.upload_attachment(
271
+ project_code,
272
+ file=files_for_upload,
273
+ _request_timeout=self.config.testops.api.timeout
274
+ )
275
+
276
+ send_with_retry(
277
+ upload_batch,
278
+ attempts=self.config.testops.api.retries,
279
+ backoff=self.config.testops.api.retry_backoff,
280
+ logger=self.logger,
281
+ )
282
+
247
283
  if response.result:
248
284
  all_uploaded.extend(response.result)
249
285
  self.logger.log_debug(
@@ -294,7 +330,8 @@ class ApiV1Client(BaseApiClient):
294
330
  try:
295
331
  result = RunsApi(self.client).create_run(
296
332
  code=project_code,
297
- run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None})
333
+ run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None}),
334
+ _request_timeout=self.config.testops.api.timeout
298
335
  )
299
336
 
300
337
  run_id = result.result.id
@@ -311,7 +348,9 @@ class ApiV1Client(BaseApiClient):
311
348
 
312
349
  def check_test_run(self, project_code: str, run_id: int) -> bool:
313
350
  api_runs = RunsApi(self.client)
314
- run = api_runs.get_run(code=project_code, id=run_id)
351
+ run = api_runs.get_run(
352
+ code=project_code, id=run_id, _request_timeout=self.config.testops.api.timeout
353
+ )
315
354
  if run.result.id:
316
355
  return True
317
356
  return False
@@ -332,7 +371,10 @@ class ApiV1Client(BaseApiClient):
332
371
  run_public = RunPublic(status=True)
333
372
 
334
373
  # Call the API to enable public report
335
- response = api_runs.update_run_publicity(project_code, run_id, run_public)
374
+ response = api_runs.update_run_publicity(
375
+ project_code, run_id, run_public,
376
+ _request_timeout=self.config.testops.api.timeout
377
+ )
336
378
 
337
379
  # Extract the public URL from response
338
380
  if response.result and response.result.url:
@@ -368,7 +410,8 @@ class ApiV1Client(BaseApiClient):
368
410
 
369
411
  RunsApi(self.client).run_update_external_issue(
370
412
  code=project_code,
371
- runexternal_issues=run_external_issues
413
+ runexternal_issues=run_external_issues,
414
+ _request_timeout=self.config.testops.api.timeout
372
415
  )
373
416
 
374
417
  self.logger.log(f"External link updated for run {run_id}: {external_link.link}", "debug")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qase-python-commons
3
- Version: 5.1.4
3
+ Version: 5.1.5
4
4
  Summary: A library for Qase TestOps and Qase Report
5
5
  Author-email: Qase Team <support@qase.io>
6
6
  Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/main/qase-python-commons
@@ -435,6 +435,12 @@ When a 429 carries a `Retry-After` header, that value replaces the computed
435
435
  backoff. Qase sends roughly 60 seconds, so a run that hits the rate limit takes
436
436
  longer to finish rather than losing the batch.
437
437
 
438
+ The same timeout and retry settings apply to attachment uploads, which go
439
+ through the API v1 client in batches of up to 20 files. **If an attachment batch
440
+ cannot be uploaded after all attempts**, the reporter logs an error, drops those
441
+ attachments and still submits the results they belong to — a failed attachment
442
+ never costs you the test results.
443
+
438
444
  **If a batch cannot be delivered after all attempts**, the reporter logs an
439
445
  error naming how many results were lost and **does not mark the run complete**.
440
446
  An open run is the signal that its data is incomplete; a completed run over