eva-sub-cli 0.4.6__tar.gz → 0.4.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. eva_sub_cli-0.4.8/.github/workflows/trigger-eva-sub-cli-integration-tests.yml +16 -0
  2. {eva_sub_cli-0.4.6/eva_sub_cli.egg-info → eva_sub_cli-0.4.8}/PKG-INFO +4 -2
  3. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/__init__.py +1 -1
  4. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/_version.py +2 -2
  5. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/etc/EVA_Submission_Example.xlsx +0 -0
  6. eva_sub_cli-0.4.8/eva_sub_cli/etc/EVA_Submission_template.xlsx +0 -0
  7. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/etc/eva_schema.json +10 -7
  8. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/check_fasta_insdc.py +18 -25
  9. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/samples_checker.py +13 -19
  10. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/trim_down.py +21 -11
  11. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/xlsx2json.py +3 -3
  12. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/file_utils.py +8 -3
  13. eva_sub_cli-0.4.8/eva_sub_cli/metadata.py +95 -0
  14. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/nextflow/validation.nf +1 -0
  15. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/orchestrator.py +53 -52
  16. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/semantic_metadata.py +42 -5
  17. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/validators/docker_validator.py +15 -11
  18. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/validators/native_validator.py +4 -0
  19. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/validators/validator.py +44 -45
  20. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8/eva_sub_cli.egg-info}/PKG-INFO +4 -2
  21. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli.egg-info/SOURCES.txt +8 -1
  22. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli.egg-info/requires.txt +3 -1
  23. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/requirements.txt +3 -1
  24. eva_sub_cli-0.4.8/tests/build_and_test_docker_locally.py +293 -0
  25. eva_sub_cli-0.4.8/tests/resources/EVA_Submission_Docker_Test.xlsx +0 -0
  26. eva_sub_cli-0.4.8/tests/resources/EVA_Submission_test.xlsx +0 -0
  27. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/EVA_Submission_test_fails.xlsx +0 -0
  28. eva_sub_cli-0.4.8/tests/resources/EVA_Submission_test_version_missing.xlsx +0 -0
  29. eva_sub_cli-0.4.8/tests/resources/fasta_files/multi_seq.fa +8 -0
  30. eva_sub_cli-0.4.8/tests/resources/metadata_with_filename.json +19 -0
  31. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/other_validations/metadata_validation.txt +2 -0
  32. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_check_fasta_insdc.py +35 -3
  33. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_cli.py +5 -0
  34. eva_sub_cli-0.4.8/tests/test_metadata.py +69 -0
  35. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_orchestrator.py +27 -6
  36. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_samples_checker.py +29 -2
  37. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_semantic_metadata.py +41 -17
  38. eva_sub_cli-0.4.8/tests/test_trim_down.py +108 -0
  39. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_validator.py +46 -2
  40. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_xlsx2json.py +41 -0
  41. eva_sub_cli-0.4.6/eva_sub_cli/etc/EVA_Submission_template.xlsx +0 -0
  42. eva_sub_cli-0.4.6/eva_sub_cli/metadata_utils.py +0 -46
  43. eva_sub_cli-0.4.6/tests/resources/EVA_Submission_test.xlsx +0 -0
  44. eva_sub_cli-0.4.6/tests/resources/EVA_Submission_test_version_missing.xlsx +0 -0
  45. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/.github/workflows/publish.yml +0 -0
  46. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/.github/workflows/publish_packages.yml +0 -0
  47. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/.github/workflows/tests.yml +0 -0
  48. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/.gitignore +0 -0
  49. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/LICENSE +0 -0
  50. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/README.md +0 -0
  51. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/conda/build.sh +0 -0
  52. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/conda/meta.yaml +0 -0
  53. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/docker/Dockerfile +0 -0
  54. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/docs/Getting_Started_with_eva_sub_cli.md +0 -0
  55. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/auth.py +0 -0
  56. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/date_utils.py +0 -0
  57. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/etc/eva-biosamples.json +0 -0
  58. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/etc/eva_logo.png +0 -0
  59. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/etc/spreadsheet2json_conf.yaml +0 -0
  60. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/exceptions/__init__.py +0 -0
  61. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/exceptions/invalid_file_type_exception.py +0 -0
  62. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/exceptions/metadata_template_version_exception.py +0 -0
  63. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/exceptions/submission_not_found_exception.py +0 -0
  64. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/exceptions/submission_status_exception.py +0 -0
  65. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/__init__.py +0 -0
  66. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/check_metadata_semantics.py +0 -0
  67. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/executables/cli.py +0 -0
  68. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/fasta_check.html +0 -0
  69. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/file_validation.html +0 -0
  70. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/html_report.html +0 -0
  71. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/metadata_validation.html +0 -0
  72. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/project_details.html +0 -0
  73. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/sample_name_check.html +0 -0
  74. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/jinja_templates/shallow_validation.html +0 -0
  75. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/report.py +0 -0
  76. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/submission_ws.py +0 -0
  77. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/submit.py +0 -0
  78. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/validators/__init__.py +0 -0
  79. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli/validators/validation_results_parsers.py +0 -0
  80. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli.egg-info/dependency_links.txt +0 -0
  81. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli.egg-info/entry_points.txt +0 -0
  82. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/eva_sub_cli.egg-info/top_level.txt +0 -0
  83. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/pyproject.toml +0 -0
  84. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/pytest.ini +0 -0
  85. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/setup.cfg +0 -0
  86. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/setup.py +0 -0
  87. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/ignore_test_nextflow_validation.yml +0 -0
  88. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/EVA_Submission_test.json +0 -0
  89. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/EVA_Submission_test_with_asm_report.json +0 -0
  90. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/assembly_check/invalid.vcf.assembly_check.log +0 -0
  91. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/assembly_check/invalid.vcf.text_assembly_report.txt +0 -0
  92. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/assembly_reports/input_passed.txt +0 -0
  93. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/fasta_files/Saccharomyces_cerevisiae_I.fa +0 -0
  94. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/fasta_files/input_passed.fa +0 -0
  95. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/sample_checker/example1.vcf.gz +0 -0
  96. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/sample_checker/example2.vcf +0 -0
  97. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/sample_checker/example3.vcf +0 -0
  98. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/sample_checker/metadata.json +0 -0
  99. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/expected_report_metadata_json.html +0 -0
  100. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/expected_report_metadata_xlsx.html +0 -0
  101. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/expected_shallow_metadata_xlsx_report.html +0 -0
  102. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/assembly_check/input_passed.vcf.assembly_check.log +0 -0
  103. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/assembly_check/input_passed.vcf.text_assembly_report.1680102476700.txt +0 -0
  104. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/assembly_check/input_passed.vcf.valid_assembly_report.1680102476700.txt +0 -0
  105. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/other_validations/input_passed.fa_check.yml +0 -0
  106. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/other_validations/metadata_conversion_errors.yml +0 -0
  107. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/other_validations/metadata_semantic_check.yml +0 -0
  108. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/other_validations/sample_checker.yml +0 -0
  109. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/vcf_format/input_passed.vcf.errors.1680102476854.db +0 -0
  110. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/vcf_format/input_passed.vcf.errors.1680102476854.txt +0 -0
  111. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/validation_reports/validation_output/vcf_format/input_passed.vcf.vcf_format.log +0 -0
  112. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/vcf_files/example2.vcf.gz +0 -0
  113. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/resources/vcf_files/input_passed.vcf +0 -0
  114. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_auth.py +0 -0
  115. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_docker_validator.py +0 -0
  116. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_file_utils.py +0 -0
  117. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_report.py +0 -0
  118. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_submit.py +0 -0
  119. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_utils.py +0 -0
  120. {eva_sub_cli-0.4.6 → eva_sub_cli-0.4.8}/tests/test_validaton_results_parsers.py +0 -0
@@ -0,0 +1,16 @@
1
+ name: Trigger Eva Sub Cli Integration Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, test-trigger-integration-tests]
6
+
7
+ jobs:
8
+ trigger-integration-tests:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Trigger eva sub cli integration tests
12
+ uses: peter-evans/repository-dispatch@v2
13
+ with:
14
+ token: ${{ secrets.TRIGGER_INTEGRATION_TESTS }}
15
+ repository: EBIvariation/eva-integration-tests
16
+ event-type: trigger-eva-sub-cli-tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eva_sub_cli
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: EBI EVA - validation and submission command line tool
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Environment :: Console
@@ -19,12 +19,14 @@ Classifier: License :: OSI Approved :: Apache Software License
19
19
  Requires-Python: >=3.6
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: ebi_eva_common_pyutils==0.*,>0.6.11
22
+ Requires-Dist: ebi_eva_common_pyutils==0.*,>=0.7.1
23
23
  Requires-Dist: jinja2==3.*,>=3.0.0
24
24
  Requires-Dist: jsonschema==4.*
25
25
  Requires-Dist: openpyxl==3.*
26
26
  Requires-Dist: pyyaml==6.*
27
27
  Requires-Dist: requests==2.*
28
+ Requires-Dist: packaging==24.*
29
+ Requires-Dist: setuptools-scm
28
30
  Dynamic: license-file
29
31
 
30
32
  # eva-sub-cli
@@ -6,7 +6,7 @@ PACKAGE_DIR = os.path.dirname(os.path.abspath(__file__))
6
6
  NEXTFLOW_DIR = os.path.join(PACKAGE_DIR, 'nextflow')
7
7
  ETC_DIR = os.path.join(PACKAGE_DIR, 'etc')
8
8
  LSRI_CLIENT_ID = "aa0fcc42-096a-4f9d-b871-aceb1a97d174"
9
- DEFAULT_METADATA_XLSX_TEMPLATE_VERSION = '1.1.6'
9
+ MINIMUM_METADATA_XLSX_TEMPLATE_VERSION = '2.0.0'
10
10
 
11
11
  SUB_CLI_CONFIG_FILE = ".eva_sub_cli_config.yml"
12
12
 
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.6'
21
- __version_tuple__ = version_tuple = (0, 4, 6)
20
+ __version__ = version = '0.4.8'
21
+ __version_tuple__ = version_tuple = (0, 4, 8)
@@ -75,11 +75,13 @@
75
75
  "properties": {
76
76
  "title": {
77
77
  "type": "string",
78
- "description": "Title of the project"
78
+ "description": "Title of the project",
79
+ "maxLength": 500
79
80
  },
80
81
  "description": {
81
82
  "type": "string",
82
- "description": "Description of the project"
83
+ "description": "Description of the project",
84
+ "maxLength": 5000
83
85
  },
84
86
  "centre": {
85
87
  "type": "string",
@@ -166,11 +168,11 @@
166
168
  "type": "string",
167
169
  "description": "Reference against which the analysis was performed. ENA or NCBI accession (starting with GCA) accepted."
168
170
  },
169
- "referenceFasta": {
171
+ "referenceFasta": {
170
172
  "type": "string",
171
173
  "description": "The path to the reference in fasta format that was used to create the VCF. (This is a local path)"
172
174
  },
173
- "assemblyReport": {
175
+ "assemblyReport": {
174
176
  "type": "string",
175
177
  "description": "The path to the assembly report associated with the assembly as found in NCBI assemblies. (This is a local path and only applicable for GCAs)"
176
178
  },
@@ -308,9 +310,10 @@
308
310
  "type": "string",
309
311
  "description": "Sample Name used in the VCF file"
310
312
  },
311
- "sampleAccession": {
313
+ "bioSampleAccession": {
312
314
  "type": "string",
313
- "description": "Accession of the sample (BioSamples, ENA or EGA)"
315
+ "pattern": "^SAM(E|D|N)[A-Z]?[0-9]+$",
316
+ "description": "BioSample accession of pre-existing sample. Only use for preregistered samples"
314
317
  },
315
318
  "bioSampleObject": {
316
319
  "type": "object",
@@ -319,7 +322,7 @@
319
322
  "$ref": "eva-biosamples.json"
320
323
  },
321
324
  {
322
- "$ref": "https://www.ebi.ac.uk/biosamples/schema/store/registry/schemas/ERC000011"
325
+ "$ref": "https://www.ebi.ac.uk/biosamples/schema/store/registry/schemas/ERC000011"
323
326
  },
324
327
  {
325
328
  "$ref": "https://www.ebi.ac.uk/biosamples/schemas/certification/plant-miappe.json"
@@ -1,8 +1,5 @@
1
1
  import argparse
2
- import gzip
3
2
  import hashlib
4
- import json
5
- from itertools import groupby
6
3
 
7
4
  import requests
8
5
  import yaml
@@ -11,8 +8,7 @@ from requests import HTTPError
11
8
  from retry import retry
12
9
 
13
10
  from eva_sub_cli.file_utils import fasta_iter
14
- from eva_sub_cli.metadata_utils import get_files_per_analysis, get_analysis_for_vcf_file, \
15
- get_reference_assembly_for_analysis
11
+ from eva_sub_cli.metadata import EvaMetadataJson
16
12
 
17
13
  REFGET_SERVER = 'https://www.ebi.ac.uk/ena/cram'
18
14
  CONTIG_ALIAS_SERVER = 'https://www.ebi.ac.uk/eva/webservices/contig-alias/v1/chromosomes/md5checksum'
@@ -52,7 +48,7 @@ def _get_containing_assemblies_paged(url):
52
48
  results.add(contigEntity['assembly']['insdcAccession'])
53
49
  if '_links' in response_data and 'next' in response_data['_links']:
54
50
  # Add results from next page if needed
55
- results |= _get_containing_assemblies_paged(response_data['_links']['next'])
51
+ results = results.union(_get_containing_assemblies_paged(response_data['_links']['next']['href']))
56
52
  return results
57
53
  return set()
58
54
 
@@ -94,7 +90,7 @@ def assess_fasta(input_fasta, analyses, assembly_in_metadata):
94
90
  possible_assemblies = containing_assemblies
95
91
  else:
96
92
  possible_assemblies &= containing_assemblies
97
- except (ConnectionError, HTTPError) as e:
93
+ except Exception as e:
98
94
  # Server errors from either ENA refget or EVA contig alias will halt the check prematurely.
99
95
  # Report the error but do not return from the method, so that incomplete results can be reported
100
96
  # (i.e. any sequences found to be INSDC and any compatible assemblies so far)
@@ -121,23 +117,21 @@ def get_analyses_and_reference_genome_from_metadata(vcf_files_for_fasta, json_fi
121
117
  single assembly accession
122
118
  :param json_file: JSON file of the metadata
123
119
  """
124
- with open(json_file) as open_json:
125
- metadata = json.load(open_json)
126
- files_per_analysis = get_files_per_analysis(metadata)
127
- # Get all analyses associated with all vcf files that are linked with a single fasta file
128
- all_analyses = set()
129
- for vcf_file in vcf_files_for_fasta:
130
- analysis_aliases = get_analysis_for_vcf_file(vcf_file, files_per_analysis)
131
- if len(analysis_aliases) != 1:
132
- logger.error(f'Could not determine analysis associated with VCF file: {vcf_file}')
133
- else:
134
- all_analyses.add(analysis_aliases[0])
135
- # Get (single) assembly associated with these analyses
136
- assemblies = [get_reference_assembly_for_analysis(metadata, analysis) for analysis in all_analyses]
137
- if len(assemblies) != 1:
138
- logger.error(f'Could not determine assembly accession to check against fasta file, out of: {assemblies}')
139
- return all_analyses, None
140
- return all_analyses, assemblies[0]
120
+ metadata = EvaMetadataJson(json_file)
121
+ # Get all analyses associated with all vcf files that are linked with a single fasta file
122
+ all_analyses = set()
123
+ for vcf_file in vcf_files_for_fasta:
124
+ analysis_aliases = metadata.get_analysis_for_vcf_file(vcf_file)
125
+ if len(analysis_aliases) != 1:
126
+ logger.error(f'Could not determine analysis associated with VCF file: {vcf_file}')
127
+ else:
128
+ all_analyses.add(analysis_aliases[0])
129
+ # Get (single) assembly associated with these analyses
130
+ assemblies = [metadata.get_reference_assembly_for_analysis(analysis) for analysis in all_analyses]
131
+ if len(assemblies) != 1:
132
+ logger.error(f'Could not determine assembly accession to check against fasta file, out of: {assemblies}')
133
+ return all_analyses, None
134
+ return all_analyses, assemblies[0]
141
135
 
142
136
 
143
137
  def main():
@@ -156,4 +150,3 @@ def main():
156
150
  analyses, metadata_insdc = get_analyses_and_reference_genome_from_metadata(args.vcf_files, args.metadata_json)
157
151
  results = assess_fasta(args.input_fasta, analyses, metadata_insdc)
158
152
  write_result_yaml(args.output_yaml, results)
159
-
@@ -1,6 +1,4 @@
1
1
  import argparse
2
- import gzip
3
- import json
4
2
  import os
5
3
 
6
4
  from ebi_eva_common_pyutils.logger import logging_config
@@ -8,7 +6,7 @@ from ebi_eva_common_pyutils.logger import logging_config
8
6
  import yaml
9
7
 
10
8
  from eva_sub_cli.file_utils import open_gzip_if_required
11
- from eva_sub_cli.metadata_utils import get_samples_per_analysis, get_files_per_analysis, get_analysis_for_vcf_file
9
+ from eva_sub_cli.metadata import EvaMetadataJson
12
10
 
13
11
  logger = logging_config.get_logger(__name__)
14
12
 
@@ -24,8 +22,10 @@ def get_samples_from_vcf(vcf_file):
24
22
  if len(sp_line) > 9:
25
23
  return sp_line[9:]
26
24
  else:
27
- logger.warning(f"No Sample names found in file {vcf_file}")
25
+ logger.warning(f"No sample names found in file {vcf_file}")
28
26
  return []
27
+ logger.warning(f"No sample names found in file {vcf_file}")
28
+ return []
29
29
 
30
30
 
31
31
  def compare_names_in_files_and_samples(sample_name_in_analysis, sample_name_per_file):
@@ -54,12 +54,12 @@ def compare_names_in_files_and_samples(sample_name_in_analysis, sample_name_per_
54
54
  more_metadata_submitted_files)
55
55
 
56
56
 
57
- def compare_all_analysis(samples_per_analysis, files_per_analysis):
57
+ def compare_all_analysis(metadata, files_per_analysis):
58
58
  overall_differences = False
59
59
  results_per_analysis_alias = {}
60
- all_analysis_alias = set(samples_per_analysis) | set(files_per_analysis)
60
+ all_analysis_alias = set(metadata.samples_per_analysis) | set(files_per_analysis)
61
61
  for analysis_alias in all_analysis_alias:
62
- sample_name_in_analysis = samples_per_analysis.get(analysis_alias, [])
62
+ sample_name_in_analysis = metadata.samples_per_analysis.get(analysis_alias, [])
63
63
  sample_name_per_file = {
64
64
  file_path: get_samples_from_vcf(file_path)
65
65
  for file_path in files_per_analysis.get(analysis_alias, [])
@@ -78,13 +78,7 @@ def compare_all_analysis(samples_per_analysis, files_per_analysis):
78
78
  return overall_differences, results_per_analysis_alias
79
79
 
80
80
 
81
- def read_metadata_json(json_file):
82
- with open(json_file) as open_json:
83
- metadata = json.load(open_json)
84
- return get_samples_per_analysis(metadata), get_files_per_analysis(metadata)
85
-
86
-
87
- def associate_vcf_path_with_analysis(vcf_files, files_per_analysis):
81
+ def associate_vcf_path_with_analysis(metadata, vcf_files):
88
82
  """
89
83
  Match the files names associated with analysis provided in the metadata with the file path given on the command
90
84
  line.
@@ -93,10 +87,10 @@ def associate_vcf_path_with_analysis(vcf_files, files_per_analysis):
93
87
  :returns dictionary of analysis and their associated vcf file path
94
88
  """
95
89
  result_files_per_analysis = dict()
96
- for analysis in files_per_analysis:
90
+ for analysis in metadata.files_per_analysis:
97
91
  result_files_per_analysis[analysis] = []
98
92
  for vcf_file in vcf_files:
99
- analysis_aliases = get_analysis_for_vcf_file(vcf_file, files_per_analysis)
93
+ analysis_aliases = metadata.get_analysis_for_vcf_file(vcf_file)
100
94
  if len(analysis_aliases) == 1:
101
95
  result_files_per_analysis[analysis_aliases[0]].append(vcf_file)
102
96
  elif len(analysis_aliases) == 0:
@@ -123,9 +117,9 @@ def check_sample_name_concordance(metadata_json, vcf_files, output_yaml):
123
117
  Take the metadata following EVA standard and formatted in JSON then compare the sample names in it to the ones
124
118
  found in the VCF files
125
119
  """
126
- samples_per_analysis, files_per_analysis = read_metadata_json(metadata_json)
127
- file_path_per_analysis = associate_vcf_path_with_analysis(vcf_files, files_per_analysis)
128
- overall_differences, results_per_analysis_alias = compare_all_analysis(samples_per_analysis, file_path_per_analysis)
120
+ metadata = EvaMetadataJson(metadata_json)
121
+ file_path_per_analysis = associate_vcf_path_with_analysis(metadata, vcf_files)
122
+ overall_differences, results_per_analysis_alias = compare_all_analysis(metadata, file_path_per_analysis)
129
123
  write_result_yaml(output_yaml, overall_differences, results_per_analysis_alias)
130
124
 
131
125
 
@@ -1,6 +1,7 @@
1
1
  import argparse
2
2
  import os
3
3
 
4
+ import sys
4
5
  import yaml
5
6
  from ebi_eva_common_pyutils.logger import logging_config
6
7
  from eva_sub_cli.file_utils import open_gzip_if_required, fasta_iter
@@ -8,15 +9,16 @@ from eva_sub_cli.file_utils import open_gzip_if_required, fasta_iter
8
9
  logger = logging_config.get_logger(__name__)
9
10
 
10
11
 
11
- max_nb_lines = 10000
12
+ MAX_NB_LINES = 10000
12
13
 
13
14
 
14
- def trim_down_vcf(vcf_file, output_vcf):
15
+ def trim_down_vcf(vcf_file, output_vcf, max_nb_lines=MAX_NB_LINES):
15
16
  """
16
17
  Produce a smaller vcf files containing a maximum of 10000 records
17
18
  """
18
- with open_gzip_if_required(vcf_file) as vcf_in, open(output_vcf, 'w') as vcf_out:
19
+ with open_gzip_if_required(vcf_file) as vcf_in, open_gzip_if_required(output_vcf, 'w') as vcf_out:
19
20
  line_count = 0
21
+ trimmed_down = False
20
22
  ref_seq_names = set()
21
23
  for line in vcf_in:
22
24
  if line.startswith('#') or line_count < max_nb_lines:
@@ -25,10 +27,12 @@ def trim_down_vcf(vcf_file, output_vcf):
25
27
  line_count += 1
26
28
  ref_seq_names.add(line.split('\t')[0])
27
29
  else:
30
+ trimmed_down = True
28
31
  break
29
32
  if line_count != max_nb_lines:
30
33
  logger.warning(f'Only {line_count} found in the source VCF {vcf_file} ')
31
- return line_count, ref_seq_names
34
+
35
+ return line_count, trimmed_down, ref_seq_names
32
36
 
33
37
 
34
38
  def trim_down_fasta(fasta_file, output_fasta, ref_seq_names):
@@ -46,10 +50,9 @@ def trim_down_fasta(fasta_file, output_fasta, ref_seq_names):
46
50
  print(sequence[i:i+80], file=fasta_out)
47
51
  return found_sequences
48
52
 
49
-
50
- def main():
53
+ def parse_args(cmd_line_args):
51
54
  arg_parser = argparse.ArgumentParser(
52
- description=f'Take a VCF file and only keep {max_nb_lines} lines and remove unused fasta sequence from the '
55
+ description=f'Take a VCF file and only keep {MAX_NB_LINES} lines and remove unused fasta sequence from the '
53
56
  f'associated reference genome')
54
57
  arg_parser.add_argument('--vcf_file', dest='vcf_file', required=True,
55
58
  help='Path to the vcf file to be trimmed down')
@@ -63,16 +66,23 @@ def main():
63
66
  help='Path to the yaml file containing the trim down metrics')
64
67
 
65
68
  args = arg_parser.parse_args()
69
+ return args
70
+
71
+ def main():
72
+ args = parse_args(sys.argv[1:])
73
+
66
74
  logging_config.add_stdout_handler()
67
75
 
68
- line_count, ref_sequence = trim_down_vcf(args.vcf_file, args.output_vcf_file)
76
+ line_count, trimmed_down, ref_sequence = trim_down_vcf(args.vcf_file, args.output_vcf_file)
69
77
  sequence_found = trim_down_fasta(args.fasta_file, args.output_fasta_file, ref_sequence)
70
- trim_down_metrics = {'trim_down_vcf_record': line_count, 'number_sequence_found': sequence_found,
71
- 'trim_down_required': line_count == max_nb_lines}
78
+ trim_down_metrics = {'trim_down_vcf_record': line_count, 'number_sequence_found': len(sequence_found),
79
+ 'trim_down_required': trimmed_down}
72
80
  if len(sequence_found) != len(ref_sequence):
73
81
  logger.warning(f'Not all sequences were found in the fasta file. Cancelling trimming down of fasta file')
82
+ if os.path.exists(args.output_fasta_file):
83
+ os.remove(args.output_fasta_file)
74
84
  os.link(args.fasta_file, args.output_fasta_file)
75
85
  trim_down_metrics.pop('number_sequence_found')
76
- with open(args.output_yaml_file) as open_file:
86
+ with open(args.output_yaml_file, 'w') as open_file:
77
87
  yaml.safe_dump(trim_down_metrics, open_file)
78
88
 
@@ -105,11 +105,11 @@ class XlsxParser:
105
105
  if type_name == 'string':
106
106
  return str(value)
107
107
  if type_name == 'boolean':
108
- return str(value).lower() in ['true', '1', 't', 'y', 'yes']
108
+ return str(value).lower() in ['true', '1', "'1'", 't', 'y', 'yes']
109
109
  if type_name == 'list':
110
110
  # split and remove empty values
111
- return [XlsxParser.trim_value(v)
112
- for v in value.split(',') if v]
111
+ split_values = [XlsxParser.trim_value(v) for v in value.split(',') if v]
112
+ return [v for v in split_values if v]
113
113
  if type_name == 'date':
114
114
  return XlsxParser.serialize(value)
115
115
  return value
@@ -23,6 +23,11 @@ def is_submission_dir_writable(submission_dir):
23
23
  return False
24
24
  return True
25
25
 
26
+ def is_vcf_file(file_path):
27
+ if file_path:
28
+ file_path = file_path.strip().lower()
29
+ return file_path.endswith('.vcf') or file_path.endswith('.vcf.gz')
30
+ return False
26
31
 
27
32
  def backup_file_or_directory(file_name, max_backups=None):
28
33
  """
@@ -46,12 +51,12 @@ def backup_file_or_directory(file_name, max_backups=None):
46
51
  os.rename(file_name, file_name + '.1')
47
52
 
48
53
 
49
- def open_gzip_if_required(input_file):
54
+ def open_gzip_if_required(input_file, mode='r'):
50
55
  """Open a file in read mode using gzip if the file extension says .gz"""
51
56
  if input_file.endswith('.gz'):
52
- return gzip.open(input_file, 'rt')
57
+ return gzip.open(input_file, mode + 't')
53
58
  else:
54
- return open(input_file, 'r')
59
+ return open(input_file, mode)
55
60
 
56
61
 
57
62
  def fasta_iter(input_fasta):
@@ -0,0 +1,95 @@
1
+ import json
2
+ import os
3
+ from collections import defaultdict
4
+ from copy import deepcopy
5
+ from functools import cached_property
6
+
7
+ from ebi_eva_common_pyutils.logger import AppLogger
8
+
9
+
10
+ class EvaMetadataJson(AppLogger):
11
+
12
+ def __init__(self, path_to_json):
13
+ with open(path_to_json) as open_json:
14
+ self.content = json.load(open_json)
15
+
16
+ @property
17
+ def project(self):
18
+ return self.content.get('project', {})
19
+
20
+ @property
21
+ def submitter_details(self):
22
+ return self.content.get('submitterDetails', {})
23
+
24
+ @property
25
+ def analyses(self):
26
+ return self.content.get('analysis', [])
27
+
28
+ @property
29
+ def samples(self):
30
+ return self.content.get('sample', [])
31
+
32
+ @property
33
+ def files(self):
34
+ return self.content.get('files', [])
35
+
36
+ @cached_property
37
+ def resolved_files(self):
38
+ """Returns list of files with fileName resolved to an absolute path."""
39
+ mod_files = deepcopy(self.content.get('files', []))
40
+ for file_info in mod_files:
41
+ if 'fileName' in file_info:
42
+ file_info['fileName'] = os.path.abspath(file_info['fileName'])
43
+ return mod_files
44
+
45
+ def set_files(self, file_list):
46
+ assert isinstance(file_list, list)
47
+ self.content['files'] = file_list
48
+ # invalidate the cached property
49
+ if self.resolved_files:
50
+ del self.resolved_files
51
+
52
+ @cached_property
53
+ def samples_per_analysis(self):
54
+ """Returns mapping of analysis alias to sample names, based on metadata."""
55
+ samples_per_analysis = defaultdict(list)
56
+ for sample_info in self.samples:
57
+ for analysis_alias in sample_info.get('analysisAlias', []):
58
+ samples_per_analysis[analysis_alias].append(sample_info.get('sampleInVCF'))
59
+ return {
60
+ analysis_alias: set(samples)
61
+ for analysis_alias, samples in samples_per_analysis.items()
62
+ }
63
+
64
+ @cached_property
65
+ def files_per_analysis(self):
66
+ """Returns mapping of analysis alias to filenames, based on metadata."""
67
+ files_per_analysis = defaultdict(list)
68
+ for file_info in self.resolved_files:
69
+ files_per_analysis[file_info.get('analysisAlias')].append(file_info.get('fileName'))
70
+ return {
71
+ analysis_alias: set(filepaths)
72
+ for analysis_alias, filepaths in files_per_analysis.items()
73
+ }
74
+
75
+ def get_reference_assembly_for_analysis(self, analysis_alias):
76
+ """Returns the reference assembly for this analysis (does not validate format)."""
77
+ for analysis in self.analyses:
78
+ if analysis.get('analysisAlias') == analysis_alias:
79
+ return analysis.get('referenceGenome')
80
+ return None
81
+
82
+ def get_analysis_for_vcf_file(self, vcf_file):
83
+ """Returns list of analysis aliases associated with the vcf file path."""
84
+ if not os.path.exists(vcf_file):
85
+ raise FileNotFoundError(f'{vcf_file} cannot be resolved')
86
+ analysis_aliases = [analysis_alias for analysis_alias in self.files_per_analysis
87
+ if vcf_file in self.files_per_analysis[analysis_alias]
88
+ or os.path.basename(vcf_file) in [
89
+ os.path.basename(p) for p in self.files_per_analysis[analysis_alias]
90
+ ]]
91
+ return analysis_aliases
92
+
93
+ def write(self, output_path):
94
+ with open(output_path, 'w') as open_file:
95
+ json.dump(self.content, open_file)
@@ -116,6 +116,7 @@ process trim_down_vcf {
116
116
  path "${vcf.getBaseName()}_trim_down.log", emit: trim_down_log
117
117
  path "${vcf.getBaseName()}_trim_down.yml", emit: trim_down_metric
118
118
 
119
+ script:
119
120
  """
120
121
  mkdir output
121
122
  $params.python_scripts.trim_down --vcf_file $vcf --output_vcf_file output/$vcf --fasta_file $fasta --output_fasta_file output/$fasta --output_yaml_file ${vcf.getBaseName()}_trim_down.yml > ${vcf.getBaseName()}_trim_down.log