dkist-header-validator 4.1.0__tar.gz → 5.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {dkist-header-validator-4.1.0/dkist_header_validator.egg-info → dkist-header-validator-5.0.0}/PKG-INFO +1 -1
  2. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/bitbucket-pipelines.yml +5 -4
  3. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/base_validator.py +56 -8
  4. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/spec_validators.py +4 -2
  5. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/conftest.py +37 -1
  6. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_spec214_validation-.py +29 -0
  7. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/translator.py +2 -2
  8. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/utils/expansions.py +2 -2
  9. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/version.py +1 -1
  10. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0/dkist_header_validator.egg-info}/PKG-INFO +1 -1
  11. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator.egg-info/requires.txt +1 -1
  12. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/setup.cfg +1 -1
  13. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/.gitignore +0 -0
  14. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/.pre-commit-config.yaml +0 -0
  15. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/.readthedocs.yml +0 -0
  16. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/README.rst +0 -0
  17. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/__init__.py +0 -0
  18. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/api/__init__.py +0 -0
  19. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/api/validate.py +0 -0
  20. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/exceptions.py +0 -0
  21. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/__init__.py +0 -0
  22. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_base_validator.py +0 -0
  23. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_spec122_translation.py +0 -0
  24. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_spec122_validation+.py +0 -0
  25. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_spec122_validation-.py +0 -0
  26. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_spec214_validation+.py +0 -0
  27. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/tests/test_translator.py +0 -0
  28. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator/utils/__init__.py +0 -0
  29. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator.egg-info/SOURCES.txt +0 -0
  30. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator.egg-info/dependency_links.txt +0 -0
  31. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator.egg-info/entry_points.txt +0 -0
  32. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/dkist_header_validator.egg-info/top_level.txt +0 -0
  33. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/docs/Makefile +0 -0
  34. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/docs/conf.py +0 -0
  35. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/docs/index.rst +0 -0
  36. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/docs/make.bat +0 -0
  37. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/docs/requirements.txt +0 -0
  38. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/setup.py +0 -0
  39. {dkist-header-validator-4.1.0 → dkist-header-validator-5.0.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dkist-header-validator
3
- Version: 4.1.0
3
+ Version: 5.0.0
4
4
  Summary: DKIST data validator
5
5
  Home-page: https://bitbucket.org/dkistdc/dkist-header-validator/src/main/
6
6
  Author: NSO / AURA
@@ -49,13 +49,14 @@ definitions:
49
49
  pipelines:
50
50
  default:
51
51
  - step: *lint
52
- - step: *test
53
- - step: *docs
54
- - step: *scan
52
+ - parallel:
53
+ - step: *test
54
+ - step: *docs
55
+ - step: *scan
55
56
  tags:
56
57
  'v*':
58
+ - step: *lint
57
59
  - parallel:
58
- - step: *lint
59
60
  - step: *test
60
61
  - step: *docs
61
62
  - step: *scan
@@ -9,7 +9,9 @@ from numbers import Integral
9
9
  from numbers import Real
10
10
  from pathlib import Path
11
11
  from typing import Any
12
+ from typing import Callable
12
13
  from typing import IO
14
+ from typing import Optional
13
15
  from typing import Type
14
16
 
15
17
  import astropy.time as t
@@ -453,21 +455,14 @@ class SpecValidator:
453
455
  """
454
456
  try:
455
457
  with fits.open(input_headers) as hdul:
456
- if len(hdul) > 2:
457
- raise ValidationException(
458
- "Too many HDUs in your HDUList! May only have two HDUs at most."
459
- )
460
458
  # verify fits headers with astropy verify library
461
459
  hdul.verify("exception")
462
460
  # normalize headers into a dict
463
461
  try:
464
- hdus = self._headers_to_dict(hdul[1].header)
465
462
  data = hdul[1].data
466
463
  except IndexError: # non-compressed
467
- hdus = self._headers_to_dict(hdul[0].header)
468
464
  data = hdul[0].data
469
- fits_cards = self._capture_fits_cards(hdus)
470
- verified_headers = self.verify_headers(hdus, extra)
465
+ verified_headers, fits_cards = self._validate_headers(hdul, extra)
471
466
  return verified_headers, fits_cards, data
472
467
  except (ValueError, FileNotFoundError, OSError, IndexError) as exc:
473
468
  logger.debug(f"Cannot parse headers: detail = {exc}")
@@ -702,3 +697,56 @@ class SpecValidator:
702
697
  return self._format_output(
703
698
  return_type, translated_headers, input_headers, data, fits_cards
704
699
  )
700
+
701
+
702
+ class ProcessedSpecValidator(SpecValidator):
703
+ """
704
+ Validates FITS Headers against a schema that is updated based on the actual headers.
705
+
706
+ The two current examples of a "processed" spec are keys that are updated based on expansion or on conditional
707
+ requiredness.
708
+
709
+ Parameters
710
+ ----------
711
+ spec_processor_function
712
+ A function that can process a spec based on an input header. Probably `load_processed_spec214`.
713
+
714
+ SchemaValidationException
715
+ SpecValidationException or subclass of SpecValidationException
716
+ to raise if spec_validator validation fails
717
+
718
+ """
719
+
720
+ def __init__(
721
+ self,
722
+ spec_processor_function: Callable,
723
+ SchemaValidationException: Type[SpecValidationException] = SpecValidationException,
724
+ ):
725
+ self.spec_processor_function = spec_processor_function
726
+
727
+ # Use an unprocessed (i.e., no header given) spec to initialize the base Validator.
728
+ base_schema = self.spec_processor_function()
729
+ super().__init__(
730
+ spec_schema=base_schema, SchemaValidationException=SchemaValidationException
731
+ )
732
+
733
+ def verify_headers(self, headers, extra) -> dict:
734
+ """
735
+ Validates file headers against the instance spec_validator
736
+
737
+ Parameters
738
+ ----------
739
+ headers
740
+ file headers
741
+ extra
742
+ switch for validation to allow extra keys in schema
743
+ Returns
744
+ -------
745
+ dict of headers
746
+
747
+ Raises
748
+ ------
749
+ SchemaValidationException
750
+ """
751
+ self.spec_schema = SpecSchema(self.spec_processor_function(**headers))
752
+ return super().verify_headers(headers, extra)
@@ -4,7 +4,9 @@ Validators configured for specific Fits Specs
4
4
  from dkist_fits_specifications import spec122
5
5
  from dkist_fits_specifications import spec214
6
6
  from dkist_fits_specifications.spec214 import level0
7
+ from dkist_fits_specifications.spec214 import load_processed_spec214
7
8
 
9
+ from dkist_header_validator.base_validator import ProcessedSpecValidator
8
10
  from dkist_header_validator.base_validator import SpecValidator
9
11
  from dkist_header_validator.exceptions import SpecSchemaDefinitionException
10
12
  from dkist_header_validator.exceptions import SpecValidationException
@@ -49,7 +51,7 @@ spec214_l0_validator = SpecValidator(
49
51
  SchemaValidationException=Spec214ValidationException,
50
52
  )
51
53
 
52
- spec214_validator = SpecValidator(
53
- spec_schema=spec214.load_spec214(),
54
+ spec214_validator = ProcessedSpecValidator(
55
+ spec_processor_function=load_processed_spec214,
54
56
  SchemaValidationException=Spec214ValidationException,
55
57
  )
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+ from random import choice
2
3
  from uuid import uuid4
3
4
 
4
5
  import numpy as np
@@ -7,6 +8,7 @@ from astropy.io import fits
7
8
  from astropy.wcs import WCS
8
9
  from dkist_data_simulator.spec122 import Spec122Dataset
9
10
  from dkist_data_simulator.spec214 import Spec214Dataset
11
+ from dkist_fits_specifications.spec214 import load_spec214
10
12
 
11
13
  FITS_OBJECT_TYPES = [
12
14
  "fits",
@@ -536,7 +538,6 @@ class BaseSpec214DatasetCaseSensitive(Spec214Dataset):
536
538
  time_delta=1,
537
539
  instrument=instrument,
538
540
  )
539
- self.add_remove_key("VBISYNCM")
540
541
  self.add_constant_key("VBISYNCM", "fIxED")
541
542
 
542
543
  @property
@@ -556,3 +557,38 @@ def valid_spec_214_casesensitive(tmpdir, request):
556
557
  yield get_fits_object(
557
558
  object_type=request.param, tmpdir=tmpdir, ds=BaseSpec214DatasetCaseSensitive()
558
559
  )
560
+
561
+
562
+ class InvalidPolarimetricSpec214Dataset(BaseSpec214Dataset):
563
+ def __init__(self):
564
+ super().__init__(instrument="visp")
565
+ self.add_constant_key("VSPPOLMD", "observe_polarimetric")
566
+ self.add_remove_key("POL_SENS")
567
+
568
+
569
+ @pytest.fixture(scope="function", params=FITS_OBJECT_TYPES)
570
+ def invalid_polarimetric_spec_214_object(tmpdir, request):
571
+ yield get_fits_object(
572
+ object_type=request.param, tmpdir=tmpdir, ds=InvalidPolarimetricSpec214Dataset()
573
+ )
574
+
575
+
576
+ class InvalidInstrumentTableSpec214Dataset(BaseSpec214Dataset):
577
+ def __init__(self, instrument: str):
578
+ super().__init__(instrument=instrument)
579
+
580
+ # Remove a random key from the instrument table
581
+ glob_name = instrument.lower().replace(
582
+ "-", ""
583
+ ) # For cryo and dl b/c the yamls don't have dashes
584
+ instrument_keys = load_spec214(glob=glob_name)[glob_name]
585
+ removal_candidates = [k for k, v in instrument_keys.items() if "instrument_required" in v]
586
+ removed_key = choice(removal_candidates)
587
+ self.add_remove_key(removed_key)
588
+ self.removed_key = removed_key
589
+
590
+
591
+ @pytest.fixture(scope="function", params=FITS_OBJECT_TYPES)
592
+ def invalid_instrument_table_spec_214_object(tmpdir, request, instrument):
593
+ dataset = InvalidInstrumentTableSpec214Dataset(instrument)
594
+ yield get_fits_object(object_type=request.param, tmpdir=tmpdir, ds=dataset), dataset.removed_key
@@ -3,10 +3,12 @@ from pathlib import Path
3
3
 
4
4
  import numpy as np
5
5
  import pytest
6
+ from dkist_fits_specifications.spec214 import load_processed_spec214
6
7
 
7
8
  from dkist_header_validator import spec214_l0_validator
8
9
  from dkist_header_validator import spec214_validator
9
10
  from dkist_header_validator import Spec214ValidationException
11
+ from dkist_header_validator.base_validator import ProcessedSpecValidator
10
12
  from dkist_header_validator.exceptions import ReturnTypeException
11
13
  from dkist_header_validator.exceptions import ValidationException
12
14
 
@@ -168,3 +170,30 @@ def test_validate_toomanyHDUs(valid_spec_214_too_many_HDUs):
168
170
  # raises exception on failure
169
171
  with pytest.raises(ValidationException):
170
172
  spec214_validator.validate(valid_spec_214_too_many_HDUs)
173
+
174
+
175
+ def test_polarimetric_required_key_missing(invalid_polarimetric_spec_214_object):
176
+ """
177
+ Given: Polarimetric headers with a missing `polarimetric_required` key
178
+ When: Validating headers
179
+ Then: The correct Error is raised
180
+ """
181
+ with pytest.raises(
182
+ Spec214ValidationException,
183
+ match="'POL_SENS': 'required key not provided. Required keyword not present'",
184
+ ):
185
+ spec214_validator.validate(invalid_polarimetric_spec_214_object)
186
+
187
+
188
+ @pytest.mark.parametrize("instrument", ["cryo-nirsp", "dlnirsp", "vbi", "visp", "vtf"])
189
+ def test_instrument_required_key_missing(invalid_instrument_table_spec_214_object):
190
+ """
191
+ Given: Headers from a specific instrument, but with one of the required header keys removed
192
+ When: Validating headers
193
+ Then: The correct Error is raised
194
+ """
195
+ fits_object, missing_key = invalid_instrument_table_spec_214_object
196
+ with pytest.raises(
197
+ Spec214ValidationException, match=f"'{missing_key}': 'required key not provided"
198
+ ):
199
+ spec214_validator.validate(fits_object)
@@ -7,7 +7,7 @@ from typing import IO
7
7
  from astropy.io import fits
8
8
  from astropy.io.fits.hdu.hdulist import HDUList
9
9
  from dkist_fits_specifications.spec214 import level0
10
- from dkist_fits_specifications.spec214 import load_expanded_spec214
10
+ from dkist_fits_specifications.spec214 import load_processed_spec214
11
11
  from dkist_fits_specifications.spec214 import load_spec214
12
12
 
13
13
  from dkist_header_validator.utils.expansions import expand_index_d
@@ -67,7 +67,7 @@ def sanitize_to_spec214_level1(
67
67
  input_headers, input_data = _parse_fits_like_input(input_headers)
68
68
  header = fits.Header(input_headers)
69
69
  # convert headers
70
- expanded_214 = load_expanded_spec214(**dict(input_headers))
70
+ expanded_214 = load_processed_spec214(**dict(input_headers))
71
71
  all_214_keys = reduce(list.__add__, map(list, expanded_214.values()))
72
72
 
73
73
  for keyword in tuple(header.keys()):
@@ -1,6 +1,6 @@
1
- from dkist_fits_specifications.utils import expand_schema
2
- from dkist_fits_specifications.utils import ExpansionIndex
3
1
  from dkist_fits_specifications.utils import schema_type_hint
2
+ from dkist_fits_specifications.utils.spec_processors.expansion import expand_schema
3
+ from dkist_fits_specifications.utils.spec_processors.expansion import ExpansionIndex
4
4
 
5
5
 
6
6
  def expand_naxis(naxis: int, schema: schema_type_hint) -> schema_type_hint:
@@ -5,4 +5,4 @@ try:
5
5
  from setuptools_scm import get_version
6
6
  __version__ = get_version(root='..', relative_to=__file__)
7
7
  except Exception:
8
- __version__ = '4.1.0'
8
+ __version__ = '5.0.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dkist-header-validator
3
- Version: 4.1.0
3
+ Version: 5.0.0
4
4
  Summary: DKIST data validator
5
5
  Home-page: https://bitbucket.org/dkistdc/dkist-header-validator/src/main/
6
6
  Author: NSO / AURA
@@ -1,7 +1,7 @@
1
1
  astropy>=5.0
2
2
  voluptuous<1.0.0,>=0.11.7
3
3
  pyyaml>=6.0
4
- dkist-fits-specifications>=2.0.0
4
+ dkist-fits-specifications>=4.0.0
5
5
 
6
6
  [cli]
7
7
  typer
@@ -22,7 +22,7 @@ install_requires =
22
22
  astropy >= 5.0
23
23
  voluptuous >=0.11.7, < 1.0.0
24
24
  pyyaml >=6.0
25
- dkist-fits-specifications >= 2.0.0
25
+ dkist-fits-specifications >= 4.0.0
26
26
 
27
27
  [options.extras_require]
28
28
  test =