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.
- {csvpath-0.0.532 → csvpath-0.0.533}/PKG-INFO +2 -1
- csvpath-0.0.533/assets/config/jenkins-local-gcs.ini +129 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/csvpaths.py +4 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_registrar.py +1 -1
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_manager.py +13 -7
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_data_reader.py +0 -14
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_nos.py +3 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/box.py +1 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/config.py +4 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_readers.py +18 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_writers.py +6 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_data_reader.py +52 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_data_writer.py +33 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_fingerprinter.py +44 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_nos.py +98 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_utils.py +73 -0
- csvpath-0.0.533/csvpath/util/gcs/gcs_xlsx_data_reader.py +34 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/nos.py +15 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/path_util.py +1 -2
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_nos.py +3 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_nos.py +3 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/pyproject.toml +2 -1
- {csvpath-0.0.532 → csvpath-0.0.533}/LICENSE +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/README.md +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/config.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/function.imports +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-azure.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-filesystem.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-local-sftp.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-s3.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-azure.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-local.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/jenkins-windows-sftp.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/config/local-localhost-sftp.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.bat +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_auto_arrival.sh +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.bat +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sftpplus/handle_mailbox_arrival.sh +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sqlite/csvpath +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/assets/integrations/sqlite/schema.sql +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/config/config.ini +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/asker.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/cli.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/const.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/debug_config.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/drill_down.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/function_describer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/function_lister.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/cli/selecter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/csvpath.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_collector.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_comms.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/errors/error_manager.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_manager.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/file_metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/files_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/files/lines_and_headers_cacher.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/ckan.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/ckan_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/datafile.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ckan/dataset.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/event.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/event_result.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/file_listener_ol.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/job.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/ol_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/paths_listener_ol.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/result_listener_ol.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/results_listener_ol.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run_listener_ol.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/run_state.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/ol/sender.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/error_metrics.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/metrics.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_error_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_result_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/otlp/otlp_results_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftp/sftp_sender.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/arrival_handler.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/rpc.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/sftpplus_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/transfer_creator.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sftpplus/transfers.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/slack/event.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/slack/sender.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/schema.sql +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/sqlite_result_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/integrations/sqlite/sqlite_results_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_listener.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_manager.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/paths/paths_registrar.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/registrar.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_errors_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_lines_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_printouts_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/file_unmatched_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/readers/readers.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_file_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_registrar.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/result_serializer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/results/results_registrar.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_listener_stdout.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_metadata.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/managers/run/run_registrar.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/args.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/args_helper.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/all.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/andf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/any.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/between.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/empty.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/exists.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/inf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/no.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/notf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/orf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/boolean/yes.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_bytes.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_headers.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_lines.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/count_scans.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/counter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/every.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/has_matches.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/increment.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/tally.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/counting/total_lines.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/dates/now.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_factory.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_finder.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/function_focus.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/append.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/collect.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/empty_stack.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/end.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/header_name.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/header_names_mismatch.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/headers.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/insert.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/mismatch.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/replace.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/headers/reset_headers.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/advance.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/after_blank.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/dups.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/first.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/first_line.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/last.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/lines/stop.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/above.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/add.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/divide.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/equals.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/intf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/mod.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/multiply.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/odd.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/round.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/subtotal.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/subtract.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/math/sum.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/fingerprint.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/importf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/misc/random.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/jinjaf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/print_line.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/print_queue.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/printf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/print/table.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/minf.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/percent.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/percent_unique.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/stats/stdev.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/alter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/caps.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/concat.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/contains.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/length.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/lower.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/metaphone.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/regex.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/starts_with.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/strip.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/substring.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/strings/upper.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/testing/debug.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/boolean.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/datef.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/decimal.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/email.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/nonef.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/string.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/type.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/types/url.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/fail.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/failed.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/validity/line.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/get.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/pushpop.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/put.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/track.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/functions/variables/variables.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/lark_parser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/lark_transformer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/matcher.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/equality.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/expression.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/header.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/matchable.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/qualified.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/reference.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/term.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/productions/variable.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/exceptions.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/expression_encoder.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/expression_utility.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/lark_print_parser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/print_parser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/matching/util/runtime_data_collector.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/error_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/explain_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/files_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/logic_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/mode_controller.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/print_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/return_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/run_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/source_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/transfer_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/unmatched_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/modes/validation_mode.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/__init__.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/exceptions.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/parser.out +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/parsetab.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/scanner.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/scanning/scanning_lexer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_data_writer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_fingerprinter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_utils.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/azure/azure_xlsx_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/cache.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/caser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/class_loader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/code.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/config_exception.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/exceptions.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/file_info.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/hasher.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/http/http_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/intermediary.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/last_line_stats.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_counter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_monitor.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/line_spooler.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/log_utility.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/metadata_parser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/pandas_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/printer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/reference_finder.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/reference_parser.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_data_writer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_fingerprinter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_utils.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/s3/s3_xlsx_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_config.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_data_writer.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_fingerprinter.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sftp/sftp_xlsx_data_reader.py +0 -0
- {csvpath-0.0.532 → csvpath-0.0.533}/csvpath/util/sqliter.py +0 -0
- {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.
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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)
|
|
@@ -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
|