urgap 3.3.5__py2.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.
Files changed (163) hide show
  1. urgap/__init__.py +116 -0
  2. urgap/citation.txt +1 -0
  3. urgap/config_defaults/credentials_lookup.json +86 -0
  4. urgap/config_defaults/urgap.json +90 -0
  5. urgap/resources/Compressor/1_0_0/__init__.py +1 -0
  6. urgap/resources/Compressor/1_0_0/compressor.py +109 -0
  7. urgap/resources/Compressor/__init__.py +1 -0
  8. urgap/resources/FilterTabular/1_0_0/__init__.py +1 -0
  9. urgap/resources/FilterTabular/1_0_0/filter_tabular.py +229 -0
  10. urgap/resources/FilterTabular/2_0_0/__init__.py +1 -0
  11. urgap/resources/FilterTabular/2_0_0/filter_tabular.py +186 -0
  12. urgap/resources/FilterTabular/__init__.py +1 -0
  13. urgap/resources/TestNodes/BasicFunctionTestNode/1_1_0/__init__.py +1 -0
  14. urgap/resources/TestNodes/BasicFunctionTestNode/1_1_0/basic_function.py +45 -0
  15. urgap/resources/TestNodes/BasicFunctionTestNode/1_3_0/__init__.py +1 -0
  16. urgap/resources/TestNodes/BasicFunctionTestNode/1_3_0/basic_function.py +50 -0
  17. urgap/resources/TestNodes/BasicFunctionTestNode/__init__.py +1 -0
  18. urgap/resources/TestNodes/TestNode1/1_0_0/__init__.py +1 -0
  19. urgap/resources/TestNodes/TestNode1/1_0_0/test_resource_1.py +35 -0
  20. urgap/resources/TestNodes/TestNode1/__init__.py +1 -0
  21. urgap/resources/TestNodes/TestNode5/1_0_0/__init__.py +1 -0
  22. urgap/resources/TestNodes/TestNode5/1_0_0/test_resource_5.py +28 -0
  23. urgap/resources/TestNodes/TestNode5/__init__.py +1 -0
  24. urgap/resources/TestNodes/TestNode6/1_0_0/__init__.py +1 -0
  25. urgap/resources/TestNodes/TestNode6/1_0_0/test_resource_6.py +21 -0
  26. urgap/resources/TestNodes/TestNode6/__init__.py +1 -0
  27. urgap/resources/TestNodes/__init__.py +1 -0
  28. urgap/resources/VennDiagram/2_0_0/__init__.py +1 -0
  29. urgap/resources/VennDiagram/2_0_0/venn_diagram.py +897 -0
  30. urgap/resources/VennDiagram/__init__.py +1 -0
  31. urgap/resources/__init__.py +1 -0
  32. urgap/uconvert/__init__.py +5 -0
  33. urgap/uconvert/json.py +104 -0
  34. urgap/ucore.py +153 -0
  35. urgap/ucredentials/__init__.py +1 -0
  36. urgap/ucredentials/io/__init__.py +1 -0
  37. urgap/ucredentials/io/_base.py +25 -0
  38. urgap/ucredentials/io/akv.py +45 -0
  39. urgap/ucredentials/io/echo.py +23 -0
  40. urgap/ucredentials/io/env.py +25 -0
  41. urgap/ucredentials/io/gcp.py +64 -0
  42. urgap/ucredentials/ucredentials.py +342 -0
  43. urgap/uctl/__init__.py +1 -0
  44. urgap/uctl/describe.py +196 -0
  45. urgap/uctl/info.py +116 -0
  46. urgap/uctl/mcp/__init__.py +1 -0
  47. urgap/uctl/mcp/prompts.py +230 -0
  48. urgap/uctl/mcp/resources.py +117 -0
  49. urgap/uctl/mcp/tools.py +186 -0
  50. urgap/uctl/run.py +714 -0
  51. urgap/uctl/server/static/css/bootstrap.min.css +7 -0
  52. urgap/uctl/server/static/css/udashboard.css +26 -0
  53. urgap/uctl/server/static/js/bootstrap.bundle.min.js +7 -0
  54. urgap/uctl/server/static/js/d3.v4.min.js +2 -0
  55. urgap/uctl/server/static/js/ugraph.js +182 -0
  56. urgap/uctl/server/templates/dashboard.html +119 -0
  57. urgap/uctl/set.py +170 -0
  58. urgap/uctl/show.py +75 -0
  59. urgap/uctl/uctl.py +27 -0
  60. urgap/uctl/upload.py +80 -0
  61. urgap/ufile/__init__.py +1 -0
  62. urgap/ufile/io/__init__.py +1 -0
  63. urgap/ufile/io/_base.py +79 -0
  64. urgap/ufile/io/azure_blob.py +232 -0
  65. urgap/ufile/io/azure_datalake.py +244 -0
  66. urgap/ufile/io/azure_smb.py +322 -0
  67. urgap/ufile/io/file.py +168 -0
  68. urgap/ufile/io/github.py +271 -0
  69. urgap/ufile/io/google_storage.py +166 -0
  70. urgap/ufile/io/https.py +114 -0
  71. urgap/ufile/io/mylabdata.py +339 -0
  72. urgap/ufile/io/omiq.py +369 -0
  73. urgap/ufile/io/samba.py +272 -0
  74. urgap/ufile/ufile.py +978 -0
  75. urgap/ufile/ufile_io_manager.py +57 -0
  76. urgap/ufile/uuri.py +362 -0
  77. urgap/ufile_list.py +808 -0
  78. urgap/uftypes.py +357 -0
  79. urgap/uhelpers/__init__.py +5 -0
  80. urgap/uhelpers/beam.py +349 -0
  81. urgap/uhelpers/prefect.py +285 -0
  82. urgap/uinit.py +236 -0
  83. urgap/umeta/__init__.py +1 -0
  84. urgap/umeta/io/__init__.py +11 -0
  85. urgap/umeta/io/_base.py +126 -0
  86. urgap/umeta/io/_sqalchemy_base.py +510 -0
  87. urgap/umeta/io/dummy.py +23 -0
  88. urgap/umeta/io/gcpsql.py +38 -0
  89. urgap/umeta/io/postgresql.py +35 -0
  90. urgap/umeta/io/sqlite3.py +30 -0
  91. urgap/umeta/umeta.py +306 -0
  92. urgap/unode.py +1014 -0
  93. urgap/unode_manager.py +497 -0
  94. urgap/unodes/__init__.py +1 -0
  95. urgap/unodes/compress_to_tar/__init__.py +1 -0
  96. urgap/unodes/compress_to_tar/compress_to_tar.py +132 -0
  97. urgap/unodes/compress_to_zip/__init__.py +1 -0
  98. urgap/unodes/compress_to_zip/compress_to_zip.py +69 -0
  99. urgap/unodes/filter_tabular_to_csv/__init__.py +1 -0
  100. urgap/unodes/filter_tabular_to_csv/filter_tabular_to_csv.py +184 -0
  101. urgap/unodes/filter_tabular_to_parquet/__init__.py +1 -0
  102. urgap/unodes/filter_tabular_to_parquet/filter_tabular_to_parquet.py +81 -0
  103. urgap/unodes/filter_tabular_to_xlsx/__init__.py +1 -0
  104. urgap/unodes/filter_tabular_to_xlsx/filter_tabular_to_xlsx.py +81 -0
  105. urgap/unodes/gwas/__init__.py +1 -0
  106. urgap/unodes/gwas/plink/__init__.py +1 -0
  107. urgap/unodes/gwas/plink/plink_freq.py +122 -0
  108. urgap/unodes/test_nodes/__init__.py +1 -0
  109. urgap/unodes/test_nodes/basic_function.py +75 -0
  110. urgap/unodes/test_nodes/test_node_1.py +91 -0
  111. urgap/unodes/test_nodes/test_node_2.py +49 -0
  112. urgap/unodes/test_nodes/test_node_3.py +48 -0
  113. urgap/unodes/test_nodes/test_node_4.py +52 -0
  114. urgap/unodes/test_nodes/test_node_5.py +99 -0
  115. urgap/unodes/test_nodes/test_node_6.py +74 -0
  116. urgap/unodes/test_nodes/test_node_7.py +74 -0
  117. urgap/unodes/test_nodes/test_node_8.py +68 -0
  118. urgap/unodes/transcriptomics/__init__.py +1 -0
  119. urgap/unodes/transcriptomics/bowtie/__init__.py +1 -0
  120. urgap/unodes/transcriptomics/bowtie/bowtie.py +166 -0
  121. urgap/unodes/transcriptomics/bowtie/bowtie_build.py +150 -0
  122. urgap/unodes/transcriptomics/cutadapt/__init__.py +1 -0
  123. urgap/unodes/transcriptomics/cutadapt/cutadapt.py +98 -0
  124. urgap/unodes/transcriptomics/fastqc/__init__.py +1 -0
  125. urgap/unodes/transcriptomics/fastqc/fastqc.py +142 -0
  126. urgap/unodes/transcriptomics/kallisto/__init__.py +1 -0
  127. urgap/unodes/transcriptomics/kallisto/kallisto_index.py +135 -0
  128. urgap/unodes/transcriptomics/kallisto/kallisto_quant.py +131 -0
  129. urgap/unodes/transcriptomics/metrics/__init__.py +1 -0
  130. urgap/unodes/transcriptomics/metrics/rp_codon_metrics.py +121 -0
  131. urgap/unodes/transcriptomics/samtools/__init__.py +1 -0
  132. urgap/unodes/transcriptomics/samtools/samtools_bam_to_sam.py +93 -0
  133. urgap/unodes/transcriptomics/samtools/samtools_index_bam.py +91 -0
  134. urgap/unodes/transcriptomics/samtools/samtools_sam_to_bam.py +93 -0
  135. urgap/unodes/transcriptomics/samtools/samtools_sort_bam.py +92 -0
  136. urgap/unodes/transcriptomics/star/__init__.py +1 -0
  137. urgap/unodes/transcriptomics/star/star.py +152 -0
  138. urgap/unodes/transcriptomics/star/star_index.py +133 -0
  139. urgap/unodes/transcriptomics/umitools/__init__.py +1 -0
  140. urgap/unodes/transcriptomics/umitools/umi_tools_dedup.py +108 -0
  141. urgap/unodes/transcriptomics/umitools/umi_tools_extract.py +94 -0
  142. urgap/unodes/visualizers/__init__.py +1 -0
  143. urgap/unodes/visualizers/venn_diagram.py +83 -0
  144. urgap/ureport/__init__.py +1 -0
  145. urgap/ureport/templates/basic.html +86 -0
  146. urgap/ureport/ureport.py +678 -0
  147. urgap/urun_dict.py +569 -0
  148. urgap/utelemetry.py +762 -0
  149. urgap/util.py +160 -0
  150. urgap/utrace.py +778 -0
  151. urgap/utree.py +221 -0
  152. urgap/uwid/__init__.py +7 -0
  153. urgap/uwid/adjectives.txt +1313 -0
  154. urgap/uwid/adverbs.txt +317 -0
  155. urgap/uwid/indefinitpronomen.txt +36 -0
  156. urgap/uwid/nouns.txt +1433 -0
  157. urgap/uwid/prepositions.txt +68 -0
  158. urgap/uwid/uwid.py +95 -0
  159. urgap/uwid/verbs_3rd_form.txt +8324 -0
  160. urgap-3.3.5.dist-info/METADATA +209 -0
  161. urgap-3.3.5.dist-info/RECORD +163 -0
  162. urgap-3.3.5.dist-info/WHEEL +5 -0
  163. urgap-3.3.5.dist-info/entry_points.txt +2 -0
urgap/__init__.py ADDED
@@ -0,0 +1,116 @@
1
+ """urgap."""
2
+
3
+ import atexit
4
+ import logging
5
+ import os
6
+ import sys
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+ from pathlib import Path
10
+ from types import SimpleNamespace
11
+
12
+ # Urgap imports
13
+ import urgap.uconvert
14
+ import urgap.ucore
15
+ import urgap.ufile
16
+ import urgap.uftypes
17
+ import urgap.uinit
18
+ import urgap.util
19
+ import urgap.utree
20
+
21
+ from .ucredentials.ucredentials import UCredentialManager
22
+ from .ufile.ufile import UFile
23
+ from .ufile.ufile_io_manager import UFileIOManager
24
+ from .ufile.uuri import UUri
25
+ from .ufile_list import UFileList
26
+
27
+ # Importing into namespace a level higher
28
+ from .umeta import io
29
+ from .umeta.umeta import UMeta
30
+ from .unode import UNodeBase
31
+ from .unode_manager import UNodeManager
32
+ from .ureport.ureport import UReport
33
+ from .urun_dict import URunDict
34
+ from .utelemetry import UTelemetry
35
+ from .utrace import UTrace
36
+ from .uwid import UWIDGenerator
37
+
38
+ __all__ = [
39
+ "UCredentialManager",
40
+ "UFile",
41
+ "UFileIOManager",
42
+ "UFileList",
43
+ "UMeta",
44
+ "UNodeBase",
45
+ "UNodeManager",
46
+ "UReport",
47
+ "URunDict",
48
+ "UTelemetry",
49
+ "UTrace",
50
+ "UUri",
51
+ "UWIDGenerator",
52
+ "io",
53
+ "util",
54
+ ]
55
+
56
+ try:
57
+ __version__ = version(__name__)
58
+ except PackageNotFoundError:
59
+ __version__ = None
60
+
61
+ __version_str__ = str(__version__)
62
+ package_dir = Path(__file__).parent
63
+ if not hasattr(sys, "version_info") or sys.version_info < (3, 10):
64
+ msg = "Urgap requires Python 3.10 or later."
65
+ raise RuntimeError(msg)
66
+
67
+ urgap.uinit.configure_logger()
68
+ logger = logging.getLogger(__name__)
69
+ uwid_obj = urgap.UWIDGenerator()
70
+
71
+ home = Path(os.getenv("URGAP_HOME", str(Path.home() / ".urgap")))
72
+ info_box = [urgap.uinit.show_banner()]
73
+ info_format_string = " {k: <23}: {v}"
74
+ info_box.append(info_format_string.format(k="version", v=f"{urgap.__version__}"))
75
+
76
+ if urgap.home.exists() is False:
77
+ urgap.uinit.create_home_folder(home_dir_parent=urgap.home.parent)
78
+ else:
79
+ info_box.append(info_format_string.format(k="urgap home", v=f"{home}"))
80
+
81
+ project_folder = Path.cwd()
82
+ info_box.append(info_format_string.format(k="project_folder", v=f"{project_folder}"))
83
+ info_box.append(
84
+ info_format_string.format(k="urgap config", v=f"{urgap.home}/urgap.json"),
85
+ )
86
+
87
+ urgap.config = urgap.uinit.read_config()
88
+ urgap.uinit.load_certificates()
89
+
90
+ urgap.session_uwid = uwid_obj.generate_wid()
91
+ scratch_disk_base = urgap.uinit.set_scratch_disk_path(wid=urgap.session_uwid)
92
+ scratch_disk = scratch_disk_base # Temporary as we will update during run with WID
93
+ info_box.append(info_format_string.format(k="scratch disk", v=scratch_disk_base))
94
+ logger.info("\n".join([*info_box]))
95
+
96
+ urgap.uinit.copy_resources_if_needed(
97
+ target_dir=urgap.home,
98
+ force=urgap.config.get("update_resources", False),
99
+ )
100
+ # Instances
101
+ urgap.instances = SimpleNamespace()
102
+ urgap.instances.unode_manager = urgap.UNodeManager()
103
+ urgap.instances.ufile_io_manager = urgap.UFileIOManager()
104
+ urgap.instances.ucredential_manager = urgap.UCredentialManager()
105
+ urgap.instances.utelemetry_manager = urgap.UTelemetry()
106
+ urgap.utl = urgap.instances.utelemetry_manager
107
+ urgap.instances.utree_querier = urgap.utree.UTreeQuerier(namespace=urgap.uftypes)
108
+
109
+ urgap.init_node = urgap.instances.unode_manager.init_unode
110
+ urgap.init_unode = urgap.instances.unode_manager.init_unode
111
+
112
+ atexit.register(urgap.ucore.shutdown_local_upi_servers)
113
+ if urgap.config.get("clean_scratch_at_exit", True) is True:
114
+ atexit.register(urgap.ucore.clean_up_scratch_space)
115
+
116
+ atexit.register(urgap.ucore.shutdown_telemetry)
urgap/citation.txt ADDED
@@ -0,0 +1 @@
1
+ <put in your name>, Fufezan, C. (2022) Journal of awesomeness
@@ -0,0 +1,86 @@
1
+ {
2
+ "description": "user and password are pointing to the ENV variables that contain the actual data. host and schema are required for urgap lookup",
3
+ "credentials": [
4
+ {
5
+ "description": "github token https raw, here user=password will be formatted into the query",
6
+ "scheme": "https",
7
+ "host": "raw.githubusercontent.com",
8
+ "user": "U_GITHUB_GSK_USER",
9
+ "password": "U_GITHUB_GSK_PASSWORD",
10
+ "secure": true,
11
+ "secret_store": "env",
12
+ "base_url": "https://raw.githubusercontent.com"
13
+ },
14
+ {
15
+ "description": "local mongodb",
16
+ "scheme": "mongodb",
17
+ "host": "localhost:27017",
18
+ "user": "U_MONGODB_USER",
19
+ "password": "U_MONGODB_PASSWORD",
20
+ "secure": true,
21
+ "secret_store": "env",
22
+ "base_url": "mongodb://localhost:27017"
23
+ },
24
+ {
25
+ "description": "local ssh",
26
+ "scheme": "ssh",
27
+ "host": "localhost",
28
+ "user": "ENV_VARIABLE_WITH_SSH_USER",
29
+ "password": "ENV_VARIABLE_WITH_SSH_KEY",
30
+ "secure": true,
31
+ "secret_store": "env",
32
+ "base_url": "ssh://localhost"
33
+ },
34
+ {
35
+ "description": "if project_id is defined, it will be used instead of host for GCP secret extraction",
36
+ "scheme": "postgresql+pg8000",
37
+ "host": "10.138.0.5:5432",
38
+ "user": "U_GCS_SQL_USER",
39
+ "password": "U_GCS_SQL_PASSWORD",
40
+ "secure": true,
41
+ "secret_store": "gcp",
42
+ "cloud_host_pid": "gsk-rd-ngs-dev",
43
+ "base_url": "postgresql+pg8000://10.138.0.5:5432"
44
+ },
45
+ {
46
+ "description": "mylabdata interface",
47
+ "scheme": "mylabdata",
48
+ "host": "<mylabdata-files-api-url-without-https>",
49
+ "user": "U_MYLABDATA_USER",
50
+ "password": "U_MYLABDATA_PASSWORD",
51
+ "secure": true,
52
+ "secret_store": "env",
53
+ "base_url": "mylabdata://<mylabdata-files-api-url-without-https>"
54
+ },
55
+ {
56
+ "description": "smb test interface",
57
+ "scheme": "smb",
58
+ "host": "10.0.20.165:445",
59
+ "user": "U_SMB_TEST_USER",
60
+ "password": "U_SMB_TEST_PASSWORD",
61
+ "secure": true,
62
+ "secret_store": "env",
63
+ "base_url": "smb://10.0.20.165:445"
64
+ },
65
+ {
66
+ "description": "OpenTelemetry Azure (dsoappinstprod). We store the connection string in password and user is just a dummy.",
67
+ "scheme": "https",
68
+ "host": "eastus2-3.in.applicationinsights.azure.com",
69
+ "user": "U_AZ_INSIGHT_USER",
70
+ "password": "U_AZ_INSIGHT_PASSWORD",
71
+ "secure": true,
72
+ "secret_store": "env",
73
+ "base_url": "https://eastus2-3.in.applicationinsights.azure.com"
74
+ },
75
+ {
76
+ "description": "Azure Service Bus namespace connection string. Password env var must contain full Service Bus connection string (starts with 'Endpoint=sb://'). User not used (set to null).",
77
+ "scheme": "azure-servicebus",
78
+ "host": "<namespace>.servicebus.windows.net",
79
+ "user": null,
80
+ "password": "U_AZ_SERVICEBUS_CONNECTION_STRING",
81
+ "secure": true,
82
+ "secret_store": "env",
83
+ "base_url": "azure-servicebus://<namespace>.servicebus.windows.net"
84
+ }
85
+ ]
86
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "info": "Urgap will internally reformat this config into a simple dictionary (k, v[v'value']) available under urgap.config",
3
+ "description": "urgap default config",
4
+ "umeta": {
5
+ "value": "sqlite3",
6
+ "description": "Storage interface for meta data. Might require 3rd party installations",
7
+ "options": ["mongodb", "sqlite3", "postgresql", "gcpsql"]
8
+ },
9
+ "logging_level": {
10
+ "value": "DEBUG",
11
+ "description": "Verbosity of urgap, can be set to any python logger level"
12
+ },
13
+ "update_resources": {
14
+ "value": false,
15
+ "description": "Define whether resources from the repo will be force copied into $URGAP_HOME on startup. This is useful if resources are developed in the urgap repo, yet code execution will use $URGAP_HOME/resource code."
16
+ },
17
+ "update_uparma": {
18
+ "value": false,
19
+ "description": "Define whether uparma json download should be forced (part of u2 dev work). Not required for release"
20
+ },
21
+ "hash_algorithm": {
22
+ "value": "md5",
23
+ "description": "Hash algorithm used in Urgap in order to create unique identifiers"
24
+ },
25
+ "max_parallel_cores": {
26
+ "value": null,
27
+ "description": "Number of CPU cores to use for parallel processing."
28
+ },
29
+ "umeta-postgresql-url": {
30
+ "value": "postgresql://localhost:5432",
31
+ "description": "url for postgresql instance, used if umeta is set to postgresql. Login and password are extracted via ENV or alternative secret store defined in credentials_lookup.json"
32
+ },
33
+ "umeta-sqlite3-url": {
34
+ "value": null,
35
+ "description": "url for sqlite3 db file, used if umeta is set to sqlite. Should start with file:///"
36
+ },
37
+ "umeta-gcpsql-url": {
38
+ "value": null
39
+ },
40
+ "scratch_disk": {
41
+ "value": null,
42
+ "description": "Temporary urgap processing disk"
43
+ },
44
+ "mode": {
45
+ "value": "dev",
46
+ "description": "Mode in which urgap is running. Can be set to prod for stricter rules.",
47
+ "options": ["dev", "prod"]
48
+ },
49
+ "clean_scratch_at_exit": {
50
+ "value": true,
51
+ "description": "Urgap uses scratch <scratch_disk>/<wid> as temporary spaces. This flag will ensure that temporary directories (<wid>s) will be deleted when interpreter exits."
52
+ },
53
+ "requests_timeout_connect": {
54
+ "value": null,
55
+ "description": "Connect timeout for python requests"
56
+ },
57
+ "requests_timeout_read": {
58
+ "value": null,
59
+ "description": "Read timeout for python requests"
60
+ },
61
+ "terminate_remote_servers_on_exit": {
62
+ "value": false,
63
+ "description": "Send a termination signal to all ports on localhost during urgap exit"
64
+ },
65
+ "opentelemetry_collector_url": {
66
+ "value": null,
67
+ "description": "Open Telementry Collector url, e.g. http://localhost:4317"
68
+ },
69
+ "opentelemetry_exporter_type": {
70
+ "value": null,
71
+ "description": "Open Telementry Exporter Type. Null will disable OTEL.",
72
+ "options": [null, "OTPL", "AZ-Insights", "Console"]
73
+ },
74
+ "service_bus_topic": {
75
+ "value": "urgap_queue",
76
+ "description": "Topic for pulling work from Service Bus worker."
77
+ },
78
+ "service_bus_completion_topic": {
79
+ "value": "urgap_completed",
80
+ "description": "Optional topic to publish completion events to."
81
+ },
82
+ "service_bus_exit_after_first_message": {
83
+ "value": true,
84
+ "description": "If true, worker exits successfully after processing one message."
85
+ },
86
+ "service_bus_max_autorenewal_minutes": {
87
+ "value": 0,
88
+ "description": "Maximum time after which to stop auto renewing lock on message. 0 disables this."
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ """Init Compressor 1_0_0 resource."""
@@ -0,0 +1,109 @@
1
+ """Compressor resource."""
2
+
3
+ import argparse
4
+ import logging
5
+ import os.path
6
+ import subprocess
7
+ import tarfile
8
+
9
+ from ast import literal_eval
10
+ from pathlib import Path
11
+ from zipfile import ZipFile
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def main(
17
+ compression_format: str,
18
+ file_list: list,
19
+ output_file_path: str,
20
+ max_tar_size: int | None = None,
21
+ ) -> None:
22
+ """Create tar or zip archive from input file list.
23
+
24
+ Selecting split_tar splits tar archive into parts.
25
+
26
+ Args:
27
+ compression_format: Currently supported are tar, zip and split_tar.
28
+ file_list (list): list of tuples of file Path objects and tag Path objects (or None).
29
+ max_tar_size: Maximum size of the split tar.
30
+ output_file_path (Path): path object for output.
31
+ """
32
+ if max_tar_size is not None:
33
+ files_to_archive = [files[0] for files in file_list]
34
+ common_base = Path(os.path.commonpath(files_to_archive)).parent
35
+ relative_files = [Path(f).relative_to(common_base) for f in files_to_archive]
36
+ tar_process = subprocess.Popen(
37
+ ["tar", "-cf", "-", "-C", str(common_base), *map(str, relative_files)],
38
+ stdout=subprocess.PIPE,
39
+ )
40
+ split_process = subprocess.Popen(
41
+ [
42
+ "split",
43
+ "-b",
44
+ f"{max_tar_size}",
45
+ "-",
46
+ f"{Path(output_file_path).parent / 'part.'}",
47
+ ],
48
+ stdin=tar_process.stdout,
49
+ stdout=subprocess.PIPE,
50
+ stderr=subprocess.PIPE,
51
+ )
52
+ tar_process.stdout.close()
53
+ split_process.communicate()
54
+ tar_process.wait()
55
+ if tar_process.returncode != 0:
56
+ msg = f"Tar process failed with code {tar_process.returncode}"
57
+ logger.error(msg)
58
+ if split_process.returncode != 0:
59
+ msg = f"Split process failed with code {split_process.returncode}"
60
+ logger.error(msg)
61
+ else:
62
+ logger.info("Tar archive created and split successfully")
63
+ elif compression_format == "tar":
64
+ with tarfile.open(output_file_path, mode="w:") as tar:
65
+ for file, tag in file_list:
66
+ tar.add(file, arcname=Path(file).name)
67
+ if tag is not None:
68
+ tar.add(tag, arcname=Path(tag).name)
69
+ elif compression_format == "zip":
70
+ with ZipFile(output_file_path, "w") as zip_file:
71
+ for file, tag in file_list:
72
+ zip_file.write(file, arcname=Path(file).name)
73
+ if tag is not None:
74
+ zip_file.write(tag, arcname=Path(tag).name)
75
+ else:
76
+ msg = "Only zip and tar are valid compression formats."
77
+ raise NotImplementedError(msg)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ parser = argparse.ArgumentParser()
82
+ parser.add_argument(
83
+ "-i",
84
+ "--input_files",
85
+ dest="input_files",
86
+ action="append",
87
+ help="input files to be compressed",
88
+ )
89
+ parser.add_argument("-o", "--output_file", dest="output_file", help="output file")
90
+ parser.add_argument(
91
+ "-cf",
92
+ "--compression_format",
93
+ dest="compression_format",
94
+ help="compression format: tar or zip",
95
+ )
96
+ parser.add_argument(
97
+ "-s",
98
+ "--size",
99
+ dest="max_tar_size",
100
+ help="maximum tar size",
101
+ )
102
+ args = parser.parse_args()
103
+
104
+ main(
105
+ file_list=[literal_eval(str_tuple) for str_tuple in args.input_files],
106
+ output_file_path=args.output_file,
107
+ compression_format=args.compression_format,
108
+ max_tar_size=args.max_tar_size,
109
+ )
@@ -0,0 +1 @@
1
+ """Init Compressor resources."""
@@ -0,0 +1 @@
1
+ """Init FilterTabular 1_0_0 resource."""
@@ -0,0 +1,229 @@
1
+ """Filter / and or merge files with pandas operations."""
2
+
3
+ import argparse
4
+ import logging
5
+ import pprint
6
+ import zipfile
7
+
8
+ from pathlib import Path
9
+
10
+ import pandas as pd
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def sense_file_format(file: str) -> str:
16
+ """Sense the format of a file.
17
+
18
+ Args:
19
+ file: File to analyze.
20
+
21
+ Returns:
22
+ File format as string.
23
+ """
24
+ with Path(file).open("rb") as f:
25
+ match f.read(4):
26
+ case b"PAR1":
27
+ f.seek(-4, 2)
28
+ if f.read(4) == b"PAR1":
29
+ return "parquet"
30
+ case b"PK\x03\x04":
31
+ with zipfile.ZipFile(file, "r") as zip_file:
32
+ required_files = ["[Content_Types].xml", "xl/"]
33
+ if any(file in zip_file.namelist() for file in required_files):
34
+ return "xlsx"
35
+ return "csv"
36
+
37
+
38
+ def get_input_file_lists(input_files: list) -> dict:
39
+ """Get the separate input file lists for csv, parquet and xlsx.
40
+
41
+ Args:
42
+ input_files: All input files.
43
+
44
+ Returns:
45
+ Input files split up in corresponding lists.
46
+ """
47
+ files = {"csv": [], "parquet": [], "xlsx": []}
48
+ for file in input_files:
49
+ input_file_type = sense_file_format(file)
50
+ files[input_file_type].append(file)
51
+ return files
52
+
53
+
54
+ def read_dfs(grouped_input_files: dict, sep: str | None) -> list:
55
+ """Read any of the CSV, Parquet and XLSX files.
56
+
57
+ Args:
58
+ grouped_input_files: Dict with all files mapped to filetype.
59
+ sep: Seperator or delimiter for read csv function.
60
+
61
+ Returns:
62
+ List with Dataframe from each file.
63
+ """
64
+ pd_functions = {
65
+ "csv": {"function": pd.read_csv},
66
+ "parquet": {"function": pd.read_parquet},
67
+ "xlsx": {"function": pd.read_excel},
68
+ }
69
+ dfs = []
70
+ for file_type, file_list in grouped_input_files.items():
71
+ if file_type == "csv":
72
+ dfs.extend(
73
+ pd_functions[file_type]["function"](file, sep=sep, engine="python")
74
+ for file in file_list
75
+ )
76
+ else:
77
+ dfs.extend(pd_functions[file_type]["function"](file) for file in file_list)
78
+ return dfs
79
+
80
+
81
+ def write_dfs(df: pd.DataFrame, mode: str, output: str) -> None:
82
+ """Write any of the CSV, Parquet and XLSX files.
83
+
84
+ Args:
85
+ df: Dataframe to write.
86
+ mode: Defines output file format.
87
+ output: Output path.
88
+
89
+ Raises:
90
+ ValueError: Unsupported mode is requested.
91
+ """
92
+ if mode == "csv":
93
+ df.to_csv(output, index=False)
94
+ elif mode == "parquet":
95
+ df.to_parquet(output, index=False)
96
+ elif mode == "xlsx":
97
+ df.to_excel(output, index=False)
98
+ else:
99
+ msg = (
100
+ f"Filter mode has to be defined with -m."
101
+ f"You defined filter mode {mode} which is currently not supported."
102
+ f"Supported modes are 'csv', 'parquet' and 'xlsx'."
103
+ )
104
+ raise ValueError(msg)
105
+
106
+
107
+ def merge_parquet_files(grouped_input_files: dict, output_file_path: str) -> None:
108
+ """Merge all parquet files and write to the destination file path.
109
+
110
+ Args:
111
+ grouped_input_files: Input grouped_input_files.
112
+ output_file_path: Output parquet file path.
113
+
114
+ Returns:
115
+ True when successful else False.
116
+ """
117
+ import pyarrow as pa
118
+ import pyarrow.parquet as pq
119
+
120
+ parquet_files = grouped_input_files["parquet"]
121
+ with pq.ParquetWriter(
122
+ output_file_path,
123
+ schema=pq.read_table(parquet_files[0]).schema,
124
+ ) as writer:
125
+ for parquet in parquet_files:
126
+ if Path(parquet).exists() is False:
127
+ msg = f"Parquet file {parquet} does not exist!"
128
+ raise FileNotFoundError(msg)
129
+ pf = pq.ParquetFile(parquet)
130
+
131
+ for batch in pf.iter_batches(batch_size=6553600):
132
+ table = pa.Table.from_batches([batch])
133
+ writer.write_table(table)
134
+
135
+
136
+ def main(
137
+ input_files: str | Path | list | None = None,
138
+ query_string: str | None = None,
139
+ output: str | None = None,
140
+ mode: str | None = None,
141
+ sep: str | None = None,
142
+ ) -> None:
143
+ r"""Filter and merge node.
144
+
145
+ Args:
146
+ sep: Seperator or delimiter. Defaults to ','
147
+ mode: Defines the output file type.
148
+ input_files: Path to files (can be multiple).
149
+
150
+ query_string (str): pandas df.query style strings.
151
+ Note: It is worth selecting each column by encapsulating it with
152
+ `\ in order to avoid errors, e.g.:
153
+ " -1 < \`Accuracy (ppm)\` < 1 and \`MS-GF:RawScore\` > 10"
154
+
155
+ output (str): Path to output file post filtering.
156
+ """
157
+ if len(input_files) == 0:
158
+ msg = "I need input files!..."
159
+ raise OSError(msg)
160
+
161
+ grouped_input_files = get_input_file_lists(input_files)
162
+ logger.info(pprint.pformat(grouped_input_files))
163
+ if (
164
+ mode == "parquet"
165
+ and len(grouped_input_files.get("csv", [])) == 0
166
+ and len(grouped_input_files.get("parquet", [])) > 1
167
+ and len(grouped_input_files.get("xlsx", [])) == 0
168
+ and query_string is None
169
+ ):
170
+ merge_parquet_files(grouped_input_files, output)
171
+
172
+ else:
173
+ dfs = read_dfs(grouped_input_files, sep)
174
+ concatenated_df = pd.concat(dfs)
175
+ if query_string is not None:
176
+ old_len = len(concatenated_df)
177
+ try:
178
+ concatenated_df = concatenated_df.query(query_string)
179
+ except (pd.errors.UndefinedVariableError, SyntaxError, ValueError) as e:
180
+ msg = f"Query string {query_string} is invalid"
181
+ f"Dataframe columns: {list(concatenated_df.columns)}"
182
+ logger.warning(msg)
183
+ raise RuntimeError(msg) from e
184
+
185
+ msg = f"Filtered {old_len - concatenated_df.shape[0]} rows"
186
+
187
+ logger.info(msg)
188
+ if concatenated_df.empty is True:
189
+ logger.warning("All rows have been filtered out!")
190
+ concatenated_df = concatenated_df.reset_index(drop=True)
191
+ write_dfs(concatenated_df, mode=mode, output=output)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ parser = argparse.ArgumentParser()
196
+ parser.add_argument(
197
+ "-i",
198
+ "--input_files",
199
+ dest="input_files",
200
+ action="append",
201
+ help="input files to be compressed",
202
+ )
203
+ parser.add_argument("-o", "--output_file", dest="output_file", help="output file")
204
+ parser.add_argument(
205
+ "-q",
206
+ "--query_string",
207
+ dest="query_string",
208
+ help="pandas query string",
209
+ )
210
+ parser.add_argument(
211
+ "-m",
212
+ "--mode",
213
+ dest="mode",
214
+ help="filter mode like csv or parquet",
215
+ )
216
+ parser.add_argument(
217
+ "-s",
218
+ "--sep",
219
+ dest="sep",
220
+ help="Seperator or delimiter. Defaults to ','",
221
+ )
222
+ args = parser.parse_args()
223
+ main(
224
+ input_files=args.input_files,
225
+ output=args.output_file,
226
+ query_string=args.query_string,
227
+ mode=args.mode,
228
+ sep=args.sep,
229
+ )
@@ -0,0 +1 @@
1
+ """Init FilterTabular 2_0_0 resource."""