Coverage for python / lsst / resources / eups.py: 33%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 08:44 +0000

1# This file is part of lsst-resources. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12from __future__ import annotations 

13 

14__all__ = ("EupsResourcePath",) 

15 

16import logging 

17import posixpath 

18import urllib.parse 

19 

20from lsst.utils import getPackageDir 

21 

22from ._resourcePath import ResourcePath 

23from .proxied import ProxiedResourcePath 

24from .utils import os2posix 

25 

26log = logging.getLogger(__name__) 

27 

28 

29class EupsResourcePath(ProxiedResourcePath): 

30 """URI referring to an EUPS package. 

31 

32 These URIs look like: ``eups://daf_butler/configs/file.yaml`` 

33 where the network location is the EUPS package name. 

34 

35 Internally they are proxied by either ``file`` URIs or ``resource`` URIs. 

36 If an ``{product}_DIR`` environment variable is found it will be used 

37 and internally a ``file`` URI will be created. If no environment variable 

38 is found an attempt will be made to convert the EUPS product name to 

39 a python package and a ``resource`` URI will be returned with 

40 ``/resources`` appended. The convention is that any package supporting 

41 an EUPS URI outside an EUPS environment will also have made available 

42 the support files as package resources. 

43 

44 If it is known that the package supports package resources it is always 

45 better to use that URI form explicitly since it is more robust since 

46 not all EUPS packages can reliably be converted to python packages 

47 without EUPS. 

48 """ 

49 

50 quotePaths = False 

51 _proxy: ResourcePath | None = None 

52 _default_namespace: str = "lsst" 

53 

54 def _set_proxy(self) -> None: 

55 """Calculate the internal `ResourcePath` corresponding to the public 

56 version. 

57 """ 

58 # getPackageDir returns an absolute path. 

59 try: 

60 eups_path = getPackageDir(self.netloc) 

61 log.debug("Found EUPS package %s via env var", self.netloc) 

62 except LookupError: 

63 eups_path = "" 

64 if eups_path: 

65 # Must convert this path into a file URI. 

66 new_path = posixpath.join(os2posix(eups_path), os2posix(self.path.lstrip("/"))) 

67 parsed = self._uri._replace(path=urllib.parse.quote(new_path), scheme="file", netloc="") 

68 self._proxy = ResourcePath(parsed, forceDirectory=self.dirLike, forceAbsolute=True) 

69 return 

70 

71 # If there is no _DIR env var we need to look for python package 

72 # resource. There is no guaranteed way to generated a python package 

73 # from an EUPS product name. 

74 # daf_butler -> lsst.daf.butler 

75 # image_cutout_backend -> lsst.image_cutout_backend 

76 # astro_metadata_translator -> astro_metadata_translator 

77 product = self.netloc 

78 variants = ( 

79 product, 

80 self._default_namespace + "." + product.replace("_", "."), 

81 self._default_namespace + "." + product, 

82 product.replace("_", "."), 

83 ) 

84 for variant in variants: 

85 proxy = ResourcePath(f"resource://{variant}/resources", forceDirectory=True) 

86 # This can be slow because package imports happen but there is 

87 # no other way to check that we have the correct variant. 

88 log.debug("Trying variant %s", proxy) 

89 if proxy.exists(): 

90 self._proxy = proxy 

91 if self.path: 

92 self._proxy = self._proxy.join(self.path.lstrip("/"), forceDirectory=self.dirLike) 

93 log.debug(f"Found variant {variant}") 

94 return 

95 

96 # Can not find an actively set up package or resources for this EUPS 

97 # product. Return without setting a proxy to allow standard URI 

98 # path manipulations to happen but defer failure until someone tries 

99 # to use the proxy for read. 

100 log.debug("Could not find any files corresponding to %s", self) 

101 return