"$DEDUPE_STATE" 2>/dev/null; then
- DEDUPE_STATE="/tmp/fswatch-ingest-dedupe-$$"
- : >"$DEDUPE_STATE"
+ DEDUPE_STATE="/tmp/fswatch-ingest-dedupe-$$"
+ : >"$DEDUPE_STATE"
fi
echo "Watching $PATH_TO_CHECK (dedupe=$DEDUPE_STATE)"
# Detect GNU vs BSD stat once (not per-file — missing files must not flip dialects).
if stat -c%s / >/dev/null 2>&1; then
- file_size() {
- [ -f "$1" ] || return 1
- stat -c%s "$1"
- }
+ file_size() {
+ [ -f "$1" ] || return 1
+ stat -c%s "$1"
+ }
else
- file_size() {
- [ -f "$1" ] || return 1
- stat -f%z "$1"
- }
+ file_size() {
+ [ -f "$1" ] || return 1
+ stat -f%z "$1"
+ }
fi
urlencode() {
- # Prefer python3 (available in fswatch image); fall back to raw name.
- if command -v python3 >/dev/null 2>&1; then
- python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
- else
- printf '%s' "$1"
- fi
+ # Prefer python3 (available in fswatch image); fall back to raw name.
+ if command -v python3 >/dev/null 2>&1; then
+ python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
+ else
+ printf '%s' "$1"
+ fi
}
should_ingest() {
- name=$1
- case "$name" in
- .*|*.crdownload|*.part|*.tmp|*.download)
- return 1
- ;;
- Unconfirmed\ *)
- return 1
- ;;
- esac
- case "$name" in
- *.txt|*.zip|*.tar.gz|*.tgz)
- return 0
- ;;
- *)
- return 1
- ;;
- esac
+ name=$1
+ case "$name" in
+ .* | *.crdownload | *.part | *.tmp | *.download)
+ return 1
+ ;;
+ Unconfirmed\ *)
+ return 1
+ ;;
+ esac
+ case "$name" in
+ *.txt | *.zip | *.tar.gz | *.tgz)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
}
wait_stable() {
- path=$1
- i=0
- while [ "$i" -lt 60 ]; do
- [ -f "$path" ] || return 1
- a=$(file_size "$path") || return 1
- sleep 0.5
- [ -f "$path" ] || return 1
- b=$(file_size "$path") || return 1
- if [ "$a" -eq "$b" ]; then
- return 0
- fi
- i=$((i + 1))
- done
- return 1
+ path=$1
+ i=0
+ while [ "$i" -lt 60 ]; do
+ [ -f "$path" ] || return 1
+ a=$(file_size "$path") || return 1
+ sleep 0.5
+ [ -f "$path" ] || return 1
+ b=$(file_size "$path") || return 1
+ if [ "$a" -eq "$b" ]; then
+ return 0
+ fi
+ i=$((i + 1))
+ done
+ return 1
}
already_ingested() {
- path=$1
- size=$2
- [ -f "$DEDUPE_STATE" ] || return 1
- {
- read -r last_path
- read -r last_size
- } <"$DEDUPE_STATE" || return 1
- [ "$path" = "$last_path" ] && [ "$size" = "$last_size" ]
+ path=$1
+ size=$2
+ [ -f "$DEDUPE_STATE" ] || return 1
+ {
+ read -r last_path
+ read -r last_size
+ } <"$DEDUPE_STATE" || return 1
+ [ "$path" = "$last_path" ] && [ "$size" = "$last_size" ]
}
ingest_file() {
- line=$1
- filename=${line##*/}
- if ! should_ingest "$filename"; then
- echo "Skipping non-ingestible: $filename"
- return 0
- fi
- echo "Contents of $PATH_TO_CHECK changed; Checking: $filename"
- if ! wait_stable "$line"; then
- echo "Gave up waiting for stable file: $filename"
- return 0
- fi
- size=$(file_size "$line") || return 0
- if already_ingested "$line" "$size"; then
- echo "Already ingested at this size, skipping: $filename"
- return 0
- fi
- echo "Processing: $filename"
- enc=$(urlencode "$filename")
- ctype=application/octet-stream
- case "$filename" in
- *.txt) ctype=text/plain ;;
- esac
- if ! curl -sS -f -X POST \
- -H "accept: application/json" \
- -H "Content-Type: ${ctype}" \
- -T "$line" \
- "${TAGBASE_INGEST_BASE}/ingest?filename=${enc}&type=etuff"; then
- echo "Ingest failed for $filename (see tagbase_server logs)"
- else
- printf '%s\n%s\n' "$line" "$size" >"$DEDUPE_STATE"
- echo
- echo "Ingest OK: $filename"
- fi
+ line=$1
+ filename=${line##*/}
+ if ! should_ingest "$filename"; then
+ echo "Skipping non-ingestible: $filename"
+ return 0
+ fi
+ echo "Contents of $PATH_TO_CHECK changed; Checking: $filename"
+ if ! wait_stable "$line"; then
+ echo "Gave up waiting for stable file: $filename"
+ return 0
+ fi
+ size=$(file_size "$line") || return 0
+ if already_ingested "$line" "$size"; then
+ echo "Already ingested at this size, skipping: $filename"
+ return 0
+ fi
+ echo "Processing: $filename"
+ enc=$(urlencode "$filename")
+ ctype=application/octet-stream
+ case "$filename" in
+ *.txt) ctype=text/plain ;;
+ esac
+ if ! curl -sS -f -X POST \
+ -H "accept: application/json" \
+ -H "Content-Type: ${ctype}" \
+ -T "$line" \
+ "${TAGBASE_INGEST_BASE}/ingest?filename=${enc}&type=etuff"; then
+ echo "Ingest failed for $filename (see tagbase_server logs)"
+ else
+ printf '%s\n%s\n' "$line" "$size" >"$DEDUPE_STATE"
+ echo
+ echo "Ingest OK: $filename"
+ fi
}
scan_existing() {
- # Process files already present (startup / leftover from a prior blind window).
- # shellcheck disable=SC2039
- for path in "$PATH_TO_CHECK"*; do
- [ -f "$path" ] || continue
- ingest_file "$path"
- done
+ # Process files already present (startup / leftover from a prior blind window).
+ # shellcheck disable=SC2039
+ for path in "$PATH_TO_CHECK"*; do
+ [ -f "$path" ] || continue
+ ingest_file "$path"
+ done
}
scan_existing
@@ -135,16 +135,16 @@ scan_existing
# Outer loop only restarts fswatch if the binary exits unexpectedly.
FSWATCH_CMD="fswatch"
if command -v stdbuf >/dev/null 2>&1; then
- FSWATCH_CMD="stdbuf -oL fswatch"
+ FSWATCH_CMD="stdbuf -oL fswatch"
fi
while true; do
- # shellcheck disable=SC2086
- $FSWATCH_CMD "$PATH_TO_CHECK" \
- --event Created --event MovedTo --event IsFile --event Updated \
- | while IFS= read -r line; do
- ingest_file "$line"
- done
- echo "fswatch exited; restarting watch on $PATH_TO_CHECK"
- sleep 1
+ # shellcheck disable=SC2086
+ $FSWATCH_CMD "$PATH_TO_CHECK" \
+ --event Created --event MovedTo --event IsFile --event Updated |
+ while IFS= read -r line; do
+ ingest_file "$line"
+ done
+ echo "fswatch exited; restarting watch on $PATH_TO_CHECK"
+ sleep 1
done
diff --git a/services/nginx/proxy/index.html b/services/nginx/proxy/index.html
index 8965b8a0..cc005544 100644
--- a/services/nginx/proxy/index.html
+++ b/services/nginx/proxy/index.html
@@ -1,26 +1,86 @@
-
+
+
+
+
+ tagbase-server
+
+
+
-
+
-Navigation
-
- - /docs: visualize and interact with tagbase-server REST APIs.
- - /pgadmin4/: administration and development platform for Tagbase PostGIS instances.
- - /tagbase/api/v0.14.0: REST API base path.
-
+ Navigation
+
+ -
+ /docs: visualize and interact with tagbase-server REST APIs.
+
+ -
+ /pgadmin4/: administration and development platform for Tagbase PostGIS
+ instances.
+
+ -
+ /tagbase/api/v0.14.0: REST API base path.
+
+
-Observability
-Requires docker compose --profile observability. Protected by the same nginx basic auth.
-
+ Observability
+
+ Requires docker compose --profile observability. Protected by
+ the same nginx basic auth.
+
+
-Documentation and Support
-
+ Documentation and Support
+
+
+
diff --git a/services/observability/.env.observability.example b/services/observability/.env.observability.example
index 3555e334..7948501d 100644
--- a/services/observability/.env.observability.example
+++ b/services/observability/.env.observability.example
@@ -1,29 +1,27 @@
-# Stack credentials (required by docker-compose.yml; these match local defaults)
-POSTGRES_PASSWORD=tagbase
-POSTGRES_PORT=5432
-PGADMIN_DEFAULT_EMAIL=admin@example.com
-PGADMIN_DEFAULT_PASSWORD=tagbase
-SLACK_BOT_TOKEN=
-webhook=
+# Example env for the observability Compose profile.
+# Keys are alphabetical (dotenv-linter UnorderedKey).
+#
+# Stack credentials match local docker-compose defaults.
+# Set ALLOY_CONFIG_FILE=config.cloud.alloy and fill PROM_/LOKI_/TEMPO_
+# when exporting to Grafana Cloud instead of the local LGTM stack.
+# OTEL_* enables app OTLP export (required with the observability profile).
-# Which Alloy config to mount (local LGTM is default)
ALLOY_CONFIG_FILE=config.local.alloy
-
-# Enable app OTLP export (required when using the observability profile)
-OTEL_SDK_DISABLED=false
+LOKI_ENDPOINT=
+LOKI_PASSWORD=
+LOKI_USERNAME=
OTEL_EXPORTER_OTLP_ENDPOINT=http://alloy:4317
+OTEL_SDK_DISABLED=false
OTEL_SERVICE_NAME=tagbase_server
-
-# ── Grafana Cloud (required only when ALLOY_CONFIG_FILE=config.cloud.alloy) ──
-# From Grafana Cloud → Connections → OpenTelemetry / Prometheus / Loki / Tempo
+PGADMIN_DEFAULT_EMAIL=admin@example.com
+PGADMIN_DEFAULT_PASSWORD=tagbase
+POSTGRES_PASSWORD=tagbase
+POSTGRES_PORT=5432
PROM_ENDPOINT=
-PROM_USERNAME=
PROM_PASSWORD=
-
-LOKI_ENDPOINT=
-LOKI_USERNAME=
-LOKI_PASSWORD=
-
+PROM_USERNAME=
+SLACK_BOT_TOKEN=
TEMPO_ENDPOINT=
-TEMPO_USERNAME=
TEMPO_PASSWORD=
+TEMPO_USERNAME=
+WEBHOOK=
diff --git a/services/postgis/tagbase_materialized_views.sql b/services/postgis/tagbase_materialized_views.sql
index fd87a6ca..69b122aa 100644
--- a/services/postgis/tagbase_materialized_views.sql
+++ b/services/postgis/tagbase_materialized_views.sql
@@ -3,136 +3,252 @@
-- MATERIALIZED VIEW
-CREATE MATERIALIZED VIEW mview_vis_data
-AS
- SELECT
- variable.submission_id AS source_id,
- variable.variable_value AS measurement_value,
- variable.variable_name AS measurement_name,
- variable.variable_units AS measurement_units,
- depth.depth,
- variable.date_time AS measurement_date_time,
- data_position.date_time AS position_date_time,
- data_position.lat,
- CASE WHEN data_position.lon::double precision > 180 THEN data_position.lon::double precision - 360 ELSE data_position.lon::double precision END,
- data_position.lat_err,
- data_position.lon_err
- FROM ( SELECT x.variable_value,
- y.variable_name,
- x.date_time,
- x.submission_id,
- y.variable_units,
- x.position_date_time
- FROM data_time_series x,
- observation_types y
- WHERE x.variable_id = y.variable_id AND y.variable_name <> 'depth' AND y.variable_name <> 'datetime') variable,
- data_position,
- ( SELECT x.variable_value AS depth,
- x.date_time,
- x.submission_id
- FROM data_time_series x,
- observation_types y
- WHERE x.variable_id = y.variable_id AND y.variable_name = 'depth') depth
- WHERE variable.submission_id = data_position.submission_id AND variable.submission_id = depth.submission_id AND variable.position_date_time = data_position.date_time AND depth.date_time = variable.date_time
+CREATE MATERIALIZED VIEW mview_vis_data AS
+WITH var_rows AS (
+ SELECT
+ x.variable_value,
+ y.variable_name,
+ x.date_time,
+ x.submission_id,
+ y.variable_units,
+ x.position_date_time
+ FROM data_time_series AS x
+ INNER JOIN observation_types AS y
+ ON x.variable_id = y.variable_id
+ WHERE
+ y.variable_name <> 'depth'
+ AND y.variable_name <> 'datetime'
+),
+
+depth_rows AS (
+ SELECT
+ x.variable_value AS depth_value,
+ x.date_time,
+ x.submission_id
+ FROM data_time_series AS x
+ INNER JOIN observation_types AS y
+ ON x.variable_id = y.variable_id
+ WHERE y.variable_name = 'depth'
+)
+
+SELECT
+ var_rows.submission_id AS source_id,
+ var_rows.variable_value AS measurement_value,
+ var_rows.variable_name AS measurement_name,
+ var_rows.variable_units AS measurement_units,
+ depth_rows.depth_value AS depth,
+ var_rows.date_time AS measurement_date_time,
+ data_position.date_time AS position_date_time,
+ data_position.lat,
+ CASE
+ WHEN data_position.lon::double precision > 180
+ THEN data_position.lon::double precision - 360
+ ELSE data_position.lon::double precision
+ END AS lon,
+ data_position.lat_err,
+ data_position.lon_err
+FROM var_rows
+INNER JOIN data_position
+ ON var_rows.submission_id = data_position.submission_id
+ AND var_rows.position_date_time = data_position.date_time
+INNER JOIN depth_rows
+ ON var_rows.submission_id = depth_rows.submission_id
+ AND var_rows.date_time = depth_rows.date_time
WITH DATA;
-CREATE MATERIALIZED VIEW mview_vis_data_histogram
-AS
- SELECT
- data.submission_id AS source_id,
- data.min_value AS bin_class,
- data.variable_value AS measurement_value,
- data.date_time AS measurement_date_time,
- data_position.date_time AS position_date_time,
- data_position.lat,
- CASE WHEN data_position.lon::double precision > 180 THEN data_position.lon::double precision - 360 ELSE data_position.lon::double precision END,
- data_position.lat_err,
- data_position.lon_err
- FROM ( SELECT data_histogram_bin_info.min_value,
- data_histogram_bin_data.submission_id,
- data_histogram_bin_data.date_time,
- data_histogram_bin_data.variable_value,
- data_histogram_bin_data.position_date_time
- FROM data_histogram_bin_info,
- data_histogram_bin_data
- WHERE data_histogram_bin_info.bin_id = data_histogram_bin_data.bin_id AND data_histogram_bin_info.bin_class = data_histogram_bin_data.bin_class) data,
- data_position
- WHERE data.submission_id = data_position.submission_id AND data.position_date_time = data_position.date_time
+CREATE MATERIALIZED VIEW mview_vis_data_histogram AS
+WITH hist_rows AS (
+ SELECT
+ data_histogram_bin_info.min_value,
+ data_histogram_bin_data.submission_id,
+ data_histogram_bin_data.date_time,
+ data_histogram_bin_data.variable_value,
+ data_histogram_bin_data.position_date_time
+ FROM data_histogram_bin_info
+ INNER JOIN data_histogram_bin_data
+ ON data_histogram_bin_info.bin_id = data_histogram_bin_data.bin_id
+ AND data_histogram_bin_info.bin_class = data_histogram_bin_data.bin_class
+)
+
+SELECT
+ hist_rows.submission_id AS source_id,
+ hist_rows.min_value AS bin_class,
+ hist_rows.variable_value AS measurement_value,
+ hist_rows.date_time AS measurement_date_time,
+ data_position.date_time AS position_date_time,
+ data_position.lat,
+ CASE
+ WHEN data_position.lon::double precision > 180
+ THEN data_position.lon::double precision - 360
+ ELSE data_position.lon::double precision
+ END AS lon,
+ data_position.lat_err,
+ data_position.lon_err
+FROM hist_rows
+INNER JOIN data_position
+ ON hist_rows.submission_id = data_position.submission_id
+ AND hist_rows.position_date_time = data_position.date_time
WITH DATA;
+CREATE MATERIALIZED VIEW mview_vis_data_profile AS
+WITH profile_rows AS (
+ SELECT
+ data_profile.submission_id,
+ data_profile.date_time,
+ data_profile.depth,
+ data_profile.variable_value,
+ data_profile.position_date_time
+ FROM data_profile
+)
-CREATE MATERIALIZED VIEW mview_vis_data_profile
-AS
- SELECT
- data.submission_id AS source_id,
- data.depth,
- data.variable_value AS measurement_value,
- data.date_time AS measurement_date_time,
- data_position.date_time AS position_date_time,
- data_position.lat,
- CASE WHEN data_position.lon::double precision > 180 THEN data_position.lon::double precision - 360 ELSE data_position.lon::double precision END,
- data_position.lat_err,
- data_position.lon_err
- FROM ( SELECT data_profile.submission_id,
- data_profile.date_time,
- data_profile.depth,
- data_profile.variable_value,
- data_profile.position_date_time
- FROM data_profile) data, data_position
- WHERE data.submission_id = data_position.submission_id AND data.position_date_time = data_position.date_time
+SELECT
+ profile_rows.submission_id AS source_id,
+ profile_rows.depth,
+ profile_rows.variable_value AS measurement_value,
+ profile_rows.date_time AS measurement_date_time,
+ data_position.date_time AS position_date_time,
+ data_position.lat,
+ CASE
+ WHEN data_position.lon::double precision > 180
+ THEN data_position.lon::double precision - 360
+ ELSE data_position.lon::double precision
+ END AS lon,
+ data_position.lat_err,
+ data_position.lon_err
+FROM profile_rows
+INNER JOIN data_position
+ ON profile_rows.submission_id = data_position.submission_id
+ AND profile_rows.position_date_time = data_position.date_time
WITH DATA;
-CREATE MATERIALIZED VIEW mview_vis_metadata
-AS
- SELECT metadata.submission_id AS source_id,
- 'Global Attributes'::text AS attribute_type,
- NULL::character varying AS variable,
- metadata_types.category,
- metadata_types.attribute_name,
- "left"("right"(metadata.attribute_value, length(metadata.attribute_value) - 1), '-1'::integer) AS attribute_value
- FROM metadata_types,
- metadata
- WHERE metadata_types.attribute_id = metadata.attribute_id AND (metadata_types.category::text = 'instrument'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['instrument_name'::character varying, 'instrument_type'::character varying, 'firmware'::character varying, 'manufacturer'::character varying, 'model'::character varying, 'owner_contact'::character varying, 'person_owner'::character varying, 'serial_number'::character varying]::text[])) OR metadata_types.category::text = 'programming'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['programming_report'::character varying, 'programming_software'::character varying]::text[])) OR metadata_types.category::text = 'attachment'::text AND metadata_types.attribute_name::text = 'attachment_method'::text OR metadata_types.category::text = 'deployment'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['geospatial_lat_start'::character varying, 'geospatial_lon_start'::character varying, 'person_tagger_capture'::character varying, 'time_coverage_start'::character varying]::text[])) OR metadata_types.category::text = 'animal'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['condition_capture'::character varying, 'length_capture'::character varying, 'length_method_capture'::character varying, 'length_type_capture'::character varying, 'length_unit_capture'::character varying, 'platform'::character varying, 'taxonomic_serial_number'::character varying]::text[])) OR metadata_types.category::text = 'end_of_mission'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['time_coverage_end'::character varying, 'end_details'::character varying, 'end_type'::character varying, 'geospatial_lat_end'::character varying, 'geospatial_lon_end'::character varying]::text[])) OR metadata_types.category::text = 'waypoints'::text AND metadata_types.attribute_name::text = 'waypoints_source'::text OR metadata_types.category::text = 'quality'::text AND (metadata_types.attribute_name::text = ANY (ARRAY['found_problem'::character varying, 'person_qc'::character varying]::text[])))
+CREATE MATERIALIZED VIEW mview_vis_metadata AS
+WITH dts AS (
+ SELECT
+ data_time_series_1.variable_id,
+ data_time_series_1.submission_id
+ FROM data_time_series AS data_time_series_1
+ GROUP BY data_time_series_1.variable_id, data_time_series_1.submission_id
+)
+
+SELECT
+ metadata.submission_id AS source_id,
+ 'Global Attributes'::text AS attribute_type,
+ NULL::character varying AS variable,
+ metadata_types.category,
+ metadata_types.attribute_name,
+ left(
+ right(metadata.attribute_value, length(metadata.attribute_value) - 1),
+ -1
+ ) AS attribute_value
+FROM metadata_types
+INNER JOIN metadata
+ ON metadata_types.attribute_id = metadata.attribute_id
+WHERE
+ (
+ (
+ metadata_types.category::text = 'instrument'::text
+ AND metadata_types.attribute_name::text IN (
+ 'instrument_name',
+ 'instrument_type',
+ 'firmware',
+ 'manufacturer',
+ 'model',
+ 'owner_contact',
+ 'person_owner',
+ 'serial_number'
+ )
+ )
+ OR (
+ metadata_types.category::text = 'programming'::text
+ AND metadata_types.attribute_name::text IN (
+ 'programming_report',
+ 'programming_software'
+ )
+ )
+ OR (
+ metadata_types.category::text = 'attachment'::text
+ AND metadata_types.attribute_name::text = 'attachment_method'::text
+ )
+ OR (
+ metadata_types.category::text = 'deployment'::text
+ AND metadata_types.attribute_name::text IN (
+ 'geospatial_lat_start',
+ 'geospatial_lon_start',
+ 'person_tagger_capture',
+ 'time_coverage_start'
+ )
+ )
+ OR (
+ metadata_types.category::text = 'animal'::text
+ AND metadata_types.attribute_name::text IN (
+ 'condition_capture',
+ 'length_capture',
+ 'length_method_capture',
+ 'length_type_capture',
+ 'length_unit_capture',
+ 'platform',
+ 'taxonomic_serial_number'
+ )
+ )
+ OR (
+ metadata_types.category::text = 'end_of_mission'::text
+ AND metadata_types.attribute_name::text IN (
+ 'time_coverage_end',
+ 'end_details',
+ 'end_type',
+ 'geospatial_lat_end',
+ 'geospatial_lon_end'
+ )
+ )
+ OR (
+ metadata_types.category::text = 'waypoints'::text
+ AND metadata_types.attribute_name::text = 'waypoints_source'::text
+ )
+ OR (
+ metadata_types.category::text = 'quality'::text
+ AND metadata_types.attribute_name::text IN (
+ 'found_problem',
+ 'person_qc'
+ )
+ )
+ )
UNION
- SELECT data_time_series.submission_id AS source_id,
- 'Variable Attributes'::text AS attribute_type,
- observation_types.standard_name AS variable,
- NULL::character varying AS category,
- 'units'::character varying AS attribute_name,
- observation_types.variable_units AS attribute_value
- FROM observation_types,
- ( SELECT data_time_series_1.variable_id,
- data_time_series_1.submission_id
- FROM data_time_series data_time_series_1
- GROUP BY data_time_series_1.variable_id, data_time_series_1.submission_id) data_time_series
- WHERE observation_types.standard_name IS NOT NULL AND observation_types.variable_id = data_time_series.variable_id
+SELECT
+ dts.submission_id AS source_id,
+ 'Variable Attributes'::text AS attribute_type,
+ observation_types.standard_name AS variable,
+ NULL::character varying AS category,
+ 'units'::character varying AS attribute_name,
+ observation_types.variable_units AS attribute_value
+FROM observation_types
+INNER JOIN dts
+ ON observation_types.variable_id = dts.variable_id
+WHERE observation_types.standard_name IS NOT NULL
UNION
- SELECT data_time_series.submission_id AS source_id,
- 'Variable Attributes'::text AS attribute_type,
- observation_types.standard_name AS variable,
- NULL::character varying AS category,
- 'standard_name'::character varying AS attribute_name,
- observation_types.standard_name AS attribute_value
- FROM observation_types,
- ( SELECT data_time_series_1.variable_id,
- data_time_series_1.submission_id
- FROM data_time_series data_time_series_1
- GROUP BY data_time_series_1.variable_id, data_time_series_1.submission_id) data_time_series
- WHERE observation_types.standard_name IS NOT NULL AND observation_types.variable_id = data_time_series.variable_id
+SELECT
+ dts.submission_id AS source_id,
+ 'Variable Attributes'::text AS attribute_type,
+ observation_types.standard_name AS variable,
+ NULL::character varying AS category,
+ 'standard_name'::character varying AS attribute_name,
+ observation_types.standard_name AS attribute_value
+FROM observation_types
+INNER JOIN dts
+ ON observation_types.variable_id = dts.variable_id
+WHERE observation_types.standard_name IS NOT NULL
UNION
- SELECT data_time_series.submission_id AS source_id,
- 'Variable Attributes'::text AS attribute_type,
- observation_types.standard_name AS variable,
- NULL::character varying AS category,
- 'long_name'::character varying AS attribute_name,
- observation_types.variable_name AS attribute_value
- FROM observation_types,
- ( SELECT data_time_series_1.variable_id,
- data_time_series_1.submission_id
- FROM data_time_series data_time_series_1
- GROUP BY data_time_series_1.variable_id, data_time_series_1.submission_id) data_time_series
- WHERE observation_types.standard_name IS NOT NULL AND observation_types.variable_id = data_time_series.variable_id
-WITH DATA;
\ No newline at end of file
+SELECT
+ dts.submission_id AS source_id,
+ 'Variable Attributes'::text AS attribute_type,
+ observation_types.standard_name AS variable,
+ NULL::character varying AS category,
+ 'long_name'::character varying AS attribute_name,
+ observation_types.variable_name AS attribute_value
+FROM observation_types
+INNER JOIN dts
+ ON observation_types.variable_id = dts.variable_id
+WHERE observation_types.standard_name IS NOT NULL
+WITH DATA;
diff --git a/tagbase_server/.coveragerc b/tagbase_server/.coveragerc
index d99990d3..c7bebb74 100644
--- a/tagbase_server/.coveragerc
+++ b/tagbase_server/.coveragerc
@@ -1,3 +1,3 @@
[run]
-omit =
- tagbase_server/test/*
\ No newline at end of file
+omit =
+ tagbase_server/test/*
diff --git a/tagbase_server/.dockerignore b/tagbase_server/.dockerignore
index 1c96c39d..35f1dc94 100644
--- a/tagbase_server/.dockerignore
+++ b/tagbase_server/.dockerignore
@@ -1,8 +1,5 @@
-.travis.yaml
-.openapi-generator-ignore
README.md
tox.ini
-git_push.sh
test-requirements.txt
setup.py
pyproject.toml
diff --git a/tagbase_server/.openapi-generator-ignore b/tagbase_server/.openapi-generator-ignore
deleted file mode 100644
index d2485c6f..00000000
--- a/tagbase_server/.openapi-generator-ignore
+++ /dev/null
@@ -1,31 +0,0 @@
-# OpenAPI Generator Ignore
-# Generated by openapi-generator https://github.com/openapitools/openapi-generator
-
-# Use this file to prevent files from being overwritten by the generator.
-# The patterns follow closely to .gitignore or .dockerignore.
-
-# As an example, the C# client generator defines ApiClient.cs.
-# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
-#ApiClient.cs
-
-# You can match any string of characters against a directory, file or extension with a single asterisk (*):
-#foo/*/qux
-# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
-
-# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
-#foo/**/qux
-# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
-
-# You can also negate patterns with an exclamation (!).
-# For example, you can ignore all files in a docs folder with the file extension .md:
-#docs/*.md
-# Then explicitly reverse the ignore rule for a single file:
-#!docs/README.
-
-Dockerfile
-setup.py
-tagbase_server/__main__.py
-requirements.txt
-test-requirements.txt
-tox.ini
-.dockerignore
diff --git a/tagbase_server/.openapi-generator/FILES b/tagbase_server/.openapi-generator/FILES
deleted file mode 100644
index 71533959..00000000
--- a/tagbase_server/.openapi-generator/FILES
+++ /dev/null
@@ -1,24 +0,0 @@
-.gitignore
-.travis.yml
-README.md
-git_push.sh
-tagbase_server/__init__.py
-tagbase_server/controllers/__init__.py
-tagbase_server/controllers/ingest_controller.py
-tagbase_server/controllers/security_controller_.py
-tagbase_server/controllers/tags_controller.py
-tagbase_server/encoder.py
-tagbase_server/models/__init__.py
-tagbase_server/models/base_model_.py
-tagbase_server/models/error.py
-tagbase_server/models/error_container.py
-tagbase_server/models/ingest200.py
-tagbase_server/models/tag.py
-tagbase_server/models/tag200.py
-tagbase_server/models/tag_put200.py
-tagbase_server/models/tag_submission.py
-tagbase_server/models/tags200.py
-tagbase_server/openapi/openapi.yaml
-tagbase_server/test/__init__.py
-tagbase_server/typing_utils.py
-tagbase_server/util.py
diff --git a/tagbase_server/.openapi-generator/VERSION b/tagbase_server/.openapi-generator/VERSION
deleted file mode 100644
index 4be2c727..00000000
--- a/tagbase_server/.openapi-generator/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-6.5.0
\ No newline at end of file
diff --git a/tagbase_server/README.md b/tagbase_server/README.md
index 7cbcaa68..c6c05e7a 100644
--- a/tagbase_server/README.md
+++ b/tagbase_server/README.md
@@ -1,37 +1,41 @@
# OpenAPI generated server
## Overview
+
This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the
-[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This
+[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This
is an example of building a OpenAPI-enabled Flask server.
This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask.
## Requirements
+
Python 3.10–3.14
## Usage
+
To run the server, please execute the following from the root directory:
-```
+```text
pip3 install -r requirements.txt
python3 -m tagbase_server
```
and open your browser to here:
-```
+```text
http://localhost:8080/tagbase/api/v0.14.0/ui/
```
Your OpenAPI definition lives here:
-```
+```text
http://localhost:8080/tagbase/api/v0.14.0/openapi.json
```
To launch the integration tests, use tox:
-```
+
+```text
sudo pip install tox
tox
```
@@ -46,4 +50,4 @@ docker build -t tagbase_server .
# starting up a container
docker run -p 8080:8080 tagbase_server
-```
\ No newline at end of file
+```
diff --git a/tagbase_server/git_push.sh b/tagbase_server/git_push.sh
deleted file mode 100644
index f53a75d4..00000000
--- a/tagbase_server/git_push.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
-#
-# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
-
-git_user_id=$1
-git_repo_id=$2
-release_note=$3
-git_host=$4
-
-if [ "$git_host" = "" ]; then
- git_host="github.com"
- echo "[INFO] No command line input provided. Set \$git_host to $git_host"
-fi
-
-if [ "$git_user_id" = "" ]; then
- git_user_id="GIT_USER_ID"
- echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
-fi
-
-if [ "$git_repo_id" = "" ]; then
- git_repo_id="GIT_REPO_ID"
- echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
-fi
-
-if [ "$release_note" = "" ]; then
- release_note="Minor update"
- echo "[INFO] No command line input provided. Set \$release_note to $release_note"
-fi
-
-# Initialize the local directory as a Git repository
-git init
-
-# Adds the files in the local repository and stages them for commit.
-git add .
-
-# Commits the tracked changes and prepares them to be pushed to a remote repository.
-git commit -m "$release_note"
-
-# Sets the new remote
-git_remote=$(git remote)
-if [ "$git_remote" = "" ]; then # git remote not defined
-
- if [ "$GIT_TOKEN" = "" ]; then
- echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
- git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
- else
- git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
- fi
-
-fi
-
-git pull origin master
-
-# Pushes (Forces) the changes in the local repository up to the remote repository
-echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
-git push origin master 2>&1 | grep -v 'To https'
diff --git a/tagbase_server/tagbase_server/controllers/ingest_controller.py b/tagbase_server/tagbase_server/controllers/ingest_controller.py
index f29e5e61..4d0e8fb4 100644
--- a/tagbase_server/tagbase_server/controllers/ingest_controller.py
+++ b/tagbase_server/tagbase_server/controllers/ingest_controller.py
@@ -1,8 +1,9 @@
import logging
-from multiprocessing import cpu_count
import os
import time
from functools import partial
+from multiprocessing import cpu_count
+
import parmap
from tqdm import tqdm as std_tqdm
from tqdm.contrib.slack import tqdm as slack_tqdm
@@ -13,8 +14,8 @@
from tagbase_server.utils.io_utils import (
process_get_input_data,
process_post_input_data,
+ unpack_compressed_binary,
)
-from tagbase_server.utils.io_utils import unpack_compressed_binary
from tagbase_server.utils.processing_utils import process_etuff_file
logger = logging.getLogger(__name__)
@@ -26,8 +27,7 @@ def _resolve_ingest_file_type(type):
ingest_file_type = type if type is not None else SUPPORTED_INGEST_FILE_TYPE
if ingest_file_type != SUPPORTED_INGEST_FILE_TYPE:
raise TagbaseClientError(
- f"Unsupported ingest file type '{ingest_file_type}'; "
- f"only '{SUPPORTED_INGEST_FILE_TYPE}' is supported."
+ f"Unsupported ingest file type '{ingest_file_type}'; only '{SUPPORTED_INGEST_FILE_TYPE}' is supported."
)
return ingest_file_type
diff --git a/tagbase_server/tagbase_server/controllers/security_controller_.py b/tagbase_server/tagbase_server/controllers/security_controller_.py
deleted file mode 100644
index 7d686cba..00000000
--- a/tagbase_server/tagbase_server/controllers/security_controller_.py
+++ /dev/null
@@ -1 +0,0 @@
-from typing import List
diff --git a/tagbase_server/tagbase_server/controllers/tags_controller.py b/tagbase_server/tagbase_server/controllers/tags_controller.py
index 54dcd72f..36340d9e 100644
--- a/tagbase_server/tagbase_server/controllers/tags_controller.py
+++ b/tagbase_server/tagbase_server/controllers/tags_controller.py
@@ -1,10 +1,9 @@
-from tagbase_server.utils.db_utils import connect
+import logging
from tagbase_server.models.tag200 import Tag200 # noqa: E501
from tagbase_server.models.tag_put200 import TagPut200 # noqa: E501
from tagbase_server.problem import as_json
-
-import logging
+from tagbase_server.utils.db_utils import connect
logger = logging.getLogger(__name__)
diff --git a/tagbase_server/tagbase_server/models/__init__.py b/tagbase_server/tagbase_server/models/__init__.py
index e2fa364b..65a21d02 100644
--- a/tagbase_server/tagbase_server/models/__init__.py
+++ b/tagbase_server/tagbase_server/models/__init__.py
@@ -3,9 +3,10 @@
# flake8: noqa
from __future__ import absolute_import
+from tagbase_server.models.ingest200 import Ingest200
+
# import models into model package
from tagbase_server.models.problem import Problem
-from tagbase_server.models.ingest200 import Ingest200
from tagbase_server.models.tag200 import Tag200
from tagbase_server.models.tag200_collection import Tag200Collection
from tagbase_server.models.tag200_summary import Tag200Summary
diff --git a/tagbase_server/tagbase_server/models/base_model_.py b/tagbase_server/tagbase_server/models/base_model_.py
index 8c0e9a20..a6b67fa9 100644
--- a/tagbase_server/tagbase_server/models/base_model_.py
+++ b/tagbase_server/tagbase_server/models/base_model_.py
@@ -1,5 +1,4 @@
import pprint
-
import typing
from tagbase_server import util
diff --git a/tagbase_server/tagbase_server/models/ingest200.py b/tagbase_server/tagbase_server/models/ingest200.py
index 3d220a72..369fb75f 100644
--- a/tagbase_server/tagbase_server/models/ingest200.py
+++ b/tagbase_server/tagbase_server/models/ingest200.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Ingest200(Model):
diff --git a/tagbase_server/tagbase_server/models/problem.py b/tagbase_server/tagbase_server/models/problem.py
index b99275fd..0e8e976c 100644
--- a/tagbase_server/tagbase_server/models/problem.py
+++ b/tagbase_server/tagbase_server/models/problem.py
@@ -2,8 +2,8 @@
from __future__ import absolute_import
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Problem(Model):
diff --git a/tagbase_server/tagbase_server/models/response200.py b/tagbase_server/tagbase_server/models/response200.py
index 75df9259..659b2188 100644
--- a/tagbase_server/tagbase_server/models/response200.py
+++ b/tagbase_server/tagbase_server/models/response200.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Response200(Model):
diff --git a/tagbase_server/tagbase_server/models/response500.py b/tagbase_server/tagbase_server/models/response500.py
index c2ff82c5..6c534b6d 100644
--- a/tagbase_server/tagbase_server/models/response500.py
+++ b/tagbase_server/tagbase_server/models/response500.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Response500(Model):
diff --git a/tagbase_server/tagbase_server/models/tag200.py b/tagbase_server/tagbase_server/models/tag200.py
index c334f93e..cdb00e61 100644
--- a/tagbase_server/tagbase_server/models/tag200.py
+++ b/tagbase_server/tagbase_server/models/tag200.py
@@ -1,14 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
-from tagbase_server.models.tag_submission import TagSubmission
from tagbase_server import util
-
+from tagbase_server.models.base_model_ import Model
from tagbase_server.models.tag_submission import TagSubmission # noqa: E501
diff --git a/tagbase_server/tagbase_server/models/tag200_collection.py b/tagbase_server/tagbase_server/models/tag200_collection.py
index 8a6c19c7..d44b5579 100644
--- a/tagbase_server/tagbase_server/models/tag200_collection.py
+++ b/tagbase_server/tagbase_server/models/tag200_collection.py
@@ -1,14 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
-from tagbase_server.models.tag200_summary import Tag200Summary
from tagbase_server import util
-
+from tagbase_server.models.base_model_ import Model
from tagbase_server.models.tag200_summary import Tag200Summary # noqa: E501
diff --git a/tagbase_server/tagbase_server/models/tag200_summary.py b/tagbase_server/tagbase_server/models/tag200_summary.py
index 695be935..09261d3e 100644
--- a/tagbase_server/tagbase_server/models/tag200_summary.py
+++ b/tagbase_server/tagbase_server/models/tag200_summary.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Tag200Summary(Model):
diff --git a/tagbase_server/tagbase_server/models/tag200_tag.py b/tagbase_server/tagbase_server/models/tag200_tag.py
index 8623ea5f..d281d46d 100644
--- a/tagbase_server/tagbase_server/models/tag200_tag.py
+++ b/tagbase_server/tagbase_server/models/tag200_tag.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Tag200Tag(Model):
diff --git a/tagbase_server/tagbase_server/models/tag200_tag_inner.py b/tagbase_server/tagbase_server/models/tag200_tag_inner.py
index a46eae95..1178d3e1 100644
--- a/tagbase_server/tagbase_server/models/tag200_tag_inner.py
+++ b/tagbase_server/tagbase_server/models/tag200_tag_inner.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class Tag200TagInner(Model):
diff --git a/tagbase_server/tagbase_server/models/tag_put200.py b/tagbase_server/tagbase_server/models/tag_put200.py
index 1f604cde..dfec61fe 100644
--- a/tagbase_server/tagbase_server/models/tag_put200.py
+++ b/tagbase_server/tagbase_server/models/tag_put200.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class TagPut200(Model):
diff --git a/tagbase_server/tagbase_server/models/tag_submission.py b/tagbase_server/tagbase_server/models/tag_submission.py
index 44aa07cc..d50b6b56 100644
--- a/tagbase_server/tagbase_server/models/tag_submission.py
+++ b/tagbase_server/tagbase_server/models/tag_submission.py
@@ -1,12 +1,12 @@
# coding: utf-8
from __future__ import absolute_import
-from datetime import date, datetime # noqa: F401
-from typing import List, Dict # noqa: F401
+from datetime import date, datetime # noqa: F401
+from typing import Dict, List # noqa: F401
-from tagbase_server.models.base_model_ import Model
from tagbase_server import util
+from tagbase_server.models.base_model_ import Model
class TagSubmission(Model):
diff --git a/tagbase_server/tagbase_server/openapi/openapi.yaml b/tagbase_server/tagbase_server/openapi/openapi.yaml
index 405b6374..45277469 100644
--- a/tagbase_server/tagbase_server/openapi/openapi.yaml
+++ b/tagbase_server/tagbase_server/openapi/openapi.yaml
@@ -15,59 +15,59 @@ info:
title: tagbase-server API
version: v0.14.0
servers:
-- description: Local development tagbase-server
- url: https://localhost/tagbase/api/v0.14.0
-- description: ICCAT Test tagbase-server
- url: https://162.13.162.49/tagbase/api/v0.14.0
+ - description: Local development tagbase-server
+ url: https://localhost/tagbase/api/v0.14.0
+ - description: ICCAT Test tagbase-server
+ url: https://162.13.162.49/tagbase/api/v0.14.0
tags:
-- description: Ingestion operations.
- name: ingest
-- description: Tag Operations.
- name: tags
+ - description: Ingestion operations.
+ name: ingest
+ - description: Tag Operations.
+ name: tags
paths:
/ingest:
get:
description: Get network accessible file and execute ingestion
operationId: ingest_get
parameters:
- - description: Location of a network accessible (file, ftp, http, https)
- file e.g. 'file:///usr/src/app/data/eTUFF-sailfish-117259.txt'.
- explode: true
- in: query
- name: file
- required: true
- schema:
- maxLength: 100
- minLength: 10
- pattern: ^(?!\s*$).+$
- type: string
- style: form
- - $ref: '#/components/parameters/notes'
- - $ref: '#/components/parameters/type'
- - $ref: '#/components/parameters/version'
+ - description: Location of a network accessible (file, ftp, http, https)
+ file e.g. 'file:///usr/src/app/data/eTUFF-sailfish-117259.txt'.
+ explode: true
+ in: query
+ name: file
+ required: true
+ schema:
+ maxLength: 100
+ minLength: 10
+ pattern: ^(?!\s*$).+$
+ type: string
+ style: form
+ - $ref: "#/components/parameters/notes"
+ - $ref: "#/components/parameters/type"
+ - $ref: "#/components/parameters/version"
responses:
"200":
content:
application/json:
schema:
- $ref: '#/components/schemas/Ingest200'
+ $ref: "#/components/schemas/Ingest200"
description: A success message confirming ingestion.
"400":
- $ref: '#/components/responses/BadRequest'
+ $ref: "#/components/responses/BadRequest"
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Get network accessible file and execute ingestion
tags:
- - ingest
+ - ingest
x-openapi-router-controller: tagbase_server.controllers.ingest_controller
post:
description: Post a local file and perform a ingest operation
operationId: ingest_post
parameters:
- - $ref: '#/components/parameters/filename'
- - $ref: '#/components/parameters/notes'
- - $ref: '#/components/parameters/type'
- - $ref: '#/components/parameters/version'
+ - $ref: "#/components/parameters/filename"
+ - $ref: "#/components/parameters/notes"
+ - $ref: "#/components/parameters/type"
+ - $ref: "#/components/parameters/version"
requestBody:
content:
application/octet-stream:
@@ -91,15 +91,15 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/Ingest200'
+ $ref: "#/components/schemas/Ingest200"
description: A success message confirming ingestion.
"400":
- $ref: '#/components/responses/BadRequest'
+ $ref: "#/components/responses/BadRequest"
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Post a local file and perform a ingest operation
tags:
- - ingest
+ - ingest
x-codegen-request-body-name: body
x-openapi-router-controller: tagbase_server.controllers.ingest_controller
/tags:
@@ -110,10 +110,10 @@ paths:
"204":
description: Successful delete operation.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Delete all tags
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
get:
description: Get information about all tags
@@ -123,17 +123,17 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/Tag200Collection'
+ $ref: "#/components/schemas/Tag200Collection"
description: A success message confirming ingestion.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Get information about all tags
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
/tags/{tag_id}:
parameters:
- - $ref: '#/components/parameters/tagId'
+ - $ref: "#/components/parameters/tagId"
delete:
description: Delete an individual tag
operationId: delete_tag
@@ -141,10 +141,10 @@ paths:
"204":
description: Successful delete operation.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Delete an individual tag
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
get:
description: Get information about an individual tag
@@ -154,18 +154,18 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/Tag200'
+ $ref: "#/components/schemas/Tag200"
description: A success message confirming ingestion.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Get information about an individual tag
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
/tags/{tag_id}/subs/{sub_id}:
parameters:
- - $ref: '#/components/parameters/subId'
- - $ref: '#/components/parameters/tagId'
+ - $ref: "#/components/parameters/subId"
+ - $ref: "#/components/parameters/tagId"
delete:
description: Delete a tag submission
operationId: delete_tag_sub
@@ -173,29 +173,29 @@ paths:
"204":
description: Successful delete operation.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Delete a tag submission
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
put:
description: Update a tag submission
operationId: replace_tag_sub
parameters:
- - $ref: '#/components/parameters/notes'
- - $ref: '#/components/parameters/version'
+ - $ref: "#/components/parameters/notes"
+ - $ref: "#/components/parameters/version"
responses:
"200":
content:
application/json:
schema:
- $ref: '#/components/schemas/TagPut200'
+ $ref: "#/components/schemas/TagPut200"
description: A success message confirming ingestion.
"500":
- $ref: '#/components/responses/InternalError'
+ $ref: "#/components/responses/InternalError"
summary: Update the 'notes' and/or 'version' associated with a tag submission
tags:
- - tags
+ - tags
x-openapi-router-controller: tagbase_server.controllers.tags_controller
components:
responses:
@@ -204,13 +204,13 @@ components:
content:
application/problem+json:
schema:
- $ref: '#/components/schemas/Problem'
+ $ref: "#/components/schemas/Problem"
InternalError:
description: Unexpected server error. Contact admin detailed in openapi.yaml.
content:
application/problem+json:
schema:
- $ref: '#/components/schemas/Problem'
+ $ref: "#/components/schemas/Problem"
parameters:
filename:
description: Free-form text field to explicitly define the name of the file to be persisted
@@ -357,8 +357,9 @@ components:
description: HTTP 200 success response
example:
code: "200"
- elapsed: '0:00:06.506691'
- message: Data file eTUFF-sailfish-117259.txt successfully ingested into Tagbase
+ elapsed: "0:00:06.506691"
+ message:
+ Data file eTUFF-sailfish-117259.txt successfully ingested into Tagbase
DB.
properties:
code:
@@ -376,7 +377,8 @@ components:
pattern: ^(?!\s*$).+$
message:
description: A string detailing specifics of an HTTP operation
- example: Data file eTUFF-sailfish-117259.txt successfully ingested into
+ example:
+ Data file eTUFF-sailfish-117259.txt successfully ingested into
Tagbase DB.
type: string
minLength: 1
@@ -404,45 +406,45 @@ components:
- tag
example:
tag_id: 3
- filename: 'eTUFF-sailfish-117259_2.txt'
+ filename: "eTUFF-sailfish-117259_2.txt"
tag:
- - dataset_id: 1
- date_time: '2022-04-01T04:58:21.319061+00:00'
- filename: 'eTUFF-sailfish-117259_2.txt'
- hash_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b977'
- metadata:
- person_owner: John Do
- owner_contect: john@do.net
- manufacturer: telemetry inc.
- model: new_gen
- attachment_method: anchor
- notes: 'Ingested by admin on 2022-06-01 for Sailfish tagging campaign.'
- submission_id: 5
- tag_id: 3
- version: '1'
- - dataset_id: 1
- date_time: '2022-06-01T05:39:46.896088+00:00'
- filename: 'eTUFF-sailfish-117259_2.txt'
- hash_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
- metadata:
- person_owner: Jane Do
- owner_contect: jane@do.net
- manufacturer: telemetry inc.
- model: newer_gen
- attachment_method: anchor
- notes: 'Ingested by admin on 2022-06-01 for version 2 of the Sailfish tagging campaign.'
- submission_id: 6
- tag_id: 3
- version: '2'
+ - dataset_id: 1
+ date_time: "2022-04-01T04:58:21.319061+00:00"
+ filename: "eTUFF-sailfish-117259_2.txt"
+ hash_sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b977"
+ metadata:
+ person_owner: John Do
+ owner_contect: john@do.net
+ manufacturer: telemetry inc.
+ model: new_gen
+ attachment_method: anchor
+ notes: "Ingested by admin on 2022-06-01 for Sailfish tagging campaign."
+ submission_id: 5
+ tag_id: 3
+ version: "1"
+ - dataset_id: 1
+ date_time: "2022-06-01T05:39:46.896088+00:00"
+ filename: "eTUFF-sailfish-117259_2.txt"
+ hash_sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ metadata:
+ person_owner: Jane Do
+ owner_contect: jane@do.net
+ manufacturer: telemetry inc.
+ model: newer_gen
+ attachment_method: anchor
+ notes: "Ingested by admin on 2022-06-01 for version 2 of the Sailfish tagging campaign."
+ submission_id: 6
+ tag_id: 3
+ version: "2"
properties:
tag_id:
- $ref: '#/components/schemas/TagId'
+ $ref: "#/components/schemas/TagId"
filename:
- $ref: '#/components/schemas/Filename'
+ $ref: "#/components/schemas/Filename"
tag:
description: List containing one or more submissions for a given tag
items:
- $ref: '#/components/schemas/TagSubmission'
+ $ref: "#/components/schemas/TagSubmission"
minItems: 1
maxItems: 100
type: array
@@ -477,9 +479,9 @@ components:
filename: eTUFF-sailfish-117259_2.txt
properties:
tag_id:
- $ref: '#/components/schemas/TagId'
+ $ref: "#/components/schemas/TagId"
filename:
- $ref: '#/components/schemas/Filename'
+ $ref: "#/components/schemas/Filename"
type: object
TagSubmission:
type: object
@@ -493,22 +495,22 @@ components:
maximum: 2147483647
date_time:
description: Local datetime stamp at the time of eTUFF tag data file ingestion
- example: '2022-04-01T04:58:21.319061+00:00'
+ example: "2022-04-01T04:58:21.319061+00:00"
format: date-time
type: string
minLength: 20
maxLength: 64
filename:
- $ref: '#/components/schemas/Filename'
+ $ref: "#/components/schemas/Filename"
hash_sha256:
description: SHA256 hash representing the contents of the submission eTUFF file
- example: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
+ example: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
type: string
minLength: 64
maxLength: 64
pattern: ^[0-9a-fA-F]{64}$
metadata:
- $ref: '#/components/schemas/Metadata'
+ $ref: "#/components/schemas/Metadata"
notes:
description: Free-form text field where details of submitted eTUFF file for ingest can be provided e.g. submitter name, etuff data contents (tag metadata and measurements + primary position data, or just secondary solutionpositional meta/data)
example: "Ingested by admin on XXXX-XX-XX to back-process campaign XYZ."
@@ -524,7 +526,7 @@ components:
minimum: 1
maximum: 2147483647
tag_id:
- $ref: '#/components/schemas/TagId'
+ $ref: "#/components/schemas/TagId"
version:
description: Version identifier for the eTUFF tag data file ingested
example: "1"
@@ -539,10 +541,10 @@ components:
example:
count: 2
tags:
- - tag_id: 1
- filename: eTUFF-sailfish-117259_2.txt
- - tag_id: 2
- filename: eTUFF-sailfish-117259.txt
+ - tag_id: 1
+ filename: eTUFF-sailfish-117259_2.txt
+ - tag_id: 2
+ filename: eTUFF-sailfish-117259.txt
properties:
count:
description: Total count of unique tags
@@ -555,7 +557,7 @@ components:
description: List of unique numeric Tag IDs and associated filename
type: array
items:
- $ref: '#/components/schemas/Tag200Summary'
+ $ref: "#/components/schemas/Tag200Summary"
minItems: 1
maxItems: 100000
title: Tag200Collection
diff --git a/tagbase_server/tagbase_server/problem.py b/tagbase_server/tagbase_server/problem.py
index 93e6adc5..8b058fc4 100644
--- a/tagbase_server/tagbase_server/problem.py
+++ b/tagbase_server/tagbase_server/problem.py
@@ -134,8 +134,7 @@ def _handle_unexpected(exc):
status=500,
title="Internal Server Error",
detail=(
- "An unexpected error occurred while processing the request. "
- f"Reference trace_id={trace_id}."
+ f"An unexpected error occurred while processing the request. Reference trace_id={trace_id}."
),
type_=TYPE_INTERNAL,
trace_id=trace_id,
diff --git a/tagbase_server/tagbase_server/test/integration/test_processing_utils_db.py b/tagbase_server/tagbase_server/test/integration/test_processing_utils_db.py
index 9e461201..3a178c82 100644
--- a/tagbase_server/tagbase_server/test/integration/test_processing_utils_db.py
+++ b/tagbase_server/tagbase_server/test/integration/test_processing_utils_db.py
@@ -137,8 +137,7 @@ def test_is_only_metadata_change_and_update(clean_db):
metadata = [(str(submission_id), "1", '"new-value"')]
# attribute_id 1 must exist from seed
cur.execute(
- "INSERT INTO metadata (submission_id, attribute_id, attribute_value, tag_id) "
- "VALUES (%s, %s, %s, %s)",
+ "INSERT INTO metadata (submission_id, attribute_id, attribute_value, tag_id) VALUES (%s, %s, %s, %s)",
(submission_id, 1, "old-value", tag_id),
)
pu.update_submission_metadata(
diff --git a/tagbase_server/tagbase_server/test/stack/test_fswatch_contract.py b/tagbase_server/tagbase_server/test/stack/test_fswatch_contract.py
index 07f57f72..ce970be4 100644
--- a/tagbase_server/tagbase_server/test/stack/test_fswatch_contract.py
+++ b/tagbase_server/tagbase_server/test/stack/test_fswatch_contract.py
@@ -274,8 +274,7 @@ def test_fswatch_ingests_multiple_files_dropped_quickly(mock_ingest_server):
else:
got = [(r["query"].get("filename") or [None])[0] for r in handler.requests]
pytest.fail(
- f"expected POSTs for {names}, got {got}. "
- f"post.sh output:\n{_read_log(log_path)}"
+ f"expected POSTs for {names}, got {got}. post.sh output:\n{_read_log(log_path)}"
)
by_name = {
(r["query"].get("filename") or [None])[0]: r["body"]
diff --git a/tagbase_server/tagbase_server/test/stack/test_fswatch_smoke.py b/tagbase_server/tagbase_server/test/stack/test_fswatch_smoke.py
index 875c940d..57a1eff8 100644
--- a/tagbase_server/tagbase_server/test/stack/test_fswatch_smoke.py
+++ b/tagbase_server/tagbase_server/test/stack/test_fswatch_smoke.py
@@ -43,8 +43,7 @@ def _stack_ready():
def require_full_stack():
if not _stack_ready():
pytest.skip(
- "full stack not ready; "
- "docker compose -f docker-compose.test.yml --profile stack up -d --build"
+ "full stack not ready; docker compose -f docker-compose.test.yml --profile stack up -d --build"
)
diff --git a/tagbase_server/tagbase_server/test/stack/test_nginx_auth.py b/tagbase_server/tagbase_server/test/stack/test_nginx_auth.py
index aed348fc..19b4a1a1 100644
--- a/tagbase_server/tagbase_server/test/stack/test_nginx_auth.py
+++ b/tagbase_server/tagbase_server/test/stack/test_nginx_auth.py
@@ -92,8 +92,7 @@ def test_grafana_health_with_basic_auth_does_not_rechallenge():
response = client.get(_base() + "/grafana/api/health", auth=_auth())
if response.status_code in (502, 503, 504):
pytest.skip("Grafana not reachable; start with --profile observability")
- assert response.status_code == 200, (
- f"expected 200 from Grafana health, got {response.status_code}: "
- f"{response.text[:200]}"
- )
+ assert (
+ response.status_code == 200
+ ), f"expected 200 from Grafana health, got {response.status_code}: {response.text[:200]}"
assert "invalid username or password" not in response.text.lower()
diff --git a/tagbase_server/tagbase_server/test/test_ingest.py b/tagbase_server/tagbase_server/test/test_ingest.py
index 16a21e9e..0eb86618 100644
--- a/tagbase_server/tagbase_server/test/test_ingest.py
+++ b/tagbase_server/tagbase_server/test/test_ingest.py
@@ -4,8 +4,9 @@
from unittest import mock
import psycopg2
-import tagbase_server.utils.processing_utils as pu
+
import tagbase_server.utils.io_utils as io_utils
+import tagbase_server.utils.processing_utils as pu
class TestIngest(unittest.TestCase):
@@ -83,9 +84,10 @@ def test_insert_metadata_continues_after_database_error(self):
None,
]
- with mock.patch.object(pu.logger, "error") as mock_error, mock.patch.object(
- pu, "record_db_error"
- ) as mock_record_db_error:
+ with (
+ mock.patch.object(pu.logger, "error") as mock_error,
+ mock.patch.object(pu, "record_db_error") as mock_record_db_error,
+ ):
pu.insert_metadata(
cur,
[
@@ -107,9 +109,10 @@ def test_update_metadata_continues_after_database_error(self):
None, # second metadata update
]
- with mock.patch.object(pu.logger, "error") as mock_error, mock.patch.object(
- pu, "record_db_error"
- ) as mock_record_db_error:
+ with (
+ mock.patch.object(pu.logger, "error") as mock_error,
+ mock.patch.object(pu, "record_db_error") as mock_record_db_error,
+ ):
pu.update_submission_metadata(
cur,
tag_id=10,
diff --git a/tagbase_server/tagbase_server/test/test_slack_and_schema.py b/tagbase_server/tagbase_server/test/test_slack_and_schema.py
index 5f5b88c2..dd90adb2 100644
--- a/tagbase_server/tagbase_server/test/test_slack_and_schema.py
+++ b/tagbase_server/tagbase_server/test/test_slack_and_schema.py
@@ -67,11 +67,12 @@ def test_connect_reraises_when_schema_missing(monkeypatch):
fake_conn.cursor.return_value.__exit__ = mock.Mock(return_value=False)
monkeypatch.setattr(db_utils.psycopg2, "connect", mock.Mock(return_value=fake_conn))
- monkeypatch.setattr(db_utils, "record_db_error", mock.Mock())
+ record_db_error = mock.Mock()
+ monkeypatch.setattr(db_utils, "record_db_error", record_db_error)
with pytest.raises(RuntimeError, match="submission"):
db_utils.connect()
- db_utils.record_db_error.assert_called_with("schema")
+ record_db_error.assert_called_with("schema")
def test_assert_schema_ready_raises_when_missing(monkeypatch):
diff --git a/tagbase_server/tagbase_server/test/test_sonar_cleanup.py b/tagbase_server/tagbase_server/test/test_sonar_cleanup.py
index c7f0fdb5..2cbc38d5 100644
--- a/tagbase_server/tagbase_server/test/test_sonar_cleanup.py
+++ b/tagbase_server/tagbase_server/test/test_sonar_cleanup.py
@@ -14,9 +14,8 @@
from tagbase_server.controllers.ingest_controller import _resolve_ingest_file_type
from tagbase_server.models.base_model_ import Model
from tagbase_server.models.ingest200 import Ingest200
-from tagbase_server.utils import io_utils
+from tagbase_server.utils import io_utils, slack_utils
from tagbase_server.utils import processing_utils as pu
-from tagbase_server.utils import slack_utils
def test_parse_cors_origins_empty_and_list():
diff --git a/tagbase_server/tagbase_server/test/test_telemetry.py b/tagbase_server/tagbase_server/test/test_telemetry.py
index 29d481fc..5996f616 100644
--- a/tagbase_server/tagbase_server/test/test_telemetry.py
+++ b/tagbase_server/tagbase_server/test/test_telemetry.py
@@ -75,18 +75,19 @@ def _stub_setup_dependencies(monkeypatch):
"Psycopg2Instrumentor",
mock.Mock(return_value=mock.Mock()),
)
- monkeypatch.setattr(telemetry, "_ensure_instruments", mock.Mock())
- return otel_handler
+ ensure_instruments = mock.Mock()
+ monkeypatch.setattr(telemetry, "_ensure_instruments", ensure_instruments)
+ return otel_handler, ensure_instruments
def test_setup_telemetry_initializes_and_is_idempotent(monkeypatch):
monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
monkeypatch.setenv("OTEL_SERVICE_NAME", " ") # blank → default SERVICE_NAME
- _stub_setup_dependencies(monkeypatch)
+ _, ensure_instruments = _stub_setup_dependencies(monkeypatch)
assert telemetry.setup_telemetry() is True
assert telemetry.setup_telemetry() is True # already initialized
- telemetry._ensure_instruments.assert_called_once()
+ ensure_instruments.assert_called_once()
def test_setup_telemetry_instruments_flask_app(monkeypatch):
diff --git a/tagbase_server/tagbase_server/util.py b/tagbase_server/tagbase_server/util.py
index 533429eb..5a5d76b6 100644
--- a/tagbase_server/tagbase_server/util.py
+++ b/tagbase_server/tagbase_server/util.py
@@ -1,6 +1,6 @@
import datetime
-
import typing
+
from tagbase_server import typing_utils
diff --git a/tagbase_server/tagbase_server/utils/db_utils.py b/tagbase_server/tagbase_server/utils/db_utils.py
index db1040cb..d3cabe97 100644
--- a/tagbase_server/tagbase_server/utils/db_utils.py
+++ b/tagbase_server/tagbase_server/utils/db_utils.py
@@ -1,5 +1,6 @@
import logging
import os
+
import psycopg2
from tagbase_server.models.response500 import Response500 # noqa: E501
diff --git a/tagbase_server/tagbase_server/utils/io_utils.py b/tagbase_server/tagbase_server/utils/io_utils.py
index b2eba09f..fc752fc6 100644
--- a/tagbase_server/tagbase_server/utils/io_utils.py
+++ b/tagbase_server/tagbase_server/utils/io_utils.py
@@ -4,9 +4,10 @@
import re
import tempfile
from time import time
-from pyunpack import Archive
from urllib.request import urlopen
+from pyunpack import Archive
+
logger = logging.getLogger(__name__)
TEMP_DIR = tempfile.gettempdir()
diff --git a/tagbase_server/tagbase_server/utils/processing_utils.py b/tagbase_server/tagbase_server/utils/processing_utils.py
index f63c9d12..c8e1a03a 100644
--- a/tagbase_server/tagbase_server/utils/processing_utils.py
+++ b/tagbase_server/tagbase_server/utils/processing_utils.py
@@ -1,8 +1,8 @@
import logging
+import time
from datetime import datetime as dt
from datetime import timezone
from io import StringIO
-import time
import pandas as pd
import psycopg2.extras
@@ -41,9 +41,8 @@ def process_global_attributes_metadata(
attributes_names = ", ".join(
["'{}'".format(attrib_name) for attrib_name in attributes_map.keys()]
)
- attribute_ids_query = (
- "SELECT attribute_id, attribute_name FROM metadata_types "
- "WHERE attribute_name IN ({})".format(attributes_names)
+ attribute_ids_query = "SELECT attribute_id, attribute_name FROM metadata_types WHERE attribute_name IN ({})".format(
+ attributes_names
)
logger.debug("Query=%s", attribute_ids_query)
cur.execute(attribute_ids_query)
@@ -91,7 +90,7 @@ def get_tag_id(cur, dataset_id):
def get_dataset_id(cur, instrument_name, serial_number, ptt, platform):
"""
- Retreive or create a dataset entry for a submission. If a dataset entry exists then grab the existing
+ Retrieve or create a dataset entry for a submission. If a dataset entry exists then grab the existing
id, if not, create a new one.
:param cur: A database cursor
@@ -131,9 +130,9 @@ def get_dataset_id(cur, instrument_name, serial_number, ptt, platform):
def get_submission_id(cur, tag_id, dataset_id, data_sha256):
cur.execute(
- "SELECT submission_id FROM submission"
- " WHERE tag_id = '{}' AND dataset_id = '{}'"
- " AND data_sha256 = '{}'".format(tag_id, dataset_id, data_sha256)
+ "SELECT submission_id FROM submission WHERE tag_id = '{}' AND dataset_id = '{}' AND data_sha256 = '{}'".format(
+ tag_id, dataset_id, data_sha256
+ )
)
db_results = cur.fetchone()
if not db_results:
@@ -435,10 +434,7 @@ def _build_proc_observations(
)
else:
stripped_line = line.strip("\n")
- msg = (
- f"*{submission_filename}* _line:{line_number}_ - "
- f"No datetime... skipping line: {stripped_line}"
- )
+ msg = f"*{submission_filename}* _line:{line_number}_ - No datetime... skipping line: {stripped_line}"
post_msg(msg)
continue
proc_obs.append(
diff --git a/tagbase_server/test-requirements.txt b/tagbase_server/test-requirements.txt
index db116842..5753bb9e 100644
--- a/tagbase_server/test-requirements.txt
+++ b/tagbase_server/test-requirements.txt
@@ -2,4 +2,3 @@ pytest~=9.1.1
pytest-cov>=2.8.1
pytest-randomly==4.1.0
httpx>=0.27.0
-diff-cover==10.4.1
diff --git a/trivy.yaml b/trivy.yaml
new file mode 100644
index 00000000..cbfcaf9f
--- /dev/null
+++ b/trivy.yaml
@@ -0,0 +1,12 @@
+# Trivy config for Super Linter — skip intentional local TLS fixtures.
+scan:
+ skip-files:
+ - "**/ssl/key.pem"
+ - "**/ssl/cert.pem"
+ - "**/fixtures/nginx/key.pem"
+ - "**/fixtures/nginx/cert.pem"
+ - "**/staging_data/**"
+ - "**/staging_data.bk/**"
+severity:
+ exit-code: 1
+ ignore-unfixed: true