emso-metadata-harmonizer 1.1.0.dev0__tar.gz → 1.1.0.dev1__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.
- {emso_metadata_harmonizer-1.1.0.dev0/src/emso_metadata_harmonizer.egg-info → emso_metadata_harmonizer-1.1.0.dev1}/PKG-INFO +1 -1
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/__init__.py +1 -1
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/waterframe.py +171 -116
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1/src/emso_metadata_harmonizer.egg-info}/PKG-INFO +1 -1
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/LICENSE +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/README.md +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/pypi.md +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/pyproject.toml +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/setup.cfg +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/dataset_generator.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/erddap/__init__.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/erddap/datasets_xml.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/erddap/erddap.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/erddap_config.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/__init__.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/constants.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/dataset.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/emso.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/metadata_templates.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/resource_manager.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/tests.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/utils.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/vocabularies.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/metadata/xmlutils.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer/report.py +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer.egg-info/SOURCES.txt +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer.egg-info/dependency_links.txt +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer.egg-info/requires.txt +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/src/emso_metadata_harmonizer.egg-info/top_level.txt +0 -0
- {emso_metadata_harmonizer-1.1.0.dev0 → emso_metadata_harmonizer-1.1.0.dev1}/tests/test_metadata_harmonizer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emso-metadata-harmonizer
|
|
3
|
-
Version: 1.1.0.
|
|
3
|
+
Version: 1.1.0.dev1
|
|
4
4
|
Summary: Toolbox for creating/assessing EMSO-compliant NetCDF datasets and integrate them into ERDDAP services
|
|
5
5
|
Author-email: Enoc Martínez <enoc.martinez@upc.edu>
|
|
6
6
|
Maintainer-email: Enoc Martínez <enoc.martinez@upc.edu>
|
|
@@ -12,7 +12,7 @@ created: 6/6/24
|
|
|
12
12
|
import datetime
|
|
13
13
|
import logging
|
|
14
14
|
import os.path
|
|
15
|
-
from typing import List, Tuple
|
|
15
|
+
from typing import List, Tuple, Dict, Any
|
|
16
16
|
from .utils import assert_type
|
|
17
17
|
import numpy as np
|
|
18
18
|
import pandas as pd
|
|
@@ -20,13 +20,13 @@ import netCDF4 as nc
|
|
|
20
20
|
import xarray as xr
|
|
21
21
|
import rich
|
|
22
22
|
import warnings
|
|
23
|
+
import requests
|
|
24
|
+
|
|
23
25
|
from .metadata_templates import time_valid_names, depth_valid_names, latitude_valid_names, longitude_valid_names, sensor_id_valid_names, platform_id_valid_names, is_coordinate
|
|
24
26
|
from . import init_emso_metadata
|
|
25
27
|
from .constants import iso_time_format
|
|
26
28
|
from .metadata_templates import dimension_metadata, quality_control_metadata
|
|
27
29
|
from .utils import LoggerSuperclass, CYN
|
|
28
|
-
import requests
|
|
29
|
-
|
|
30
30
|
from .vocabularies import Keyword
|
|
31
31
|
|
|
32
32
|
try:
|
|
@@ -1259,127 +1259,182 @@ def collect_sensor_metadata(metadata:dict, df: pd.DataFrame) -> dict:
|
|
|
1259
1259
|
def collect_platform_metadata(metadata:dict, df: pd.DataFrame) -> dict:
|
|
1260
1260
|
return __collect_metadata(metadata, df, "platform")
|
|
1261
1261
|
|
|
1262
|
+
def operational_tests(wf: WaterFrame, quiet=False, verbose=False) -> Tuple[bool, dict]:
|
|
1263
|
+
return OperationalTester(quiet=quiet, verbose=verbose).validate(wf)
|
|
1262
1264
|
|
|
1263
1265
|
|
|
1264
|
-
|
|
1266
|
+
class OperationalTester:
|
|
1265
1267
|
"""
|
|
1266
|
-
Ensures that
|
|
1267
|
-
|
|
1268
|
-
2. Ensure that we have coordinates with the following names: time, depth, latitude, longitude, sensor_id, platform_id
|
|
1269
|
-
3. sensor_id and platform_id values are resolvable identifiers of sensors and platforms metadata variables
|
|
1268
|
+
Ensures that a WaterFrame is operationally sound by checking variable types,
|
|
1269
|
+
mandatory coordinates, P01 parameter codes, standard names, and sensor/platform IDs.
|
|
1270
1270
|
"""
|
|
1271
|
-
errors = []
|
|
1272
|
-
warnings = []
|
|
1273
|
-
infos = []
|
|
1274
|
-
__valid_coordinates = ["time", "depth", "latitude", "longitude", "sensor_id", "platform_id", "precise_latitude", "precise_longitude", "time_end"]
|
|
1275
|
-
__mandatory_coords = ["time", "depth", "latitude", "longitude", "sensor_id", "platform_id"]
|
|
1276
|
-
__valid_variable_types = ["environmental", "biological", "technical", "coordinate", "quality_control", "sensor", "platform"]
|
|
1277
|
-
|
|
1278
|
-
for varname, meta in wf.vocabulary.items():
|
|
1279
|
-
if "variable_type" not in meta.keys():
|
|
1280
|
-
errors.append(f"variable '{varname}' does not have the mandatory variable_type attribute")
|
|
1281
|
-
elif meta["variable_type"] not in __valid_variable_types:
|
|
1282
|
-
errors.append(f"variable '{varname}' does not have a valid variable_type attribute: '{meta['variable_type']}'")
|
|
1283
|
-
elif meta["variable_type"] == "coordinate" and varname not in __valid_coordinates:
|
|
1284
|
-
errors.append(f"not a valid coordinate name: '{varname}'")
|
|
1285
|
-
|
|
1286
|
-
# check mandatory coordinates
|
|
1287
|
-
for man in __mandatory_coords:
|
|
1288
|
-
if man not in wf.data.keys():
|
|
1289
|
-
errors.append(f"Mandatory coordinate '{man}' not found in sensors metadata")
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
# check unique P01 codes across variables
|
|
1293
|
-
p01 = {}
|
|
1294
|
-
for varname, meta in wf.vocabulary.items():
|
|
1295
|
-
if "sdn_parameter_uri" in meta.keys():
|
|
1296
|
-
code = meta["sdn_parameter_uri"]
|
|
1297
|
-
|
|
1298
|
-
if code not in p01.keys():
|
|
1299
|
-
p01[code] = []
|
|
1300
|
-
|
|
1301
|
-
if code: # avoid nulls
|
|
1302
|
-
p01[code].append(varname)
|
|
1303
|
-
|
|
1304
|
-
for key, values in p01.items():
|
|
1305
|
-
if len(values) > 1:
|
|
1306
|
-
errors.append(f"P01 code shared across variables {values} ({key})")
|
|
1307
|
-
|
|
1308
|
-
# check unique standard_names across variables
|
|
1309
|
-
std_names = {}
|
|
1310
|
-
for varname, meta in wf.vocabulary.items():
|
|
1311
|
-
if "standard_name" in meta.keys():
|
|
1312
|
-
code = meta["standard_name"]
|
|
1313
|
-
|
|
1314
|
-
if code not in std_names.keys():
|
|
1315
|
-
std_names[code] = []
|
|
1316
|
-
|
|
1317
|
-
if code: # avoid nulls
|
|
1318
|
-
std_names[code].append(varname)
|
|
1319
|
-
|
|
1320
|
-
for key, values in std_names.items():
|
|
1321
|
-
if len(values) > 1:
|
|
1322
|
-
errors.append(f"standard_name attributes shared across variables {values} ({key})")
|
|
1323
|
-
|
|
1324
|
-
# Check that sensor_id values are resolvable
|
|
1325
|
-
|
|
1326
|
-
if "sensor_id" in wf.data.keys():
|
|
1327
|
-
sensor_ids = wf.data["sensor_id"].unique().astype(str)
|
|
1328
|
-
for sensor_id in sensor_ids:
|
|
1329
|
-
if sensor_id not in wf.sensors.keys():
|
|
1330
|
-
errors.append(f"sensor_id not found in sensors metadata: '{sensor_id}'")
|
|
1331
|
-
|
|
1332
|
-
# Check that sensor_id values are resolvable
|
|
1333
|
-
if "platform_id" in wf.data.keys():
|
|
1334
|
-
platform_ids = wf.data["platform_id"].unique().astype(str)
|
|
1335
|
-
for platform_id in platform_ids:
|
|
1336
|
-
if platform_id not in wf.platforms.keys():
|
|
1337
|
-
errors.append(f"not a valid platform_id: '{platform_id}'")
|
|
1338
|
-
|
|
1339
|
-
if len(errors) > 0:
|
|
1340
|
-
print(wf)
|
|
1341
|
-
|
|
1342
|
-
success = True
|
|
1343
|
-
if len(errors) > 0:
|
|
1344
|
-
success = False
|
|
1345
|
-
|
|
1346
|
-
if not quiet:
|
|
1347
|
-
rich.print("[cyan]=========== Running Operational tests ===========")
|
|
1348
|
-
rich.print(f"ERRORS: {len(errors)}")
|
|
1349
|
-
for e in errors:
|
|
1350
|
-
rich.print(f"[red] ERROR: {e}[/red]")
|
|
1351
|
-
|
|
1352
|
-
rich.print(f"WARNINGS: {len(warnings)}")
|
|
1353
|
-
for w in warnings:
|
|
1354
|
-
rich.print(f"[yellow]{w}")
|
|
1355
|
-
|
|
1356
|
-
rich.print(f"INFO: {len(warnings)}")
|
|
1357
|
-
for i in infos:
|
|
1358
|
-
rich.print(f"[cyan]{i}")
|
|
1359
|
-
|
|
1360
|
-
if success:
|
|
1361
|
-
rich.print(f"✅ the NetCDF file is operationally sound!")
|
|
1362
|
-
else:
|
|
1363
|
-
rich.print(f"❌ the NetCDF file is not operationally valid")
|
|
1364
1271
|
|
|
1365
|
-
|
|
1272
|
+
VALID_COORDINATES = [
|
|
1273
|
+
"time", "depth", "latitude", "longitude", "sensor_id",
|
|
1274
|
+
"platform_id", "precise_latitude", "precise_longitude", "time_end"
|
|
1275
|
+
]
|
|
1276
|
+
MANDATORY_COORDS = [
|
|
1277
|
+
"time", "depth", "latitude", "longitude", "sensor_id", "platform_id"
|
|
1278
|
+
]
|
|
1279
|
+
VALID_VARIABLE_TYPES = [
|
|
1280
|
+
"environmental", "biological", "technical", "coordinate",
|
|
1281
|
+
"quality_control", "sensor", "platform"
|
|
1282
|
+
]
|
|
1283
|
+
|
|
1284
|
+
def __init__(self, quiet: bool = False, verbose: bool = False):
|
|
1285
|
+
"""
|
|
1286
|
+
:param quiet: if True, nothing is written to stdout; outcome is reported via logger instead
|
|
1287
|
+
:param verbose: if True, extra information is written to stdout (has no effect if quiet is set)
|
|
1288
|
+
"""
|
|
1289
|
+
self.quiet = quiet
|
|
1290
|
+
self.verbose = verbose
|
|
1291
|
+
self.errors = []
|
|
1292
|
+
self.warnings = []
|
|
1293
|
+
self.infos = []
|
|
1366
1294
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1295
|
+
# ------------------------------------------------------------------ #
|
|
1296
|
+
# output helpers #
|
|
1297
|
+
# ------------------------------------------------------------------ #
|
|
1298
|
+
def __print(self, message: str = "", end: str = "\n"):
|
|
1299
|
+
"""Prints a message unless the validator is in quiet mode"""
|
|
1300
|
+
if not self.quiet:
|
|
1301
|
+
rich.print(message, end=end)
|
|
1302
|
+
|
|
1303
|
+
def __print_verbose(self, message: str = "", end: str = "\n"):
|
|
1304
|
+
"""Prints a message only in verbose mode"""
|
|
1305
|
+
if self.verbose:
|
|
1306
|
+
self.__print(message, end=end)
|
|
1307
|
+
|
|
1308
|
+
def __error(self, error: str, message: str = "", end: str = "\n"):
|
|
1309
|
+
"""Registers an error and prints it."""
|
|
1310
|
+
self.errors.append(error)
|
|
1311
|
+
self.__print(message if message else f"[red] ERROR: {error}[/red]", end=end)
|
|
1312
|
+
|
|
1313
|
+
def __warning(self, warning: str, message: str = "", end: str = "\n"):
|
|
1314
|
+
"""Registers a warning and prints it."""
|
|
1315
|
+
self.warnings.append(warning)
|
|
1316
|
+
self.__print(message if message else f"[yellow] WARNING: {warning}[/yellow]", end=end)
|
|
1317
|
+
|
|
1318
|
+
def __info(self, info: str, message: str = "", end: str = "\n"):
|
|
1319
|
+
"""Registers an info message and prints it."""
|
|
1320
|
+
self.infos.append(info)
|
|
1321
|
+
self.__print(message if message else f"[cyan] INFO: {info}[/cyan]", end=end)
|
|
1322
|
+
|
|
1323
|
+
# ------------------------------------------------------------------ #
|
|
1324
|
+
# public API #
|
|
1325
|
+
# ------------------------------------------------------------------ #
|
|
1326
|
+
def validate(self, wf: WaterFrame) -> Tuple[bool, Dict[str, Any]]:
|
|
1327
|
+
"""
|
|
1328
|
+
Validates the operational soundness of a WaterFrame. Previous results are discarded.
|
|
1329
|
+
|
|
1330
|
+
:param wf: WaterFrame to be checked
|
|
1331
|
+
:returns: Tuple (success: bool, report: dict)
|
|
1332
|
+
"""
|
|
1333
|
+
self.errors = []
|
|
1334
|
+
self.warnings = []
|
|
1335
|
+
self.infos = []
|
|
1336
|
+
|
|
1337
|
+
self.__validate(wf)
|
|
1338
|
+
|
|
1339
|
+
success = len(self.errors) == 0
|
|
1340
|
+
|
|
1341
|
+
if self.quiet:
|
|
1342
|
+
log = logging.getLogger()
|
|
1343
|
+
if success:
|
|
1344
|
+
log.info("Operational tests: ✅ the NetCDF file is operationally sound!")
|
|
1345
|
+
else:
|
|
1346
|
+
log.info("Operational tests: ❌ the NetCDF file is not operationally valid")
|
|
1347
|
+
|
|
1348
|
+
report = {
|
|
1349
|
+
"valid": success,
|
|
1350
|
+
"report": {
|
|
1351
|
+
"errors": self.errors,
|
|
1352
|
+
"warnings": self.warnings,
|
|
1353
|
+
"infos": self.infos,
|
|
1354
|
+
},
|
|
1379
1355
|
}
|
|
1380
|
-
}
|
|
1381
|
-
return success, report
|
|
1382
1356
|
|
|
1357
|
+
return success, report
|
|
1358
|
+
|
|
1359
|
+
# ------------------------------------------------------------------ #
|
|
1360
|
+
# internal checks #
|
|
1361
|
+
# ------------------------------------------------------------------ #
|
|
1362
|
+
def __validate(self, wf: WaterFrame):
|
|
1363
|
+
"""Runs all operational tests on the given WaterFrame."""
|
|
1364
|
+
self.__print("[cyan]=========== Running Operational tests ===========")
|
|
1365
|
+
|
|
1366
|
+
# 1. Check variable_type attributes
|
|
1367
|
+
for varname, meta in wf.vocabulary.items():
|
|
1368
|
+
if "variable_type" not in meta:
|
|
1369
|
+
self.__error(f"variable '{varname}' does not have the mandatory variable_type attribute")
|
|
1370
|
+
elif meta["variable_type"] not in self.VALID_VARIABLE_TYPES:
|
|
1371
|
+
self.__error(
|
|
1372
|
+
f"variable '{varname}' does not have a valid variable_type attribute: '{meta['variable_type']}'"
|
|
1373
|
+
)
|
|
1374
|
+
elif meta["variable_type"] == "coordinate" and varname not in self.VALID_COORDINATES:
|
|
1375
|
+
self.__error(f"not a valid coordinate name: '{varname}'")
|
|
1376
|
+
|
|
1377
|
+
# 2. Check mandatory coordinates
|
|
1378
|
+
for man in self.MANDATORY_COORDS:
|
|
1379
|
+
if man not in wf.data.keys():
|
|
1380
|
+
self.__error(f"Mandatory coordinate '{man}' not found in sensors metadata")
|
|
1381
|
+
|
|
1382
|
+
# 3. Check unique P01 codes across variables
|
|
1383
|
+
p01 = {}
|
|
1384
|
+
for varname, meta in wf.vocabulary.items():
|
|
1385
|
+
if "sdn_parameter_uri" in meta:
|
|
1386
|
+
code = meta["sdn_parameter_uri"]
|
|
1387
|
+
if code not in p01:
|
|
1388
|
+
p01[code] = []
|
|
1389
|
+
if code: # avoid nulls
|
|
1390
|
+
p01[code].append(varname)
|
|
1391
|
+
|
|
1392
|
+
for key, values in p01.items():
|
|
1393
|
+
if len(values) > 1:
|
|
1394
|
+
self.__error(f"P01 code shared across variables {values} ({key})")
|
|
1395
|
+
|
|
1396
|
+
# 4. Check unique standard_names across variables
|
|
1397
|
+
std_names = {}
|
|
1398
|
+
for varname, meta in wf.vocabulary.items():
|
|
1399
|
+
if "standard_name" in meta:
|
|
1400
|
+
code = meta["standard_name"]
|
|
1401
|
+
if code not in std_names:
|
|
1402
|
+
std_names[code] = []
|
|
1403
|
+
if code: # avoid nulls
|
|
1404
|
+
std_names[code].append(varname)
|
|
1405
|
+
|
|
1406
|
+
for key, values in std_names.items():
|
|
1407
|
+
if len(values) > 1:
|
|
1408
|
+
self.__error(f"standard_name attributes shared across variables {values} ({key})")
|
|
1409
|
+
|
|
1410
|
+
# 5. Check resolvable sensor_id values
|
|
1411
|
+
if "sensor_id" in wf.data.keys():
|
|
1412
|
+
sensor_ids = wf.data["sensor_id"].unique().astype(str)
|
|
1413
|
+
for sensor_id in sensor_ids:
|
|
1414
|
+
if sensor_id not in wf.sensors.keys():
|
|
1415
|
+
self.__error(f"sensor_id not found in sensors metadata: '{sensor_id}'")
|
|
1416
|
+
|
|
1417
|
+
# 6. Check resolvable platform_id values
|
|
1418
|
+
if "platform_id" in wf.data.keys():
|
|
1419
|
+
platform_ids = wf.data["platform_id"].unique().astype(str)
|
|
1420
|
+
for platform_id in platform_ids:
|
|
1421
|
+
if platform_id not in wf.platforms.keys():
|
|
1422
|
+
self.__error(f"not a valid platform_id: '{platform_id}'")
|
|
1423
|
+
|
|
1424
|
+
success = len(self.errors) == 0
|
|
1425
|
+
|
|
1426
|
+
# Print summary section if not quiet
|
|
1427
|
+
if not self.quiet:
|
|
1428
|
+
self.__print_verbose(f"ERRORS: {len(self.errors)}")
|
|
1429
|
+
self.__print_verbose(f"WARNINGS: {len(self.warnings)}")
|
|
1430
|
+
self.__print_verbose(f"INFO: {len(self.infos)}")
|
|
1431
|
+
|
|
1432
|
+
if success:
|
|
1433
|
+
self.__print("✅ the NetCDF file is operationally sound!")
|
|
1434
|
+
else:
|
|
1435
|
+
self.__print("❌ the NetCDF file is not operationally valid")
|
|
1436
|
+
|
|
1437
|
+
self.__print("[cyan]=================================================\n")
|
|
1383
1438
|
|
|
1384
1439
|
class KeywordTester:
|
|
1385
1440
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emso-metadata-harmonizer
|
|
3
|
-
Version: 1.1.0.
|
|
3
|
+
Version: 1.1.0.dev1
|
|
4
4
|
Summary: Toolbox for creating/assessing EMSO-compliant NetCDF datasets and integrate them into ERDDAP services
|
|
5
5
|
Author-email: Enoc Martínez <enoc.martinez@upc.edu>
|
|
6
6
|
Maintainer-email: Enoc Martínez <enoc.martinez@upc.edu>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|