caom2utils 1.7.3__tar.gz → 1.7.4__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 (59) hide show
  1. {caom2utils-1.7.3/caom2utils.egg-info → caom2utils-1.7.4}/PKG-INFO +8 -3
  2. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/blueprints.py +1 -3
  3. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/caom2blueprint.py +19 -12
  4. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/data_util.py +21 -10
  5. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/legacy.py +13 -5
  6. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/parsers.py +2 -1
  7. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/help.txt +3 -1
  8. caom2utils-1.7.4/caom2utils/tests/test_cli_parsers.py +146 -0
  9. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_collections.py +4 -1
  10. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_data_util.py +26 -5
  11. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_fits2caom2.py +20 -46
  12. caom2utils-1.7.4/caom2utils/version.py +1 -0
  13. {caom2utils-1.7.3 → caom2utils-1.7.4/caom2utils.egg-info}/PKG-INFO +8 -3
  14. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/SOURCES.txt +2 -4
  15. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/requires.txt +1 -1
  16. {caom2utils-1.7.3 → caom2utils-1.7.4}/setup.cfg +5 -5
  17. {caom2utils-1.7.3 → caom2utils-1.7.4}/setup.py +6 -1
  18. caom2utils-1.7.3/caom2utils/tests/data/missing_observation_help.txt +0 -8
  19. caom2utils-1.7.3/caom2utils/tests/data/missing_positional_argument_help.txt +0 -10
  20. caom2utils-1.7.3/caom2utils/tests/data/too_few_arguments_help.txt +0 -0
  21. caom2utils-1.7.3/caom2utils/version.py +0 -1
  22. {caom2utils-1.7.3 → caom2utils-1.7.4}/LICENSE +0 -0
  23. {caom2utils-1.7.3 → caom2utils-1.7.4}/MANIFEST.in +0 -0
  24. {caom2utils-1.7.3 → caom2utils-1.7.4}/README.rst +0 -0
  25. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/__init__.py +0 -0
  26. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/caomvalidator.py +0 -0
  27. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/polygonvalidator.py +0 -0
  28. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/__init__.py +0 -0
  29. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/conftest.py +0 -0
  30. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/4axes.fits +0 -0
  31. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/4axes.override +0 -0
  32. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/4axes_obs.fits +0 -0
  33. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/SampleComposite-CAOM-2.3.xml +0 -0
  34. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/bad_product_id.txt +0 -0
  35. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/bad_product_id.xml +0 -0
  36. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/edge_case.blueprint +0 -0
  37. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/fits2caom2.config +0 -0
  38. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/java.config +0 -0
  39. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/missing_product_id.txt +0 -0
  40. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/nonconformant.py +0 -0
  41. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/test.override +0 -0
  42. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/test_plugin.py +0 -0
  43. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/test_plugin_class.py +0 -0
  44. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/text.override +0 -0
  45. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/data/time_axes.fits +0 -0
  46. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_caomvalidator.py +0 -0
  47. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_convert_from_java.py +0 -0
  48. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_custom_axis_util.py +0 -0
  49. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_obs_blueprint.py +0 -0
  50. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_polygonvalidator.py +0 -0
  51. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_si_uris.py +0 -0
  52. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/tests/test_wcsvalidator.py +0 -0
  53. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/wcs_parsers.py +0 -0
  54. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/wcs_util.py +0 -0
  55. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils/wcsvalidator.py +0 -0
  56. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/dependency_links.txt +0 -0
  57. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/entry_points.txt +0 -0
  58. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/not-zip-safe +0 -0
  59. {caom2utils-1.7.3 → caom2utils-1.7.4}/caom2utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: caom2utils
3
- Version: 1.7.3
3
+ Version: 1.7.4
4
4
  Summary: CAOM-2.4 utils
5
5
  Home-page: https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2
6
6
  Author: Canadian Astronomy Data Centre
@@ -10,10 +10,15 @@ Classifier: Natural Language :: English
10
10
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
- Requires-Python: >=3.8, <4
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: >=3.10, <3.15
14
19
  License-File: LICENSE
15
20
  Requires-Dist: cadcdata>=2.5.2
16
- Requires-Dist: caom2>=2.6
21
+ Requires-Dist: caom2<3,>=2.6
17
22
  Requires-Dist: astropy>=2.0
18
23
  Requires-Dist: spherical-geometry>=1.2.11
19
24
  Requires-Dist: vos>=3.1.1
@@ -982,9 +982,7 @@ class ObsBlueprint:
982
982
  self._extensions[extension][caom2_element][1].insert(0, ttype_attribute)
983
983
  else:
984
984
  raise AttributeError(
985
- ('No TTYPE attributes in extension {} associated ' 'with keyword {}').format(
986
- extension, caom2_element
987
- )
985
+ f'No TTYPE attributes in extension {extension} associated with keyword {caom2_element}'
988
986
  )
989
987
  else:
990
988
  self._extensions[extension][caom2_element] = ('BINTABLE', [ttype_attribute], index)
@@ -467,7 +467,10 @@ def _load_module(module):
467
467
  raise e
468
468
 
469
469
 
470
- def caom2gen():
470
+ def build_caom2gen_parser():
471
+ """
472
+ Build the ArgumentParser for caom2gen (without parsing argv).
473
+ """
471
474
  parser = get_gen_proc_arg_parser()
472
475
  parser.add_argument(
473
476
  '--blueprint',
@@ -479,6 +482,11 @@ def caom2gen():
479
482
  'per lineage entry.'
480
483
  ),
481
484
  )
485
+ return parser
486
+
487
+
488
+ def caom2gen():
489
+ parser = build_caom2gen_parser()
482
490
 
483
491
  if len(sys.argv) < 2:
484
492
  parser.print_usage(file=sys.stderr)
@@ -528,10 +536,11 @@ def caom2gen():
528
536
  try:
529
537
  gen_proc(args, blueprints)
530
538
  except Exception as e:
539
+ from cadcutils.util.cli_errors import format_user_error
531
540
  logging.error('Failed caom2gen execution.')
532
- logging.error(e)
533
- tb = traceback.format_exc()
534
- logging.error(tb)
541
+ logging.error(format_user_error(e))
542
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
543
+ logging.debug(traceback.format_exc())
535
544
  sys.exit(-1)
536
545
 
537
546
  logging.debug(f'Done {APP_NAME} processing.')
@@ -711,7 +720,9 @@ def proc(args, obs_blueprints):
711
720
  raise RuntimeError(msg)
712
721
 
713
722
  subject = net.Subject.from_cmd_line_args(args)
714
- client = data_util.StorageClientWrapper(subject, resource_id=args.resource_id)
723
+ client = data_util.StorageClientWrapper(
724
+ subject, resource_id=args.resource_id, host=args.host,
725
+ insecure=args.insecure)
715
726
  validate_wcs = True
716
727
  if args.no_validate:
717
728
  validate_wcs = False
@@ -831,13 +842,9 @@ def gen_proc(args, blueprints, **kwargs):
831
842
  connected = False
832
843
  else:
833
844
  subject = net.Subject.from_cmd_line_args(args)
834
- if args.resource_id is None:
835
- # if the resource_id is Undefined, using CadcDataClient
836
- client = data_util.StorageClientWrapper(subject, using_storage_inventory=False)
837
- else:
838
- # if the resource_id is defined, assume that the caller intends to use the Storage Inventory system, as
839
- # it's the CADC storage client that depends on a resource_id
840
- client = data_util.StorageClientWrapper(subject, resource_id=args.resource_id)
845
+ client = data_util.StorageClientWrapper(
846
+ subject, resource_id=args.resource_id, host=args.host,
847
+ insecure=args.insecure)
841
848
 
842
849
  for ii, cardinality in enumerate(args.lineage):
843
850
  product_id, uri = _extract_ids(cardinality)
@@ -95,7 +95,8 @@ class StorageClientWrapper:
95
95
  Wrap the metrics collection with StorageInventoryClient.
96
96
  """
97
97
 
98
- def __init__(self, subject, resource_id='ivo://cadc.nrc.ca/uvic/minoc', metrics=None):
98
+ def __init__(self, subject, resource_id='ivo://cadc.nrc.ca/uvic/minoc', metrics=None,
99
+ host=None, insecure=False):
99
100
  """
100
101
  :param subject: net.Subject instance for authentication and authorization
101
102
  :param resource_id: str identifies the StorageInventoryClient endpoint. Defaults to the installation closest to
@@ -103,8 +104,11 @@ class StorageClientWrapper:
103
104
  :param metrics: caom2pipe.manaage_composable.Metrics instance. If set, will track execution times, by action,
104
105
  from the beginning of the method invocation to the end of the method invocation, success or failure.
105
106
  Defaults to None, because fits2caom2 is a stand-alone application.
107
+ :param host: Host server for the storage inventory service
108
+ :param insecure: skip SSL server certificate verification (for testing only)
106
109
  """
107
- self._cadc_client = StorageInventoryClient(subject=subject, resource_id=resource_id)
110
+ self._cadc_client = StorageInventoryClient(
111
+ subject=subject, resource_id=resource_id, host=host, insecure=insecure)
108
112
  self._metrics = metrics
109
113
  self._logger = logging.getLogger(self.__class__.__name__)
110
114
 
@@ -274,15 +278,22 @@ def _clean_headers(fits_header):
274
278
 
275
279
 
276
280
  def get_local_headers_from_fits(fqn):
277
- """Create a list of fits.Header instances from a fits file.
278
- :param fqn str fully-qualified name of the FITS file on disk
279
- :return list of fits.Header instances
281
+ """Create a list of fits.Header instances from a FITS file or plain-text
282
+ header file.
283
+
284
+ When the file is not a FITS binary (e.g. a ``*.fits.header`` text file),
285
+ headers are parsed from text instead.
286
+
287
+ :param fqn: fully-qualified name of the file on disk
288
+ :return: list of fits.Header instances
280
289
  """
281
- hdulist = fits.open(fqn, memmap=True, lazy_load_hdus=True)
282
- hdulist.verify('fix')
283
- hdulist.close()
284
- headers = [h.header for h in hdulist]
285
- return headers
290
+ try:
291
+ with fits.open(fqn, memmap=True, lazy_load_hdus=True) as hdulist:
292
+ hdulist.verify('fix')
293
+ return [h.header for h in hdulist]
294
+ except OSError:
295
+ with open(fqn, encoding='utf-8', errors='replace') as f:
296
+ return make_headers_from_string(f.read())
286
297
 
287
298
 
288
299
  def get_local_file_headers(fqn):
@@ -457,10 +457,12 @@ def update_blueprint(obs_blueprint, artifact_uri=None, config=None, defaults=Non
457
457
  return None
458
458
 
459
459
 
460
- def main_app():
460
+ def build_fits2caom2_parser():
461
+ """
462
+ Build the ArgumentParser for fits2caom2 (without parsing argv).
463
+ """
461
464
  parser = caom2blueprint.get_arg_parser()
462
465
 
463
- # add legacy fits2caom2 arguments
464
466
  parser.add_argument(
465
467
  '--config',
466
468
  required=False,
@@ -469,6 +471,11 @@ def main_app():
469
471
 
470
472
  parser.add_argument('--default', help='file with default values for keywords')
471
473
  parser.add_argument('--override', help='file with override values for keywords')
474
+ return parser
475
+
476
+
477
+ def main_app():
478
+ parser = build_fits2caom2_parser()
472
479
 
473
480
  if len(sys.argv) < 2:
474
481
  # correct error message when running python3
@@ -507,9 +514,10 @@ def main_app():
507
514
  try:
508
515
  caom2blueprint.proc(args, obs_blueprint)
509
516
  except Exception as e:
510
- logging.error(e)
511
- tb = traceback.format_exc()
512
- logging.debug(tb)
517
+ from cadcutils.util.cli_errors import format_user_error
518
+ logging.error(format_user_error(e))
519
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
520
+ logging.debug(traceback.format_exc())
513
521
  sys.exit(-1)
514
522
 
515
523
  logging.info("DONE")
@@ -1899,7 +1899,8 @@ class Hdf5Parser(ContentParser):
1899
1899
  Retrieve metadata from file, cache in the blueprint.
1900
1900
  """
1901
1901
  self.logger.debug('Begin apply_blueprint_from_file')
1902
- # h5py is an extra in this package since most collections do not require it
1902
+ # h5py is an extra in this package since most collections do not
1903
+ # require it
1903
1904
  import h5py
1904
1905
 
1905
1906
  individual, multi, attributes, candidate_extensions = self._extract_path_names_from_blueprint()
@@ -1,4 +1,5 @@
1
- usage: fits2caom2 [-h] [--cert CERT | -n | --netrc-file NETRC_FILE | -u USER]
1
+ usage: fits2caom2 [-h]
2
+ [--cert CERT | -n | --netrc-file NETRC_FILE | -u USER | --token TOKEN]
2
3
  [--resource-id RESOURCE_ID] [-q | -v] [-V] [--dumpconfig]
3
4
  [--not_connected] [--no_validate] [-o OUT_OBS_XML]
4
5
  (-i IN_OBS_XML | --observation collection observationID)
@@ -41,6 +42,7 @@ optional arguments:
41
42
  --resource-id RESOURCE_ID
42
43
  resource identifier (default
43
44
  ivo://cadc.nrc.ca/global/raven)
45
+ --token TOKEN authentication token to use.
44
46
  -u, --user USER name of user to authenticate. Note: application
45
47
  prompts for the corresponding password!
46
48
  -v, --verbose verbose messages
@@ -0,0 +1,146 @@
1
+ # ***********************************************************************
2
+ # ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
3
+ # ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
4
+ #
5
+ # (c) 2026. (c) 2026.
6
+ # Government of Canada Gouvernement du Canada
7
+ # National Research Council Conseil national de recherches
8
+ # Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
9
+ # All rights reserved Tous droits réservés
10
+ #
11
+ # NRC disclaims any warranties, Le CNRC dénie toute garantie
12
+ # expressed, implied, or énoncée, implicite ou légale,
13
+ # statutory, of any kind with de quelque nature que ce
14
+ # respect to the software, soit, concernant le logiciel,
15
+ # including without limitation y compris sans restriction
16
+ # any warranty of merchantability toute garantie de valeur
17
+ # or fitness for a particular marchande ou de pertinence
18
+ # purpose. NRC shall not be pour un usage particulier.
19
+ # liable in any event for any Le CNRC ne pourra en aucun cas
20
+ # damages, whether direct or être tenu responsable de tout
21
+ # indirect, special or general, dommage, direct ou indirect,
22
+ # consequential or incidental, particulier ou général,
23
+ # arising from the use of the accessoire ou fortuit, résultant
24
+ # software. Neither the name de l'utilisation du logiciel. Ni
25
+ # of the National Research le nom du Conseil National de
26
+ # Council of Canada nor the Recherches du Canada ni les noms
27
+ # names of its contributors may de ses participants ne peuvent
28
+ # be used to endorse or promote être utilisés pour approuver ou
29
+ # products derived from this promouvoir les produits dérivés
30
+ # software without specific prior de ce logiciel sans autorisation
31
+ # written permission. préalable et particulière
32
+ # par écrit.
33
+ #
34
+ # This file is part of the Ce fichier fait partie du projet
35
+ # OpenCADC project. OpenCADC.
36
+ #
37
+ # OpenCADC is free software: OpenCADC est un logiciel libre ;
38
+ # you can redistribute it and/or vous pouvez le redistribuer ou le
39
+ # modify it under the terms of modifier suivant les termes de
40
+ # the GNU Affero General Public la “GNU Affero General Public
41
+ # License as published by the License” telle que publiée
42
+ # Free Software Foundation, par la Free Software Foundation
43
+ # either version 3 of the : soit la version 3 de cette
44
+ # License, or (at your option) licence, soit (à votre gré)
45
+ # any later version. toute version ultérieure.
46
+ #
47
+ # OpenCADC is distributed in the OpenCADC est distribué
48
+ # hope that it will be useful, dans l’espoir qu’il vous
49
+ # but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
50
+ # without even the implied GARANTIE : sans même la garantie
51
+ # warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
52
+ # or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
53
+ # PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
54
+ # General Public License for Générale Publique GNU Affero pour
55
+ # more details. plus de détails.
56
+ #
57
+ # You should have received Vous devriez avoir reçu une
58
+ # a copy of the GNU Affero copie de la Licence Générale
59
+ # General Public License along Publique GNU Affero avec
60
+ # with OpenCADC. If not, see OpenCADC ; si ce n’est pas le cas,
61
+ # <http://www.gnu.org/licenses/>. consultez :
62
+ # <http://www.gnu.org/licenses/>.
63
+ #
64
+ # ***********************************************************************
65
+
66
+ """Contract tests for fits2caom2 and caom2gen CLI parsers."""
67
+
68
+ import os
69
+ import sys
70
+ from unittest.mock import patch
71
+
72
+ import pytest
73
+
74
+ from caom2utils import caom2blueprint
75
+ from caom2utils.caom2blueprint import (
76
+ GLOBAL_STORAGE_RESOURCE_ID, build_caom2gen_parser, get_arg_parser,
77
+ )
78
+ from caom2utils.legacy import build_fits2caom2_parser
79
+ from cadcutils.util.tests.parser_helpers import (
80
+ assert_has_base_dests, assert_has_dests, assert_help_contains,
81
+ )
82
+
83
+ THIS_DIR = os.path.dirname(os.path.realpath(__file__))
84
+ TESTDATA_DIR = os.path.join(THIS_DIR, 'data')
85
+
86
+
87
+ class MyExitError(Exception):
88
+ pass
89
+
90
+
91
+ _COMMON_DESTS = (
92
+ 'dumpconfig', 'not_connected', 'no_validate', 'out_obs_xml',
93
+ 'in_obs_xml', 'observation', 'local',
94
+ )
95
+
96
+
97
+ def test_fits2caom2_base_parser_contract():
98
+ parser = get_arg_parser()
99
+ assert_has_base_dests(parser)
100
+ assert_has_dests(parser, *_COMMON_DESTS, 'productID', 'fileURI')
101
+ assert_help_contains(
102
+ parser,
103
+ GLOBAL_STORAGE_RESOURCE_ID,
104
+ 'Augments an observation',
105
+ )
106
+
107
+
108
+ def test_fits2caom2_legacy_parser_contract():
109
+ parser = build_fits2caom2_parser()
110
+ assert_has_base_dests(parser)
111
+ assert_has_dests(
112
+ parser, *_COMMON_DESTS, 'productID', 'fileURI',
113
+ 'config', 'default', 'override',
114
+ )
115
+ assert_help_contains(parser, 'utype to keyword config')
116
+
117
+
118
+ def test_caom2gen_parser_contract():
119
+ parser = build_caom2gen_parser()
120
+ assert_has_base_dests(parser)
121
+ assert_has_dests(
122
+ parser, *_COMMON_DESTS, 'external_url', 'module', 'plugin',
123
+ 'lineage', 'use_blueprint_parser', 'blueprint',
124
+ )
125
+ assert_help_contains(
126
+ parser,
127
+ GLOBAL_STORAGE_RESOURCE_ID,
128
+ 'productID/artifactURI',
129
+ )
130
+
131
+
132
+ @patch('caom2utils.caom2blueprint.gen_proc', side_effect=RuntimeError('boom'))
133
+ @patch('sys.exit', side_effect=MyExitError)
134
+ def test_caom2gen_logs_debug_traceback_on_error(exit_mock, gen_mock):
135
+ blueprint = os.path.join(TESTDATA_DIR, 'si', 'si.blueprint')
136
+ sys.argv = (
137
+ 'caom2gen --debug -o /tmp/out.xml --no_validate '
138
+ '--resource-id ivo://cadc.nrc.ca/test '
139
+ '--observation TEST_COLLECTION TEST_OBS_ID '
140
+ '--lineage test_product_id/cadc:TEST/test_file.fits '
141
+ '--blueprint {}'.format(blueprint)
142
+ ).split()
143
+ with pytest.raises(MyExitError):
144
+ caom2blueprint.caom2gen()
145
+ gen_mock.assert_called_once()
146
+ exit_mock.assert_called_once_with(-1)
@@ -350,5 +350,8 @@ def _read_observation(fname):
350
350
 
351
351
 
352
352
  def _write_observation(obs):
353
+ fd, path = tempfile.mkstemp(suffix='.xml', prefix='caom2utils_failed_obs_')
354
+ os.close(fd)
353
355
  writer = ObservationWriter(True, False, 'caom2', 'http://www.opencadc.org/caom2/xml/v2.4')
354
- writer.write(obs, './x.xml')
356
+ writer.write(obs, path)
357
+ return path
@@ -66,7 +66,6 @@
66
66
  # ***********************************************************************
67
67
  #
68
68
 
69
- from pathlib import Path
70
69
 
71
70
  from astropy.io import fits
72
71
  from cadcdata import FileInfo
@@ -104,7 +103,7 @@ def test_get_file_type():
104
103
 
105
104
 
106
105
  @patch('caom2utils.data_util.StorageInventoryClient')
107
- def test_storage_inventory_client(cadc_client_mock):
106
+ def test_storage_inventory_client(cadc_client_mock, tmp_path):
108
107
  test_subject = Mock(autospec=True)
109
108
  test_uri = 'cadc:TEST/test_file.fits'
110
109
 
@@ -118,10 +117,11 @@ def test_storage_inventory_client(cadc_client_mock):
118
117
  else:
119
118
  test_fqn.write_text('StorageInventoryClient')
120
119
 
121
- for test_working_directory in [Path(test_fits2caom2.TESTDATA_DIR), Path('./')]:
120
+ for subdir in (None, 'nested'):
121
+ test_working_directory = tmp_path / subdir if subdir else tmp_path
122
+ if subdir:
123
+ test_working_directory.mkdir()
122
124
  test_fqn = test_working_directory / 'test_file.fits'
123
- if test_fqn.exists():
124
- test_fqn.unlink()
125
125
 
126
126
  cadc_client_mock.return_value.cadcinfo.side_effect = info_si_mock
127
127
  cadc_client_mock.return_value.cadcget.side_effect = get_si_mock
@@ -227,6 +227,27 @@ def test_unicode_decode_error():
227
227
  assert result is not None, 'expect retry using a different method'
228
228
 
229
229
 
230
+ def test_get_local_headers_from_text_header_file():
231
+ """Plain-text header files should be readable (issue #135)."""
232
+ test_fqn = join(
233
+ test_fits2caom2.TESTDATA_DIR,
234
+ 'dao/dao_c122_2016_007830/dao_c122_2016_007830.fits.header',
235
+ )
236
+ result = data_util.get_local_headers_from_fits(test_fqn)
237
+ assert result is not None
238
+ assert len(result) == 1
239
+ assert result[0].get('OBJECT') == 'FLAT'
240
+
241
+
242
+ def test_get_local_headers_from_multi_extension_text_header():
243
+ test_fqn = join(
244
+ test_fits2caom2.TESTDATA_DIR,
245
+ 'gemini/N20250101M0624/N20250101M0624.fits.header',
246
+ )
247
+ result = data_util.get_local_headers_from_fits(test_fqn)
248
+ assert len(result) == 3
249
+
250
+
230
251
  def test_get_file_encoding():
231
252
  test_subjects = {'abc.fits': None, 'abc.fits.gz': 'gzip', 'abc.fits.fz': 'x-fits'}
232
253
  for test_subject in test_subjects.keys():
@@ -93,6 +93,7 @@ from unittest.mock import Mock, patch
93
93
  from io import StringIO, BytesIO
94
94
 
95
95
  import importlib
96
+ import logging
96
97
  import os
97
98
  import sys
98
99
 
@@ -536,52 +537,30 @@ def _get_from_str_xml(string_xml, get_func, element_tag):
536
537
  return act_obj
537
538
 
538
539
 
539
- @patch('sys.exit', Mock(side_effect=[MyExitError, MyExitError, MyExitError, MyExitError, MyExitError, MyExitError]))
540
- def test_help():
541
- """Tests the helper displays for commands in main"""
540
+ @patch('sys.exit', Mock(side_effect=[MyExitError, MyExitError, MyExitError, MyExitError]))
541
+ def test_cli_errors():
542
+ """Tests CLI validation and error messages."""
542
543
 
543
- # expected helper messages
544
544
  with open(os.path.join(TESTDATA_DIR, 'bad_product_id.txt')) as myfile:
545
545
  bad_product_id = myfile.read().strip()
546
546
  with open(os.path.join(TESTDATA_DIR, 'missing_product_id.txt')) as myfile:
547
547
  missing_product_id = myfile.read()
548
- with open(os.path.join(TESTDATA_DIR, 'too_few_arguments_help.txt')) as myfile:
549
- too_few_arguments_usage = myfile.read()
550
- with open(os.path.join(TESTDATA_DIR, 'help.txt')) as myfile:
551
- usage = myfile.read()
552
- with open(os.path.join(TESTDATA_DIR, 'missing_observation_help.txt')) as myfile:
553
- myfile.read()
554
- with open(os.path.join(TESTDATA_DIR, 'missing_positional_argument_help.txt')) as myfile:
555
- myfile.read()
556
-
557
- # too few arguments error message when running python3
558
- with patch('sys.stdout', new_callable=StringIO) as stdout_mock:
559
- sys.argv = ["fits2caom2"]
560
- with pytest.raises(MyExitError):
561
- main_app()
562
- if stdout_mock.getvalue():
563
- assert too_few_arguments_usage == stdout_mock.getvalue()
564
548
 
565
- # --help
566
- with patch('sys.stdout', new_callable=StringIO) as stdout_mock:
567
- sys.argv = ["fits2caom2", "-h"]
549
+ with patch('sys.stderr', new_callable=StringIO) as stderr_mock:
550
+ sys.argv = ["fits2caom2"]
568
551
  with pytest.raises(MyExitError):
569
552
  main_app()
570
- expected = stdout_mock.getvalue().replace('options:', 'optional arguments:').strip('\n')
571
- assert usage.strip('\n') == expected
553
+ assert 'too few arguments' in stderr_mock.getvalue()
572
554
 
573
- # missing productID when plane count is wrong
574
555
  with patch('sys.stderr', new_callable=StringIO) as stderr_mock:
575
556
  with patch('sys.stdout', new_callable=StringIO) as stdout_mock:
576
557
  bad_product_file = os.path.join(TESTDATA_DIR, 'bad_product_id.xml')
577
558
  sys.argv = ["fits2caom2", "--in", bad_product_file, "ad:CGPS/CGPS_MA1_HI_line_image.fits"]
578
559
  with pytest.raises(MyExitError):
579
560
  main_app()
580
- # inconsistencies between Python 3.7 and later versions. this should be on stderr_mmock only
581
561
  result = stderr_mock.getvalue() + stdout_mock.getvalue()
582
562
  assert bad_product_id in result, result
583
563
 
584
- # missing productID when blueprint doesn't have one either
585
564
  with patch('sys.stdout', new_callable=StringIO) as stdout_mock:
586
565
  with patch('sys.stderr', new_callable=StringIO) as stderr_mock, patch(
587
566
  'caom2utils.data_util.StorageClientWrapper'
@@ -597,27 +576,22 @@ def test_help():
597
576
  ]
598
577
  with pytest.raises(MyExitError):
599
578
  main_app()
600
- # inconsistencies between Python 3.7 and later versions. this should be on stderr_mmock only
601
579
  result = stderr_mock.getvalue() + stdout_mock.getvalue()
602
580
  assert missing_product_id.strip() in result, result
603
581
 
604
- # missing required --observation
605
- """
606
- TODO: fix the tests
607
- with patch('sys.stderr', new_callable=StringIO) as stdout_mock:
608
- sys.argv = ["fits2caom2", "testProductID", "testpathto/testFileURI"]
609
- with pytest.raises(MyExitError):
610
- main_app()
611
- assert(missing_observation_usage == stdout_mock.getvalue())
612
582
 
613
- # missing positional argument
614
- with patch('sys.stderr', new_callable=StringIO) as stdout_mock:
615
- sys.argv = ["fits2caom2", "--observation", "testCollection",
616
- "testObservationID", "testPathTo/testFileURI"]
617
- with pytest.raises(MyExitError):
618
- main_app()
619
- assert(missing_positional_argument_usage == stdout_mock.getvalue())
620
- """
583
+ @patch('caom2utils.caom2blueprint.proc', side_effect=RuntimeError('fail'))
584
+ @patch('sys.exit', side_effect=MyExitError)
585
+ def test_main_app_logs_debug_traceback_on_error(exit_mock, proc_mock):
586
+ logging.getLogger().setLevel(logging.DEBUG)
587
+ sys.argv = [
588
+ "fits2caom2", "--debug", "--observation", "cfht", "7000000o",
589
+ "ad:CGPS/CGPS_MA1_HI_line_image.fits",
590
+ ]
591
+ with pytest.raises(MyExitError):
592
+ main_app()
593
+ proc_mock.assert_called_once()
594
+ exit_mock.assert_called_once_with(-1)
621
595
 
622
596
 
623
597
  EXPECTED_OBS_XML = (
@@ -1073,7 +1047,7 @@ EXPECTED_GENERIC_PARSER_FILE_SCHEME_XML = (
1073
1047
  <caom2:productType>thumbnail</caom2:productType>
1074
1048
  <caom2:releaseType>data</caom2:releaseType>
1075
1049
  <caom2:contentType>text/plain</caom2:contentType>
1076
- <caom2:contentLength>2486</caom2:contentLength>
1050
+ <caom2:contentLength>2573</caom2:contentLength>
1077
1051
  <caom2:contentChecksum>md5:e6c08f3b8309f05a5a3330e27e3b44eb</caom2:contentChecksum>
1078
1052
  <caom2:uri>file://"""
1079
1053
  + text_file
@@ -0,0 +1 @@
1
+ version = '1.7.4'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: caom2utils
3
- Version: 1.7.3
3
+ Version: 1.7.4
4
4
  Summary: CAOM-2.4 utils
5
5
  Home-page: https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2
6
6
  Author: Canadian Astronomy Data Centre
@@ -10,10 +10,15 @@ Classifier: Natural Language :: English
10
10
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
- Requires-Python: >=3.8, <4
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: >=3.10, <3.15
14
19
  License-File: LICENSE
15
20
  Requires-Dist: cadcdata>=2.5.2
16
- Requires-Dist: caom2>=2.6
21
+ Requires-Dist: caom2<3,>=2.6
17
22
  Requires-Dist: astropy>=2.0
18
23
  Requires-Dist: spherical-geometry>=1.2.11
19
24
  Requires-Dist: vos>=3.1.1
@@ -25,6 +25,7 @@ caom2utils.egg-info/top_level.txt
25
25
  caom2utils/tests/__init__.py
26
26
  caom2utils/tests/conftest.py
27
27
  caom2utils/tests/test_caomvalidator.py
28
+ caom2utils/tests/test_cli_parsers.py
28
29
  caom2utils/tests/test_collections.py
29
30
  caom2utils/tests/test_convert_from_java.py
30
31
  caom2utils/tests/test_custom_axis_util.py
@@ -44,13 +45,10 @@ caom2utils/tests/data/edge_case.blueprint
44
45
  caom2utils/tests/data/fits2caom2.config
45
46
  caom2utils/tests/data/help.txt
46
47
  caom2utils/tests/data/java.config
47
- caom2utils/tests/data/missing_observation_help.txt
48
- caom2utils/tests/data/missing_positional_argument_help.txt
49
48
  caom2utils/tests/data/missing_product_id.txt
50
49
  caom2utils/tests/data/nonconformant.py
51
50
  caom2utils/tests/data/test.override
52
51
  caom2utils/tests/data/test_plugin.py
53
52
  caom2utils/tests/data/test_plugin_class.py
54
53
  caom2utils/tests/data/text.override
55
- caom2utils/tests/data/time_axes.fits
56
- caom2utils/tests/data/too_few_arguments_help.txt
54
+ caom2utils/tests/data/time_axes.fits
@@ -1,5 +1,5 @@
1
1
  cadcdata>=2.5.2
2
- caom2>=2.6
2
+ caom2<3,>=2.6
3
3
  astropy>=2.0
4
4
  spherical-geometry>=1.2.11
5
5
  vos>=3.1.1
@@ -23,6 +23,9 @@ norecursedirs = build docs/_build
23
23
  doctest_plus = enabled
24
24
  testpaths = caom2utils
25
25
 
26
+ [flake8]
27
+ max-line-length = 120
28
+
26
29
  [metadata]
27
30
  package_name = caom2utils
28
31
  description = CAOM-2.4 utils
@@ -32,12 +35,12 @@ license = AGPLv3
32
35
  url = https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2
33
36
  edit_on_github = False
34
37
  github_project = opencadc/caom2tools
35
- version = 1.7.3
38
+ version = 1.7.4
36
39
 
37
40
  [options]
38
41
  install_requires =
39
42
  cadcdata>=2.5.2
40
- caom2>=2.6
43
+ caom2>=2.6,<3
41
44
  astropy>=2.0
42
45
  spherical-geometry>=1.2.11
43
46
  vos>=3.1.1
@@ -51,9 +54,6 @@ test =
51
54
  flake8>=3.9
52
55
  xml-compare>=1.0.5
53
56
 
54
- [flake8]
55
- max-line-length = 120
56
-
57
57
  [entry_points]
58
58
  fits2caom2 = caom2utils.legacy:main_app
59
59
  caom2gen = caom2utils.caom2blueprint:caom2gen
@@ -106,7 +106,7 @@ setup(name=PACKAGENAME,
106
106
  use_2to3=False,
107
107
  setup_requires=['pytest-runner'],
108
108
  entry_points=entry_points,
109
- python_requires='>=3.8, <4',
109
+ python_requires='>=3.10, <3.15',
110
110
  packages=find_packages(),
111
111
  package_data={PACKAGENAME: ['data/*', 'tests/data/*', '*/data/*', '*/tests/data/*']},
112
112
  classifiers=[
@@ -114,6 +114,11 @@ setup(name=PACKAGENAME,
114
114
  'License :: OSI Approved :: GNU Affero General Public License v3',
115
115
  'Programming Language :: Python',
116
116
  'Programming Language :: Python :: 3',
117
+ 'Programming Language :: Python :: 3.10',
118
+ 'Programming Language :: Python :: 3.11',
119
+ 'Programming Language :: Python :: 3.12',
120
+ 'Programming Language :: Python :: 3.13',
121
+ 'Programming Language :: Python :: 3.14',
117
122
  ],
118
123
  cmdclass={
119
124
  'coverage': PyTest,
@@ -1,8 +0,0 @@
1
- usage: fits2caom2 [-h] [-V] [-d | -q | -v] [--dumpconfig] [--ignorePartialWCS]
2
- [-o OUT_OBS_XML]
3
- (-i IN_OBS_XML | --observation collection observationID)
4
- [--config CONFIG] [--default DEFAULT] [--override OVERRIDE]
5
- [--local LOCAL [LOCAL ...]] [--log LOG] [--keep] [--test]
6
- [--cert CERT]
7
- productID fileURI [fileURI ...]
8
- fits2caom2: error: one of the arguments -i/--in --observation is required
@@ -1,10 +0,0 @@
1
- usage: fits2caom2 [-h] [-V] [-d | -q | -v] [--dumpconfig] [--ignorePartialWCS]
2
- [-o OUT_OBS_XML]
3
- (-i IN_OBS_XML | --observation collection observationID)
4
- [--config CONFIG] [--default DEFAULT] [--override OVERRIDE]
5
- [--local LOCAL [LOCAL ...]] [--log LOG] [--keep] [--test]
6
- [--cert CERT]
7
- productID fileURI [fileURI ...]
8
- fits2caom2: error: too few arguments
9
-
10
-
@@ -1 +0,0 @@
1
- version = '1.7.3'
File without changes
File without changes
File without changes