Compare commits

..

2 Commits

Author SHA1 Message Date
Tyler Goodlet 01f38d2f22 Use `platformdirs` for `.config.get_rt_dir()`
Thanks to the `tox`-dev community for such a lovely pkg which seems to
solves all the current cross-platform user-dir problems B)

Also this,
- now passes `platformdirs.user_runtime_dir(appname='tractor')`
  and allows caller to pass an optional `subdir` under `tractor/`
  if desired.
- drops the `.config._rtdir: Path` mod var.
- bumps the lock file with the new dep.
2025-10-06 11:40:56 -04:00
Tyler Goodlet 6b3cc72e5c Mv `load_module_from_path()` to a new `._code_load` submod 2025-09-25 12:19:12 -04:00
5 changed files with 81 additions and 40 deletions

View File

@ -63,11 +63,11 @@ dev = [
"stackscope>=0.2.2,<0.3", "stackscope>=0.2.2,<0.3",
# ^ requires this? # ^ requires this?
"typing-extensions>=4.14.1", "typing-extensions>=4.14.1",
"pyperclip>=1.9.0", "pyperclip>=1.9.0",
"prompt-toolkit>=3.0.50", "prompt-toolkit>=3.0.50",
"xonsh>=0.19.2", "xonsh>=0.19.2",
"psutil>=7.0.0", "psutil>=7.0.0",
"platformdirs>=4.4.0",
] ]
# TODO, add these with sane versions; were originally in # TODO, add these with sane versions; were originally in
# `requirements-docs.txt`.. # `requirements-docs.txt`..

View File

@ -2,14 +2,12 @@
`tractor.log`-wrapping unit tests. `tractor.log`-wrapping unit tests.
''' '''
import importlib
from pathlib import Path from pathlib import Path
import shutil import shutil
import sys
from types import ModuleType
import pytest import pytest
import tractor import tractor
from tractor import _code_load
def test_root_pkg_not_duplicated_in_logger_name(): def test_root_pkg_not_duplicated_in_logger_name():
@ -37,31 +35,6 @@ def test_root_pkg_not_duplicated_in_logger_name():
assert 'mod' not in sublog.name assert 'mod' not in sublog.name
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def test_implicit_mod_name_applied_for_child( def test_implicit_mod_name_applied_for_child(
testdir: pytest.Pytester, testdir: pytest.Pytester,
loglevel: str, loglevel: str,
@ -109,7 +82,7 @@ def test_implicit_mod_name_applied_for_child(
# XXX NOTE, once the "top level" pkg mod has been # XXX NOTE, once the "top level" pkg mod has been
# imported, we can then use `import` syntax to # imported, we can then use `import` syntax to
# import it's sub-pkgs and modules. # import it's sub-pkgs and modules.
pkgmod = load_module_from_path( pkgmod = _code_load.load_module_from_path(
Path(pkg / '__init__.py'), Path(pkg / '__init__.py'),
module_name=proj_name, module_name=proj_name,
) )

View File

@ -0,0 +1,48 @@
# tractor: structured concurrent "actors".
# Copyright 2018-eternity Tyler Goodlet.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
(Hot) coad (re-)load utils for python.
'''
import importlib
from pathlib import Path
import sys
from types import ModuleType
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module

View File

@ -22,7 +22,6 @@ from __future__ import annotations
from contextvars import ( from contextvars import (
ContextVar, ContextVar,
) )
import os
from pathlib import Path from pathlib import Path
from typing import ( from typing import (
Any, Any,
@ -30,6 +29,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
) )
import platformdirs
from trio.lowlevel import current_task from trio.lowlevel import current_task
if TYPE_CHECKING: if TYPE_CHECKING:
@ -172,22 +172,31 @@ def current_ipc_ctx(
return ctx return ctx
# std ODE (mutable) app state location
_rtdir: Path = Path(os.environ['XDG_RUNTIME_DIR'])
def get_rt_dir( def get_rt_dir(
subdir: str = 'tractor' subdir: str|Path|None = None,
) -> Path: ) -> Path:
''' '''
Return the user "runtime dir" where most userspace apps stick Return the user "runtime dir", the file-sys location where most
their IPC and cache related system util-files; we take hold userspace apps stick their IPC and cache related system
of a `'XDG_RUNTIME_DIR'/tractor/` subdir by default. util-files.
On linux we take use a `'${XDG_RUNTIME_DIR}/tractor/` subdir by
default but equivalents are mapped for each platform using
the lovely `platformdirs`.
''' '''
rtdir: Path = _rtdir / subdir _rt_dir: Path = Path(
platformdirs.user_runtime_dir(
appname='tractor',
),
)
if subdir:
rtdir: Path = _rt_dir / subdir
if not rtdir.is_dir(): if not rtdir.is_dir():
rtdir.mkdir() rtdir.mkdir()
return rtdir return rtdir

13
uv.lock
View File

@ -1,5 +1,5 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.11" requires-python = ">=3.11"
[[package]] [[package]]
@ -236,6 +236,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
] ]
[[package]]
name = "platformdirs"
version = "4.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.5.0" version = "1.5.0"
@ -387,6 +396,7 @@ dependencies = [
dev = [ dev = [
{ name = "greenback" }, { name = "greenback" },
{ name = "pexpect" }, { name = "pexpect" },
{ name = "platformdirs" },
{ name = "prompt-toolkit" }, { name = "prompt-toolkit" },
{ name = "psutil" }, { name = "psutil" },
{ name = "pyperclip" }, { name = "pyperclip" },
@ -412,6 +422,7 @@ requires-dist = [
dev = [ dev = [
{ name = "greenback", specifier = ">=1.2.1,<2" }, { name = "greenback", specifier = ">=1.2.1,<2" },
{ name = "pexpect", specifier = ">=4.9.0,<5" }, { name = "pexpect", specifier = ">=4.9.0,<5" },
{ name = "platformdirs", specifier = ">=4.4.0" },
{ name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "prompt-toolkit", specifier = ">=3.0.50" },
{ name = "psutil", specifier = ">=7.0.0" }, { name = "psutil", specifier = ">=7.0.0" },
{ name = "pyperclip", specifier = ">=1.9.0" }, { name = "pyperclip", specifier = ">=1.9.0" },