Coverage for python / lsst / resources / eups.py: 33%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 08:32 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 08:32 +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.
12from __future__ import annotations
14__all__ = ("EupsResourcePath",)
16import logging
17import posixpath
18import urllib.parse
20from lsst.utils import getPackageDir
22from ._resourcePath import ResourcePath
23from .proxied import ProxiedResourcePath
24from .utils import os2posix
26log = logging.getLogger(__name__)
29class EupsResourcePath(ProxiedResourcePath):
30 """URI referring to an EUPS package.
32 These URIs look like: ``eups://daf_butler/configs/file.yaml``
33 where the network location is the EUPS package name.
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.
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 """
50 quotePaths = False
51 _proxy: ResourcePath | None = None
52 _default_namespace: str = "lsst"
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
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
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