divbase-lib 0.1.0a1__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.
@@ -0,0 +1,67 @@
1
+ """
2
+ Schemas for VCF dimensions routes.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class DimensionUpdateKwargs(BaseModel):
11
+ """Keyword arguments for dimension update task. Used to pass info to Celery task, and also for recording task history."""
12
+
13
+ bucket_name: str
14
+ project_id: int
15
+ project_name: str
16
+ user_id: int
17
+
18
+
19
+ class DimensionUpdateTaskResult(BaseModel):
20
+ """Dimension update task result details. Based on the return of tasks.update_dimensions_index."""
21
+
22
+ status: Optional[str] = None
23
+ VCF_files_added: Optional[list[str]] = Field(
24
+ None, description="VCF files that were added to dimensions index by this job"
25
+ )
26
+ VCF_files_skipped: Optional[list[str]] = Field(
27
+ None, description="VCF files skipped by this job (previous DivBase-generated result VCFs)"
28
+ )
29
+ VCF_files_deleted: Optional[list[str]] = Field(
30
+ None, description="VCF files that have been deleted from the project and thus have been dropped from the index"
31
+ )
32
+
33
+
34
+ class DimensionsShowResult(BaseModel):
35
+ """Result model for showing VCF dimensions for a project."""
36
+
37
+ project_id: int
38
+ project_name: str
39
+ vcf_file_count: int
40
+ vcf_files: list[dict]
41
+ skipped_file_count: int
42
+ skipped_files: list[dict]
43
+
44
+
45
+ class DimensionsSamplesResult(BaseModel):
46
+ """Result model for showing unique samples across project VCFs."""
47
+
48
+ unique_samples: list[str] # Already sorted, by the CRUD function get_unique_samples_by_project_async()
49
+
50
+
51
+ class DimensionsScaffoldsResult(BaseModel):
52
+ """Result model for showing unique scaffolds across project VCFs."""
53
+
54
+ unique_scaffolds: list[str] # Already sorted, by the CRUD function get_unique_scaffolds_by_project_async()
55
+
56
+
57
+ class DimensionsVCFFileEntry(BaseModel):
58
+ """A single VCF file entry in the unique VCF files response."""
59
+
60
+ vcf_file_s3_key: str
61
+ s3_version_id: str
62
+
63
+
64
+ class DimensionsVCFFilesResult(BaseModel):
65
+ """Result model for showing unique VCF file/version entries across project VCFs."""
66
+
67
+ unique_vcf_files: list[DimensionsVCFFileEntry]
@@ -0,0 +1,56 @@
1
+ """
2
+ Constants that both divbase-api and divbase-cli need to agree on.
3
+ """
4
+
5
+ ONE_MiB = 1024 * 1024
6
+
7
+ # When you download a file that has been uploaded in parts, you have
8
+ # to know the part/chunk size used in order to correctly calculate the composite checksum
9
+ S3_MULTIPART_CHUNK_SIZE = 32 * ONE_MiB
10
+
11
+ # At what point you swap from single part to multipart upload to S3.
12
+ # If server and client used the same threshold then makes life easier
13
+ # when validating the checksums of files in s3 as single part and multipart uploads use different ETag formats.
14
+ # (No benefit in constraining the download threshold, so not done here)
15
+ S3_MULTIPART_UPLOAD_THRESHOLD = 96 * ONE_MiB
16
+
17
+ # Max number of items that can be processed in a single API call to divbase-api's S3 routes
18
+ # covers e.g pre-signed urls for upload/download, soft delete and checksum comparisons
19
+ # client has to batch requests if exceeding this limit
20
+ MAX_S3_API_BATCH_SIZE = 100
21
+
22
+ # How long the pre-signed URLs divbase-api creates are valid for
23
+ SINGLE_PART_UPLOAD_URL_EXPIRATION_SECONDS = 3600 # 1 hour
24
+ MULTI_PART_UPLOAD_URL_EXPIRATION_SECONDS = 36000 # 10 hours
25
+ DOWNLOAD_URL_EXPIRATION_SECONDS = 36000 # 10 hours
26
+
27
+ # (Not used anywhere, just making it explicit)
28
+ # This is limited by our fixing of the chunk size and S3's limit to the number of chunks allowed (10,000)
29
+ # 320 GiB if using 32 MiB chunks
30
+ LARGEST_FILE_UPLOADABLE_TO_DIVBASE_BYTES = 10_000 * S3_MULTIPART_CHUNK_SIZE
31
+
32
+ # File types that DivBase supports
33
+ # Whilst we can't realistically limit what file types a user actually uploads,
34
+ # this is here to say what we know should work in DivBase.
35
+ SUPPORTED_DIVBASE_FILE_TYPES = (".tsv", ".vcf.gz", ".csi", ".tbi")
36
+
37
+ # Characters that are not allowed in file names uploaded to DivBase
38
+ # This is to prevent issues when users try to filter/query files on DivBase using these characters
39
+ # or when downloading files (e.g. ":" is used to specify file versions when downloading files
40
+ UNSUPPORTED_CHARACTERS_IN_FILENAMES = (":", "*", "?", "<", ">", "|", "\\")
41
+
42
+ # This prefix is used for all *.vcf.gz results files from a query job/task.
43
+ # After the prefix comes the job id which is a rolling integer.
44
+ # E.g. format: result_of_job_<job-id>.vcf.gz , where <job-id> = 1 and is auto-incremented for every new job.
45
+ # NOTE: If you update this, you should also update the S3 lifecycle policies associated with this.
46
+ QUERY_RESULTS_FILE_PREFIX = "result_of_job_"
47
+
48
+ # CLI includes this in every request to the server,
49
+ # and server uses it to determine if it should:
50
+ # 1. Reject the request as the version is too outdated
51
+ # 2. Add an announcement about a new CLI version being available when the user logs in.
52
+ # 3. Do Nothing as the user's CLI version is up to date.
53
+ CLI_VERSION_HEADER_KEY = "X-CLI-Version"
54
+
55
+ # Personal Access Tokens (PATs) are used for passwordless auth with DivBase API
56
+ PAT_TOKEN_PREFIX = "divbase_pat_"
@@ -0,0 +1,156 @@
1
+ """
2
+ Custom exceptions for DivBase packages.
3
+
4
+ These are raised by lover-level functions/methods which understand the context of the error.
5
+
6
+ Note: By adding the `__str__` method to each exception,
7
+ we ensure that when you manually raise a specific exception the error message looks good
8
+ """
9
+
10
+ from pathlib import Path
11
+
12
+
13
+ class BcftoolsEnvironmentError(Exception):
14
+ """Raised when there's an issue with the execution environment (Docker, etc.)."""
15
+
16
+ def __init__(self, container_name: str):
17
+ self.container_name = container_name
18
+ error_message = (
19
+ f"No running container found with name {self.container_name}. Ensure the Docker image is available.\n"
20
+ )
21
+ super().__init__(error_message)
22
+
23
+ self.error_message = error_message
24
+
25
+ def __str__(self):
26
+ return self.error_message
27
+
28
+
29
+ class BcftoolsCommandError(Exception):
30
+ """Raised when a bcftools command fails to execute properly."""
31
+
32
+ def __init__(self, command: str, error_details: Exception = None):
33
+ self.command = command
34
+ self.error_details = error_details
35
+
36
+ error_message = f"bcftools command failed: '{command}'"
37
+ if error_details:
38
+ error_message += f" with error details: {error_details}"
39
+
40
+ super().__init__(error_message)
41
+
42
+ def __str__(self):
43
+ if hasattr(self.error_details, "stderr") and self.error_details.stderr:
44
+ return f"bcftools command failed: '{self.command}' with error: {self.error_details.stderr}"
45
+ return super().__str__()
46
+
47
+
48
+ class BcftoolsPipeEmptyCommandError(Exception):
49
+ """Raised when an empty command is provided to the bcftools pipe."""
50
+
51
+ def __init__(self):
52
+ error_message = "Empty command provided. Please specify at least one valid bcftools command."
53
+ super().__init__(error_message)
54
+ self.error_message = error_message
55
+
56
+ def __str__(self):
57
+ return self.error_message
58
+
59
+
60
+ class BcftoolsPipeUnsupportedCommandError(Exception):
61
+ """Raised when a bcftools command unsupported by the BcftoolsQueryManager class is provided."""
62
+
63
+ def __init__(self, command: str, position: int, valid_commands: list[str]):
64
+ self.command = command
65
+ self.position = position
66
+ self.valid_commands = valid_commands
67
+
68
+ message = (
69
+ f"Unsupported bcftools command '{command}' at position {position}. "
70
+ f"Only the following commands are supported: {', '.join(valid_commands)}"
71
+ )
72
+ super().__init__(message)
73
+
74
+
75
+ class SidecarNoDataLoadedError(Exception):
76
+ """Raised when no data is loaded in SidecarQueryManager."""
77
+
78
+ def __init__(self, file_path: Path, submethod: str, error_details: str | None = None):
79
+ self.file_path = file_path
80
+ self.error_details = error_details
81
+
82
+ error_message = f"No data loaded from file '{file_path}', as raised in submethod '{submethod}'."
83
+ if error_details:
84
+ error_message += f"More details about the error: {error_details}"
85
+ super().__init__(error_message)
86
+ self.error_message = error_message
87
+
88
+ def __str__(self):
89
+ return self.error_message
90
+
91
+
92
+ class SidecarInvalidFilterError(Exception):
93
+ """Raised when an invalid filter is provided to SidecarQueryManager."""
94
+
95
+ pass
96
+
97
+
98
+ class SidecarColumnNotFoundError(Exception):
99
+ """Raised when a requested column is not found in the query result."""
100
+
101
+ pass
102
+
103
+
104
+ class SidecarSampleIDError(Exception):
105
+ """Raised when a Sample_ID is invalid or duplicated when loading sidecar data."""
106
+
107
+ pass
108
+
109
+
110
+ class SidecarMetadataFormatError(Exception):
111
+ """Raised when the sidecar metadata TSV file has formatting issues (duplicate columns, empty columns, commas in values, etc.)."""
112
+
113
+ pass
114
+
115
+
116
+ class TaskUserError(Exception):
117
+ """
118
+ Raised in Celery tasks when an error needs to propagate back to the CLI user.
119
+
120
+ This is intentionally kept as a simple Exception subclass (no custom __init__)
121
+ to avoid UnpicklableExceptionWrapper when passing through Celery's JSON serialization/deserialization.
122
+ Complex exception types such as those inheriting from DivBaseAPIException seem to trigger UnpicklableExceptionWrapper.
123
+
124
+ This class is essentially a wrapper to allow to use more complex exceptions in Celery tasks and catch them
125
+ in the API route handlers to return user-friendly error messages to the CLI.
126
+ In the Celery task use it like:
127
+ raise TaskUserError(str(SomeComplexError(...))) from None
128
+ """
129
+
130
+ pass
131
+
132
+
133
+ class NoVCFFilesFoundError(Exception):
134
+ """Raised when no VCF files are found in the project bucket."""
135
+
136
+ pass
137
+
138
+
139
+ class DimensionsNotUpToDateWithBucketError(Exception):
140
+ """Raised when VCF files in a bucket are missing or outdated in the dimensions index."""
141
+
142
+ pass
143
+
144
+
145
+ class ChecksumVerificationError(Exception):
146
+ """Raised when a calculated file's checksum does not match the expected value."""
147
+
148
+ def __init__(self, expected_checksum: str, calculated_checksum: str):
149
+ self.expected_checksum = expected_checksum
150
+ self.calculated_checksum = calculated_checksum
151
+
152
+ message = (
153
+ f"Checksum verification failed. Expected: {expected_checksum}, Calculated: {calculated_checksum}"
154
+ f" The file has been deleted to avoid accidental use of a corrupted file."
155
+ )
156
+ super().__init__(message)