geoseeq 0.6.14.dev7__py3-none-any.whl → 0.7.0__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.
geoseeq/cli/download.py CHANGED
@@ -414,7 +414,7 @@ def _make_read_configs(download_results, config_dir="."):
414
414
  "reads_1": ["small.fq.gz"],
415
415
  "reads_2": [],
416
416
  "fastq_checksum": "",
417
- "data_type": "short-read",
417
+ "data_type": "single",
418
418
  "bdx_result_dir": "results",
419
419
  "geoseeq_uuid": "05bf22e9-9d25-42db-af25-31bc538a7006"
420
420
  }
@@ -428,7 +428,7 @@ def _make_read_configs(download_results, config_dir="."):
428
428
  "reads_1": [],
429
429
  "reads_2": [],
430
430
  "fastq_checksum": "",
431
- "data_type": "short-read",
431
+ "data_type": read_type,
432
432
  "bdx_result_dir": "results",
433
433
  "geoseeq_uuid": sample.uuid,
434
434
  }
geoseeq/cli/main.py CHANGED
@@ -55,7 +55,7 @@ def version():
55
55
  Use of this tool implies acceptance of the GeoSeeq End User License Agreement.
56
56
  Run `geoseeq eula show` to view the EULA.
57
57
  """
58
- click.echo('0.6.14dev7') # remember to update pyproject.toml
58
+ click.echo('0.7.0') # remember to update pyproject.toml
59
59
 
60
60
 
61
61
  @main.group('advanced')
@@ -4,9 +4,11 @@ from .upload import (
4
4
  cli_upload_file,
5
5
  cli_upload_folder,
6
6
  cli_metadata,
7
+ cli_upload_smart_table,
8
+ cli_upload_smart_tree,
7
9
  )
8
10
  from .upload_reads import cli_upload_reads_wizard
9
- from .upload_advanced import cli_find_urls_for_reads
11
+ from .upload_advanced import cli_find_urls_for_reads, cli_upload_from_config
10
12
 
11
13
  @click.group('upload')
12
14
  def cli_upload():
@@ -17,10 +19,13 @@ cli_upload.add_command(cli_upload_reads_wizard)
17
19
  cli_upload.add_command(cli_upload_file)
18
20
  cli_upload.add_command(cli_upload_folder)
19
21
  cli_upload.add_command(cli_metadata)
22
+ cli_upload.add_command(cli_upload_smart_table)
23
+ cli_upload.add_command(cli_upload_smart_tree)
20
24
 
21
- @click.group('upload')
25
+ @cli_upload.group('advanced')
22
26
  def cli_upload_advanced():
23
27
  """Advanced tools to upload files to GeoSeeq."""
24
28
  pass
25
29
 
26
- cli_upload_advanced.add_command(cli_find_urls_for_reads)
30
+ cli_upload_advanced.add_command(cli_find_urls_for_reads)
31
+ cli_upload_advanced.add_command(cli_upload_from_config)
@@ -263,3 +263,117 @@ def cli_metadata(state, overwrite, yes, private, create, index_col, encoding, pr
263
263
  sample.metadata = new_meta
264
264
  sample.idem()
265
265
  click.echo(f'Wrote metadata for {len(samples)} samples')
266
+
267
+
268
+ @click.command('smart-table')
269
+ @use_common_state
270
+ @overwrite_option
271
+ @yes_option
272
+ @private_option
273
+ @click.option('-n', '--geoseeq-file-name', default=None,
274
+ help='Specify a different name for the file on GeoSeeq than the local file name.',
275
+ show_default=True)
276
+ @folder_id_arg
277
+ @click.argument('file_path', type=click.Path(exists=True), nargs=1)
278
+ def cli_upload_smart_table(state, overwrite, yes, private, folder_id, geoseeq_file_name, file_path):
279
+ """Upload a smart table to GeoSeeq.
280
+
281
+ This command uploads a smart table to a project or sample on GeoSeeq. It can be used to upload
282
+ a single file to a folder at once.
283
+
284
+ ---
285
+
286
+ Example Usage:
287
+
288
+ \b
289
+ # Upload a smart table from a file
290
+ $ geoseeq upload smart-table "My Org/My Project/My Sample/My Folder" /path/to/my_table.csv
291
+
292
+ \b
293
+ # Upload a smart table from a file but name it "My Smart Table" on GeoSeeq
294
+ $ geoseeq upload smart-table "My Org/My Project/My Sample/My Folder" /path/to/my_table.csv -n "My Smart Table"
295
+
296
+ ---
297
+
298
+ Command Arguments:
299
+
300
+ [FOLDER_ID] Can be a folder UUID, GeoSeeq Resource Number (GRN), or an
301
+ names for an org, project, sample, folder separated by a slash. Can exclude
302
+ the sample name if the folder is for a project.
303
+
304
+ [FILE_PATH] A path to a file on your local machine.
305
+
306
+ ---
307
+ """
308
+ knex = state.get_knex()
309
+ result_folder = handle_folder_id(knex, folder_id, yes=yes, private=private)
310
+
311
+ if not geoseeq_file_name:
312
+ geoseeq_file_name = basename(file_path)
313
+
314
+ if not overwrite and result_folder.result_file(geoseeq_file_name).exists():
315
+ raise click.UsageError(f'{geoseeq_file_name} already exists in {result_folder}. Use --overwrite to overwrite it.')
316
+
317
+ result_file = result_folder.result_file(geoseeq_file_name)
318
+ smart_table = result_file.as_smart_table()
319
+ smart_table.import_csv(file_path)
320
+
321
+
322
+ @click.command('smart-tree')
323
+ @use_common_state
324
+ @click.option('-m/-nm', '--make-name-map/--no-name-map', default=True, help="Create a sample name map with all samples currently in the project.")
325
+ @overwrite_option
326
+ @yes_option
327
+ @private_option
328
+ @click.option('-n', '--geoseeq-file-name', default=None,
329
+ help='Specify a different name for the file on GeoSeeq than the local file name.',
330
+ show_default=True)
331
+ @folder_id_arg
332
+ @click.argument('newick_file_path', type=click.Path(exists=True), nargs=1)
333
+ def cli_upload_smart_tree(state, make_name_map, overwrite, yes, private, folder_id, geoseeq_file_name, newick_file_path):
334
+ """Upload a smart tree to GeoSeeq.
335
+
336
+ This command uploads a smart tree to a project or sample on GeoSeeq. It can be used to upload
337
+ a single file to a folder at once.
338
+
339
+ ---
340
+
341
+ Example Usage:
342
+
343
+ \b
344
+ # Upload a smart tree from a file
345
+ $ geoseeq upload smart-tree "My Org/My Project/My Sample/My Folder" /path/to/my_tree.nwk
346
+
347
+ \b
348
+ # Upload a smart tree from a file but name it "My Smart Tree" on GeoSeeq
349
+ $ geoseeq upload smart-tree "My Org/My Project/My Sample/My Folder" /path/to/my_tree.nwk -n "My Smart Tree"
350
+
351
+ ---
352
+
353
+ Command Arguments:
354
+
355
+ [FOLDER_ID] Can be a folder UUID, GeoSeeq Resource Number (GRN), or an
356
+ names for an org, project, sample, folder separated by a slash. Can exclude
357
+ the sample name if the folder is for a project.
358
+
359
+ [NEWICK_FILE_PATH] A path to a newick file on your local machine.
360
+
361
+ ---
362
+ """
363
+ knex = state.get_knex()
364
+ result_folder = handle_folder_id(knex, folder_id, yes=yes, private=private)
365
+
366
+ if not geoseeq_file_name:
367
+ geoseeq_file_name = basename(newick_file_path)
368
+
369
+ if not overwrite and result_folder.result_file(geoseeq_file_name).exists():
370
+ raise click.UsageError(f'{geoseeq_file_name} already exists in {result_folder}. Use --overwrite to overwrite it.')
371
+
372
+ result_file = result_folder.result_file(geoseeq_file_name)
373
+ smart_tree = result_file.as_smart_tree()
374
+ with open(newick_file_path) as f:
375
+ newick_str = f.read()
376
+ smart_tree.create_from_newick(newick_str)
377
+ if make_name_map:
378
+ smart_tree.add_all_samples_to_map(result_folder.project)
379
+ smart_tree.idem()
@@ -24,6 +24,16 @@ from geoseeq.cli.shared_params import (
24
24
 
25
25
  from geoseeq.constants import FASTQ_MODULE_NAMES
26
26
  from geoseeq.cli.progress_bar import PBarManager
27
+ import pandas as pd
28
+ from typing import Dict, Optional
29
+ from geoseeq.id_constructors.from_ids import (
30
+ org_from_id,
31
+ project_from_id,
32
+ sample_from_id,
33
+ result_folder_from_id,
34
+ result_file_from_id,
35
+ )
36
+ from geoseeq.upload_download_manager import GeoSeeqUploadManager
27
37
 
28
38
  logger = logging.getLogger('geoseeq_api')
29
39
 
@@ -90,3 +100,223 @@ def cli_find_urls_for_reads(state, cores, overwrite, yes, regex, private, module
90
100
  groups = _group_files(knex, filepaths, module_name, regex, yes)
91
101
  for file_name, target_url in _find_target_urls(groups, module_name, proj, filepaths, overwrite, cores, state):
92
102
  print(f'{file_name}\t{target_url}', file=state.outfile)
103
+
104
+
105
+ def _get_result_file_from_record_with_ids(knex, record: Dict) -> Dict:
106
+ """Get all relevant objects from a record, handling GRNs, UUIDs, and absolute names without requiring parent objects.
107
+
108
+ Returns a dict with 'org', 'project', 'sample', 'folder', and 'result_file' keys.
109
+ Objects may be None if not needed/specified.
110
+ Guaranteed that at least org is not None.
111
+ """
112
+ objects = {
113
+ 'org': None,
114
+ 'project': None,
115
+ 'sample': None,
116
+ 'folder': None,
117
+ 'result_file': None
118
+ }
119
+
120
+ # Try to get file directly - if it's a GRN/UUID we don't need parent objects
121
+ try:
122
+ objects['result_file'] = result_file_from_id(knex, record['filename'])
123
+ objects['folder'] = objects['result_file'].folder
124
+ if hasattr(objects['folder'], 'sample'):
125
+ objects['sample'] = objects['folder'].sample
126
+ objects['project'] = objects['sample'].project
127
+ else:
128
+ objects['project'] = objects['folder'].project
129
+ objects['org'] = objects['project'].org
130
+ return objects
131
+ except ValueError:
132
+ pass # Not a GRN, UUID or abs name. Continue with normal flow
133
+
134
+ # Try to get folder directly - if it's a GRN/UUID we don't need parent objects
135
+ try:
136
+ objects['folder'] = result_folder_from_id(knex, record['folder'])
137
+ # Get parent objects from folder
138
+ if hasattr(objects['folder'], 'sample'):
139
+ objects['sample'] = objects['folder'].sample
140
+ objects['project'] = objects['sample'].project
141
+ else:
142
+ objects['project'] = objects['folder'].project
143
+ objects['org'] = objects['project'].org
144
+ return objects
145
+ except ValueError:
146
+ pass # Not a GRN, UUID or abs name. Continue with normal flow
147
+
148
+ # Try to get sample directly if specified
149
+ if pd.notna(record['sample']):
150
+ try:
151
+ objects['sample'] = sample_from_id(knex, record['sample'])
152
+ objects['project'] = objects['sample'].project
153
+ objects['org'] = objects['project'].org
154
+ return objects
155
+ except ValueError:
156
+ pass # Not a GRN, UUID or abs name. Continue with normal flow
157
+
158
+ # Try to get project directly
159
+ try:
160
+ objects['project'] = project_from_id(knex, record['project'])
161
+ objects['org'] = objects['project'].org
162
+ return objects
163
+ except ValueError:
164
+ pass # Not a GRN/UUID, continue
165
+
166
+
167
+ if objects['org'] is None: # Get org directly if we don't have one yet
168
+ objects['org'] = org_from_id(knex, record['organization'])
169
+
170
+ return objects
171
+
172
+
173
+ def _get_result_file_from_record(knex, record: Dict) -> Dict:
174
+ """Get all relevant objects from a record, handling GRNs/UUIDs without requiring parent objects.
175
+
176
+ Returns a dict with 'org', 'project', 'sample', 'folder', and 'result_file' keys.
177
+ Objects may be None if not needed/specified.
178
+ """
179
+ objects = _get_result_file_from_record_with_ids(knex, record)
180
+
181
+ if objects['project'] is None:
182
+ objects['project'] = objects['org'].project(record['project'])
183
+
184
+ if objects['sample'] is None:
185
+ if pd.notna(record['sample']):
186
+ objects['sample'] = objects['project'].sample(record['sample'])
187
+ parent = objects['sample']
188
+ else:
189
+ parent = objects['project']
190
+
191
+ if objects['folder'] is None:
192
+ objects['folder'] = parent.result_folder(record['folder'])
193
+
194
+ if objects['result_file'] is None:
195
+ objects['result_file'] = objects['folder'].result_file(record['filename'])
196
+
197
+ objects['result_file'].idem()
198
+ print(objects)
199
+ return objects
200
+
201
+
202
+ def _add_record_to_upload_manager_local_file(record: Dict, result_file, upload_manager: GeoSeeqUploadManager) -> None:
203
+ """Add a local file upload to the upload manager."""
204
+ upload_manager.add_result_file(result_file, record['path'], link_type='upload')
205
+
206
+
207
+ def _add_record_to_upload_manager_s3_file(record: Dict, result_file, upload_manager: GeoSeeqUploadManager) -> None:
208
+ """Add an S3 file link to the upload manager.
209
+
210
+ Handles two types of S3 URLs:
211
+ 1. https://endpoint/bucket/key - Full URL with endpoint included
212
+ 2. s3://bucket/key - S3 protocol URL that needs endpoint added
213
+ """
214
+ path = record['path']
215
+
216
+ if path.startswith('s3://'):
217
+ # Convert s3:// URL to https:// URL
218
+ if not record['endpoint_url']:
219
+ raise ValueError("endpoint_url is required for s3:// URLs")
220
+
221
+ # Remove s3:// prefix and combine with endpoint
222
+ bucket_and_key = path[5:] # len('s3://') == 5
223
+ path = f"{record['endpoint_url'].rstrip('/')}/{bucket_and_key}"
224
+ elif not path.startswith('https://'):
225
+ raise ValueError("S3 URLs must start with either 's3://' or 'https://'")
226
+
227
+ upload_manager.add_result_file(result_file, path, link_type='s3')
228
+
229
+
230
+ def _upload_one_record(knex, record: Dict, overwrite: bool, upload_manager: GeoSeeqUploadManager) -> Dict:
231
+ """Process a single record from the config file and add it to the upload manager."""
232
+ objects = _get_result_file_from_record(knex, record)
233
+ if not objects['result_file']:
234
+ raise ValueError(f"Could not find or create result_file from record: {record}")
235
+
236
+ # Add to upload manager based on type
237
+ if record['type'].lower() == 'local':
238
+ _add_record_to_upload_manager_local_file(record, objects["result_file"], upload_manager)
239
+ elif record['type'].lower() == 's3':
240
+ _add_record_to_upload_manager_s3_file(record, objects["result_file"], upload_manager)
241
+ else:
242
+ raise ValueError(f"Unknown file type: {record['type']}")
243
+
244
+ return objects
245
+
246
+
247
+ REQUIRED_COLUMNS = [
248
+ 'organization', 'project', 'sample', 'folder',
249
+ 'filename', 'path', 'type', 'endpoint_url'
250
+ ]
251
+
252
+
253
+ @click.command('from-config')
254
+ @use_common_state
255
+ @click.option('--cores', default=1, help='Number of uploads to run in parallel')
256
+ @click.option('--sep', default=',', help='Separator character for the CSV file')
257
+ @overwrite_option
258
+ @yes_option
259
+ @click.argument('config_file', type=click.Path(exists=True))
260
+ def cli_upload_from_config(state, cores, sep, overwrite, yes, config_file):
261
+ """Upload files to GeoSeeq based on a configuration CSV file.
262
+
263
+ \b
264
+ The CSV file must have the following columns:
265
+ - organization: Organization name, GRN, or UUID (optional if project/sample/folder specified by GRN/UUID)
266
+ - project: Project name, GRN, or UUID (optional if sample/folder specified by GRN/UUID)
267
+ - sample: Sample name, GRN, or UUID (optional, also optional if folder specified by GRN/UUID)
268
+ - folder: Folder name, GRN, or UUID
269
+ - filename: Name to give the file on GeoSeeq
270
+ - path: Path to local file or S3 URL
271
+ - type: Either "local" or "s3"
272
+ - endpoint_url: S3 endpoint URL (required for S3 files)
273
+
274
+ \b
275
+ When using GRNs or UUIDs, you can omit the parent object IDs. For example:
276
+ - If folder is a GRN/UUID, organization/project/sample can be blank
277
+ - If sample is a GRN/UUID, organization/project can be blank
278
+ - If project is a GRN/UUID, organization can be blank
279
+
280
+ \b
281
+ Example config.csv:
282
+ organization,project,sample,folder,filename,path,type,endpoint_url
283
+ MyOrg,MyProject,Sample1,reads,file1.fastq,/path/to/file1.fastq,local,
284
+ ,grn:project:uuid,Sample2,reads,file2.fastq,/path/to/file2.fastq,local,
285
+ ,,grn:sample:uuid,reads,file3.fastq,/path/to/file3.fastq,local,
286
+ ,,,grn:folder:uuid,file4.fastq,s3://bucket/file4.fastq,s3,https://s3.amazonaws.com
287
+
288
+ \b
289
+ Example with tab separator:
290
+ $ geoseeq upload advanced from-config --sep $'\t' config.tsv
291
+ """
292
+ knex = state.get_knex()
293
+
294
+ # Read and validate config file
295
+ df = pd.read_csv(config_file, sep=sep)
296
+ missing_cols = set(REQUIRED_COLUMNS) - set(df.columns)
297
+ if missing_cols:
298
+ raise click.UsageError(f"Config file missing required columns: {missing_cols}")
299
+
300
+ # Create upload manager
301
+ upload_manager = GeoSeeqUploadManager(
302
+ n_parallel_uploads=cores,
303
+ progress_tracker_factory=PBarManager().get_new_bar,
304
+ log_level=state.log_level,
305
+ overwrite=overwrite,
306
+ use_cache=state.use_cache,
307
+ )
308
+
309
+ # Process records and add to upload manager
310
+ objects_by_record = {} # Store objects for human readable paths
311
+ for _, record in df.iterrows():
312
+ objects = _upload_one_record(knex, record, overwrite, upload_manager)
313
+ objects_by_record[record['path']] = objects
314
+
315
+ # Show preview with both technical and human readable paths
316
+ click.echo(upload_manager.get_preview_string(), err=True)
317
+
318
+ if not yes:
319
+ click.confirm('Do you want to proceed with these uploads?', abort=True)
320
+
321
+ # Perform uploads
322
+ upload_manager.upload_files()
@@ -2,7 +2,7 @@ import logging
2
2
  import click
3
3
  import requests
4
4
  from os.path import basename
5
-
5
+ import pandas as pd
6
6
  from multiprocessing import Pool, current_process
7
7
 
8
8
  from geoseeq.cli.constants import *
@@ -67,8 +67,12 @@ def _get_regex(knex, filepaths, module_name, lib, regex):
67
67
  return regex
68
68
 
69
69
 
70
- def _group_files(knex, filepaths, module_name, regex, yes):
70
+ def _group_files(knex, filepaths, module_name, regex, yes, name_map):
71
71
  """Group the files into samples, confirm, and return the groups."""
72
+ if name_map is not None:
73
+ name_map_filename, cur_col, new_col = name_map
74
+ name_map = pd.read_csv(name_map_filename)[[cur_col, new_col]]
75
+ name_map = name_map.set_index(cur_col).to_dict()
72
76
  seq_length, seq_type = module_name.split('::')[:2]
73
77
  groups = knex.post('bulk_upload/group_files', json={
74
78
  'filenames': list(filepaths.keys()),
@@ -76,7 +80,11 @@ def _group_files(knex, filepaths, module_name, regex, yes):
76
80
  'regex': regex
77
81
  })
78
82
  for group in groups:
79
- click.echo(f'sample_name: {group["sample_name"]}', err=True)
83
+ sample_name = group["sample_name"]
84
+ if name_map:
85
+ sample_name = name_map.get(sample_name, sample_name)
86
+ group["sample_name"] = sample_name
87
+ click.echo(f'sample_name: {sample_name}', err=True)
80
88
  click.echo(f' module_name: {module_name}', err=True)
81
89
  for field_name, filename in group['fields'].items():
82
90
  path = filepaths[filename]
@@ -173,10 +181,11 @@ def flatten_list_of_bams(filepaths):
173
181
  @private_option
174
182
  @link_option
175
183
  @no_new_versions_option
184
+ @click.option('--name-map', default=None, nargs=3, help="A file to use for converting names. Takes three arguments: a file name, a column name for current names, and a column name for new names.")
176
185
  @module_option(FASTQ_MODULE_NAMES)
177
186
  @project_id_arg
178
187
  @click.argument('fastq_files', type=click.Path(exists=True), nargs=-1)
179
- def cli_upload_reads_wizard(state, cores, overwrite, yes, regex, private, link_type, no_new_versions, module_name, project_id, fastq_files):
188
+ def cli_upload_reads_wizard(state, cores, overwrite, yes, regex, private, link_type, no_new_versions, name_map, module_name, project_id, fastq_files):
180
189
  """Upload fastq read files to GeoSeeq.
181
190
 
182
191
  This command automatically groups files by their sample name, lane number
geoseeq/constants.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from os import environ
2
2
  from os.path import join
3
+ from typing import Literal
3
4
 
4
5
  FIVE_MB = 5 * (1024 ** 2)
5
6
  FASTQ_MODULE_NAMES = [
@@ -13,4 +14,6 @@ DEFAULT_ENDPOINT = "https://backend.geoseeq.com"
13
14
 
14
15
  CONFIG_FOLDER = environ.get("XDG_CONFIG_HOME", join(environ["HOME"], ".config"))
15
16
  CONFIG_DIR = environ.get("GEOSEEQ_CONFIG_DIR", join(CONFIG_FOLDER, "geoseeq"))
16
- PROFILES_PATH = join(CONFIG_DIR, "profiles.json")
17
+ PROFILES_PATH = join(CONFIG_DIR, "profiles.json")
18
+
19
+ OBJECT_TYPE_STR = Literal['org', 'project', 'sample', 'sample_result_folder', 'project_result_folder', 'sample_result_file', 'project_result_file']
@@ -22,51 +22,51 @@ from .from_names import (
22
22
  project_result_file_from_name,
23
23
  result_file_from_name,
24
24
  )
25
- from .utils import is_grn_or_uuid, is_name
25
+ from .utils import is_grn_or_uuid, is_name, is_abs_name
26
26
  from geoseeq.knex import with_knex
27
-
27
+ from geoseeq.constants import OBJECT_TYPE_STR
28
28
  logger = logging.getLogger("geoseeq_api") # Same name as calling module
29
29
 
30
30
 
31
- def _generic_from_id(knex, id, from_uuid_func, from_name_func):
31
+ def _generic_from_id(knex, id, from_uuid_func, from_name_func, object_type_str: OBJECT_TYPE_STR):
32
32
  """Return the object which the id points to."""
33
33
  logger.debug(f'Getting object from id: {id}, knex: {knex}, from_uuid_func: {from_uuid_func}, from_name_func: {from_name_func}')
34
34
  if is_grn_or_uuid(id):
35
35
  id = id.split(':')[-1] # if this is a GRN, get the UUID. Won't hurt if it's already a UUID.
36
36
  return from_uuid_func(knex, id)
37
- if is_name(id):
37
+ if is_abs_name(id, object_type_str):
38
38
  return from_name_func(knex, id)
39
- raise ValueError(f'"{id}" is not a GRN, UUID, or name')
39
+ raise ValueError(f'"{id}" is not a GRN, UUID, or absolute name for {object_type_str}')
40
40
 
41
41
 
42
42
  @with_knex
43
43
  def org_from_id(knex, id):
44
44
  """Return the organization object which the id points to."""
45
- return _generic_from_id(knex, id, org_from_uuid, org_from_name)
45
+ return _generic_from_id(knex, id, org_from_uuid, org_from_name, 'org')
46
46
 
47
47
 
48
48
  @with_knex
49
49
  def project_from_id(knex, id):
50
50
  """Return the project object which the id points to."""
51
- return _generic_from_id(knex, id, project_from_uuid, project_from_name)
51
+ return _generic_from_id(knex, id, project_from_uuid, project_from_name, 'project')
52
52
 
53
53
 
54
54
  @with_knex
55
55
  def sample_from_id(knex, id):
56
56
  """Return the sample object which the id points to."""
57
- return _generic_from_id(knex, id, sample_from_uuid, sample_from_name)
57
+ return _generic_from_id(knex, id, sample_from_uuid, sample_from_name, 'sample')
58
58
 
59
59
 
60
60
  @with_knex
61
61
  def sample_result_folder_from_id(knex, id):
62
62
  """Return the sample result folder object which the id points to."""
63
- return _generic_from_id(knex, id, sample_result_folder_from_uuid, sample_result_folder_from_name)
63
+ return _generic_from_id(knex, id, sample_result_folder_from_uuid, sample_result_folder_from_name, 'sample_result_folder')
64
64
 
65
65
 
66
66
  @with_knex
67
67
  def project_result_folder_from_id(knex, id):
68
68
  """Return the project result folder object which the id points to."""
69
- return _generic_from_id(knex, id, project_result_folder_from_uuid, project_result_folder_from_name)
69
+ return _generic_from_id(knex, id, project_result_folder_from_uuid, project_result_folder_from_name, 'project_result_folder')
70
70
 
71
71
 
72
72
  @with_knex
@@ -75,19 +75,22 @@ def result_folder_from_id(knex, id):
75
75
 
76
76
  Guess the result folder is a sample result folder. If not, try a project result folder.
77
77
  """
78
- return _generic_from_id(knex, id, result_folder_from_uuid, result_folder_from_name)
78
+ try:
79
+ return _generic_from_id(knex, id, result_folder_from_uuid, result_folder_from_name, 'sample_result_folder')
80
+ except ValueError:
81
+ return _generic_from_id(knex, id, result_folder_from_uuid, result_folder_from_name, 'project_result_folder')
79
82
 
80
83
 
81
84
  @with_knex
82
85
  def sample_result_file_from_id(knex, id):
83
86
  """Return the sample result file object which the id points to."""
84
- return _generic_from_id(knex, id, sample_result_file_from_uuid, sample_result_file_from_name)
87
+ return _generic_from_id(knex, id, sample_result_file_from_uuid, sample_result_file_from_name, 'sample_result_file')
85
88
 
86
89
 
87
90
  @with_knex
88
91
  def project_result_file_from_id(knex, id):
89
92
  """Return the project result file object which the id points to."""
90
- return _generic_from_id(knex, id, project_result_file_from_uuid, project_result_file_from_name)
93
+ return _generic_from_id(knex, id, project_result_file_from_uuid, project_result_file_from_name, 'project_result_file')
91
94
 
92
95
 
93
96
  @with_knex
@@ -96,7 +99,10 @@ def result_file_from_id(knex, id):
96
99
 
97
100
  Guess the result file is a sample result file. If not, try a project result file.
98
101
  """
99
- return _generic_from_id(knex, id, result_file_from_uuid, result_file_from_name)
102
+ try:
103
+ return _generic_from_id(knex, id, result_file_from_uuid, result_file_from_name, 'sample_result_file')
104
+ except ValueError:
105
+ return _generic_from_id(knex, id, result_file_from_uuid, result_file_from_name, 'project_result_file')
100
106
 
101
107
 
102
108
 
@@ -1,4 +1,5 @@
1
1
  import uuid
2
+ from geoseeq.constants import OBJECT_TYPE_STR
2
3
 
3
4
 
4
5
  def is_grn(el):
@@ -29,6 +30,22 @@ def is_name(el):
29
30
  return False
30
31
 
31
32
 
33
+ def is_abs_name(el, object_type_str: OBJECT_TYPE_STR) -> bool:
34
+ """Return True if `el` is an absolute name for the given object type."""
35
+ if is_grn_or_uuid(el):
36
+ return False
37
+ n_required_slashes = {
38
+ 'org': 0,
39
+ 'project': 1,
40
+ 'sample': 2,
41
+ 'sample_result_folder': 3,
42
+ 'sample_result_file': 4,
43
+ 'project_result_folder': 2,
44
+ 'project_result_file': 3,
45
+ }[object_type_str]
46
+ return el.count('/') == n_required_slashes
47
+
48
+
32
49
  def is_grn_or_uuid(el):
33
50
  """Return True if `el` is a GRN or a UUID"""
34
51
  return is_grn(el) or is_uuid(el)
@@ -18,9 +18,10 @@ from geoseeq.knex import GeoseeqOtherError
18
18
  from .utils import *
19
19
  from .file_upload import ResultFileUpload
20
20
  from .file_download import ResultFileDownload
21
+ from .smart_objects import ResultFileSmartObjects
21
22
 
22
23
 
23
- class ResultFile(RemoteObject, ResultFileUpload, ResultFileDownload):
24
+ class ResultFile(RemoteObject, ResultFileUpload, ResultFileDownload, ResultFileSmartObjects):
24
25
  remote_fields = [
25
26
  "uuid",
26
27
  "created_at",
@@ -225,6 +225,10 @@ class SampleResultFolder(ResultFolder, SampleBioInfoFolder):
225
225
 
226
226
  def get_fields(self, *args, **kwargs):
227
227
  return self.get_result_files(*args, **kwargs)
228
+
229
+ @property
230
+ def project(self):
231
+ return self.sample.project
228
232
 
229
233
  def __str__(self):
230
234
  return f"<Geoseeq::SampleResultFolder {self.module_name} {self.replicate} {self.uuid} />"
@@ -288,6 +292,10 @@ class ProjectResultFolder(ResultFolder):
288
292
 
289
293
  def get_fields(self, *args, **kwargs):
290
294
  return self.get_result_files(*args, **kwargs)
295
+
296
+ @property
297
+ def project(self):
298
+ return self.grp
291
299
 
292
300
  def __str__(self):
293
301
  return f"<Geoseeq::ProjectResultFolder {self.module_name} {self.replicate} {self.uuid} />"
@@ -0,0 +1,40 @@
1
+
2
+
3
+ class ResultFileSmartObjects:
4
+ """Interface for ResultFiles to easily create and use smart objects like tables"""
5
+
6
+ def is_smart_table(self):
7
+ """Return True iff this ResultFile is a smart table."""
8
+ return self.stored_data.get("__type__") == "smartTable"
9
+
10
+ def as_smart_table(self, description=""):
11
+ """Return a SmartTable object for this ResultFile. Will create a new SmartTable if it doesn't exist yet."""
12
+ if self.exists() and not self.is_smart_table():
13
+ raise ValueError("ResultFile exists but is not a smart table")
14
+ from geoseeq.smart_table import SmartTable # import here to avoid circular import
15
+ if self.exists(): # already a smart table
16
+ smart_table = SmartTable(self.knex, self.name, connected_file_id=self.uuid).get()
17
+ else: # create a new smart table
18
+ smart_table = SmartTable(self.knex, self.name, connected_file_id=self.uuid, description=description)
19
+ smart_table.create(self.parent)
20
+ return smart_table
21
+
22
+ def is_smart_tree(self):
23
+ """Return True iff this ResultFile is a smart tree."""
24
+ return self.stored_data.get("__type__") == "phylodynamics"
25
+
26
+ def as_smart_tree(self, newick_str=None, sample_name_id_map=None):
27
+ """Return a SmartTree object for this ResultFile. Will create a new SmartTree if it doesn't exist yet."""
28
+ if self.exists() and not self.is_smart_tree():
29
+ raise ValueError("ResultFile exists but is not a smart tree")
30
+ from geoseeq.smart_tree import SmartTree # import here to avoid circular import
31
+ if self.exists(): # already a smart tree
32
+ smart_tree = SmartTree.from_blob(self, self.stored_data)
33
+ else: # create a new smart tree
34
+ smart_tree = SmartTree(self)
35
+ if newick_str is not None:
36
+ smart_tree.create_from_newick(newick_str, sample_name_id_map)
37
+ smart_tree.create()
38
+ return smart_tree
39
+
40
+
geoseeq/smart_table.py CHANGED
@@ -53,11 +53,12 @@ class SmartTable(RemoteObject):
53
53
 
54
54
  without_default_columns: if False the server creates 3 example columns.
55
55
  """
56
-
56
+ if description:
57
+ self.description = description
57
58
  data = {
58
59
  "name": self.name,
59
60
  "folder_id": result_folder.uuid,
60
- "description": description,
61
+ "description": self.description,
61
62
  }
62
63
  url = f"table?without_default_columns={without_default_columns}"
63
64
  blob = self.knex.post(url, json=data)
geoseeq/smart_tree.py ADDED
@@ -0,0 +1,57 @@
1
+ class SmartTree:
2
+
3
+ def __init__(self, result_file):
4
+ self.result_file = result_file # the result file that contains the smart tree
5
+ self.content = None
6
+
7
+ def create_from_newick(self, newick_str, sample_name_id_map=None):
8
+ self.content = {
9
+ "__type__": "phylodynamics",
10
+ "version": "1.0",
11
+ "tree": {
12
+ "value": newick_str,
13
+ "kind": "newick",
14
+ "sampleNameIdMap": {} if sample_name_id_map is None else sample_name_id_map,
15
+ }
16
+ }
17
+ return self
18
+
19
+ def add_sample_to_map(self, sample):
20
+ if self.content is None:
21
+ raise ValueError("Must create tree before adding samples")
22
+ self.content["tree"]["sampleNameIdMap"][sample.name] = sample.uuid
23
+ return self
24
+
25
+ def add_all_samples_to_map(self, project):
26
+ for sample in project.get_samples():
27
+ self.add_sample_to_map(sample)
28
+
29
+ @classmethod
30
+ def from_blob(cls, result_file, blob):
31
+ smart_tree = cls(result_file)
32
+ smart_tree.content = blob
33
+ return smart_tree
34
+
35
+ def save(self):
36
+ if self.content is None:
37
+ raise ValueError("Must create tree before saving")
38
+ self.result_file.upload_json(self.content)
39
+ return self
40
+
41
+ def idem(self):
42
+ if self.content is None:
43
+ raise ValueError("Must create tree before saving")
44
+ self.result_file.upload_json(self.content)
45
+ return self
46
+
47
+ def get(self):
48
+ self.result_file.get()
49
+ self.content = self.result_file.stored_data
50
+ return self
51
+
52
+ def create(self):
53
+ if self.content is None:
54
+ raise ValueError("Must create tree before saving")
55
+ self.result_file.upload_json(self.content)
56
+ return self
57
+
@@ -77,8 +77,17 @@ class GeoSeeqUploadManager:
77
77
  self.chunk_size_mb = chunk_size_mb
78
78
  self.use_atomic_upload = use_atomic_upload
79
79
 
80
- def add_result_file(self, result_file, local_path):
81
- self._result_files.append((result_file, local_path))
80
+ def add_result_file(self, result_file, local_path, link_type=None):
81
+ """Add a result file to be uploaded or linked.
82
+
83
+ Args:
84
+ result_file: A ResultFile object to upload to or link
85
+ local_path: Path to local file or URL to link
86
+ link_type: One of 'upload', 's3', 'ftp', 'azure', 'sra', 'http'
87
+ """
88
+ if link_type is None:
89
+ link_type = self.link_type
90
+ self._result_files.append((result_file, local_path, link_type))
82
91
 
83
92
  def add_local_file_to_result_folder(self, result_folder, local_path, geoseeq_file_name=None):
84
93
  if not geoseeq_file_name:
@@ -96,19 +105,20 @@ class GeoSeeqUploadManager:
96
105
 
97
106
  def get_preview_string(self):
98
107
  out = ["Upload Preview:"]
99
- for result_file, local_path in self._result_files:
100
- out.append(f"{local_path} -> {result_file}")
108
+ for result_file, local_path, link_type in self._result_files:
109
+ action = "link" if link_type != 'upload' else "upload"
110
+ out.append(f"{local_path} -> {result_file} ({action})")
101
111
  return "\n".join(out)
102
112
 
103
113
  def upload_files(self):
104
114
  upload_args = [(
105
115
  result_file, local_path,
106
116
  self.session, self.progress_tracker_factory(local_path),
107
- self.link_type, self.overwrite, self.log_level,
117
+ link_type, self.overwrite, self.log_level,
108
118
  self.n_parallel_uploads > 1, self.use_cache, self.no_new_versions,
109
119
  self.threads_per_upload, self.num_retries, self.ignore_errors,
110
120
  self.chunk_size_mb, self.use_atomic_upload
111
- ) for result_file, local_path in self._result_files
121
+ ) for result_file, local_path, link_type in self._result_files
112
122
  ]
113
123
  out = []
114
124
  if self.n_parallel_uploads == 1:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: geoseeq
3
- Version: 0.6.14.dev7
3
+ Version: 0.7.0
4
4
  Summary: GeoSeeq command line tools and python API
5
5
  Author: David C. Danko
6
6
  Author-email: "David C. Danko" <dcdanko@biotia.io>
@@ -18,6 +18,7 @@ Requires-Dist: pandas
18
18
  Requires-Dist: biopython
19
19
  Requires-Dist: tqdm
20
20
  Dynamic: author
21
+ Dynamic: license-file
21
22
 
22
23
  # Geoseeq API Client
23
24
 
@@ -2,7 +2,7 @@ geoseeq/__init__.py,sha256=YqYqeHbqjgWI5OBIxkPXNvLISOjVWaNwVFy6AGJ7uwc,982
2
2
  geoseeq/app.py,sha256=Y6d1UzxFLfE3RNccATbFCVi6kH3eFmzwoUbeR2Ry09A,2387
3
3
  geoseeq/blob_constructors.py,sha256=AkWpDQY0EdGMxF1p6eRspyHKubcUdiW4it-_Q7S2QWk,188
4
4
  geoseeq/bulk_creators.py,sha256=pdn-Dv7yv5SFv-PfDuQbuOnw2W4-BfIfRJVRAhM8U6s,2115
5
- geoseeq/constants.py,sha256=g9YEczT2ssteTrdgFrRimXo5I0C8-MqftoG-hE3KAkg,489
5
+ geoseeq/constants.py,sha256=KmWH3gwHLd3toTWLWSViV_1eAYzPOPZ5bv6INpgwzQI,665
6
6
  geoseeq/file_system_cache.py,sha256=HzVZWtwLD2fjWWSo_UfWmGeBltm9He4lP_OqzKwNGWg,4138
7
7
  geoseeq/knex.py,sha256=zcjafsmUn9SC3LlRnvvaXpr-pHYZ0IXk7LpzuUoE3MI,8312
8
8
  geoseeq/organization.py,sha256=bJkYL8_D-k6IYAaii2ZbxjwYnXy6lvu6iLXscxKlA3w,2542
@@ -11,8 +11,9 @@ geoseeq/project.py,sha256=Ba3yvrSyPVnEM5_VGSQu6YPH3CTwE3fHs4JjAepEK7g,13880
11
11
  geoseeq/remote_object.py,sha256=GYN6PKU7Zz3htIdpFjfZiFejzGqqJHbJyKlefM1Eixk,7151
12
12
  geoseeq/sample.py,sha256=HAfMiDPHp1UJgIA2lI6oGnNit4YKyj7nx9X07CCN98U,8316
13
13
  geoseeq/search.py,sha256=gawad6Cx5FxJBPlYkXWb-UKAO-UC0_yhvyU9Ca1kaNI,3388
14
- geoseeq/smart_table.py,sha256=X1y9nBr8BkMNcBqdaiXtlqLCTfgc7lvdjSlAGxppvLo,6098
15
- geoseeq/upload_download_manager.py,sha256=eNR_Lw6JJstZ2X1Si43MM-0JZGv9GT-4fDjykaoH38A,9368
14
+ geoseeq/smart_table.py,sha256=rihMsFUIn-vn4w6ukVZTHI9bjDSEr8xHExBfX8mwCHM,6169
15
+ geoseeq/smart_tree.py,sha256=bSjDlwmOuNXutYJhytA1RovwRCHV6ZxXXJPiIGFhPaA,1825
16
+ geoseeq/upload_download_manager.py,sha256=DxzwhkpuzuXTQt28ORULspDi2rzXqqIHqROEAs99SxQ,9849
16
17
  geoseeq/user.py,sha256=tol8i1UGLRrbMw5jeJDnna1ikRgrCDd50Jxz0a1lSgg,690
17
18
  geoseeq/utils.py,sha256=ZXpWb2MetUIeLrExiXb7IaOXYrW1pvrdP3o0KWzbwCs,4035
18
19
  geoseeq/work_orders.py,sha256=5uLVVfdKE8qh4gGaHkdBpXJGRTujuSg59knWCqEET4A,8071
@@ -20,11 +21,11 @@ geoseeq/cli/__init__.py,sha256=4WnK87K5seRK3SGJAxNWnQTqyg5uBhdhrOrzB1D4b3M,24
20
21
  geoseeq/cli/constants.py,sha256=Do5AUf9lMO9_P8KpFJ3XwwFBAWsxSjZ6sx9_QEGyC_c,176
21
22
  geoseeq/cli/copy.py,sha256=02U9kdrAIbbM8MlRMLL6p-LMYFSuRObE3h5jyvcL__M,2275
22
23
  geoseeq/cli/detail.py,sha256=q8Suu-j2k18knfSVFG-SWWGNsKM-n8y9RMA3LcIIi9Y,4132
23
- geoseeq/cli/download.py,sha256=cOLIVzAfxpOJnSd4WKWR-HCkOrKjPMbgYaNXDSd9i90,21305
24
+ geoseeq/cli/download.py,sha256=Znjuc9IFOcIa5_Od9mFXHJdYAJtgw9Bc_wPPcOVXn7s,21298
24
25
  geoseeq/cli/fastq_utils.py,sha256=-bmeQLaiMBm57zWOF0R5OlWTU0_3sh1JBC1RYw2BOFM,3083
25
26
  geoseeq/cli/find_grn.py,sha256=oMDxkzGQBQb2_cCuvmwoeHOsFHqyO9RLeJzrB6bAe5M,439
26
27
  geoseeq/cli/get_eula.py,sha256=79mbUwyiF7O1r0g6UTxG9kJGQEqKuH805E6eLkPC6Y4,997
27
- geoseeq/cli/main.py,sha256=guwmM9RE-vVZcu7_rMyOLiet-Ua1Jw_VP0mk62cT4eQ,3987
28
+ geoseeq/cli/main.py,sha256=pkpjc_5dZ8T0F92777OKRXT_EFoHelfw4DhKjEcXqIE,3982
28
29
  geoseeq/cli/manage.py,sha256=wGXAcVaXqE5JQEU8Jh6OlHr02nB396bpS_SFcOZdrEo,5929
29
30
  geoseeq/cli/progress_bar.py,sha256=p1Xl01nkYxSBZCB30ue2verIIi22W93m3ZAMAxipD0g,738
30
31
  geoseeq/cli/project.py,sha256=V5SdXm2Hwo2lxrkpwRDedw-mAE4XnM2uwT-Gj1D90VQ,3030
@@ -40,10 +41,10 @@ geoseeq/cli/shared_params/config.py,sha256=HQ0xQh_jdt3EKI5VXYqQXzo-s8Rm6YlziMyVX
40
41
  geoseeq/cli/shared_params/id_handlers.py,sha256=KtzflnplYVkXsyqI5Ej6r-_BwQnuXVHPr7JcYumTKNc,10700
41
42
  geoseeq/cli/shared_params/obj_getters.py,sha256=ZSkt6LnDkVFlNVYKgLrjzg60-6BthZMr3eeD3HNqzac,2741
42
43
  geoseeq/cli/shared_params/opts_and_args.py,sha256=_DcJ-TqgrbBaeDd-kuHEx2gLZPQN6EHZYWh8Ag-d8Vg,2091
43
- geoseeq/cli/upload/__init__.py,sha256=3C9_S9t7chmYU-2ot89NV03x-EtmsjibulErKaU9w1k,627
44
- geoseeq/cli/upload/upload.py,sha256=JZkhe1q3KOp7-tKyzwi860TQhZoNDnZs4yB2PJhOjl0,10081
45
- geoseeq/cli/upload/upload_advanced.py,sha256=tXP7SWaLbLay5FVtqOoj-oToBM1ha2NWzywMnE0Foeo,3434
46
- geoseeq/cli/upload/upload_reads.py,sha256=IlO-9b8dHbA-fx8FK_R0BwyoRgtqQUoowgXJcyx_O3k,11018
44
+ geoseeq/cli/upload/__init__.py,sha256=tRrljqG7uBM6zVBKpDw1zevHz6Vy418HuIYZKwByyWg,862
45
+ geoseeq/cli/upload/upload.py,sha256=U9w18sG6dZ0uJoAMcQDLdrEtgmRaSaPS7-8KM5LkVd4,14220
46
+ geoseeq/cli/upload/upload_advanced.py,sha256=1pjtbe8EidIo4eR1_oVD00rPCTxYj8CRqB8D9buA62M,12432
47
+ geoseeq/cli/upload/upload_reads.py,sha256=m9ujcjSYOG88LhxMj1vEt8v7FpuhRciL4ntE6oNlVuM,11632
47
48
  geoseeq/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  geoseeq/contrib/ncbi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  geoseeq/contrib/ncbi/api.py,sha256=WQeLoGA_-Zha-QeSO8_i7HpvXyD8UkV0qc5okm11KiA,1056
@@ -52,11 +53,11 @@ geoseeq/contrib/ncbi/cli.py,sha256=j9zEcaZPTryK3a4xluRxigcJKDhRpRxbp3KZSx-Bfhk,2
52
53
  geoseeq/contrib/ncbi/setup_logging.py,sha256=Tp1bY1U0f-o739aHpvVYriG2qdd1lFvCYBXZeXQgt-w,175
53
54
  geoseeq/id_constructors/__init__.py,sha256=w5E0PNQ9UuAxBeZbDI7KBnUoERd85gGz3nScz45bd2o,126
54
55
  geoseeq/id_constructors/from_blobs.py,sha256=wZp6P6m7X0lhZAQxorHKJQn1CTBvKyfANzu2VFexp44,6315
55
- geoseeq/id_constructors/from_ids.py,sha256=vwv_R1uZ5hdeUvpKmNLOTz02PCvwfd6yQ7aAXqi0hZo,3372
56
+ geoseeq/id_constructors/from_ids.py,sha256=SRylajToFdnRON5EhPIvcAi64XuIzseaasZlOooqTpg,3984
56
57
  geoseeq/id_constructors/from_names.py,sha256=RqgFjDsAwQcidMkZwX7oB00OvBAKTiilHYetTPogJ40,4174
57
58
  geoseeq/id_constructors/from_uuids.py,sha256=loqTeZqDZuvfVVHpS7m-Y_spNaY3h9VRjt0wDiMxHbs,3522
58
59
  geoseeq/id_constructors/resolvers.py,sha256=8hp5xJSCoZrAXtMT54Hp4okt63l909XqJU3IQx-VCgc,2676
59
- geoseeq/id_constructors/utils.py,sha256=CKlZHGMiqi1b6r1KtgD3czSAomH99Gdfx5ziqaahz-0,723
60
+ geoseeq/id_constructors/utils.py,sha256=Up_vXoZDwa2mJGllJ6CmcbaOGUvA-sM0gMmFnXK8wn4,1255
60
61
  geoseeq/plotting/__init__.py,sha256=RkGoXxgu7jEfK0B7NmdalPS2AbU7I7dZwDbi4rn9CKM,154
61
62
  geoseeq/plotting/constants.py,sha256=CGUlm8WAFG3YRKdicc9Rcy5hFxUdUm2RgK0iXZWLuX8,285
62
63
  geoseeq/plotting/highcharts.py,sha256=AGzdW4VSUsL_rfEI-RiVbbtaTLFMARvzLzVUDrKTlnU,4096
@@ -70,10 +71,11 @@ geoseeq/result/bioinfo.py,sha256=QQtbyogrdro9avJSN0713sxLVnVeA24mFw3hWtKDKyw,178
70
71
  geoseeq/result/file_chunker.py,sha256=bXq1csuRtqMB5sbH-AfWo6gdPwrivv5DJPuHVj-h08w,1758
71
72
  geoseeq/result/file_download.py,sha256=5IXg_dIWlrRHBJQssO42da5_bIJOyH0_b8K2KWVAFBE,8210
72
73
  geoseeq/result/file_upload.py,sha256=xs1DrI-h4ZP7xN8HPBc3SFpcPAxR5HAolraP1Zu7tvE,10648
73
- geoseeq/result/result_file.py,sha256=A2PIdkuNY0czXLXPenSRdUPdCFdjJGGqs3nBOflNwnA,9099
74
- geoseeq/result/result_folder.py,sha256=-m1lDVLpNHKy-JUGihboVzvdMJEnHossyRnxmBe1XLo,11140
74
+ geoseeq/result/result_file.py,sha256=mkFh2DpKO1-kEAARCMYjkc7TmkJh41azyauGIHl_VZo,9173
75
+ geoseeq/result/result_folder.py,sha256=iyO0hwZWokrH6oWhBgHlunWMpCMpejKb8v2sHFhecws,11283
75
76
  geoseeq/result/resumable_download_tracker.py,sha256=YEzqHBBnE7L3XokTvlTAhHZ8TcDTIE_pyTQ7YadOfbU,3667
76
77
  geoseeq/result/resumable_upload_tracker.py,sha256=2aI09gYz2yw63jEXqs8lmCRKQ79TIc3YuPETvP0Jeek,3811
78
+ geoseeq/result/smart_objects.py,sha256=-krK9h9HcznIglf189uPghOidF_BzOeSxmBWxrtFFo8,1991
77
79
  geoseeq/result/utils.py,sha256=C-CxGzB3WddlnRiqFSkrY78I_m0yFgNqsTBRzGU-y8Q,2772
78
80
  geoseeq/vc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
81
  geoseeq/vc/checksum.py,sha256=y8rh1asUZNbE_NLiFO0-9hImLNiTOc2YXQBRKORWK7k,710
@@ -84,12 +86,12 @@ geoseeq/vc/vc_cache.py,sha256=P4LXTbq2zOIv1OhP7Iw5MmypR2vXuy29Pq5K6gRvi-M,730
84
86
  geoseeq/vc/vc_dir.py,sha256=A9CLTh2wWCRzZjiLyqXD1vhtsWZGD3OjaMT5KqlfAXI,457
85
87
  geoseeq/vc/vc_sample.py,sha256=qZeioWydXvfu4rGMs20nICfNcp46y_XkND-bHdV6P5M,3850
86
88
  geoseeq/vc/vc_stub.py,sha256=IQr8dI0zsWKVAeY_5ybDD6n49_3othcgfHS3P0O9tuY,3110
89
+ geoseeq-0.7.0.dist-info/licenses/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
87
90
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
91
  tests/test_api_client.py,sha256=TS5njc5pcPP_Ycy-ljcfPVT1hQRBsFVdQ0lCqBmoesU,12810
89
92
  tests/test_plotting.py,sha256=TcTu-2ARr8sxZJ7wPQxmbs3-gHw7uRvsgrhhhg0qKik,784
90
- geoseeq-0.6.14.dev7.dist-info/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
91
- geoseeq-0.6.14.dev7.dist-info/METADATA,sha256=q9ZgGURnAxFyUKqLaF6J5db1e9rrrqEcQbGhJ8MmFUc,4937
92
- geoseeq-0.6.14.dev7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
93
- geoseeq-0.6.14.dev7.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
94
- geoseeq-0.6.14.dev7.dist-info/top_level.txt,sha256=zZk7mmeaqAYqFJG8nq2DTgSQPbflRjJwkDIhNURPDEU,14
95
- geoseeq-0.6.14.dev7.dist-info/RECORD,,
93
+ geoseeq-0.7.0.dist-info/METADATA,sha256=lEr_Lja53CYN53DTQTR3CKG3cqPdwgnuB03sbiwtzTs,4953
94
+ geoseeq-0.7.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
95
+ geoseeq-0.7.0.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
96
+ geoseeq-0.7.0.dist-info/top_level.txt,sha256=zZk7mmeaqAYqFJG8nq2DTgSQPbflRjJwkDIhNURPDEU,14
97
+ geoseeq-0.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5