geoseeq 0.6.8a1__tar.gz → 0.6.9__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/PKG-INFO +1 -1
  2. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/download.py +29 -3
  3. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/main.py +1 -1
  4. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/id_handlers.py +31 -3
  5. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/knex.py +2 -1
  6. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/project.py +8 -3
  7. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/remote_object.py +1 -0
  8. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/utils.py +13 -5
  9. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/PKG-INFO +1 -1
  10. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/pyproject.toml +1 -1
  11. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/LICENSE +0 -0
  12. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/README.md +0 -0
  13. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/__init__.py +0 -0
  14. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/app.py +0 -0
  15. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/blob_constructors.py +0 -0
  16. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/bulk_creators.py +0 -0
  17. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/__init__.py +0 -0
  18. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/constants.py +0 -0
  19. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/copy.py +0 -0
  20. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/detail.py +0 -0
  21. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/fastq_utils.py +0 -0
  22. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/get_eula.py +0 -0
  23. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/manage.py +0 -0
  24. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/progress_bar.py +0 -0
  25. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/project.py +0 -0
  26. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/raw.py +0 -0
  27. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/run.py +0 -0
  28. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/search.py +0 -0
  29. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/__init__.py +0 -0
  30. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/common_state.py +0 -0
  31. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/config.py +0 -0
  32. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/obj_getters.py +0 -0
  33. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/shared_params/opts_and_args.py +0 -0
  34. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/upload/__init__.py +0 -0
  35. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/upload/upload.py +0 -0
  36. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/upload/upload_advanced.py +0 -0
  37. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/upload/upload_reads.py +0 -0
  38. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/user.py +0 -0
  39. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/utils.py +0 -0
  40. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/cli/view.py +0 -0
  41. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/constants.py +0 -0
  42. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/__init__.py +0 -0
  43. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/ncbi/__init__.py +0 -0
  44. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/ncbi/api.py +0 -0
  45. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/ncbi/bioproject.py +0 -0
  46. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/ncbi/cli.py +0 -0
  47. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/contrib/ncbi/setup_logging.py +0 -0
  48. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/file_system_cache.py +0 -0
  49. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/__init__.py +0 -0
  50. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/from_blobs.py +0 -0
  51. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/from_ids.py +0 -0
  52. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/from_names.py +0 -0
  53. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/from_uuids.py +0 -0
  54. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/resolvers.py +0 -0
  55. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/id_constructors/utils.py +0 -0
  56. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/organization.py +0 -0
  57. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/pipeline.py +0 -0
  58. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/__init__.py +0 -0
  59. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/constants.py +0 -0
  60. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/highcharts.py +0 -0
  61. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/map/__init__.py +0 -0
  62. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/map/base_layer.py +0 -0
  63. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/map/map.py +0 -0
  64. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/map/overlay.py +0 -0
  65. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/plotting/selectable.py +0 -0
  66. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/__init__.py +0 -0
  67. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/bioinfo.py +0 -0
  68. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/file_chunker.py +0 -0
  69. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/file_download.py +0 -0
  70. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/file_upload.py +0 -0
  71. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/result_file.py +0 -0
  72. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/result_folder.py +0 -0
  73. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/resumable_download_tracker.py +0 -0
  74. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/resumable_upload_tracker.py +0 -0
  75. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/result/utils.py +0 -0
  76. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/sample.py +0 -0
  77. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/search.py +0 -0
  78. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/upload_download_manager.py +0 -0
  79. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/user.py +0 -0
  80. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/__init__.py +0 -0
  81. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/checksum.py +0 -0
  82. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/cli.py +0 -0
  83. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/clone.py +0 -0
  84. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/constants.py +0 -0
  85. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/vc_cache.py +0 -0
  86. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/vc_dir.py +0 -0
  87. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/vc_sample.py +0 -0
  88. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/vc/vc_stub.py +0 -0
  89. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq/work_orders.py +0 -0
  90. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/SOURCES.txt +0 -0
  91. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/dependency_links.txt +0 -0
  92. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/entry_points.txt +0 -0
  93. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/requires.txt +0 -0
  94. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/geoseeq.egg-info/top_level.txt +0 -0
  95. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/setup.cfg +0 -0
  96. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/setup.py +0 -0
  97. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/tests/__init__.py +0 -0
  98. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/tests/test_api_client.py +0 -0
  99. {geoseeq-0.6.8a1 → geoseeq-0.6.9}/tests/test_plotting.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geoseeq
3
- Version: 0.6.8a1
3
+ Version: 0.6.9
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>
@@ -98,6 +98,7 @@ def cli_download_metadata(state, sample_ids):
98
98
 
99
99
  cores_option = click.option('--cores', default=1, help='Number of downloads to run in parallel')
100
100
  head_option = click.option('--head', default=None, type=int, help='Download the first N bytes of each file')
101
+ alt_id_option = click.option('--alt-sample-id', default=None, help='Specify an alternate sample id from the project metadata to id samples')
101
102
 
102
103
  @cli_download.command("files")
103
104
  @use_common_state
@@ -113,6 +114,7 @@ head_option = click.option('--head', default=None, type=int, help='Download the
113
114
  @click.option("--extension", multiple=True, help="Only download files with this extension. e.g. 'fastq.gz', 'bam', 'csv'")
114
115
  @click.option("--with-versions/--without-versions", default=False, help="Download all versions of a file, not just the latest")
115
116
  @ignore_errors_option
117
+ @alt_id_option
116
118
  @project_id_arg
117
119
  @sample_ids_arg
118
120
  def cli_download_files(
@@ -129,6 +131,7 @@ def cli_download_files(
129
131
  with_versions,
130
132
  download,
131
133
  ignore_errors,
134
+ alt_sample_id,
132
135
  project_id,
133
136
  sample_ids,
134
137
  ):
@@ -164,6 +167,13 @@ def cli_download_files(
164
167
  haib17CEM4890_H2NYMCCXY_SL254769 haib17CEM4890_H2NYMCCXY_SL254773 `# specify the samples by name` \\
165
168
  --folder-type sample --extension '.contigs.fasta' # filter for contig files
166
169
 
170
+ \b
171
+ # Download files from a sample in the metasub project using an alternate sample id called "barcode"
172
+ $ geoseeq download files 'MetaSUB Consortium/Cell Paper' `# specify the project` \\
173
+ 235183938 `# the alternate sample name (in this case a barcode number)` \\
174
+ --alt-sample-id barcode `# specify the alternate sample id column name` \\
175
+ --folder-type 'sample' `# only download files from sample folders`
176
+
167
177
  ---
168
178
 
169
179
  Command Arguments:
@@ -184,7 +194,7 @@ def cli_download_files(
184
194
  samples = []
185
195
  if sample_ids:
186
196
  logger.info(f"Fetching info for {len(sample_ids)} samples.")
187
- samples = handle_multiple_sample_ids(knex, sample_ids, proj=proj)
197
+ samples = handle_multiple_sample_ids(knex, sample_ids, proj=proj, alternate_id_col=alt_sample_id)
188
198
 
189
199
  response = proj.bulk_find_files(
190
200
  sample_uuids=[s.uuid for s in samples],
@@ -377,9 +387,21 @@ def cli_download_ids(state, cores, target_dir, file_name, yes, download, head, i
377
387
  @click.option("--download/--urls-only", default=True, help="Download files or just print urls")
378
388
  @module_option(FASTQ_MODULE_NAMES, use_default=False)
379
389
  @ignore_errors_option
390
+ @alt_id_option
380
391
  @project_id_arg
381
392
  @sample_ids_arg
382
- def cli_download_fastqs(state, cores, target_dir, yes, first, download, module_name, ignore_errors, project_id, sample_ids):
393
+ def cli_download_fastqs(state,
394
+ cores,
395
+ target_dir,
396
+ yes,
397
+ first,
398
+ download,
399
+ module_name,
400
+ ignore_errors,
401
+ alt_sample_id,
402
+ project_id,
403
+ sample_ids
404
+ ):
383
405
  """Download fastq files from a GeoSeeq project.
384
406
 
385
407
  This command will download fastq files from a GeoSeeq project. You can filter
@@ -401,6 +423,10 @@ def cli_download_fastqs(state, cores, target_dir, yes, first, download, module_n
401
423
  # Download all fastq files from two samples in "My Org/My Project"
402
424
  $ geoseeq download fastqs "My Org/My Project" S1 S2
403
425
 
426
+ \b
427
+ # Download all fastq files from a single sample using an alternate sample id called "barcode"
428
+ $ geoseeq download fastqs 'MetaSUB Consortium/Cell Paper' 235183938 --alt-sample-id barcode
429
+
404
430
  ---
405
431
 
406
432
  Command Arguments:
@@ -422,7 +448,7 @@ def cli_download_fastqs(state, cores, target_dir, yes, first, download, module_n
422
448
  samples = []
423
449
  if sample_ids:
424
450
  logger.info(f"Fetching info for {len(sample_ids)} samples.")
425
- samples = handle_multiple_sample_ids(knex, sample_ids, proj=proj)
451
+ samples = handle_multiple_sample_ids(knex, sample_ids, proj=proj, alternate_id_col=alt_sample_id)
426
452
  else:
427
453
  logger.info("Fetching info for all samples in project.")
428
454
  samples = proj.get_samples()
@@ -54,7 +54,7 @@ def version():
54
54
  Use of this tool implies acceptance of the GeoSeeq End User License Agreement.
55
55
  Run `geoseeq eula show` to view the EULA.
56
56
  """
57
- click.echo('0.6.8') # remember to update setup
57
+ click.echo('0.6.9') # remember to update pyproject.toml
58
58
 
59
59
 
60
60
  @main.group('advanced')
@@ -133,7 +133,30 @@ def handle_folder_id(knex, folder_id, yes=False, private=True, create=True):
133
133
  raise ValueError('sample_folder_id must be a UUID, an organization name and project name, or a GRN')
134
134
 
135
135
 
136
- def handle_multiple_sample_ids(knex, sample_ids, proj=None):
136
+ def map_alternate_ids_to_uuids(proj, alternate_id_col, sample_ids):
137
+ """Return a list of sample UUIDs
138
+
139
+ `proj` is a project object
140
+ `alternate_id_col` is the name of the column containing alternate IDs
141
+ `sample_ids` is a list of alternate IDs
142
+ """
143
+ metadata = proj.get_sample_metadata()
144
+ if alternate_id_col not in metadata:
145
+ raise ValueError(f'Column "{alternate_id_col}" not found in project metadata')
146
+ alt_col_df = metadata[["uuid", alternate_id_col]]
147
+ # filter to the alt ids in our list- it is possible alt_id_col as a whole is not
148
+ # unique but that our list of alt ids is
149
+ alt_col_df = alt_col_df[alt_col_df[alternate_id_col].isin(sample_ids)]
150
+ if alt_col_df.shape[0] == 0:
151
+ raise ValueError(f'No samples found with the given alternate IDs in list')
152
+ if alt_col_df.shape[0] < len(sample_ids):
153
+ raise ValueError(f'Not all alternate IDs in list are found')
154
+ if alt_col_df.shape[0] > len(sample_ids):
155
+ raise ValueError(f'More than one sample found with the same alternate ID')
156
+ return list(alt_col_df['uuid'])
157
+
158
+
159
+ def handle_multiple_sample_ids(knex, sample_ids, proj=None, alternate_id_col=None):
137
160
  """Return a list of fetched sample objects
138
161
 
139
162
  `sample_ids` may have three different structures:
@@ -144,7 +167,9 @@ def handle_multiple_sample_ids(knex, sample_ids, proj=None):
144
167
  Any sample may in fact be a file containing sample IDs, in which case the file will be read line by line
145
168
  and each element will be a sample ID
146
169
 
147
- If `one_project` is True, all samples must be from the same project
170
+ If `proj` is provided then `alternate_id_col` may also be provided.
171
+ If so then alternate IDs will be used to fetch samples. If alternate ids are
172
+ not present or not unique then fail.
148
173
  """
149
174
  project_as_arg = bool(proj)
150
175
  if proj or (proj := el_is_project_id(knex, sample_ids[0])):
@@ -155,7 +180,10 @@ def handle_multiple_sample_ids(knex, sample_ids, proj=None):
155
180
  return list(proj.get_samples(cache=False))
156
181
  else:
157
182
  samples = []
158
- for el in flatten_list_of_els_and_files(sample_ids):
183
+ sample_ids = flatten_list_of_els_and_files(sample_ids)
184
+ if alternate_id_col:
185
+ sample_ids = map_alternate_ids_to_uuids(proj, alternate_id_col, sample_ids)
186
+ for el in sample_ids:
159
187
  if is_grn_or_uuid(el):
160
188
  el = el.split(':')[-1]
161
189
  samples.append(sample_from_uuid(knex, el))
@@ -214,7 +214,8 @@ class Knex:
214
214
  """Return a knex authenticated with a profile."""
215
215
  endpoint, token = load_auth_profile(profile)
216
216
  knex = cls(endpoint)
217
- knex.add_api_token(token)
217
+ if token:
218
+ knex.add_api_token(token)
218
219
  return knex
219
220
 
220
221
 
@@ -256,9 +256,14 @@ class Project(RemoteObject):
256
256
 
257
257
  def get_sample_metadata(self):
258
258
  """Return a pandas dataframe with sample metadata."""
259
- url = f"sample_groups/{self.uuid}/metadata"
260
- blob = self.knex.get(url)
261
- return pd.DataFrame.from_dict(blob, orient="index")
259
+ url = f"sample_groups/{self.uuid}/samples-list?page=1&page_size=500&&"
260
+ rows = []
261
+ while url:
262
+ blob = self.knex.get(url)
263
+ rows.extend(blob["results"])
264
+ url = blob["next"]
265
+ return pd.DataFrame(rows)
266
+
262
267
 
263
268
  @property
264
269
  def n_samples(self):
@@ -165,6 +165,7 @@ class RemoteObject:
165
165
  self._modified = False
166
166
  else:
167
167
  logger.debug(f"RemoteBlob has not been modified. Nothing to save. {self}")
168
+ return self
168
169
 
169
170
  def idem(self):
170
171
  """Make the state of this object match the server."""
@@ -16,11 +16,19 @@ logger.addHandler(logging.NullHandler()) # No output unless configured by calli
16
16
  def load_auth_profile(profile=""):
17
17
  """Return an endpoit and a token"""
18
18
  profile = profile or "__default__"
19
- with open(PROFILES_PATH, "r") as f:
20
- profiles = json.load(f)
21
- if profile in profiles:
22
- return profiles[profile]["endpoint"], profiles[profile]["token"]
23
- raise KeyError(f"Profile {profile} not found.")
19
+ try:
20
+ with open(PROFILES_PATH, "r") as f:
21
+ profiles = json.load(f)
22
+ if profile in profiles:
23
+ return profiles[profile]["endpoint"], profiles[profile]["token"]
24
+ raise KeyError(f"Profile {profile} not found.")
25
+ except FileNotFoundError:
26
+ endpoint, token = environ.get("GEOSEEQ_ENDPOINT", DEFAULT_ENDPOINT), environ.get("GEOSEEQ_API_TOKEN", None)
27
+ if token:
28
+ logger.debug("Using environment variables for authentication.")
29
+ else:
30
+ logger.warning("Accessing anonymously, functionality may be limited. Configure profiles or set GEOSEEQ_API_TOKEN to authenticate.")
31
+ return endpoint, token
24
32
 
25
33
 
26
34
  def set_profile(token, endpoint=DEFAULT_ENDPOINT, profile="", overwrite=False):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geoseeq
3
- Version: 0.6.8a1
3
+ Version: 0.6.9
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>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "geoseeq"
7
- version = "0.6.8a1"
7
+ version = "0.6.9"
8
8
  authors = [
9
9
  { name="David C. Danko", email="dcdanko@biotia.io" },
10
10
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes