csvpath 0.0.532__tar.gz → 0.0.533__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 (293) hide show
  1. {csvpath-0.0.532 → csvpath-0.0.533}/PKG-INFO +2 -1
  2. csvpath-0.0.533/assets/config/jenkins-local-gcs.ini +129 -0
  3. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/csvpaths.py +4 -0
  4. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_registrar.py +1 -1
  5. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_manager.py +13 -7
  6. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_data_reader.py +0 -14
  7. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_nos.py +3 -0
  8. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/box.py +1 -0
  9. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/config.py +4 -0
  10. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_readers.py +18 -0
  11. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_writers.py +6 -0
  12. csvpath-0.0.533/csvpath/util/gcs/gcs_data_reader.py +52 -0
  13. csvpath-0.0.533/csvpath/util/gcs/gcs_data_writer.py +33 -0
  14. csvpath-0.0.533/csvpath/util/gcs/gcs_fingerprinter.py +44 -0
  15. csvpath-0.0.533/csvpath/util/gcs/gcs_nos.py +98 -0
  16. csvpath-0.0.533/csvpath/util/gcs/gcs_utils.py +73 -0
  17. csvpath-0.0.533/csvpath/util/gcs/gcs_xlsx_data_reader.py +34 -0
  18. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/nos.py +15 -0
  19. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/path_util.py +1 -2
  20. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_nos.py +3 -0
  21. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_nos.py +3 -0
  22. {csvpath-0.0.532 → csvpath-0.0.533}/pyproject.toml +2 -1
  23. {csvpath-0.0.532 → csvpath-0.0.533}/LICENSE +0 -0
  24. {csvpath-0.0.532 → csvpath-0.0.533}/README.md +0 -0
  25. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/config.ini +0 -0
  26. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/function.imports +0 -0
  27. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-azure.ini +0 -0
  28. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-filesystem.ini +0 -0
  29. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-sftp.ini +0 -0
  30. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-s3.ini +0 -0
  31. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-azure.ini +0 -0
  32. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-local.ini +0 -0
  33. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-sftp.ini +0 -0
  34. {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/local-localhost-sftp.ini +0 -0
  35. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.bat +0 -0
  36. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.py +0 -0
  37. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.sh +0 -0
  38. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.bat +0 -0
  39. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.py +0 -0
  40. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.sh +0 -0
  41. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sqlite/csvpath +0 -0
  42. {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sqlite/schema.sql +0 -0
  43. {csvpath-0.0.532 → csvpath-0.0.533}/config/config.ini +0 -0
  44. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/__init__.py +0 -0
  45. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/__init__.py +0 -0
  46. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/asker.py +0 -0
  47. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/cli.py +0 -0
  48. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/const.py +0 -0
  49. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/debug_config.py +0 -0
  50. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/drill_down.py +0 -0
  51. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/function_describer.py +0 -0
  52. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/function_lister.py +0 -0
  53. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/selecter.py +0 -0
  54. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/csvpath.py +0 -0
  55. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/__init__.py +0 -0
  56. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error.py +0 -0
  57. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_collector.py +0 -0
  58. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_comms.py +0 -0
  59. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_manager.py +0 -0
  60. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_manager.py +0 -0
  61. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_metadata.py +0 -0
  62. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/files_listener.py +0 -0
  63. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/lines_and_headers_cacher.py +0 -0
  64. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/ckan.py +0 -0
  65. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/ckan_listener.py +0 -0
  66. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/datafile.py +0 -0
  67. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/dataset.py +0 -0
  68. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/event.py +0 -0
  69. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/event_result.py +0 -0
  70. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/file_listener_ol.py +0 -0
  71. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/job.py +0 -0
  72. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/ol_listener.py +0 -0
  73. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/paths_listener_ol.py +0 -0
  74. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/result_listener_ol.py +0 -0
  75. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/results_listener_ol.py +0 -0
  76. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run.py +0 -0
  77. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run_listener_ol.py +0 -0
  78. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run_state.py +0 -0
  79. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/sender.py +0 -0
  80. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/error_metrics.py +0 -0
  81. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/metrics.py +0 -0
  82. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_error_listener.py +0 -0
  83. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_listener.py +0 -0
  84. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_result_listener.py +0 -0
  85. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_results_listener.py +0 -0
  86. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftp/sftp_sender.py +0 -0
  87. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/arrival_handler.py +0 -0
  88. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/rpc.py +0 -0
  89. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/sftpplus_listener.py +0 -0
  90. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/transfer_creator.py +0 -0
  91. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/transfers.py +0 -0
  92. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/slack/event.py +0 -0
  93. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/slack/sender.py +0 -0
  94. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/schema.sql +0 -0
  95. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/sqlite_result_listener.py +0 -0
  96. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/sqlite_results_listener.py +0 -0
  97. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/listener.py +0 -0
  98. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/metadata.py +0 -0
  99. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_listener.py +0 -0
  100. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_manager.py +0 -0
  101. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_metadata.py +0 -0
  102. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_registrar.py +0 -0
  103. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/registrar.py +0 -0
  104. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_errors_reader.py +0 -0
  105. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_lines_reader.py +0 -0
  106. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_printouts_reader.py +0 -0
  107. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_unmatched_reader.py +0 -0
  108. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/readers.py +0 -0
  109. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result.py +0 -0
  110. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_file_reader.py +0 -0
  111. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_metadata.py +0 -0
  112. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_registrar.py +0 -0
  113. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_serializer.py +0 -0
  114. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_metadata.py +0 -0
  115. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_registrar.py +0 -0
  116. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_listener_stdout.py +0 -0
  117. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_metadata.py +0 -0
  118. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_registrar.py +0 -0
  119. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/__init__.py +0 -0
  120. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/__init__.py +0 -0
  121. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/args.py +0 -0
  122. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/args_helper.py +0 -0
  123. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/all.py +0 -0
  124. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/andf.py +0 -0
  125. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/any.py +0 -0
  126. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/between.py +0 -0
  127. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/empty.py +0 -0
  128. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/exists.py +0 -0
  129. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/inf.py +0 -0
  130. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/no.py +0 -0
  131. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/notf.py +0 -0
  132. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/orf.py +0 -0
  133. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/yes.py +0 -0
  134. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count.py +0 -0
  135. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_bytes.py +0 -0
  136. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_headers.py +0 -0
  137. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_lines.py +0 -0
  138. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_scans.py +0 -0
  139. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/counter.py +0 -0
  140. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/every.py +0 -0
  141. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/has_matches.py +0 -0
  142. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/increment.py +0 -0
  143. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/tally.py +0 -0
  144. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/total_lines.py +0 -0
  145. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/dates/now.py +0 -0
  146. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function.py +0 -0
  147. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_factory.py +0 -0
  148. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_finder.py +0 -0
  149. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_focus.py +0 -0
  150. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/append.py +0 -0
  151. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/collect.py +0 -0
  152. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/empty_stack.py +0 -0
  153. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/end.py +0 -0
  154. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/header_name.py +0 -0
  155. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/header_names_mismatch.py +0 -0
  156. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/headers.py +0 -0
  157. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/insert.py +0 -0
  158. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/mismatch.py +0 -0
  159. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/replace.py +0 -0
  160. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/reset_headers.py +0 -0
  161. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/advance.py +0 -0
  162. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/after_blank.py +0 -0
  163. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/dups.py +0 -0
  164. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/first.py +0 -0
  165. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/first_line.py +0 -0
  166. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/last.py +0 -0
  167. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/stop.py +0 -0
  168. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/above.py +0 -0
  169. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/add.py +0 -0
  170. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/divide.py +0 -0
  171. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/equals.py +0 -0
  172. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/intf.py +0 -0
  173. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/mod.py +0 -0
  174. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/multiply.py +0 -0
  175. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/odd.py +0 -0
  176. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/round.py +0 -0
  177. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/subtotal.py +0 -0
  178. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/subtract.py +0 -0
  179. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/sum.py +0 -0
  180. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/fingerprint.py +0 -0
  181. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/importf.py +0 -0
  182. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/random.py +0 -0
  183. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/jinjaf.py +0 -0
  184. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/print_line.py +0 -0
  185. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/print_queue.py +0 -0
  186. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/printf.py +0 -0
  187. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/table.py +0 -0
  188. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/minf.py +0 -0
  189. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/percent.py +0 -0
  190. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/percent_unique.py +0 -0
  191. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/stdev.py +0 -0
  192. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/alter.py +0 -0
  193. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/caps.py +0 -0
  194. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/concat.py +0 -0
  195. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/contains.py +0 -0
  196. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/length.py +0 -0
  197. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/lower.py +0 -0
  198. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/metaphone.py +0 -0
  199. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/regex.py +0 -0
  200. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/starts_with.py +0 -0
  201. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/strip.py +0 -0
  202. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/substring.py +0 -0
  203. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/upper.py +0 -0
  204. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/testing/debug.py +0 -0
  205. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/__init__.py +0 -0
  206. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/boolean.py +0 -0
  207. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/datef.py +0 -0
  208. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/decimal.py +0 -0
  209. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/email.py +0 -0
  210. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/nonef.py +0 -0
  211. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/string.py +0 -0
  212. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/type.py +0 -0
  213. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/url.py +0 -0
  214. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/fail.py +0 -0
  215. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/failed.py +0 -0
  216. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/line.py +0 -0
  217. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/get.py +0 -0
  218. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/pushpop.py +0 -0
  219. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/put.py +0 -0
  220. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/track.py +0 -0
  221. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/variables.py +0 -0
  222. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/lark_parser.py +0 -0
  223. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/lark_transformer.py +0 -0
  224. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/matcher.py +0 -0
  225. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/__init__.py +0 -0
  226. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/equality.py +0 -0
  227. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/expression.py +0 -0
  228. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/header.py +0 -0
  229. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/matchable.py +0 -0
  230. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/qualified.py +0 -0
  231. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/reference.py +0 -0
  232. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/term.py +0 -0
  233. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/variable.py +0 -0
  234. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/exceptions.py +0 -0
  235. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/expression_encoder.py +0 -0
  236. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/expression_utility.py +0 -0
  237. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/lark_print_parser.py +0 -0
  238. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/print_parser.py +0 -0
  239. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/runtime_data_collector.py +0 -0
  240. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/error_mode.py +0 -0
  241. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/explain_mode.py +0 -0
  242. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/files_mode.py +0 -0
  243. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/logic_mode.py +0 -0
  244. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/mode_controller.py +0 -0
  245. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/print_mode.py +0 -0
  246. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/return_mode.py +0 -0
  247. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/run_mode.py +0 -0
  248. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/source_mode.py +0 -0
  249. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/transfer_mode.py +0 -0
  250. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/unmatched_mode.py +0 -0
  251. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/validation_mode.py +0 -0
  252. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/__init__.py +0 -0
  253. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/exceptions.py +0 -0
  254. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/parser.out +0 -0
  255. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/parsetab.py +0 -0
  256. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/scanner.py +0 -0
  257. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/scanning_lexer.py +0 -0
  258. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_data_writer.py +0 -0
  259. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_fingerprinter.py +0 -0
  260. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_utils.py +0 -0
  261. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_xlsx_data_reader.py +0 -0
  262. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/cache.py +0 -0
  263. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/caser.py +0 -0
  264. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/class_loader.py +0 -0
  265. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/code.py +0 -0
  266. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/config_exception.py +0 -0
  267. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/exceptions.py +0 -0
  268. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_info.py +0 -0
  269. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/hasher.py +0 -0
  270. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/http/http_data_reader.py +0 -0
  271. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/intermediary.py +0 -0
  272. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/last_line_stats.py +0 -0
  273. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_counter.py +0 -0
  274. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_monitor.py +0 -0
  275. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_spooler.py +0 -0
  276. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/log_utility.py +0 -0
  277. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/metadata_parser.py +0 -0
  278. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/pandas_data_reader.py +0 -0
  279. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/printer.py +0 -0
  280. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/reference_finder.py +0 -0
  281. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/reference_parser.py +0 -0
  282. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_data_reader.py +0 -0
  283. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_data_writer.py +0 -0
  284. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_fingerprinter.py +0 -0
  285. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_utils.py +0 -0
  286. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_xlsx_data_reader.py +0 -0
  287. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_config.py +0 -0
  288. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_data_reader.py +0 -0
  289. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_data_writer.py +0 -0
  290. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_fingerprinter.py +0 -0
  291. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_xlsx_data_reader.py +0 -0
  292. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sqliter.py +0 -0
  293. {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/var_utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: csvpath
3
- Version: 0.0.532
3
+ Version: 0.0.533
4
4
  Summary: A data preboarding framework for managing and validating CSV, Excel, and other tabular data files using a Collect, Store, Validate, Publish pattern to create a trusted publisher for downstream data consumers.
5
5
  Author: David Kershaw
6
6
  Author-email: dk107dk@hotmail.com
@@ -25,6 +25,7 @@ Requires-Dist: azure-storage-blob (>=12.24.1,<13.0.0)
25
25
  Requires-Dist: boto3 (>=1.35.91,<2.0.0)
26
26
  Requires-Dist: ckanapi (>=4.8,<5.0)
27
27
  Requires-Dist: email-validator (>=2.2.0,<3.0.0)
28
+ Requires-Dist: google-cloud-storage (>=3.1.0,<4.0.0)
28
29
  Requires-Dist: inflect (>=7.4.0,<8.0.0)
29
30
  Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
30
31
  Requires-Dist: lark (>=1.2.2,<2.0.0)
@@ -0,0 +1,129 @@
1
+ [csvpath_files]
2
+ extensions = txt, csvpath, csvpaths
3
+
4
+ [csv_files]
5
+ extensions = txt, csv, tsv, dat, tab, psv, ssv
6
+
7
+ [errors]
8
+ csvpath = raise, collect, stop, fail, print
9
+ csvpaths = raise, collect
10
+ use_format = full
11
+ pattern = {time}:{file}:{line}:{paths}:{instance}:{chain}: {message}
12
+
13
+ [logging]
14
+ csvpath = debug
15
+ csvpaths = debug
16
+ log_file = logs/csvpath.log
17
+ log_files_to_keep = 100
18
+ log_file_size = 52428800
19
+
20
+ [config]
21
+ path = assets/config/jenkins-local-azure.ini
22
+
23
+ [cache]
24
+ path = cache
25
+ use_cache = no
26
+
27
+ [functions]
28
+ imports = config/functions.imports
29
+
30
+ [results]
31
+ archive = gs://csvpath_dev/archive
32
+
33
+ [inputs]
34
+ files = gs://csvpath_dev/inputs/named_files
35
+ csvpaths = gs://csvpath_dev/inputs/named_paths
36
+ on_unmatched_file_fingerprints = halt
37
+
38
+ #
39
+ # integrations activation
40
+ #
41
+
42
+ [listeners]
43
+ groups =
44
+ #slack, marquez, ckan, sftp, sftpplus, otlp, default, sqlite
45
+
46
+ # add sqlite to capture staging, loads, and results in a local sqlite db
47
+ sqlite.result = from csvpath.managers.integrations.sqlite.sqlite_result_listener import SqliteResultListener
48
+ sqlite.results = from csvpath.managers.integrations.sqlite.sqlite_results_listener import SqliteResultsListener
49
+
50
+ # these simple default listeners track all files staged and all paths loaded in
51
+ # central manifests. unlike other manifest writers, if they have multiple
52
+ # concurrent users they have potential a race condition. users that do not
53
+ # share inputs directories do not need to worry about this. you should vet
54
+ # using them against your use case; potential utility vs. the modest but non-0
55
+ # risk of lost updates. they can be disabled with little loss of function.
56
+ # alternatively a database-backed version may be more suitable.
57
+ default.file = from csvpath.managers.files.files_listener import FilesListener
58
+ default.paths = from csvpath.managers.paths.paths_listener import PathsListener
59
+
60
+ # add otlp to support any OpenTelemetry backend
61
+ otlp.result = from csvpath.managers.integrations.otlp.otlp_result_listener import OpenTelemetryResultListener
62
+ otlp.results = from csvpath.managers.integrations.otlp.otlp_results_listener import OpenTelemetryResultsListener
63
+ otlp.errors = from csvpath.managers.integrations.otlp.otlp_error_listener import OpenTelemetryErrorListener
64
+
65
+ # add sftpplus to the list of groups above to automate registration and named-paths group runs on file arrival at an SFTPPlus server
66
+ sftpplus.paths = from csvpath.managers.integrations.sftpplus.sftpplus_listener import SftpPlusListener
67
+
68
+ # add sftp to the list of groups above to push results to an sftp account
69
+ sftp.results = from csvpath.managers.integrations.sftp.sftp_listener import SftpListener
70
+
71
+ # add ckan to the list of groups above for alerts to slack webhooks
72
+ ckan.results = from csvpath.managers.integrations.ckan.ckan_listener import CkanListener
73
+
74
+ #add marquez to the list of groups above for OpenLineage events to a Marquez server
75
+ marquez.file = from csvpath.managers.integrations.ol.file_listener_ol import OpenLineageFileListener
76
+ marquez.paths = from csvpath.managers.integrations.ol.paths_listener_ol import OpenLineagePathsListener
77
+ marquez.result = from csvpath.managers.integrations.ol.result_listener_ol import OpenLineageResultListener
78
+ marquez.results = from csvpath.managers.integrations.ol.results_listener_ol import OpenLineageResultsListener
79
+
80
+ # add slack to the list of groups above for alerts to slack webhooks
81
+ slack.file = from csvpath.managers.integrations.slack.sender import SlackSender
82
+ slack.paths = from csvpath.managers.integrations.slack.sender import SlackSender
83
+ slack.result = from csvpath.managers.integrations.slack.sender import SlackSender
84
+ slack.results = from csvpath.managers.integrations.slack.sender import SlackSender
85
+
86
+ #
87
+ # integrations setup
88
+ #
89
+
90
+ [sftp]
91
+ server = 172.17.0.3
92
+ port = 10022
93
+ username = tinpenny
94
+ password = tinpenny
95
+
96
+ [sqlite]
97
+ db = archive/csvpath.db
98
+
99
+ [sftpplus]
100
+ # these are only needed by the csvpath writer
101
+ mailbox_user = MAILBOX_USER
102
+ mailbox_password = MAILBOX_PASSWORD
103
+ server = SFTPPLUS_SERVER
104
+ port = SFTPPLUS_PORT
105
+ # these are only needed on the server
106
+ admin_username = SFTPPLUS_ADMIN_USERNAME
107
+ admin_password = SFTPPLUS_ADMIN_PASSWORD
108
+ api_url = https://localhost:10020/json
109
+ scripts_dir =
110
+ execute_timeout = 300
111
+
112
+ [ckan]
113
+ server = http://localhost:80
114
+ api_token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3akJwc1ZuSkVrZm1aNnBtVTJfTW5CNlJXZ211YjdOOHVXZ1l1cUFDa0Q4IiwiaWF0IjoxNzM0NzE4NDQ3fQ.QXWXoJoSxVES4NwXYBteYUD7enX9D5T2htmETLGFzrs
115
+
116
+ [marquez]
117
+ base_url = http://localhost:5000
118
+ endpoint = api/v1/lineage
119
+ api_key = "none"
120
+ timeout = 5
121
+ verify = False
122
+
123
+ [slack]
124
+ # add your main webhook here. to set webhooks on a csvpath-by-csvpath basis add
125
+ # on-valid-slack: webhook-minus-'https://' and/or
126
+ # on-invalid-slack: webhook-minus-'https://'
127
+ webhook_url =
128
+
129
+
@@ -672,6 +672,10 @@ class CsvPaths(CsvPathsCoordinator, ErrorCollector):
672
672
  ): # pylint: disable=R0914
673
673
  """Does a CsvPath.next() on filename for every line against every named path in sequence"""
674
674
  paths = self.paths_manager.get_named_paths(pathsname)
675
+ if paths is None:
676
+ print(
677
+ f"PathsManager.get_named_paths for {pathsname} must return a set of named-paths"
678
+ )
675
679
  file = self.file_manager.get_named_file(filename)
676
680
  self.logger.info("Prepping %s and %s", filename, pathsname)
677
681
  self.clean(paths=pathsname)
@@ -33,7 +33,7 @@ class FileRegistrar(Registrar, Listener):
33
33
 
34
34
  def manifest_path(self, home) -> str:
35
35
  nos = Nos(home)
36
- if not nos.dir_exists():
36
+ if nos.physical_dirs() and not nos.dir_exists():
37
37
  raise InputException(f"Named file home does not exist: {home}")
38
38
  mf = os.path.join(home, "manifest.json")
39
39
  nos.path = mf
@@ -592,13 +592,19 @@ class ResultsManager: # pylint: disable=C0115
592
592
  self.csvpaths.logger.debug(
593
593
  "Attempting to load results for %s from %s", name, path
594
594
  )
595
- if Nos(path).dir_exists():
596
- runs = Nos(path).listdir()
597
- runs.sort()
598
- run = runs[len(runs) - 1]
599
- rs = self.get_named_results_for_run(name=name, run=run)
600
- if rs is not None:
601
- return rs
595
+ nos = Nos(path)
596
+ exists = nos.dir_exists()
597
+ nonphy = nos.physical_dirs()
598
+ # is not nonphy needed?
599
+ if exists or not nonphy:
600
+ runs = nos.listdir()
601
+ if len(runs) > 0:
602
+ runs.sort()
603
+ run = runs[len(runs) - 1]
604
+ rs = self.get_named_results_for_run(name=name, run=run)
605
+ if rs is not None:
606
+ return rs
607
+
602
608
  #
603
609
  # we treat this as a recoverable error because typically the user
604
610
  # has complete control of the csvpaths environment, making the
@@ -47,20 +47,6 @@ class AzureDataReader(CsvDataReader):
47
47
  dest_container, dest_blob = self.path_to_parts(new_path)
48
48
  AzureUtility.rename(source_container, source_blob, dest_container, dest_blob)
49
49
 
50
- """
51
- #
52
- # these may happen, but S3 and this code isn't right, so let's see who needs it.
53
- #
54
- def read(self) -> str:
55
- with open(uri=self._path, mode="r", encoding="utf-8") as file:
56
- return file.read()
57
-
58
- def next_raw(self) -> str:
59
- with open(uri=self._path, mode="rb") as file:
60
- for line in file:
61
- yield line
62
- """
63
-
64
50
  def file_info(self) -> dict[str, str | int | float]:
65
51
  # TODO: what can/should we provide here for cloud services? Can leave for now.
66
52
  return {}
@@ -39,6 +39,9 @@ class AzureDo:
39
39
  #
40
40
  return bool(lst)
41
41
 
42
+ def physical_dirs(self) -> bool:
43
+ return False
44
+
42
45
  def rename(self, new_path: str) -> None:
43
46
  container, blob = AzureUtility.path_to_parts(self.path)
44
47
  new_container, new_blob = AzureUtility.path_to_parts(new_path)
@@ -17,6 +17,7 @@ class Box:
17
17
  SSH_CLIENT = "ssh_client"
18
18
  SFTP_CLIENT = "sftp_client"
19
19
  AZURE_BLOB_CLIENT = "azure_blob_client"
20
+ GCS_STORAGE_CLIENT = "gcs_storage_client"
20
21
 
21
22
  STUFF = {}
22
23
 
@@ -338,6 +338,8 @@ webhook_url =
338
338
  return
339
339
  if self.inputs_files_path.strip().lower().startswith("sftp://"):
340
340
  return
341
+ if self.inputs_files_path.strip().lower().startswith("gs://"):
342
+ return
341
343
  if not path.exists(self.inputs_files_path):
342
344
  os.makedirs(self.inputs_files_path)
343
345
 
@@ -354,6 +356,8 @@ webhook_url =
354
356
  return
355
357
  if self.inputs_csvpaths_path.strip().lower().startswith("sftp://"):
356
358
  return
359
+ if self.inputs_csvpaths_path.strip().lower().startswith("gs://"):
360
+ return
357
361
  if not path.exists(self.inputs_csvpaths_path):
358
362
  os.makedirs(self.inputs_csvpaths_path)
359
363
 
@@ -146,6 +146,17 @@ class DataFileReader(ABC):
146
146
  },
147
147
  )
148
148
  return instance
149
+ if path.find("gs://") > -1:
150
+ instance = ClassLoader.load(
151
+ "from csvpath.util.gcs.gcs_xlsx_data_reader import GcsXlsxDataReader",
152
+ args=[path],
153
+ kwargs={
154
+ "sheet": sheet if sheet != path else None,
155
+ "delimiter": delimiter,
156
+ "quotechar": quotechar,
157
+ },
158
+ )
159
+ return instance
149
160
  return XlsxDataReader(
150
161
  path,
151
162
  sheet=sheet if sheet != path else None,
@@ -183,6 +194,13 @@ class DataFileReader(ABC):
183
194
  kwargs={"delimiter": delimiter, "quotechar": quotechar},
184
195
  )
185
196
  return instance
197
+ if path.startswith("gs://"):
198
+ instance = ClassLoader.load(
199
+ "from csvpath.util.gcs.gcs_data_reader import GcsDataReader",
200
+ args=[path],
201
+ kwargs={"delimiter": delimiter, "quotechar": quotechar},
202
+ )
203
+ return instance
186
204
  return CsvDataReader(path, delimiter=delimiter, quotechar=quotechar)
187
205
  else:
188
206
  instance = super().__new__(cls)
@@ -74,6 +74,12 @@ class DataFileWriter(ABC):
74
74
  kwargs={"path": path, "mode": mode},
75
75
  )
76
76
  return instance
77
+ if path.startswith("gs://"):
78
+ instance = ClassLoader.load(
79
+ "from csvpath.util.gcs.gcs_data_writer import GcsDataWriter",
80
+ kwargs={"path": path, "mode": mode},
81
+ )
82
+ return instance
77
83
  return GeneralDataWriter(path=path, mode=mode)
78
84
  else:
79
85
  instance = super().__new__(cls)
@@ -0,0 +1,52 @@
1
+ # pylint: disable=C0114
2
+ import csv
3
+ from smart_open import open
4
+ from csvpath.util.hasher import Hasher
5
+ from ..file_readers import CsvDataReader
6
+ from .gcs_utils import GcsUtility
7
+ from .gcs_fingerprinter import GcsFingerprinter
8
+
9
+
10
+ class GcsDataReader(CsvDataReader):
11
+ def load_if(self) -> None:
12
+ if self.source is None:
13
+ client = GcsUtility.make_client()
14
+ try:
15
+ self.source = open(self.path, "r", transport_params={"client": client})
16
+ except DeprecationWarning:
17
+ ...
18
+
19
+ def next(self) -> list[str]:
20
+ with open(
21
+ uri=self.path,
22
+ mode="r",
23
+ transport_params={"client": GcsUtility.make_client()},
24
+ ) as file:
25
+ reader = csv.reader(
26
+ file, delimiter=self._delimiter, quotechar=self._quotechar
27
+ )
28
+ for line in reader:
29
+ yield line
30
+
31
+ def fingerprint(self) -> str:
32
+ self.load_if()
33
+ h = GcsFingerprinter().fingerprint(self.path)
34
+ h = Hasher.percent_encode(h)
35
+ self.close()
36
+ return h
37
+
38
+ def exists(self, path: str) -> bool:
39
+ return GcsUtility.exists(path)
40
+
41
+ def remove(self, path: str) -> None:
42
+ bucket, blob = self.path_to_parts(path)
43
+ GcsUtility.remove(bucket, blob)
44
+
45
+ def rename(self, path: str, new_path: str) -> None:
46
+ source_bucket, source_blob = self.path_to_parts(path)
47
+ dest_bucket, dest_blob = self.path_to_parts(new_path)
48
+ GcsUtility.rename(source_bucket, source_blob, dest_bucket, dest_blob)
49
+
50
+ def file_info(self) -> dict[str, str | int | float]:
51
+ # TODO: what can/should we provide here for cloud services? Can leave for now.
52
+ return {}
@@ -0,0 +1,33 @@
1
+ # pylint: disable=C0114
2
+
3
+ import os
4
+ from smart_open import open
5
+ from ..file_writers import DataFileWriter
6
+ from csvpath.util.gcs.gcs_utils import GcsUtility
7
+
8
+
9
+ class GcsDataWriter(DataFileWriter):
10
+ _write_file_count = 0
11
+
12
+ def load_if(self) -> None:
13
+ if self.sink is None:
14
+ client = GcsUtility.make_client()
15
+ self.sink = open(
16
+ self.path,
17
+ self._mode,
18
+ transport_params={"client": client},
19
+ )
20
+ GcsDataWriter._write_file_count += 1
21
+
22
+ def write(self, data) -> None:
23
+ """This is a one-and-done write in mode 'w'. You don't use the data writer
24
+ as a context manager for this method. For multiple write calls to the same
25
+ file handle, use append().
26
+ """
27
+ client = GcsUtility.make_client()
28
+ with open(self.path, "wb", transport_params={"client": client}) as file:
29
+ file.write(data.encode("utf-8"))
30
+
31
+ def file_info(self) -> dict[str, str | int | float]:
32
+ # TODO: what can/should we provide here?
33
+ return {}
@@ -0,0 +1,44 @@
1
+ import hashlib
2
+ from google.cloud import storage
3
+ from .gcs_utils import GcsUtility
4
+ from ..hasher import Hasher
5
+
6
+
7
+ class GcsFingerprinter:
8
+ def fingerprint(self, path: str) -> str:
9
+ bucket, blob = GcsUtility.path_to_parts(path)
10
+ return self.fingerprint_object(bucket, blob)
11
+
12
+ def fingerprint_object(self, bucket: str, blob: str) -> str:
13
+ h = self.get_sha(bucket, blob)
14
+ if h is None:
15
+ h = self.generate_fingerprint(bucket, blob)
16
+ #
17
+ # Set the properties for future use
18
+ #
19
+ blob_obj = GcsUtility.make_client().bucket(bucket).blob(blob)
20
+ blob_obj.metadata = {"sha256": h}
21
+ blob_obj.patch() # Update metadata in GCS
22
+ return h
23
+
24
+ def get_sha(self, bucket: str, blob: str) -> str:
25
+ blob_obj = GcsUtility.make_client().bucket(bucket).blob(blob)
26
+ blob_obj.reload() # Ensure metadata is up-to-date
27
+ if blob_obj.metadata and "sha256" in blob_obj.metadata:
28
+ return blob_obj.metadata["sha256"]
29
+ return None
30
+
31
+ def generate_fingerprint(self, bucket: str, blob: str) -> str:
32
+ """Downloads the blob and calculates its SHA256 hash."""
33
+ client = GcsUtility.make_client()
34
+ blob_obj = client.bucket(bucket).blob(blob)
35
+ sha256 = hashlib.sha256()
36
+ try:
37
+ with blob_obj.open("rb") as download_stream:
38
+ for chunk in download_stream:
39
+ sha256.update(chunk)
40
+ h = sha256.hexdigest()
41
+ h = Hasher.percent_encode(h)
42
+ return h
43
+ except Exception as e:
44
+ raise e
@@ -0,0 +1,98 @@
1
+ # pylint: disable=C0114
2
+ import os
3
+ from google.cloud import storage
4
+ from .gcs_utils import GcsUtility
5
+ from ..path_util import PathUtility as pathu
6
+
7
+
8
+ class GcsDo:
9
+ def __init__(self, path, client=None):
10
+ self._path = path
11
+
12
+ @property
13
+ def path(self) -> str:
14
+ return self._path
15
+
16
+ @path.setter
17
+ def path(self, path: str) -> None:
18
+ path = pathu.resep(path)
19
+ self._path = path
20
+
21
+ def remove(self) -> None:
22
+ bucket, blob = GcsUtility.path_to_parts(self.path)
23
+ lst = self.listdir()
24
+ for item in lst:
25
+ self.path = f"gs://{bucket}/{blob}/{item}"
26
+ self.remove()
27
+ if GcsUtility.exists(bucket, blob):
28
+ GcsUtility.remove(bucket, blob)
29
+
30
+ def exists(self) -> bool:
31
+ bucket, blob = GcsUtility.path_to_parts(self.path)
32
+ return GcsUtility.exists(bucket, blob)
33
+
34
+ def rename(self, new_path: str) -> None:
35
+ bucket, blob = GcsUtility.path_to_parts(self.path)
36
+ new_bucket, new_blob = GcsUtility.path_to_parts(new_path)
37
+ return GcsUtility.rename(bucket, blob, new_bucket, new_blob)
38
+
39
+ def copy(self, new_path: str) -> None:
40
+ bucket, blob = GcsUtility.path_to_parts(self.path)
41
+ new_bucket, new_blob = GcsUtility.path_to_parts(new_path)
42
+ return GcsUtility.copy(bucket, blob, new_bucket, new_blob)
43
+
44
+ def isfile(self) -> bool:
45
+ bucket, blob = GcsUtility.path_to_parts(self.path)
46
+ client = GcsUtility.make_client()
47
+ try:
48
+ bucket_obj = client.bucket(bucket)
49
+ blob_obj = bucket_obj.blob(blob)
50
+ return blob_obj.exists()
51
+ except Exception:
52
+ return False
53
+
54
+ def dir_exists(self) -> bool:
55
+ lst = self.listdir()
56
+ #
57
+ # Similar to Azure, we consider a directory to exist if there are blobs under its prefix.
58
+ #
59
+ return bool(lst)
60
+
61
+ def physical_dirs(self) -> bool:
62
+ return False
63
+
64
+ def listdir(self) -> list[str]:
65
+ bucket, blob = GcsUtility.path_to_parts(self.path)
66
+ # print(f"\ngcs_nos: listdir: bucket: {bucket}, blob: {blob}")
67
+ if not blob.endswith("/"):
68
+ blob = f"{blob}/"
69
+ client = GcsUtility.make_client()
70
+ bucket_obj = client.bucket(bucket)
71
+ # print(f"gcs_nos: listdir: bucket_obj: {bucket_obj}")
72
+ # blobs = client.list_blobs(bucket_obj, prefix=blob)
73
+ blobs = client.list_blobs(bucket_obj, prefix=blob, delimiter="/")
74
+ lst = [blob.name for blob in blobs]
75
+ # print(f"gcs_nos: listdir: lst: {lst}")
76
+ prefixes = [blob for blob in blobs.prefixes]
77
+ # print(f"gcs_nos: listdir: prefixes: {prefixes}")
78
+ allnames = lst + prefixes
79
+ # print(f"gcs_nos: listdir: allnames: {allnames}")
80
+ names = []
81
+ for name in allnames:
82
+ # print(f"gcs_nos: listdir: name 1: {name}")
83
+ name = name[len(blob) :]
84
+ # print(f"gcs_nos: listdir: name 2: {name}")
85
+ if "/" not in name:
86
+ names.append(name)
87
+ else:
88
+ names.append(name[0 : name.find("/")])
89
+ # print(f"gcsnos: listdir: returning: {names}")
90
+ return names
91
+
92
+ def makedirs(self) -> None:
93
+ # Not required for GCS
94
+ ...
95
+
96
+ def makedir(self) -> None:
97
+ # Not required for GCS
98
+ ...
@@ -0,0 +1,73 @@
1
+ import os
2
+ from google.cloud import storage
3
+ from csvpath.util.box import Box
4
+
5
+
6
+ class GcsUtility:
7
+ _client_count = 0
8
+ GCS_CREDENTIALS_PATH = "GCS_CREDENTIALS_PATH"
9
+
10
+ @classmethod
11
+ def make_client(cls):
12
+ """Creates a GCS Storage Client."""
13
+ box = Box()
14
+ client = box.get(Box.GCS_STORAGE_CLIENT)
15
+ if client is None:
16
+ cls._client_count += 1
17
+ credentials_path = os.getenv(cls.GCS_CREDENTIALS_PATH)
18
+ if not credentials_path:
19
+ raise ValueError(
20
+ f"{cls.GCS_CREDENTIALS_PATH} environment variable not set."
21
+ )
22
+ client = storage.Client.from_service_account_json(credentials_path)
23
+ box.add(Box.GCS_STORAGE_CLIENT, client)
24
+ return client
25
+
26
+ @classmethod
27
+ def path_to_parts(cls, path) -> tuple[str, str]:
28
+ """Splits a GCS blob path into bucket and blob parts."""
29
+ if not path.startswith("gs://"):
30
+ raise ValueError("Path must be a GCS URI with the gs protocol")
31
+ path = path[5:]
32
+ i = path.find("/", 1)
33
+ bucket = path[0:i]
34
+ blob = path[i + 1 :]
35
+ return bucket, blob
36
+
37
+ @classmethod
38
+ def exists(cls, bucket: str, blob: str) -> bool:
39
+ client = cls.make_client()
40
+ if client is None:
41
+ raise ValueError("Client cannot be None")
42
+ try:
43
+ bucket_obj = client.bucket(bucket)
44
+ blob_obj = bucket_obj.blob(blob)
45
+ return blob_obj.exists()
46
+ except Exception:
47
+ return False
48
+
49
+ @classmethod
50
+ def remove(cls, bucket: str, blob: str) -> None:
51
+ client = cls.make_client()
52
+ if client is None:
53
+ raise ValueError("Client cannot be None")
54
+ bucket_obj = client.bucket(bucket)
55
+ blob_obj = bucket_obj.blob(blob)
56
+ blob_obj.delete()
57
+
58
+ @classmethod
59
+ def copy(cls, bucket: str, blob: str, new_bucket: str, new_blob: str) -> None:
60
+ client = cls.make_client()
61
+ if client is None:
62
+ raise ValueError("Client cannot be None")
63
+ bucket_obj = client.bucket(bucket)
64
+ blob_obj = bucket_obj.blob(blob)
65
+ new_bucket_obj = client.bucket(new_bucket)
66
+ new_blob_obj = new_bucket_obj.blob(new_blob)
67
+ new_blob_obj.rewrite(blob_obj)
68
+
69
+ @classmethod
70
+ def rename(cls, bucket: str, blob: str, new_bucket: str, new_blob: str) -> None:
71
+ """Renames a blob by copying it to a new location and deleting the old one."""
72
+ cls.copy(bucket, blob, new_bucket, new_blob)
73
+ cls.remove(bucket, blob)
@@ -0,0 +1,34 @@
1
+ # pylint: disable=C0114
2
+ import os
3
+ import pylightxl as xl
4
+ from smart_open import open
5
+ from csvpath.util.box import Box
6
+ from csvpath.util.hasher import Hasher
7
+ from csvpath.util.file_readers import XlsxDataReader
8
+ from .gcs_fingerprinter import GcsFingerprinter
9
+ from .gcs_utils import GcsUtility
10
+
11
+
12
+ class GcsXlsxDataReader(XlsxDataReader):
13
+ def next(self) -> list[str]:
14
+ with self as file:
15
+ db = xl.readxl(fn=file.source)
16
+ if not self._sheet:
17
+ self._sheet = db.ws_names[0]
18
+ for row in db.ws(ws=self._sheet).rows:
19
+ yield [f"{datum}" for datum in row]
20
+
21
+ def load_if(self) -> None:
22
+ if self.source is None:
23
+ client = GcsUtility.make_client()
24
+ try:
25
+ self.source = open(self.path, "rb", transport_params={"client": client})
26
+ except DeprecationWarning:
27
+ ...
28
+
29
+ def fingerprint(self) -> str:
30
+ self.load_if()
31
+ h = GcsFingerprinter().fingerprint(self.path)
32
+ h = Hasher.percent_encode(h)
33
+ self.close()
34
+ return h