csvpath 0.0.501__tar.gz → 0.0.502__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.501 → csvpath-0.0.502}/PKG-INFO +2 -1
- csvpath-0.0.502/config/config.ini +68 -0
- csvpath-0.0.502/csvpath/managers/integrations/ckan/ckan.py +193 -0
- csvpath-0.0.502/csvpath/managers/integrations/ckan/ckan_listener.py +390 -0
- csvpath-0.0.502/csvpath/managers/integrations/ckan/datafile.py +70 -0
- csvpath-0.0.502/csvpath/managers/integrations/ckan/dataset.py +126 -0
- {csvpath-0.0.501/csvpath/managers/files → csvpath-0.0.502/csvpath/managers/integrations/ol}/file_listener_ol.py +1 -1
- {csvpath-0.0.501/csvpath/managers/paths → csvpath-0.0.502/csvpath/managers/integrations/ol}/paths_listener_ol.py +1 -1
- {csvpath-0.0.501/csvpath/managers/results → csvpath-0.0.502/csvpath/managers/integrations/ol}/result_listener_ol.py +1 -1
- {csvpath-0.0.501/csvpath/managers/results → csvpath-0.0.502/csvpath/managers/integrations/ol}/results_listener_ol.py +1 -1
- {csvpath-0.0.501/csvpath/managers/run → csvpath-0.0.502/csvpath/managers/integrations/ol}/run_listener_ol.py +1 -1
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/slack/event.py +16 -9
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/result_registrar.py +1 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/config.py +13 -7
- {csvpath-0.0.501 → csvpath-0.0.502}/pyproject.toml +2 -1
- csvpath-0.0.501/config/config.ini +0 -50
- csvpath-0.0.501/csvpath/managers/integrations/slack/sender copy.py +0 -49
- {csvpath-0.0.501 → csvpath-0.0.502}/LICENSE +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/README.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/cli/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/cli/cli.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/cli/drill_down.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/csvpath.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/csvpaths.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/files/file_cacher.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/files/file_manager.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/files/file_metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/files/file_registrar.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/event.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/event_result.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/job.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/ol_listener.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/run.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/run_state.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/ol/sender.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/integrations/slack/sender.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/listener.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/paths/paths_manager.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/paths/paths_metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/paths/paths_registrar.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/registrar.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/readers/file_errors_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/readers/file_lines_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/readers/file_printouts_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/readers/file_unmatched_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/readers/readers.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/result.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/result_file_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/result_metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/result_serializer.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/results_manager.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/results_metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/results/results_registrar.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/run/run_listener_stdout.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/run/run_metadata.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/managers/run/run_registrar.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/args.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/all.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/andf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/any.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/between.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/empty.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/exists.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/inf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/no.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/notf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/orf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/boolean/yes.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/count.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/count_bytes.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/count_headers.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/count_lines.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/count_scans.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/counter.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/every.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/has_matches.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/increment.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/tally.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/counting/total_lines.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/dates/now.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/function.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/function_factory.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/function_finder.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/function_focus.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/append.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/collect.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/empty_stack.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/end.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/header_name.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/header_names_mismatch.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/headers.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/mismatch.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/replace.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/headers/reset_headers.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/advance.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/after_blank.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/dups.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/first.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/first_line.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/last.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/lines/stop.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/above.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/add.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/divide.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/equals.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/intf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/mod.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/multiply.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/round.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/subtotal.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/subtract.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/math/sum.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/misc/fingerprint.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/misc/importf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/misc/random.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/print/jinjaf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/print/print_line.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/print/print_queue.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/print/printf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/print/table.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/stats/minf.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/stats/percent.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/stats/percent_unique.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/stats/stdev.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/concat.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/length.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/lower.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/metaphone.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/regex.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/starts_with.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/strip.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/substring.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/strings/upper.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/testing/debug.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/boolean.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/datef.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/decimal.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/nonef.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/string.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/types/type.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/validity/fail.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/validity/failed.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/validity/line.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/variables/get.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/variables/pushpop.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/variables/put.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/variables/track.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/functions/variables/variables.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/lark_parser.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/lark_transformer.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/matcher.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/equality.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/expression.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/header.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/matchable.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/qualified.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/reference.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/term.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/productions/variable.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/exceptions.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/expression_encoder.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/expression_utility.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/lark_print_parser.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/print_parser.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/matching/util/runtime_data_collector.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/explain_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/files_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/logic_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/mode_controller.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/print_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/return_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/run_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/source_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/transfer_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/unmatched_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/modes/validation_mode.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/__init__.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/exceptions.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/parser.out +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/parsetab.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/scanner.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/scanning/scanning_lexer.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/cache.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/class_loader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/config_exception.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/error.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/exceptions.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/file_readers.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/last_line_stats.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/line_counter.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/line_monitor.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/line_spooler.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/log_utility.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/metadata_parser.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/pandas_data_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/printer.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/reference_parser.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/csvpath/util/s3_data_reader.py +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/asbool.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/assignment.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/comments.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/config.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/examples.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/files.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/above.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/advance.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/after_blank.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/all.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/andor.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/any.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/average.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/between.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/collect.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/correlate.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/count.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/count_bytes.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/count_headers.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/counter.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/date.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/empty.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/empty_stack.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/end.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/every.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/fail.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/fingerprint.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/first.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/get.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/has_dups.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/has_matches.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/header.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/header_name.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/header_names_mismatch.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/implementing_functions.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/import.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/in.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/increment.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/intf.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/jinja.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/last.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/line.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/line_number.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/max.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/metaphone.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/mismatch.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/no.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/not.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/now.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/percent_unique.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/pop.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/print.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/print_line.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/print_queue.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/random.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/regex.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/replace.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/reset_headers.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/stdev.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/stop.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/string_functions.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/subtotal.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/subtract.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/sum.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/tally.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/total_lines.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/track.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/types.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/variables.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions/variables_and_headers.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/functions.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/grammar.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/headers.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/csvpath-icon-sm.png +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/csvpath-logo-wordmark-tight-2.svg +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/logo-wordmark-3.svg +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/logo-wordmark-4.svg +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/logo-wordmark-white-on-black-trimmed-padded.png +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/logo-wordmark-white-trimmed.png +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/marquez-logo-sm.png +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/images/openlineage-logo-sm.png +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/paths.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/printing.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/qualifiers.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/references.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/terms.md +0 -0
- {csvpath-0.0.501 → csvpath-0.0.502}/docs/variables.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: csvpath
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.502
|
|
4
4
|
Summary: A declarative language for validating CSV, Excel, and other tabular data files
|
|
5
5
|
Author: David Kershaw
|
|
6
6
|
Author-email: dk107dk@hotmail.com
|
|
@@ -25,6 +25,7 @@ Classifier: Topic :: Utilities
|
|
|
25
25
|
Provides-Extra: pandas
|
|
26
26
|
Provides-Extra: smartopen
|
|
27
27
|
Requires-Dist: bullet (>=2.2.0,<3.0.0)
|
|
28
|
+
Requires-Dist: ckanapi (>=4.8,<5.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,68 @@
|
|
|
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
|
+
|
|
11
|
+
[logging]
|
|
12
|
+
csvpath = info
|
|
13
|
+
csvpaths = info
|
|
14
|
+
log_file = logs/csvpath.log
|
|
15
|
+
log_files_to_keep = 100
|
|
16
|
+
log_file_size = 52428800
|
|
17
|
+
|
|
18
|
+
[config]
|
|
19
|
+
path = config/config.ini
|
|
20
|
+
|
|
21
|
+
[cache]
|
|
22
|
+
path = cache
|
|
23
|
+
|
|
24
|
+
[functions]
|
|
25
|
+
imports = config/functions.imports
|
|
26
|
+
|
|
27
|
+
[listeners]
|
|
28
|
+
groups =
|
|
29
|
+
#slack, marquez, ckan
|
|
30
|
+
|
|
31
|
+
# add ckan to the list of groups above for alerts to slack webhooks
|
|
32
|
+
ckan.paths = from csvpath.managers.integrations.ckan.ckan_listener import CkanListener
|
|
33
|
+
ckan.result = from csvpath.managers.integrations.ckan.ckan_listener import CkanListener
|
|
34
|
+
ckan.results = from csvpath.managers.integrations.ckan.ckan_listener import CkanListener
|
|
35
|
+
|
|
36
|
+
#add marquez to the list of groups above for OpenLineage events to a local Marquez
|
|
37
|
+
marquez.file = from csvpath.managers.integrations.ol.file_listener_ol import OpenLineageFileListener
|
|
38
|
+
marquez.paths = from csvpath.managers.integrations.ol.paths_listener_ol import OpenLineagePathsListener
|
|
39
|
+
marquez.result = from csvpath.managers.integrations.ol.result_listener_ol import OpenLineageResultListener
|
|
40
|
+
marquez.results = from csvpath.managers.integrations.ol.results_listener_ol import OpenLineageResultsListener
|
|
41
|
+
|
|
42
|
+
# add slack to the list of groups above for alerts to slack webhooks
|
|
43
|
+
slack.file = from csvpath.managers.integrations.slack.sender import SlackSender
|
|
44
|
+
slack.paths = from csvpath.managers.integrations.slack.sender import SlackSender
|
|
45
|
+
slack.result = from csvpath.managers.integrations.slack.sender import SlackSender
|
|
46
|
+
slack.results = from csvpath.managers.integrations.slack.sender import SlackSender
|
|
47
|
+
|
|
48
|
+
[marquez]
|
|
49
|
+
base_url = http://localhost:5000
|
|
50
|
+
endpoint = api/v1/lineage
|
|
51
|
+
api_key = "none"
|
|
52
|
+
timeout = 5
|
|
53
|
+
verify = False
|
|
54
|
+
|
|
55
|
+
[slack]
|
|
56
|
+
# add your main webhook here. to set webhooks on a csvpath-by-csvpath basis add
|
|
57
|
+
# on-valid-slack: webhook-minus-'https://' and/or
|
|
58
|
+
# on-invalid-slack: webhook-minus-'https://'
|
|
59
|
+
webhook_url =
|
|
60
|
+
|
|
61
|
+
[results]
|
|
62
|
+
archive = archive
|
|
63
|
+
|
|
64
|
+
[inputs]
|
|
65
|
+
files = inputs/named_files
|
|
66
|
+
csvpaths = inputs/named_paths
|
|
67
|
+
on_unmatched_file_fingerprints = halt
|
|
68
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from ckanapi import RemoteCKAN
|
|
2
|
+
from ckanapi.errors import NotFound
|
|
3
|
+
from csvpath import CsvPaths
|
|
4
|
+
from csvpath.util.config import Config
|
|
5
|
+
from .dataset import Dataset
|
|
6
|
+
from .datafile import Datafile
|
|
7
|
+
import hashlib
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CkanException(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Ckan:
|
|
16
|
+
def __init__(self, *, config, manifest, csvpaths=None) -> None:
|
|
17
|
+
self.config = config
|
|
18
|
+
self.csvpaths = csvpaths
|
|
19
|
+
self._manifest = manifest
|
|
20
|
+
self._client = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def manifest(self):
|
|
24
|
+
return self._manifest
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def config(self):
|
|
28
|
+
return self._config
|
|
29
|
+
|
|
30
|
+
@config.setter
|
|
31
|
+
def config(self, config):
|
|
32
|
+
self._config = config
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def client(self):
|
|
36
|
+
if self._client is None:
|
|
37
|
+
server = self.config._get("ckan", "server")
|
|
38
|
+
if server is None:
|
|
39
|
+
raise CkanException(
|
|
40
|
+
"CKAN server cannot be None. Check config for [ckan] server"
|
|
41
|
+
)
|
|
42
|
+
token = self.config._get("ckan", "api_token")
|
|
43
|
+
if token is None:
|
|
44
|
+
raise CkanException(
|
|
45
|
+
"API token cannot be None. Check config for [ckan] api_token"
|
|
46
|
+
)
|
|
47
|
+
self._client = RemoteCKAN(server, apikey=token, user_agent="CsvPath")
|
|
48
|
+
return self._client
|
|
49
|
+
|
|
50
|
+
# ==========================
|
|
51
|
+
# ==========================
|
|
52
|
+
|
|
53
|
+
def create_or_update_dataset(self, dataset: Dataset) -> None:
|
|
54
|
+
if self._has_dataset(dataset.name):
|
|
55
|
+
self._update_dataset(dataset)
|
|
56
|
+
else:
|
|
57
|
+
self._create_dataset(dataset)
|
|
58
|
+
|
|
59
|
+
def upload_datafile(self, datafile: Datafile) -> dict[str, ...]:
|
|
60
|
+
with open(datafile.path, "rb") as file:
|
|
61
|
+
id = self._has_resource(datafile.dataset_id, datafile.name)
|
|
62
|
+
if id is not None:
|
|
63
|
+
resource = self.client.action.resource_update(
|
|
64
|
+
id=id,
|
|
65
|
+
package_id=datafile.dataset_id,
|
|
66
|
+
url=datafile.url,
|
|
67
|
+
name=datafile.name,
|
|
68
|
+
mimetype=datafile.mime_type,
|
|
69
|
+
upload=file,
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
resource = self.client.action.resource_create(
|
|
73
|
+
package_id=datafile.dataset_id,
|
|
74
|
+
url=datafile.url,
|
|
75
|
+
name=datafile.name,
|
|
76
|
+
mimetype=datafile.mime_type,
|
|
77
|
+
upload=file,
|
|
78
|
+
)
|
|
79
|
+
return resource
|
|
80
|
+
|
|
81
|
+
def create_group_if(self, *, name: str, title: str) -> None:
|
|
82
|
+
#
|
|
83
|
+
# the archive turns into a ckan group. orgs may run multiple CsvPaths.
|
|
84
|
+
# and each named-group can be a dataset with multiple files.
|
|
85
|
+
#
|
|
86
|
+
thename = self._fix_name_if(name)
|
|
87
|
+
if not self._has_group(thename):
|
|
88
|
+
if title is not None and title != thename:
|
|
89
|
+
self.client.action.group_create(name=thename, title=title)
|
|
90
|
+
else:
|
|
91
|
+
self.client.action.group_create(name=thename)
|
|
92
|
+
return thename
|
|
93
|
+
|
|
94
|
+
def _create_dataset(self, dataset: Dataset) -> None:
|
|
95
|
+
if not self._has_org(dataset.org):
|
|
96
|
+
raise CkanException(
|
|
97
|
+
"You must provide the name of an organization. Ask your CKAN admin to create one for you."
|
|
98
|
+
)
|
|
99
|
+
args = {
|
|
100
|
+
"name": self._fix_name_if(dataset.name),
|
|
101
|
+
"title": dataset.title,
|
|
102
|
+
"owner_org": dataset.org,
|
|
103
|
+
"private": not dataset.visible,
|
|
104
|
+
"url": dataset.url,
|
|
105
|
+
}
|
|
106
|
+
if dataset.tags is not None:
|
|
107
|
+
args["tags"] = dataset.tags
|
|
108
|
+
if dataset.fields is not None:
|
|
109
|
+
args["extras"] = dataset.fields
|
|
110
|
+
if dataset.groups is not None:
|
|
111
|
+
args["groups"] = dataset.groups()
|
|
112
|
+
#
|
|
113
|
+
# author = dataset.author
|
|
114
|
+
# author_email = dataset.author_email
|
|
115
|
+
#
|
|
116
|
+
# create dataset as a ckan package
|
|
117
|
+
#
|
|
118
|
+
self.client.action.package_create(**args)
|
|
119
|
+
|
|
120
|
+
def _update_dataset(self, dataset: Dataset) -> None:
|
|
121
|
+
args = {
|
|
122
|
+
"id": self._fix_name_if(dataset.name),
|
|
123
|
+
"title": dataset.title,
|
|
124
|
+
"owner_org": dataset.org,
|
|
125
|
+
"private": not dataset.visible,
|
|
126
|
+
"url": dataset.url,
|
|
127
|
+
}
|
|
128
|
+
if dataset.tags is not None:
|
|
129
|
+
args["tags"] = dataset.tags
|
|
130
|
+
if dataset.fields is not None:
|
|
131
|
+
args["extras"] = dataset.fields
|
|
132
|
+
if dataset.groups is not None:
|
|
133
|
+
args["groups"] = dataset.groups()
|
|
134
|
+
#
|
|
135
|
+
# version = dataset.version
|
|
136
|
+
# author = dataset.author
|
|
137
|
+
# author_email = dataset.author_email
|
|
138
|
+
#
|
|
139
|
+
self.client.action.package_patch(**args)
|
|
140
|
+
|
|
141
|
+
# ==========================
|
|
142
|
+
# ==========================
|
|
143
|
+
|
|
144
|
+
def _fix_name_if(self, name: str) -> str:
|
|
145
|
+
name = name.strip().lower()
|
|
146
|
+
thename = ""
|
|
147
|
+
for n in name:
|
|
148
|
+
if n.isalnum() or n in ["-", "_"]:
|
|
149
|
+
thename += n
|
|
150
|
+
else:
|
|
151
|
+
thename += "_"
|
|
152
|
+
return thename
|
|
153
|
+
|
|
154
|
+
def _has_org(self, name) -> bool:
|
|
155
|
+
try:
|
|
156
|
+
org = self.client.action.organization_show(id=name)
|
|
157
|
+
except NotFound:
|
|
158
|
+
#
|
|
159
|
+
# not found is expected so we do nothing
|
|
160
|
+
#
|
|
161
|
+
...
|
|
162
|
+
return org is not None
|
|
163
|
+
|
|
164
|
+
def _has_dataset(self, name) -> bool:
|
|
165
|
+
dataset = None
|
|
166
|
+
try:
|
|
167
|
+
dataset = self.client.action.package_show(id=name)
|
|
168
|
+
except NotFound:
|
|
169
|
+
#
|
|
170
|
+
# not found is expected so we do nothing
|
|
171
|
+
#
|
|
172
|
+
...
|
|
173
|
+
return dataset is not None
|
|
174
|
+
|
|
175
|
+
def _has_group(self, name: str) -> bool:
|
|
176
|
+
name = self._fix_name_if(name)
|
|
177
|
+
groups = self.client.action.group_list(groups=[name])
|
|
178
|
+
return groups and len(groups) == 1 and groups[0] == name
|
|
179
|
+
|
|
180
|
+
def _has_resource(self, package_name, resource_name: str) -> str:
|
|
181
|
+
dataset = None
|
|
182
|
+
try:
|
|
183
|
+
dataset = self.client.action.package_show(id=package_name)
|
|
184
|
+
if "resources" in dataset:
|
|
185
|
+
for r in dataset["resources"]:
|
|
186
|
+
if r.get("name") == resource_name:
|
|
187
|
+
return r["id"]
|
|
188
|
+
except NotFound:
|
|
189
|
+
#
|
|
190
|
+
# not found is expected so we do nothing
|
|
191
|
+
#
|
|
192
|
+
...
|
|
193
|
+
return None
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from tempfile import NamedTemporaryFile
|
|
4
|
+
from csvpath.managers.listener import Listener
|
|
5
|
+
from csvpath.managers.metadata import Metadata
|
|
6
|
+
from csvpath.managers.results.results_metadata import ResultsMetadata
|
|
7
|
+
from csvpath.managers.results.result import Result
|
|
8
|
+
from csvpath.matching.util.runtime_data_collector import RuntimeDataCollector
|
|
9
|
+
from .ckan import Ckan, CkanException
|
|
10
|
+
from .dataset import Dataset
|
|
11
|
+
from .datafile import Datafile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CkanListener(Listener):
|
|
15
|
+
|
|
16
|
+
FILETYPE_MAP = {
|
|
17
|
+
"data": "data.csv",
|
|
18
|
+
"unmatched": "unmatched.csv",
|
|
19
|
+
"errors": "errors.json",
|
|
20
|
+
"vars": "vars.json",
|
|
21
|
+
"meta": "meta.json",
|
|
22
|
+
"manifest": "manifest.json",
|
|
23
|
+
"printouts": "printouts.txt",
|
|
24
|
+
}
|
|
25
|
+
FILETYPES = ["data", "unmatched", "meta", "vars", "errors", "printouts", "manifest"]
|
|
26
|
+
|
|
27
|
+
def __init__(self, config=None, csvpaths=None):
|
|
28
|
+
#
|
|
29
|
+
# we're unlikely to get either of these because
|
|
30
|
+
# we're loading dynamically. we'll get them after
|
|
31
|
+
# init.
|
|
32
|
+
#
|
|
33
|
+
super().__init__(config=config)
|
|
34
|
+
self._csvpaths = None
|
|
35
|
+
|
|
36
|
+
def manifest(self, metadata: Metadata) -> dict[str, ...]:
|
|
37
|
+
path = metadata.manifest_path
|
|
38
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
39
|
+
return json.load(file)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def csvpaths(self):
|
|
43
|
+
return self._csvpaths
|
|
44
|
+
|
|
45
|
+
@csvpaths.setter
|
|
46
|
+
def csvpaths(self, csvpaths) -> None:
|
|
47
|
+
self._csvpaths = csvpaths
|
|
48
|
+
|
|
49
|
+
def metadata_update(self, mdata: Metadata) -> None:
|
|
50
|
+
#
|
|
51
|
+
# CKAN is mainly interested in the output file and/or metadata.
|
|
52
|
+
# we can assume that we don't need to react to file, paths,
|
|
53
|
+
# and run events.
|
|
54
|
+
#
|
|
55
|
+
# CKAN cares about individual csvpath instances within the
|
|
56
|
+
# named-paths group. however, it needs to be able to react to
|
|
57
|
+
# a csvpath based on the results of all the other csvpaths in
|
|
58
|
+
# the group. that means we need to work from the results event
|
|
59
|
+
# rather than the result events. using the results event is
|
|
60
|
+
# easy because it is at the right point in time, already
|
|
61
|
+
# collects the summary good/bad indicators, and we have all
|
|
62
|
+
# the data we need to run through the individual csvpaths to
|
|
63
|
+
# look for CKAN work.
|
|
64
|
+
#
|
|
65
|
+
if isinstance(mdata, ResultsMetadata):
|
|
66
|
+
return self._send_result_files(mdata)
|
|
67
|
+
|
|
68
|
+
def _send_result_files(self, mdata: Metadata) -> None:
|
|
69
|
+
mani = self.manifest(mdata)
|
|
70
|
+
if "time_completed" not in mani or mani["time_completed"] is None:
|
|
71
|
+
#
|
|
72
|
+
# the run has started but not completed
|
|
73
|
+
#
|
|
74
|
+
return
|
|
75
|
+
#
|
|
76
|
+
# if for some reason we don't get back our correct results -- the most recent
|
|
77
|
+
# results available -- we can use results_manager.get_named_results_for_run()
|
|
78
|
+
# to find the right set
|
|
79
|
+
#
|
|
80
|
+
results = self.csvpaths.results_manager.get_named_results(
|
|
81
|
+
mdata.named_paths_name
|
|
82
|
+
)
|
|
83
|
+
if results is None:
|
|
84
|
+
raise CkanException(f"Results cannot be none for {mdata.named_paths_name}")
|
|
85
|
+
if len(results) == 0:
|
|
86
|
+
raise CkanException(f"Results cannot be 0 for {mdata.named_paths_name}")
|
|
87
|
+
if not mdata.run_home.endswith(results[0].run_dir):
|
|
88
|
+
raise CkanException(
|
|
89
|
+
f"Results of {mdata.named_paths_name} from wrong run dir: {results[0].run_dir}"
|
|
90
|
+
)
|
|
91
|
+
#
|
|
92
|
+
# we're good. we iterate all and for each we check if the instance requires
|
|
93
|
+
# all other instances to be valid,complete,no-errors. if so, we iterate for
|
|
94
|
+
# that. if all is good we follow the metadata instructions.
|
|
95
|
+
#
|
|
96
|
+
for result in results:
|
|
97
|
+
self._handle_result(results, result, mdata)
|
|
98
|
+
|
|
99
|
+
def _handle_result(
|
|
100
|
+
self, results: list[Result], result: Result, mdata: Metadata
|
|
101
|
+
) -> None:
|
|
102
|
+
#
|
|
103
|
+
# give the Dataset object a chance to find any new metadata
|
|
104
|
+
# we want to apply to the dataset in CKAN. we may need to
|
|
105
|
+
# pull the CKAN package to see what's already there, or not,
|
|
106
|
+
# or maybe leave that to the CKAN client to adjudicate, if
|
|
107
|
+
# we're ok with overwriting fields and don't lose existing
|
|
108
|
+
# metadata if we don't present it again during the update?
|
|
109
|
+
#
|
|
110
|
+
"""
|
|
111
|
+
Metadata directives for CKAN:
|
|
112
|
+
- ckan-publish: always | on-valid | on-all-valid | never
|
|
113
|
+
- ckan-group: use-archive | use-named-results | some-literal
|
|
114
|
+
- ckan-dataset-name: use-instance | use-named-results | var-value:name | a literal
|
|
115
|
+
- ckan-dataset-title: a-metadata-field-name | var-value:name
|
|
116
|
+
- ckan-visibility: private
|
|
117
|
+
- ckan-tags: static-tag1-n | instance-identity | instance-home | var-value:name
|
|
118
|
+
- ckan-show-fields: a, b, c, d....
|
|
119
|
+
- ckan-send: all, printouts, data, metadata, unmatched, vars, errors, manifest
|
|
120
|
+
- ckan-printouts-title: Background
|
|
121
|
+
- ckan-data-title: Orders
|
|
122
|
+
- ckan-unmatched-title: Orders
|
|
123
|
+
- ckan-vars-title: Orders
|
|
124
|
+
- ckan-meta-title: Orders
|
|
125
|
+
- ckan-errors-title: Orders
|
|
126
|
+
- ckan-split-printouts: (no-)split
|
|
127
|
+
"""
|
|
128
|
+
pub = self._publish(results=results, result=result)
|
|
129
|
+
if not pub:
|
|
130
|
+
return
|
|
131
|
+
#
|
|
132
|
+
# get manifest
|
|
133
|
+
#
|
|
134
|
+
mani = self.manifest(mdata)
|
|
135
|
+
dataset = Dataset(listener=self, manifest=mani, metadata=mdata)
|
|
136
|
+
#
|
|
137
|
+
# look in metadata for instructions as to if we use a group, and if so,
|
|
138
|
+
# how it is named.
|
|
139
|
+
#
|
|
140
|
+
self._set_group(dataset=dataset, result=result, mani=mani, mdata=mdata)
|
|
141
|
+
#
|
|
142
|
+
# look in metadata for instructions on how to map to the dataset; what name to use, etc.
|
|
143
|
+
#
|
|
144
|
+
self._set_dataset_name(dataset=dataset, result=result, mani=mani, mdata=mdata)
|
|
145
|
+
#
|
|
146
|
+
# find a title if any
|
|
147
|
+
#
|
|
148
|
+
self._set_dataset_title(dataset=dataset, result=result)
|
|
149
|
+
#
|
|
150
|
+
# find a visibility if any
|
|
151
|
+
#
|
|
152
|
+
self._set_visibility(dataset=dataset, result=result)
|
|
153
|
+
#
|
|
154
|
+
# tags to label the dataset with
|
|
155
|
+
#
|
|
156
|
+
self._set_tags(dataset=dataset, result=result, mdata=mdata)
|
|
157
|
+
#
|
|
158
|
+
# metadata fields to display in the dataset
|
|
159
|
+
#
|
|
160
|
+
self._set_metadata_fields(dataset=dataset, result=result)
|
|
161
|
+
#
|
|
162
|
+
# create the dataset before we load the files, or update it if existing
|
|
163
|
+
#
|
|
164
|
+
ckan = Ckan(config=self.config, manifest=mani, csvpaths=self.csvpaths)
|
|
165
|
+
ckan.create_or_update_dataset(dataset)
|
|
166
|
+
#
|
|
167
|
+
# send any files requested. one or more makes sense, but there is no requirement to send any.
|
|
168
|
+
#
|
|
169
|
+
self._send_files(result=result, mani=mani, mdata=mdata, dataset_id=dataset.name)
|
|
170
|
+
|
|
171
|
+
def _publish(self, *, results, result) -> bool:
|
|
172
|
+
pub = result.csvpath.metadata.get("ckan-publish")
|
|
173
|
+
ret = None
|
|
174
|
+
if pub is None:
|
|
175
|
+
ret = False
|
|
176
|
+
else:
|
|
177
|
+
pub = pub.strip().lower()
|
|
178
|
+
if pub == "never":
|
|
179
|
+
ret = False
|
|
180
|
+
elif pub == "always":
|
|
181
|
+
ret = True
|
|
182
|
+
elif pub == "on-valid":
|
|
183
|
+
ret = result.csvpath.is_valid
|
|
184
|
+
elif pub == "on-all-valid":
|
|
185
|
+
ret = True
|
|
186
|
+
for r in results:
|
|
187
|
+
if not r.is_valid:
|
|
188
|
+
ret = False
|
|
189
|
+
break
|
|
190
|
+
return ret
|
|
191
|
+
|
|
192
|
+
def _send_files(
|
|
193
|
+
self, *, result: Result, mani: dict, mdata: Metadata, dataset_id: str
|
|
194
|
+
) -> None:
|
|
195
|
+
filesstr = result.csvpath.metadata.get("ckan-send")
|
|
196
|
+
files = []
|
|
197
|
+
if filesstr is not None:
|
|
198
|
+
if filesstr.strip() == "all":
|
|
199
|
+
files = CkanListener.FILETYPES[:]
|
|
200
|
+
else:
|
|
201
|
+
fs = filesstr.split(",")
|
|
202
|
+
for f in fs:
|
|
203
|
+
f = f.strip().lower()
|
|
204
|
+
if f not in CkanListener.FILETYPES:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"Incorrect file identifier: {f}. Must be in {CkanListener.FILETYPES}."
|
|
207
|
+
)
|
|
208
|
+
files.append(f)
|
|
209
|
+
for file in files:
|
|
210
|
+
path = os.path.join(mani["run_home"], result.identity_or_index)
|
|
211
|
+
path = os.path.join(path, CkanListener.FILETYPE_MAP[file])
|
|
212
|
+
#
|
|
213
|
+
# move printouts out
|
|
214
|
+
#
|
|
215
|
+
if file == "printouts":
|
|
216
|
+
split = result.csvpath.metadata.get("ckan-split-printouts")
|
|
217
|
+
if split and split.strip().lower() == "split":
|
|
218
|
+
if os.path.exists(path):
|
|
219
|
+
printouts = ""
|
|
220
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
221
|
+
printouts = file.read()
|
|
222
|
+
printouts = printouts.split("---- PRINTOUT:")
|
|
223
|
+
for p in printouts:
|
|
224
|
+
self._send_a_printout(
|
|
225
|
+
p=p,
|
|
226
|
+
result=result,
|
|
227
|
+
mani=mani,
|
|
228
|
+
mdata=mdata,
|
|
229
|
+
dataset_id=dataset_id,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
datafile = Datafile(
|
|
233
|
+
listener=self,
|
|
234
|
+
result=result,
|
|
235
|
+
manifest=mani,
|
|
236
|
+
metadata=mdata,
|
|
237
|
+
filetype=file,
|
|
238
|
+
path=path,
|
|
239
|
+
)
|
|
240
|
+
datafile.dataset_id = dataset_id
|
|
241
|
+
title = result.csvpath.metadata.get(f"ckan-{file}-title")
|
|
242
|
+
if title is not None:
|
|
243
|
+
datafile.name = title
|
|
244
|
+
ckan = Ckan(config=self.config, manifest=mani, csvpaths=self.csvpaths)
|
|
245
|
+
ckan.upload_datafile(datafile)
|
|
246
|
+
|
|
247
|
+
def _send_a_printout(
|
|
248
|
+
self, *, p: str, result: Result, mani: dict, mdata: Metadata, dataset_id: str
|
|
249
|
+
) -> None:
|
|
250
|
+
i = p.find("\n")
|
|
251
|
+
name = p[0:i]
|
|
252
|
+
name = name.strip()
|
|
253
|
+
if name == "default":
|
|
254
|
+
name = result.csvpath.metadata.get("ckan-printouts-title")
|
|
255
|
+
if name is None:
|
|
256
|
+
name = "Default printer"
|
|
257
|
+
body = p[i + 1 :]
|
|
258
|
+
body = body.strip()
|
|
259
|
+
if body == "":
|
|
260
|
+
return
|
|
261
|
+
with NamedTemporaryFile(delete_on_close=False) as file:
|
|
262
|
+
file.write(body.encode("utf-8"))
|
|
263
|
+
file.close()
|
|
264
|
+
datafile = Datafile(
|
|
265
|
+
listener=self,
|
|
266
|
+
result=result,
|
|
267
|
+
manifest=mani,
|
|
268
|
+
metadata=mdata,
|
|
269
|
+
filetype="printouts",
|
|
270
|
+
path=file.name,
|
|
271
|
+
)
|
|
272
|
+
datafile.dataset_id = dataset_id
|
|
273
|
+
datafile.name = name
|
|
274
|
+
datafile.mime_type = "text/plain"
|
|
275
|
+
ckan = Ckan(config=self.config, manifest=mani, csvpaths=self.csvpaths)
|
|
276
|
+
ckan.upload_datafile(datafile)
|
|
277
|
+
|
|
278
|
+
def _set_visibility(self, dataset: Dataset, result: Result) -> None:
|
|
279
|
+
visibility = result.csvpath.metadata.get("ckan-visibility")
|
|
280
|
+
if visibility and visibility.strip().lower() == "public": # not private
|
|
281
|
+
dataset.visible = False
|
|
282
|
+
|
|
283
|
+
def _set_group(
|
|
284
|
+
self, *, dataset: Dataset, result: Result, mani: dict, mdata: Metadata
|
|
285
|
+
):
|
|
286
|
+
g = self._create_group_if(
|
|
287
|
+
dataset=dataset, result=result, mani=mani, mdata=mdata
|
|
288
|
+
)
|
|
289
|
+
dataset.group = g
|
|
290
|
+
|
|
291
|
+
def _create_group_if(
|
|
292
|
+
self, *, dataset: Dataset, result: Result, mani: dict, mdata: Metadata
|
|
293
|
+
) -> str:
|
|
294
|
+
group, title = self._get_group_name(result, mdata)
|
|
295
|
+
if group is None:
|
|
296
|
+
return
|
|
297
|
+
ckan = Ckan(config=self.config, manifest=mani, csvpaths=self.csvpaths)
|
|
298
|
+
group = ckan.create_group_if(name=group, title=title)
|
|
299
|
+
return group
|
|
300
|
+
|
|
301
|
+
def _get_group_name(self, result: Result, mdata: Metadata) -> str:
|
|
302
|
+
group = result.csvpath.metadata.get("ckan-group")
|
|
303
|
+
title = group
|
|
304
|
+
if group is not None:
|
|
305
|
+
group = group.strip().lower()
|
|
306
|
+
if group == "use-archive":
|
|
307
|
+
group = mdata.archive_name
|
|
308
|
+
title = group
|
|
309
|
+
elif group == "use-named-results":
|
|
310
|
+
group = mdata.named_results_name
|
|
311
|
+
title = group
|
|
312
|
+
group = group.strip().lower()
|
|
313
|
+
return (group, title)
|
|
314
|
+
|
|
315
|
+
def _set_dataset_name(
|
|
316
|
+
self, *, dataset: Dataset, result: Result, mani: dict, mdata: Metadata
|
|
317
|
+
) -> None:
|
|
318
|
+
slug = result.csvpath.metadata.get("ckan-dataset-name")
|
|
319
|
+
lslug = None
|
|
320
|
+
if slug is not None:
|
|
321
|
+
slug = slug.strip()
|
|
322
|
+
lslug = slug.lower()
|
|
323
|
+
if lslug == "use-instance":
|
|
324
|
+
dataset.name = result.csvpath.identity
|
|
325
|
+
elif lslug == "use-named-results":
|
|
326
|
+
dataset.name = mdata.named_results_name
|
|
327
|
+
elif lslug is not None and lslug.startswith("var-value:"):
|
|
328
|
+
v = lslug[10:]
|
|
329
|
+
val = result.csvpath.variables.get(v)
|
|
330
|
+
if val is not None:
|
|
331
|
+
dataset.name = val if val else v
|
|
332
|
+
elif slug is not None:
|
|
333
|
+
dataset.name = slug
|
|
334
|
+
else:
|
|
335
|
+
dataset.name = mdata.named_results_name
|
|
336
|
+
|
|
337
|
+
def _set_dataset_title(self, *, dataset: Dataset, result: Result) -> None:
|
|
338
|
+
title = result.csvpath.metadata.get("ckan-dataset-title")
|
|
339
|
+
if title is not None:
|
|
340
|
+
if title.startswith("var-value:"):
|
|
341
|
+
v = title[10:]
|
|
342
|
+
v = result.csvpath.variables.get(v)
|
|
343
|
+
if v is not None:
|
|
344
|
+
title = v
|
|
345
|
+
dataset.title = title
|
|
346
|
+
|
|
347
|
+
def _set_tags(self, *, dataset: Dataset, result: Result, mdata: Metadata) -> None:
|
|
348
|
+
tagsstr = result.csvpath.metadata.get("ckan-tags")
|
|
349
|
+
tags = []
|
|
350
|
+
if tagsstr is not None:
|
|
351
|
+
ts = tagsstr.split(",")
|
|
352
|
+
for t in ts:
|
|
353
|
+
t = t.strip()
|
|
354
|
+
if t == "instance-identity":
|
|
355
|
+
tags.append({"name": f"{result.csvpath.identity}"})
|
|
356
|
+
elif t == "instance-home":
|
|
357
|
+
h = mdata.run_home
|
|
358
|
+
if h is not None:
|
|
359
|
+
tags.append({"name": f"{h}"})
|
|
360
|
+
elif t.startswith("var-value:"):
|
|
361
|
+
v = t[10:]
|
|
362
|
+
v = result.csvpath.variables.get(v)
|
|
363
|
+
if v is not None:
|
|
364
|
+
tags.append({"name": f"{v}"})
|
|
365
|
+
else:
|
|
366
|
+
tags.append({"name": f"{t}"})
|
|
367
|
+
if len(tags) > 0:
|
|
368
|
+
dataset.tags = tags
|
|
369
|
+
|
|
370
|
+
def _set_metadata_fields(self, *, dataset: Dataset, result: Result) -> None:
|
|
371
|
+
fieldsstr = result.csvpath.metadata.get("ckan-show-fields")
|
|
372
|
+
fields = []
|
|
373
|
+
if fieldsstr is not None:
|
|
374
|
+
fs = fieldsstr.split(",")
|
|
375
|
+
runt = None
|
|
376
|
+
for f in fs:
|
|
377
|
+
f = f.strip()
|
|
378
|
+
v = result.csvpath.metadata.get(f)
|
|
379
|
+
if v is None and f.startswith("var-value:"):
|
|
380
|
+
f = f[10:]
|
|
381
|
+
v = result.csvpath.variables.get(f)
|
|
382
|
+
if v is None:
|
|
383
|
+
if runt is None:
|
|
384
|
+
runt = {}
|
|
385
|
+
RuntimeDataCollector.collect(result.csvpath, runt, local=True)
|
|
386
|
+
v = runt.get(f)
|
|
387
|
+
if v is not None:
|
|
388
|
+
fields.append({"key": f"{f}", "value": f"{v}"})
|
|
389
|
+
if len(fields) > 0:
|
|
390
|
+
dataset.fields = fields
|