python.py/r.py: fix type issues with classproperty/constant (#50059)

This commit is contained in:
Harmen Stoppels 2025-04-22 09:53:34 +02:00 committed by GitHub
parent 5e7925c502
commit 60f6f8d836
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 98 additions and 61 deletions

View File

@ -225,8 +225,10 @@ def setup(sphinx):
("py:class", "llnl.util.lang.T"), ("py:class", "llnl.util.lang.T"),
("py:class", "llnl.util.lang.KT"), ("py:class", "llnl.util.lang.KT"),
("py:class", "llnl.util.lang.VT"), ("py:class", "llnl.util.lang.VT"),
("py:class", "llnl.util.lang.ClassPropertyType"),
("py:obj", "llnl.util.lang.KT"), ("py:obj", "llnl.util.lang.KT"),
("py:obj", "llnl.util.lang.VT"), ("py:obj", "llnl.util.lang.VT"),
("py:obj", "llnl.util.lang.ClassPropertyType"),
] ]
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.

View File

@ -15,7 +15,19 @@
import typing import typing
import warnings import warnings
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Callable, Dict, Iterable, List, Mapping, Optional, Tuple, TypeVar from typing import (
Any,
Callable,
Dict,
Generic,
Iterable,
List,
Mapping,
Optional,
Tuple,
TypeVar,
Union,
)
# Ignore emacs backups when listing modules # Ignore emacs backups when listing modules
ignore_modules = r"^\.#|~$" ignore_modules = r"^\.#|~$"
@ -1047,19 +1059,28 @@ def __exit__(self, exc_type, exc_value, tb):
return True return True
class classproperty: ClassPropertyType = TypeVar("ClassPropertyType")
class classproperty(Generic[ClassPropertyType]):
"""Non-data descriptor to evaluate a class-level property. The function that performs """Non-data descriptor to evaluate a class-level property. The function that performs
the evaluation is injected at creation time and take an instance (could be None) and the evaluation is injected at creation time and takes an owner (i.e., the class that
an owner (i.e. the class that originated the instance) originated the instance).
""" """
def __init__(self, callback): def __init__(self, callback: Callable[[Any], ClassPropertyType]) -> None:
self.callback = callback self.callback = callback
def __get__(self, instance, owner): def __get__(self, instance, owner) -> ClassPropertyType:
return self.callback(owner) return self.callback(owner)
#: A type alias that represents either a classproperty descriptor or a constant value of the same
#: type. This allows derived classes to override a computed class-level property with a constant
#: value while retaining type compatibility.
ClassProperty = Union[ClassPropertyType, classproperty[ClassPropertyType]]
class DeprecatedProperty: class DeprecatedProperty:
"""Data descriptor to error or warn when a deprecated property is accessed. """Data descriptor to error or warn when a deprecated property is accessed.

View File

@ -13,9 +13,9 @@
import archspec import archspec
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import HeaderList, LibraryList, join_path from llnl.util.filesystem import HeaderList, LibraryList, join_path
from llnl.util.lang import ClassProperty, classproperty, match_predicate
import spack.builder import spack.builder
import spack.config import spack.config
@ -139,7 +139,7 @@ def view_file_conflicts(self, view, merge_map):
ext_map = view.extensions_layout.extension_map(self.extendee_spec) ext_map = view.extensions_layout.extension_map(self.extendee_spec)
namespaces = set(x.package.py_namespace for x in ext_map.values()) namespaces = set(x.package.py_namespace for x in ext_map.values())
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace) namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
find_namespace = lang.match_predicate(namespace_re) find_namespace = match_predicate(namespace_re)
if self.py_namespace in namespaces: if self.py_namespace in namespaces:
conflicts = list(x for x in conflicts if not find_namespace(x)) conflicts = list(x for x in conflicts if not find_namespace(x))
@ -206,7 +206,7 @@ def remove_files_from_view(self, view, merge_map):
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
) )
if self.py_namespace in remaining_namespaces: if self.py_namespace in remaining_namespaces:
namespace_init = lang.match_predicate( namespace_init = match_predicate(
r"site-packages/{0}/__init__.py".format(self.py_namespace) r"site-packages/{0}/__init__.py".format(self.py_namespace)
) )
ignore_namespace = True ignore_namespace = True
@ -324,6 +324,27 @@ def get_external_python_for_prefix(self):
raise StopIteration("No external python could be detected for %s to depend on" % self.spec) raise StopIteration("No external python could be detected for %s to depend on" % self.spec)
def _homepage(cls: "PythonPackage") -> Optional[str]:
"""Get the homepage from PyPI if available."""
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/project/{name}/"
return None
def _url(cls: "PythonPackage") -> Optional[str]:
if cls.pypi:
return f"https://files.pythonhosted.org/packages/source/{cls.pypi[0]}/{cls.pypi}"
return None
def _list_url(cls: "PythonPackage") -> Optional[str]:
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/simple/{name}/"
return None
class PythonPackage(PythonExtension): class PythonPackage(PythonExtension):
"""Specialized class for packages that are built using pip.""" """Specialized class for packages that are built using pip."""
@ -351,25 +372,9 @@ class PythonPackage(PythonExtension):
py_namespace: Optional[str] = None py_namespace: Optional[str] = None
@lang.classproperty homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
def homepage(cls) -> Optional[str]: # type: ignore[override] url: ClassProperty[Optional[str]] = classproperty(_url)
if cls.pypi: list_url: ClassProperty[Optional[str]] = classproperty(_list_url)
name = cls.pypi.split("/")[0]
return f"https://pypi.org/project/{name}/"
return None
@lang.classproperty
def url(cls) -> Optional[str]:
if cls.pypi:
return f"https://files.pythonhosted.org/packages/source/{cls.pypi[0]}/{cls.pypi}"
return None
@lang.classproperty
def list_url(cls) -> Optional[str]: # type: ignore[override]
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/simple/{name}/"
return None
@property @property
def python_spec(self) -> Spec: def python_spec(self) -> Spec:

View File

@ -3,8 +3,8 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
from typing import Optional, Tuple from typing import Optional, Tuple
import llnl.util.lang as lang
from llnl.util.filesystem import mkdirp from llnl.util.filesystem import mkdirp
from llnl.util.lang import ClassProperty, classproperty
from spack.directives import extends from spack.directives import extends
@ -54,6 +54,32 @@ def install(self, pkg, spec, prefix):
pkg.module.R(*args) pkg.module.R(*args)
def _homepage(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/package={cls.cran}"
elif cls.bioc:
return f"https://bioconductor.org/packages/{cls.bioc}"
return None
def _url(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/{cls.cran}_{str(list(cls.versions)[0])}.tar.gz"
return None
def _list_url(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/Archive/{cls.cran}/"
return None
def _git(cls: "RPackage") -> Optional[str]:
if cls.bioc:
return f"https://git.bioconductor.org/packages/{cls.bioc}"
return None
class RPackage(Package): class RPackage(Package):
"""Specialized class for packages that are built using R. """Specialized class for packages that are built using R.
@ -77,24 +103,7 @@ class RPackage(Package):
extends("r") extends("r")
@lang.classproperty homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
def homepage(cls): url: ClassProperty[Optional[str]] = classproperty(_url)
if cls.cran: list_url: ClassProperty[Optional[str]] = classproperty(_list_url)
return f"https://cloud.r-project.org/package={cls.cran}" git: ClassProperty[Optional[str]] = classproperty(_git)
elif cls.bioc:
return f"https://bioconductor.org/packages/{cls.bioc}"
@lang.classproperty
def url(cls):
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/{cls.cran}_{str(list(cls.versions)[0])}.tar.gz"
@lang.classproperty
def list_url(cls):
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/Archive/{cls.cran}/"
@lang.classproperty
def git(cls):
if cls.bioc:
return f"https://git.bioconductor.org/packages/{cls.bioc}"

View File

@ -5,8 +5,8 @@
from typing import Optional, Tuple from typing import Optional, Tuple
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.lang import ClassProperty, classproperty
import spack.builder import spack.builder
import spack.spec import spack.spec
@ -19,6 +19,12 @@
from spack.util.executable import Executable, ProcessError from spack.util.executable import Executable, ProcessError
def _homepage(cls: "RacketPackage") -> Optional[str]:
if cls.racket_name:
return f"https://pkgs.racket-lang.org/package/{cls.racket_name}"
return None
class RacketPackage(PackageBase): class RacketPackage(PackageBase):
"""Specialized class for packages that are built using Racket's """Specialized class for packages that are built using Racket's
`raco pkg install` and `raco setup` commands. `raco pkg install` and `raco setup` commands.
@ -37,13 +43,7 @@ class RacketPackage(PackageBase):
extends("racket", when="build_system=racket") extends("racket", when="build_system=racket")
racket_name: Optional[str] = None racket_name: Optional[str] = None
parallel = True homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
@lang.classproperty
def homepage(cls):
if cls.racket_name:
return "https://pkgs.racket-lang.org/package/{0}".format(cls.racket_name)
return None
@spack.builder.builder("racket") @spack.builder.builder("racket")

View File

@ -28,7 +28,7 @@
import llnl.util.filesystem as fsys import llnl.util.filesystem as fsys
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.lang import classproperty, memoized from llnl.util.lang import ClassProperty, classproperty, memoized
import spack.config import spack.config
import spack.dependency import spack.dependency
@ -701,10 +701,10 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
_verbose = None _verbose = None
#: Package homepage where users can find more information about the package #: Package homepage where users can find more information about the package
homepage: Optional[str] = None homepage: ClassProperty[Optional[str]] = None
#: Default list URL (place to find available versions) #: Default list URL (place to find available versions)
list_url: Optional[str] = None list_url: ClassProperty[Optional[str]] = None
#: Link depth to which list_url should be searched for new versions #: Link depth to which list_url should be searched for new versions
list_depth = 0 list_depth = 0