RFC: rewrite config.paths._get_dir_path as a class method#17554
RFC: rewrite config.paths._get_dir_path as a class method#17554pllim merged 1 commit intoastropy:mainfrom
config.paths._get_dir_path as a class method#17554Conversation
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
|
👋 Thank you for your draft pull request! Do you know that you can use |
astropy/config/paths.py
Outdated
| _, _, fallback = cls.__name__.rpartition("_") | ||
| if ( | ||
| (xdg_dir := os.getenv(f"XDG_{fallback.upper()}_HOME")) is not None |
There was a problem hiding this comment.
fallback is used for constructing the name of an environment variable. Having a fallback parameter with the type Literal["cache", "config"] lets type checkers ensure that the environment variable that will be looked up is valid. The change here removes that information from the type system because the __name__ of a subclass of _SetTempPath is not restricted.
The current annotation is not perfect because it does not prevent calls like _get_dir_path(..., set_temp_cache, "config"), so it can be a good idea to introduce a class attribute for set_temp_cache and set_temp_config to eliminate such calling errors, but trying to extract something from the class name is not good enough.
There was a problem hiding this comment.
I hear you. How about this ?
4f50fa9 to
4d77c3f
Compare
|
|
||
| return wrapper | ||
|
|
||
| @classmethod |
There was a problem hiding this comment.
I'm intentionally avoiding astropy.utils.decorators.classproperty because this module is so low in the stack that I don't think it's wise to make it depend on other parts of astropy
|
I'm starting to get the impression that |
config.paths._get_dir_path
|
I'm not sure what you mean here @pllim. Are you worried that we paint ourselves in a corner with this PR ? IMO, removing existing complexity will likely help more than hurt with adding new functionality. But I'm actually neutral.
This comment grew on me and it now seems clear to me that this function belongs in this class. I'm going to push this change now, hopefully you'll agree with it when you have more time. |
4d77c3f to
c716fb5
Compare
config.paths._get_dir_pathconfig.paths._get_dir_path as a class method
c716fb5 to
2ff0d45
Compare
|
I am not sure yet but I have a feeling that all this code will change when we support custom env var. |
|
It will indeed, but I expect that change will be almost completely additive. I could open a follow up PR as a draft if it helps lifting your hesitation. |
2ff0d45 to
c51d91c
Compare
astropy/config/paths.py
Outdated
| @@ -145,6 +119,7 @@ def get_cache_dir(rootname: str = "astropy") -> str: | |||
| class _SetTempPath: | |||
| _temp_path: Path | None = None | |||
| _default_path_getter: Callable[[str], str] | |||
There was a problem hiding this comment.
Do we need _default_path_getter at all anymore?
There was a problem hiding this comment.
Not really. It can easily be refactored out as follow:
diff --git a/astropy/config/paths.py b/astropy/config/paths.py
index 35ee8b2786..73e62ddb1b 100644
--- a/astropy/config/paths.py
+++ b/astropy/config/paths.py
@@ -118,7 +118,6 @@ if get_cache_dir_path.__doc__ is not None:
class _SetTempPath:
_temp_path: Path | None = None
- _default_path_getter: Callable[[str], str]
_fallback_dirname: Literal["cache", "config"]
def __init__(
@@ -133,8 +132,9 @@ class _SetTempPath:
def __enter__(self) -> str:
self.__class__._temp_path = self._path
+ mtd = get_cache_dir if self._fallback_dirname == "cache" else get_config_dir
try:
- return self._default_path_getter("astropy")
+ return mtd(rootname="astropy")
except Exception:
self.__class__._temp_path = self._prev_path
raise
@@ -211,7 +211,6 @@ class set_temp_config(_SetTempPath):
context (default: False).
"""
- _default_path_getter = staticmethod(get_config_dir)
_fallback_dirname = "config"
def __enter__(self) -> str:
@@ -267,7 +266,6 @@ class set_temp_cache(_SetTempPath):
context (default: False).
"""
- _default_path_getter = staticmethod(get_cache_dir)
_fallback_dirname = "cache"and it would be a good idea to do something similar, as it currently only serves as a confusing layer of indirection (IMO).
However, I think it's out of scope here. Can I do it as a follow up PR ?
There was a problem hiding this comment.
Can't you use cls.get_dir_path instead of mtd?
There was a problem hiding this comment.
indeed, that's a lot cleaner !
astropy/config/paths.py
Outdated
| class _SetTempPath: | ||
| _temp_path: Path | None = None | ||
| _default_path_getter: Callable[[str], str] | ||
| _fallback_dirname: Literal["cache", "config"] |
There was a problem hiding this comment.
There should be a comment to explain why there are only two allowed values.
c51d91c to
7b9fbb4
Compare
astropy/config/paths.py
Outdated
| return wrapper | ||
|
|
||
| @classmethod | ||
| def get_dir_path(cls, rootname: str) -> Path: |
There was a problem hiding this comment.
Why should this be a public method?
There was a problem hiding this comment.
My reasoning is that this method being used outside the class itself, it shouldn't be private, though I understand there's an ambiguity here about how we interpret "private".
There was a problem hiding this comment.
As Eero pointed out above, might be slightly less controversial to keep the leading underscore since the original name already has it. There is no expectation that this new class method will be used beyond this module, right?
There was a problem hiding this comment.
There is no expectation that this new class method will be used beyond this module, right?
indeed, there isn't
astropy/config/paths.py
Outdated
| else: | ||
| linkto = None | ||
|
|
||
| return _find_or_create_root_dir(cls._fallback_dirname, linkto, rootname) |
There was a problem hiding this comment.
If _find_or_create_root_dir were a class method too then we wouldn't have to pass cls._fallback_dirname as an argument when we call it.
There was a problem hiding this comment.
good point, let me give this a spin
13a9bf0 to
e6aa8af
Compare
6b9683e to
4170299
Compare
|
@eerovaher , do you want to do a final review? |
astropy/config/paths.py
Outdated
| return path.resolve() | ||
|
|
||
| if ( | ||
| (xdg_dir := os.getenv(f"XDG_{cls._fallback_dirname.upper()}_HOME")) |
There was a problem hiding this comment.
Currently the code is storing the name of the corresponding directory on the class as _fallback_dirname (by the way, that's not a good name) and constructing the name of the environment variable from _fallback_dirname at runtime. It looks like this is trying to be too clever and it would be simpler to store both the directory name and the environment variable name in separate class attributes.
There was a problem hiding this comment.
all fair points. I've taken this into account in my latest update.
4170299 to
b842871
Compare
astropy/config/paths.py
Outdated
| # This base class serves as a deduplication layer for its only two intended | ||
| # children (set_temp_cache and set_temp_config) | ||
| _directory_type: Literal["cache", "config"] | ||
| _directory_env_var: str |
There was a problem hiding this comment.
_directory_env_var should be annotated using Literal because it can only hold a small set of values which we are not free to choose because they are defined by an external standard. It is better to document that in the annotation than in a comment.
There was a problem hiding this comment.
I'm not satisfied with this suggestion because it leaves room for 2 invalid cases, namely
_directory_typ = "cache"
_directory_env_var = "XDG_CONFIG_HOME"and
_directory_typ = "config"
_directory_env_var = "XDG_CACHE_HOME"So I refactored this again into an enum. Now, the pairing is expressed in the annotation.
There was a problem hiding this comment.
update: my enum refactor was broken beyond repair. Welp, maybe I'll stop chasing perfection here.
There was a problem hiding this comment.
I still think that it's simpler to check that these two attributes that are right next to each other agree when reading the code than it would be to understand some clever mechanism that would generate one from the other at runtime.
There was a problem hiding this comment.
Agreed. I'm still slightly frustrated by the outcome but I can certainly live with it !
b05d4ea to
7531359
Compare
eerovaher
left a comment
There was a problem hiding this comment.
This pull request was triggered by the observation that the cls and fallback parameters of _get_dir_path() were not independent from each other, which suggested that the fallback parameter should instead be a class attribute in the _SetTempPath subclasses. Another observation was that because _get_dir_path() expected cls to be a subclass of _SetTempPath then it essentially was a class method of _SetTempPath even if it was not explicitly decorated as such. A few more similar changes followed from that.
Overall this pull request simplifies the code because it ensures that functions that are tightly coupled with _SetTempPath are defined as methods of that class instead of being scattered all over the module.
astropy/config/paths.py
Outdated
| # This base class serves as a deduplication layer for its only two intended | ||
| # children (set_temp_cache and set_temp_config) | ||
| _directory_type: Literal["cache", "config"] | ||
| _xdg_directory_env_var: Literal["XDG_CACHE_HOME", "XDG_CONFIG_HOME"] |
There was a problem hiding this comment.
Given the plan to follow up with #17567 for a different env var name, maybe no need to hardcode xdg into the new class attribute now?
7531359 to
6956a5c
Compare
|
Thanks for your patience! |
Description
Follow up to #17188 and #17546
This was initially discussed in #17546 (comment). I'll quote myself here for convenience: