openqa-log-local 0.0.1__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.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.3
2
+ Name: openqa-log-local
3
+ Version: 0.0.1
4
+ Summary: Create a local cache of openQA job log files
5
+ Author: Michele Pagot
6
+ Author-email: Michele Pagot <michele.pagot@suse.com>
7
+ Requires-Dist: openqa-client>=4.3.1
8
+ Requires-Dist: click
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+
12
+ # openQA log local
13
+
14
+ Library and cli to locally collect and inspect logs from openQA
15
+
16
+ File will be locally cached on disk, downloaded and read transparently.
17
+
18
+ ## Dependency
19
+
20
+ This package internally depend on [openQA-python-client](https://github.com/os-autoinst/openQA-python-client): please refer to
21
+ documentation about openQA autentication.
22
+
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install openqa_log_local
28
+ ```
29
+
30
+ To install the package from the source code you can use `uv`:
31
+
32
+ ```bash
33
+ uv pip install -e .
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Library
39
+
40
+ To use the library in your Python project, you first need to import the `openQA_log_local` class:
41
+
42
+ ```python
43
+ from openqa_log_local import openQA_log_local
44
+ ```
45
+
46
+ Then, you can create an instance of the class, providing the openQA host URL:
47
+
48
+ ```python
49
+ oll = openQA_log_local(host='http://openqa.opensuse.org')
50
+
51
+ # Get job details
52
+ log_details = oll.get_details(job_id=1234)
53
+
54
+ # Get a list of log files associated to an openQA job.
55
+ # No download any log file yet.
56
+ log_list = oll.get_log_list(job_id=1234)
57
+ log_txt_list = oll.get_log_list(job_id=4567, name_pattern=[r'*\.txt'])
58
+
59
+ # Get content of a single log file. The file is downloaded to the cache
60
+ # if not already available locally.
61
+ # All the log file content is returned in `log_data`
62
+ log_data = oll.get_log_data(job_id=1234, filename=log_list[3])
63
+
64
+ # Get absolute path with filename of a single log file from the cache.
65
+ # The file is downloaded to the cache if not already available locally.
66
+ log_filename = oll.get_log_filename(job_id=1234, filename=log_list[3])
67
+ ```
68
+
69
+ Cache can be configured:
70
+
71
+ ```python
72
+ oll = openQA_log_local(
73
+ host='http://openqa.opensuse.org',
74
+ cache_location='/home/user/.openqa_cache',
75
+ max_size=100000,
76
+ time_to_live=3600)
77
+ ```
78
+
79
+ Or also forced to be ignored and refreshed
80
+
81
+ ```python
82
+ oll = openQA_log_local(
83
+ host='http://openqa.opensuse.org',
84
+ user_ignore_cache)
85
+ ```
86
+
87
+ ### CLI
88
+
89
+ The package also provides a command-line interface (CLI) for interacting with openQA logs.
90
+
91
+ #### Get Job Details
92
+
93
+ ```bash
94
+ openqa-log-local get-details --host http://openqa.opensuse.org --job-id 1234
95
+ ```
96
+
97
+ Run via `uv` if you have used `uv` to install it
98
+
99
+ ```bash
100
+ uv run openqa-log-local get-details --host http://openqa.opensuse.org --job-id 1234
101
+ ```
102
+
103
+ #### Get Log List
104
+
105
+ ```bash
106
+ openqa-log-local get-log-list --host http://openqa.opensuse.org --job-id 1234
107
+ ```
108
+
109
+ #### Get Log Data
110
+
111
+ ```bash
112
+ openqa-log-local get-log-data --host http://openqa.opensuse.org --job-id 1234 --filename autoinst-log.txt
113
+ ```
114
+
115
+ #### Get Log Filename
116
+
117
+ ```bash
118
+ openqa-log-local get-log-filename --host http://openqa.opensuse.org --job-id 1234 --filename autoinst-log.txt
119
+ ```
120
+
121
+
@@ -0,0 +1,110 @@
1
+ # openQA log local
2
+
3
+ Library and cli to locally collect and inspect logs from openQA
4
+
5
+ File will be locally cached on disk, downloaded and read transparently.
6
+
7
+ ## Dependency
8
+
9
+ This package internally depend on [openQA-python-client](https://github.com/os-autoinst/openQA-python-client): please refer to
10
+ documentation about openQA autentication.
11
+
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install openqa_log_local
17
+ ```
18
+
19
+ To install the package from the source code you can use `uv`:
20
+
21
+ ```bash
22
+ uv pip install -e .
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Library
28
+
29
+ To use the library in your Python project, you first need to import the `openQA_log_local` class:
30
+
31
+ ```python
32
+ from openqa_log_local import openQA_log_local
33
+ ```
34
+
35
+ Then, you can create an instance of the class, providing the openQA host URL:
36
+
37
+ ```python
38
+ oll = openQA_log_local(host='http://openqa.opensuse.org')
39
+
40
+ # Get job details
41
+ log_details = oll.get_details(job_id=1234)
42
+
43
+ # Get a list of log files associated to an openQA job.
44
+ # No download any log file yet.
45
+ log_list = oll.get_log_list(job_id=1234)
46
+ log_txt_list = oll.get_log_list(job_id=4567, name_pattern=[r'*\.txt'])
47
+
48
+ # Get content of a single log file. The file is downloaded to the cache
49
+ # if not already available locally.
50
+ # All the log file content is returned in `log_data`
51
+ log_data = oll.get_log_data(job_id=1234, filename=log_list[3])
52
+
53
+ # Get absolute path with filename of a single log file from the cache.
54
+ # The file is downloaded to the cache if not already available locally.
55
+ log_filename = oll.get_log_filename(job_id=1234, filename=log_list[3])
56
+ ```
57
+
58
+ Cache can be configured:
59
+
60
+ ```python
61
+ oll = openQA_log_local(
62
+ host='http://openqa.opensuse.org',
63
+ cache_location='/home/user/.openqa_cache',
64
+ max_size=100000,
65
+ time_to_live=3600)
66
+ ```
67
+
68
+ Or also forced to be ignored and refreshed
69
+
70
+ ```python
71
+ oll = openQA_log_local(
72
+ host='http://openqa.opensuse.org',
73
+ user_ignore_cache)
74
+ ```
75
+
76
+ ### CLI
77
+
78
+ The package also provides a command-line interface (CLI) for interacting with openQA logs.
79
+
80
+ #### Get Job Details
81
+
82
+ ```bash
83
+ openqa-log-local get-details --host http://openqa.opensuse.org --job-id 1234
84
+ ```
85
+
86
+ Run via `uv` if you have used `uv` to install it
87
+
88
+ ```bash
89
+ uv run openqa-log-local get-details --host http://openqa.opensuse.org --job-id 1234
90
+ ```
91
+
92
+ #### Get Log List
93
+
94
+ ```bash
95
+ openqa-log-local get-log-list --host http://openqa.opensuse.org --job-id 1234
96
+ ```
97
+
98
+ #### Get Log Data
99
+
100
+ ```bash
101
+ openqa-log-local get-log-data --host http://openqa.opensuse.org --job-id 1234 --filename autoinst-log.txt
102
+ ```
103
+
104
+ #### Get Log Filename
105
+
106
+ ```bash
107
+ openqa-log-local get-log-filename --host http://openqa.opensuse.org --job-id 1234 --filename autoinst-log.txt
108
+ ```
109
+
110
+
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "openqa-log-local"
3
+ version = "0.0.1"
4
+ description = "Create a local cache of openQA job log files"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Michele Pagot", email = "michele.pagot@suse.com" }
8
+ ]
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "openqa-client>=4.3.1",
12
+ "click",
13
+ ]
14
+
15
+ [project.scripts]
16
+ openqa-log-local = "openqa_log_local.cli:cli"
17
+
18
+ [build-system]
19
+ requires = ["uv_build>=0.9.11,<0.10.0"]
20
+ build-backend = "uv_build"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "black>=25.11.0",
25
+ "mypy>=1.19.0",
26
+ "pytest>=8.4.2",
27
+ "ruff>=0.14.7",
28
+ "types-requests>=2.32.4.20250913",
29
+ ]
@@ -0,0 +1,4 @@
1
+ from .cli import cli
2
+ from .main import openQA_log_local
3
+
4
+ __all__ = ["openQA_log_local", "cli"]
@@ -0,0 +1,176 @@
1
+ import os
2
+ import logging
3
+ import json
4
+ from typing import Any, Optional
5
+
6
+
7
+ class openQACache:
8
+ """Handles the file-based caching mechanism for openQA job data and logs.
9
+
10
+ This module provides the `openQACache` class, which is responsible
11
+ for storing and retrieving openQA job details and log files to and from
12
+ the backend local filesystem (usually your laptop).
13
+ The primary goal is to speed up analysis by avoiding repeated downloads
14
+ of the same data from the openQA server.
15
+
16
+ Architecture and Design
17
+ -----------------------
18
+
19
+ - **Directory Structure:** The cache is organized in a hierarchical structure.
20
+ A main cache directory (configurable by `cache_dir` in `config.yaml`)
21
+ contains subdirectories for each openQA server hostname.
22
+ Inside each hostname directory, cached data for a specific job is stored
23
+ in a JSON file named after the job ID (e.g., `.cache/openqa.suse.de/12345.json`).
24
+
25
+ - **Data Format:** Each cache file is a JSON object containing two main keys:
26
+ - `job_details`: A dictionary holding the complete JSON response for a job's
27
+ details from the openQA API.
28
+ - `log_files`: a list of log files downloaded from openQA and stored as
29
+ separated files assiciated to this job_id. Log files are stored in a folder
30
+ named with the value of the job_id, log filename is the one in this list.
31
+ - [DEPRECATED] `log_content`: A string containing the full content of the
32
+ `autoinst-log.txt` for that job.
33
+
34
+ - **Data Flow:** the API provided by this class are only responsible to manage
35
+ openQA job details metadata and log file path.
36
+ There is no API to write or read any log content.
37
+
38
+ Workflow
39
+ --------
40
+ The caching logic is integrated into the main application flow in `app/main.py`:
41
+
42
+ 1. **Job Discovery (`discover_jobs`):** When discovering related jobs, the
43
+ application first checks if a cache file exists for a given job ID using
44
+ `cache.is_details_cached()`. If it does, `cache.get_job_details()` is called
45
+ to retrieve the `job_details`, and the API call to the openQA server is skipped.
46
+
47
+ 2. **Log Processing (`process_job_logs`):** Before attempting to download a
48
+ log file, the application calls `cache.get_cached_log_filepath('filename.whatever')`.
49
+ If the log is found in the cache, the download is skipped.
50
+
51
+ 3. **Cache Writing (`_get_log_from_api`):** A cache file is written only after
52
+ job data and its corresponding log file have been successfully downloaded
53
+ from the openQA API. The `cache.write_data()` method is called to save
54
+ both the `job_details` and `log_content` into a single JSON file.
55
+
56
+ Configuration and Invalidation
57
+ ------------------------------
58
+ - The cache directory and maximum size are configured in the `config.yaml` file.
59
+ - As this project only consider and care about completed jobs,
60
+ the cache never become invalid or obsolete due to changes in the openQA side.
61
+ Job details or log files are not supposed to change in the openQA server
62
+ for a completed jobs.
63
+ - The cache is persistent and does not have an automatic expiration or TTL
64
+ (Time To Live) mechanism. It can be manually cleared by deleting the cache
65
+ directory.
66
+ - The application frontend provides an `ignore_cache` option in the `/analyze`
67
+ endpoint to bypass the cache and force a fresh download of all data.
68
+ A user_ignore_cache is available in the class constructor. It allows to
69
+ annotate that the cache is there but user ask to ignore data from it.
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ cache_path: str,
75
+ hostname: str,
76
+ max_size: int,
77
+ time_to_live: int,
78
+ logger: logging.Logger,
79
+ ) -> None:
80
+ """Initializes the cache handler.
81
+
82
+ Args:
83
+ cache_path (str): The root directory for the cache.
84
+ hostname (str): The openQA host, used to create a subdirectory in the cache.
85
+ max_size (int): The maximum size of the cache in bytes.
86
+ time_to_live (int): The time in seconds after which cached data is considered stale. -1 means
87
+ data never expires, 0 means data is
88
+ always refreshed.
89
+ logger (logging.Logger): The logger instance to use.
90
+ """
91
+ self.cache_path = cache_path
92
+ self.hostname = hostname
93
+ self.cache_host_dir = os.path.join(self.cache_path, self.hostname)
94
+ self.max_size = max_size
95
+ self.time_to_leave = time_to_live
96
+ self.logger = logger
97
+
98
+ os.makedirs(self.cache_path, exist_ok=True)
99
+ # os.makedirs(self.cache_host_dir, exist_ok=True)
100
+
101
+ def _file_path(self, job_id: str) -> str:
102
+ """Constructs the full path for a job's details metadata JSON file.
103
+
104
+ Args:
105
+ job_id (str): The ID of the job.
106
+
107
+ Returns:
108
+ str: The absolute path to the cache file for the job.
109
+ """
110
+ return os.path.join(self.cache_host_dir, f"{job_id}.json")
111
+
112
+ def is_details_cached(self, job_id: str) -> bool:
113
+ """Checks if the cache metadata file exists for a given job ID.
114
+
115
+ It also considers the time_to_live setting. If time_to_live is 0,
116
+ it will always return False.
117
+
118
+ Args:
119
+ job_id (str): The ID of the job.
120
+
121
+ Returns:
122
+ bool: True if the job details are in the cache and not stale, False otherwise.
123
+ """
124
+ if self.time_to_leave == 0:
125
+ return False
126
+ return os.path.exists(self._file_path(job_id))
127
+
128
+ def get_job_details(self, job_id: str) -> Optional[dict[str, Any]]:
129
+ """Retrieves cached job details for a specific job ID.
130
+
131
+ Args:
132
+ job_id (str): The ID of the job.
133
+
134
+ Returns:
135
+ Optional[dict[str, Any]]: A dictionary containing the job details,
136
+ or None if not found in cache or on error.
137
+ """
138
+ if self.time_to_leave == 0:
139
+ return None
140
+ try:
141
+ with open(self._file_path(job_id), "r") as f:
142
+ cached_data = json.load(f)
143
+ job_details: Optional[dict[str, Any]] = cached_data.get("job_details")
144
+ if job_details:
145
+ return job_details
146
+ else:
147
+ self.logger.info(
148
+ f"Missing job_details in cached_data for job {job_id}"
149
+ )
150
+ return None
151
+ except (IOError, json.JSONDecodeError) as e:
152
+ self.logger.error(f"Error reading cache for job {job_id}: {e}")
153
+ return None
154
+
155
+ def write_details(
156
+ self, job_id: str, job_details: dict[str, Any], log_files: list[str]
157
+ ) -> None:
158
+ """Writes job details to a cache file.
159
+
160
+ Args:
161
+ job_id (str): The ID of the job.
162
+ job_details (dict[str, Any]): The dictionary of job details to cache.
163
+ log_files (list[str]): A list of log files associated with the job.
164
+ """
165
+ cache_file = self._file_path(job_id)
166
+ data_to_cache: dict[str, Any] = {
167
+ "job_details": job_details,
168
+ }
169
+
170
+ try:
171
+ os.makedirs(self.cache_host_dir, exist_ok=True)
172
+ with open(cache_file, "w") as f:
173
+ json.dump(data_to_cache, f)
174
+ self.logger.info(f"Successfully cached metadata for job {job_id}.")
175
+ except (IOError, TypeError) as e:
176
+ self.logger.error(f"Failed to write cache for job {job_id}: {e}")
@@ -0,0 +1,84 @@
1
+ import click
2
+ import logging
3
+ from .main import openQA_log_local
4
+
5
+
6
+ @click.group()
7
+ @click.option("--debug/--no-debug", default=False)
8
+ @click.pass_context
9
+ def cli(ctx, debug):
10
+ """A CLI to locally collect and inspect logs from openQA.
11
+
12
+ Files will be locally cached on disk, downloaded and read transparently.
13
+ """
14
+ ctx.ensure_object(dict)
15
+ ctx.obj["DEBUG"] = debug
16
+ if debug:
17
+ logging.basicConfig(level=logging.DEBUG)
18
+ else:
19
+ logging.basicConfig(level=logging.WARNING)
20
+
21
+
22
+ @cli.command()
23
+ @click.option("--host", required=True, help="The openQA host URL.")
24
+ @click.option("--job-id", required=True, type=int, help="The job ID.")
25
+ @click.pass_context
26
+ def get_details(ctx, host, job_id):
27
+ """Get job details for a specific openQA job."""
28
+ oll = openQA_log_local(host=host)
29
+ details = oll.get_details(job_id)
30
+ if details is None:
31
+ click.echo(f"Job {job_id} not found.", err=True)
32
+ ctx.exit(1)
33
+ click.echo(details)
34
+
35
+
36
+ @cli.command()
37
+ @click.option("--host", required=True, help="The openQA host URL.")
38
+ @click.option("--job-id", required=True, type=int, help="The job ID.")
39
+ @click.option("--name-pattern", help="A regex pattern to filter log files.")
40
+ @click.pass_context
41
+ def get_log_list(ctx, host, job_id, name_pattern):
42
+ """Get a list of log files associated to an openQA job.
43
+
44
+ This command does not download any log file.
45
+ """
46
+ oll = openQA_log_local(host=host)
47
+ log_list = oll.get_log_list(job_id, name_pattern)
48
+ for log in log_list:
49
+ click.echo(log)
50
+
51
+
52
+ @cli.command()
53
+ @click.option("--host", required=True, help="The openQA host URL.")
54
+ @click.option("--job-id", required=True, type=int, help="The job ID.")
55
+ @click.option("--filename", required=True, help="The name of the log file.")
56
+ @click.pass_context
57
+ def get_log_data(ctx, host, job_id, filename):
58
+ """Get content of a single log file.
59
+
60
+ The file is downloaded to the cache if not already available locally.
61
+ All the log file content is returned.
62
+ """
63
+ oll = openQA_log_local(host=host)
64
+ log_data = oll.get_log_data(job_id, filename)
65
+ click.echo(log_data)
66
+
67
+
68
+ @cli.command()
69
+ @click.option("--host", required=True, help="The openQA host URL.")
70
+ @click.option("--job-id", required=True, type=int, help="The job ID.")
71
+ @click.option("--filename", required=True, help="The name of the log file.")
72
+ @click.pass_context
73
+ def get_log_filename(ctx, host, job_id, filename):
74
+ """Get absolute path with filename of a single log file from the cache.
75
+
76
+ The file is downloaded to the cache if not already available locally.
77
+ """
78
+ oll = openQA_log_local(host=host)
79
+ log_filename = oll.get_log_filename(job_id, filename)
80
+ click.echo(log_filename)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ cli(obj={})
@@ -0,0 +1,105 @@
1
+ import logging
2
+ from typing import Any, Optional
3
+
4
+ import requests
5
+ import requests.exceptions
6
+ from openqa_client.client import OpenQA_Client
7
+ from openqa_client.exceptions import RequestError
8
+
9
+ """Custom exception classes for the application."""
10
+
11
+
12
+ class openQAClientError(Exception):
13
+ """Base exception for all openQAClientWrapper errors."""
14
+
15
+ pass
16
+
17
+
18
+ class openQAClientAPIError(openQAClientError):
19
+ """Raised for errors during openQA API requests."""
20
+
21
+ pass
22
+
23
+
24
+ class openQAClientConnectionError(openQAClientError):
25
+ """Raised for errors during connection to openQA."""
26
+
27
+ pass
28
+
29
+
30
+ class openQAClientWrapper:
31
+ """A wrapper class for the openqa_client to simplify interactions."""
32
+
33
+ def __init__(
34
+ self,
35
+ hostname: str,
36
+ logger: logging.Logger,
37
+ ) -> None:
38
+ """Initializes the client wrapper.
39
+
40
+ It does not create an OpenQA_Client instance immediately. The client
41
+ is lazily initialized on first use.
42
+
43
+ Args:
44
+ hostname (str): The openQA host URL.
45
+ logger (logging.Logger): The logger instance to use.
46
+ """
47
+ self.logger = logger
48
+ self.hostname = hostname
49
+ self._client: Optional[OpenQA_Client] = None
50
+
51
+ @property
52
+ def client(self) -> OpenQA_Client:
53
+ """Lazily initializes and returns the OpenQA_Client instance.
54
+
55
+ Returns:
56
+ OpenQA_Client: The initialized openqa_client instance.
57
+ """
58
+ if self._client is None:
59
+ self.logger.info("Initializing OpenQA_Client for %s", self.hostname)
60
+ client = OpenQA_Client(server=self.hostname)
61
+ client.session.verify = False
62
+ self.logger.warning(
63
+ "SSL certificate verification disabled for client connecting to %s",
64
+ self.hostname,
65
+ )
66
+ self._client = client
67
+ return self._client
68
+
69
+ def get_job_details(self, job_id: int) -> Optional[dict[str, Any]]:
70
+ """Fetches the details for a specific job from the openQA API.
71
+
72
+ Args:
73
+ job_id (int): The ID of the job.
74
+
75
+ Raises:
76
+ openQAClientAPIError: For non-404 API errors.
77
+ openQAClientConnectionError: For network connection errors.
78
+
79
+ Returns:
80
+ Optional[dict[str, Any]]: A dictionary with job details, or None if the job is not found (404).
81
+ """
82
+ self.logger.info(
83
+ "get_job_details(job_id:%s) for hostname:%s", job_id, self.hostname
84
+ )
85
+ try:
86
+ response = self.client.openqa_request("GET", f"jobs/{job_id}")
87
+ job = response.get("job")
88
+ if not job:
89
+ raise openQAClientAPIError(
90
+ f"Could not find 'job' key in API response for ID {job_id}."
91
+ )
92
+ return job
93
+ except RequestError as e:
94
+ if e.status_code == 404:
95
+ self.logger.warning("Job %s not found (404)", job_id)
96
+ return None
97
+ error_message = (
98
+ f"API Error for job {job_id}: Status {e.status_code} - {e.text}"
99
+ )
100
+ self.logger.error(error_message)
101
+ raise openQAClientAPIError(error_message) from e
102
+ except requests.exceptions.ConnectionError as e:
103
+ error_message = f"Connection to host '{self.hostname}' failed"
104
+ self.logger.error(error_message)
105
+ raise openQAClientConnectionError(error_message) from e
@@ -0,0 +1,129 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from .client import openQAClientWrapper
5
+ from .cache import openQACache
6
+
7
+
8
+ class openQA_log_local:
9
+ """
10
+ Main class for the openqa_log_local library.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ host: str,
16
+ cache_location: Optional[str] = ".cache",
17
+ max_size: Optional[int] = 1024 * 1024 * 100, # 100 MB
18
+ time_to_live: Optional[int] = -1,
19
+ logger: Optional[logging.Logger] = None,
20
+ ):
21
+ """
22
+ Initializes the openQA_log_local library.
23
+
24
+ Args:
25
+ host (str): The openQA host URL.
26
+ cache_location (Optional[str]): The directory to store cached logs.
27
+ Defaults to ".cache".
28
+ max_size (Optional[int]): The maximum size of the cache in bytes.
29
+ Defaults to 100MB.
30
+ time_to_live (Optional[int]): The time in seconds after which cached
31
+ data is considered stale. -1 means
32
+ data never expires, 0 means data is
33
+ always refreshed. Defaults to -1.
34
+ logger (Optional[logging.Logger]): A logger instance. If None, a
35
+ new one is created.
36
+ """
37
+ if logger is None:
38
+ self.logger = logging.getLogger(__name__)
39
+ else:
40
+ self.logger = logger
41
+ self.client = openQAClientWrapper(host, self.logger)
42
+ if cache_location is None:
43
+ cl = ".cache"
44
+ else:
45
+ cl = cache_location
46
+ if max_size is None:
47
+ ms = 1024 * 1024 * 100
48
+ else:
49
+ ms = max_size
50
+ if time_to_live is None:
51
+ tl = -1
52
+ else:
53
+ tl = time_to_live
54
+ self.cache = openQACache(
55
+ cl,
56
+ host,
57
+ ms,
58
+ tl,
59
+ self.logger,
60
+ )
61
+
62
+ def get_details(self, job_id: int) -> Optional[Dict[str, Any]]:
63
+ """Get job details for a specific openQA job.
64
+
65
+ Args:
66
+ job_id (int): The job ID.
67
+
68
+ Returns:
69
+ Optional[Dict[str, Any]]: A dictionary containing job details,
70
+ or None if the job is not found.
71
+ """
72
+ job_details: Optional[Dict[str, Any]] = None
73
+ if not self.cache.is_details_cached(str(job_id)):
74
+ self.logger.info(f"Cache miss for job {job_id} details.")
75
+ job_details = self.client.get_job_details(job_id)
76
+ # Assuming we don't have the log files list at this point.
77
+ # We will update the cache when we fetch the log files.
78
+ if job_details:
79
+ self.cache.write_details(str(job_id), job_details, [])
80
+ else:
81
+ self.logger.info(f"Cache hit for job {job_id} details.")
82
+ job_details = self.cache.get_job_details(str(job_id))
83
+
84
+ return job_details
85
+
86
+ def get_log_list(
87
+ self, job_id: int, name_pattern: Optional[str] = None
88
+ ) -> List[str]:
89
+ """Get a list of log files associated to an openQA job.
90
+
91
+ This method does not download any log files.
92
+
93
+ Args:
94
+ job_id (int): The job ID.
95
+ name_pattern (Optional[str]): A regex pattern to filter log files by name.
96
+
97
+ Returns:
98
+ List[str]: A list of log file names.
99
+ """
100
+ return []
101
+
102
+ def get_log_data(self, job_id: int, filename: str) -> str:
103
+ """Get content of a single log file.
104
+
105
+ The file is downloaded to the cache if not already available locally.
106
+ All the log file content is returned.
107
+
108
+ Args:
109
+ job_id (int): The job ID.
110
+ filename (str): The name of the log file.
111
+
112
+ Returns:
113
+ str: The content of the log file.
114
+ """
115
+ return ""
116
+
117
+ def get_log_filename(self, job_id: int, filename: str) -> str:
118
+ """Get absolute path with filename of a single log file from the cache.
119
+
120
+ The file is downloaded to the cache if not already available locally.
121
+
122
+ Args:
123
+ job_id (int): The job ID.
124
+ filename (str): The name of the log file.
125
+
126
+ Returns:
127
+ str: The absolute path to the cached log file.
128
+ """
129
+ return ""