Skip to content

Commit

Permalink
Merge pull request #19 from xsuite/release/v0.2.2
Browse files Browse the repository at this point in the history
Release 0.2.2
  • Loading branch information
freddieknets authored Jan 4, 2025
2 parents e5f1c5b + 08b03b3 commit 9397a02
Show file tree
Hide file tree
Showing 21 changed files with 330 additions and 92 deletions.
4 changes: 2 additions & 2 deletions make_release_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# Copyright (c) CERN, 2024. #
# ######################################### #

from xaux.tools import dev_make_release_branch
from xaux.dev_tools import make_release_branch
# sys.tracebacklimit = 0


dev_make_release_branch("xaux")
make_release_branch("xaux")
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "xaux"
version = "0.2.1"
version = "0.2.2"
description = "Support tools for Xsuite packages"
authors = ["Frederik F. Van der Veken <[email protected]>",
"Thomas Pugnat <[email protected]>",
Expand All @@ -12,7 +12,7 @@ include = ["LICENSE", "NOTICE"]


[tool.poetry.dependencies]
python = ">=3.8, <3.12"
python = ">=3.8"

[tool.poetry.dev-dependencies]
pytest = ">=7.3"
Expand Down
4 changes: 2 additions & 2 deletions release.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# Copyright (c) CERN, 2024. #
# ######################################### #

from xaux.tools import dev_release
from xaux.dev_tools import make_release
# sys.tracebacklimit = 0


dev_release("xaux")
make_release("xaux")
4 changes: 2 additions & 2 deletions rename_release_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# Copyright (c) CERN, 2024. #
# ######################################### #

from xaux.tools import dev_rename_release_branch
from xaux.dev_tools import rename_release_branch
# sys.tracebacklimit = 0


dev_rename_release_branch("xaux")
rename_release_branch("xaux")
20 changes: 20 additions & 0 deletions tests/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
for f in example_broken_link.txt example_link.txt
do
if [ -h $f ]
then
rm $f
fi
done

for f in example_file.txt
do
if [ -e $f ]
then
rm $f
fi
done

rm -r /eos/user/s/sixtadm/test_xboinc/*
rm -r /afs/cern.ch/user/s/sixtadm/public/test_xboinc
rm -r level1 level5_res

2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ def test_user(request):
+ "your access rights (is your ticket still alive?).\nAlternatively, specify the test "
+ "user account with the option `--user username`\nI Tried the following paths:\n "
+ "\n ".join(eos_paths_tried) + "\nThe relevant EosPath tests will be skipped.")
return {"test_user": test_user, "afs_path": afs_path, "skip_afs": skip_afs, "eos_path": eos_path, "skip_eos": skip_eos}
return {"test_user": test_user, "afs_path": afs_path, "skip_afs": skip_afs, "eos_path": eos_path, "skip_eos": skip_eos}
2 changes: 1 addition & 1 deletion tests/pytest_all_versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# files="test_fs.py test_fs_api.py test_fs_afs.py test_fs_eos.py"
files=''

for i in 8 9 10 11 # 12 13
for i in 8 9 10 11 12 13
do
source ~/miniforge3/bin/activate python3.$i
python -c "import sys; print(f'Testing xaux FS in Python version {sys.version.split()[0]}')"
Expand Down
26 changes: 26 additions & 0 deletions tests/test_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,41 @@ def test_nested_fs(test_user):
level6.unlink()
level6.symlink_to(level6_res)

for l in [level1, level2, level3, level4, level5, level6]:
assert l.lexists()
assert l.is_symlink()
for d in [level1_res, level2_res, level3_res, level4_res, level5_res, level6_res]:
assert d.exists()
assert d.is_dir()
assert not d.is_symlink()
for l, d in zip([level1, level2, level3, level4, level5, level6],
[level1_res, level2_res, level3_res, level4_res, level5_res, level6_res]):
assert l.resolve() == d

assert isinstance(level1, AfsPath)
assert isinstance(level2, LocalPath)
assert isinstance(level3, EosPath)
assert isinstance(level4, AfsPath)
assert isinstance(level5, EosPath)
assert isinstance(level6, LocalPath)
assert isinstance(level1_res, LocalPath)
assert isinstance(level2_res, EosPath)
assert isinstance(level3_res, AfsPath)
assert isinstance(level4_res, EosPath)
assert isinstance(level5_res, LocalPath)
assert isinstance(level6_res, AfsPath)

path = level1 / "level2" / "level3" / "level4" / "level5" / "level6"
assert isinstance(path, LocalPath)
parents = [f for f in path.parents]
assert len(parents) == 13
expected = [EosPath, AfsPath, EosPath, LocalPath, AfsPath,
AfsPath, AfsPath, AfsPath, AfsPath, AfsPath,
AfsPath, LocalPath, LocalPath]
assert all([isinstance(f, exp) for f, exp in zip(parents, expected)])
assert isinstance(path.resolve(), AfsPath)
parents_res = [f.resolve() for f in path.parents]
assert len(parents_res) == 13
expected_res = [LocalPath, EosPath, AfsPath, EosPath, LocalPath,
AfsPath, AfsPath, AfsPath, AfsPath, AfsPath,
AfsPath, LocalPath, LocalPath]
Expand Down
4 changes: 2 additions & 2 deletions tests/test_fs_eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ def test_eos_components(test_user):
broken_mgm_files.append(f"root:/eoshome.cern.ch/{file_ref}")
broken_mgm_files.append(f"root://afshome.cern.ch/{file_ref}")
for file in broken_mgm_files:
with pytest.raises(ValueError, match="Invalid EOS path specification"):
with pytest.raises(ValueError, match="Invalid EosPath specification"):
path = EosPath(file)
with pytest.raises(ValueError, match="Unknown path specification"):
with pytest.raises(ValueError, match="Unknown EosPath specification"):
path = FsPath(broken_mgm_files[-1])

2 changes: 1 addition & 1 deletion tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
from xaux import __version__

def test_version():
assert __version__ == '0.2.1'
assert __version__ == '0.2.2'

5 changes: 1 addition & 4 deletions xaux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
# Copyright (c) CERN, 2024. #
# ######################################### #

import sys
if sys.version_info[1] >= 12:
raise Exception("Must be using Python 3.8 - 3.11")

from .general import _pkg_root, __version__
from .protectfile import ProtectFile, ProtectFileError, get_hash
from .fs import FsPath, LocalPath, EosPath, AfsPath, afs_accessible, eos_accessible, is_egroup_member, cp, mv
from .dev_tools import import_package_version # Stub
7 changes: 7 additions & 0 deletions xaux/dev_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# copyright ############################### #
# This file is part of the Xaux Package. #
# Copyright (c) CERN, 2024. #
# ######################################### #

from .release_tools import make_release, make_release_branch, rename_release_branch
from .package_manager import *
50 changes: 31 additions & 19 deletions xaux/tools/gh.py → xaux/dev_tools/gh.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import json
from pathlib import Path
from subprocess import run, PIPE
from subprocess import run

_GIT_REPO = False
_GH_INSTALLED = False
Expand All @@ -11,37 +11,49 @@
def assert_git_repo():
global _GIT_REPO
if not _GIT_REPO:
cmd = run(["git", "status"], capture_output=True)
try:
cmd = run(["git", "status"], capture_output=True)
if cmd.returncode != 0:
raise GitError(f"{Path.cwd()} is not a git repository.")
else:
_GIT_REPO = True
except FileNotFoundError:
raise GitError("git is not installed.")

def assert_git_repo_name(repo):
try:
cmd = run(["git", "rev-parse", "--show-toplevel"], capture_output=True)
if cmd.returncode != 0:
raise GitError(f"{Path.cwd()} is not a git repository.")
else:
_GIT_REPO = True

def assert_git_repo_name(repo):
cmd = run(["git", "rev-parse", "--show-toplevel"], capture_output=True)
if cmd.returncode != 0:
raise GitError(f"{Path.cwd()} is not a git repository.")
else:
if Path(cmd.stdout.decode('UTF-8').strip()).name != repo:
raise GitError(f"{Path.cwd()} is not in the {repo} repository.")
if Path(cmd.stdout.decode('UTF-8').strip()).name != repo:
raise GitError(f"{Path.cwd()} is not in the {repo} repository.")
except FileNotFoundError:
raise GitError("git is not installed.")

def assert_gh_installed():
global _GH_INSTALLED
if not _GH_INSTALLED:
cmd = run(["gh", "--version"], capture_output=True)
if cmd.returncode != 0:
try:
cmd = run(["gh", "--version"], capture_output=True)
if cmd.returncode != 0:
raise GhError("gh is not installed.")
else:
_GH_INSTALLED = True
except FileNotFoundError:
raise GhError("gh is not installed.")
else:
_GH_INSTALLED = True

def assert_poetry_installed():
global _POETRY_INSTALLED
if not _POETRY_INSTALLED:
cmd = run(["poetry", "--version"], capture_output=True)
if cmd.returncode != 0:
try:
cmd = run(["poetry", "--version"], capture_output=True)
if cmd.returncode != 0:
raise PoetryError("poetry is not installed.")
else:
_POETRY_INSTALLED = True
except FileNotFoundError:
raise PoetryError("poetry is not installed.")
else:
_POETRY_INSTALLED = True


def git_assert_working_tree_clean():
Expand Down
151 changes: 151 additions & 0 deletions xaux/dev_tools/package_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# copyright ############################### #
# This file is part of the Xaux Package. #
# Copyright (c) CERN, 2024. #
# ######################################### #

import os
import sys
import json
import importlib
import urllib.request
from shutil import rmtree
from subprocess import run
from contextlib import contextmanager
from packaging.version import Version

from ..general import _pkg_root


_PACKAGE_PATH = _pkg_root / "lib" / "_xaux_pkg_vers"

xsuite_pkgs = ['xaux', 'xobjects', 'xdeps', 'xtrack', 'xpart', 'xfields', 'xcoll', 'xdyna', 'xboinc']


@contextmanager
def import_package_version(package_name, version, wipe_cache=False):
"""
Context manager to temporarily import a specific version of a package.
"""
package_path = _PACKAGE_PATH / package_name / version
if not package_path.exists():
install_package_version(package_name, version)

original_sys_path = sys.path.copy()
original_sys_modules = sys.modules.copy()
original_sys_meta_path = sys.meta_path.copy()
pkgs = _get_available_packages_in_path(package_path)
for pkg in pkgs:
_remove_import_from_sys(pkg)
for finder_name in sys.meta_path:
if package_name in str(finder_name):
sys.meta_path.remove(finder_name)
if wipe_cache:
importlib.invalidate_caches()
for finder_name in sys.meta_path:
if 'editable' in str(finder_name):
sys.meta_path.remove(finder_name)
sys.path.insert(0, package_path.as_posix())
try:
module = importlib.import_module(package_name)
yield module
finally:
sys.path = original_sys_path
sys.modules = original_sys_modules
sys.meta_path = original_sys_meta_path


def install_package_version(package_name, version, overwrite=False):
"""
Installs a specific version of a package.
"""
full_package_name = f"{package_name}=={version}"
package_path = _PACKAGE_PATH / package_name / version
if package_path.exists() and not overwrite:
print(f"Package already installed in {package_path}. "
+ "Use `overwrite=True` if this is the intended goal.")
else:
if package_path.exists():
rmtree(package_path)
package_path.mkdir(parents=True)
try:
print(f"Installing package {full_package_name}")
cmd = run([sys.executable, "-m", "pip", "install", full_package_name,
f"--target={package_path}"], capture_output=True)
if cmd.returncode != 0:
print(f"Installation failed for {full_package_name}:\n{cmd.stderr.decode()}")
except Exception as e:
print(f"An error occurred: {e}\nCommand output:{cmd.stderr.decode()}")


def get_package_versions(package_name):
"""
Get all available versions of a package from PyPI, sorted by newest last.
"""
data = json.loads(urllib.request.urlopen(f"https://pypi.org/pypi/{package_name}/json").read())
return sorted(list(data['releases'].keys()), key=Version)


def get_latest_package_version(package_name):
"""
Get the latest version of a package from PyPI.
"""
data = json.loads(urllib.request.urlopen(f"https://pypi.org/pypi/{package_name}/json").read())
return data['info']['version']


def get_package_dependencies(package_name):
"""
Get the dependencies of a package from PyPI.
"""
data = json.loads(urllib.request.urlopen(f"https://pypi.org/pypi/{package_name}/json").read())
return data['info']['requires_dist']


def get_package_version_dependencies(package_name, version, skip=[]):
"""
Get the package versions of the dependencies for a specific version of `package_name`.
TODO: This function is not working as intended when `numpy` is a dependency and already imported.
"""
if not hasattr(skip, '__iter__') or isinstance(skip, str):
skip = [skip]
deps = {}
with import_package_version(package_name, version, wipe_cache=True):
package_path = _PACKAGE_PATH / package_name / version
pkgs = _get_available_packages_in_path(package_path)
pkgs = [pkg for pkg in pkgs if pkg not in skip]
# for pkg in pkgs:
# _remove_import_from_sys(pkg)
for pkg in pkgs:
if pkg in ['numpy', 'numba', 'scipy']:
# These are being difficult
deps[pkg] = None
else:
# mod = _import_package_from_path(pkg, package_path)
mod = importlib.import_module(pkg)
assert mod.__file__.startswith(package_path.as_posix())
deps[pkg] = mod.__version__ if hasattr(mod, '__version__') else None
return deps


def _get_available_packages_in_path(path):
return [pkg.name for pkg in path.glob('*') if (path / pkg).is_dir() \
and not pkg.name.startswith('_') and not '.' in pkg.name \
and not pkg.name.endswith('dist-info') and not pkg.name == 'bin']


def _remove_import_from_sys(package_name):
for mod_name in list(sys.modules):
if package_name == mod_name or mod_name.startswith(package_name + '.'):
del sys.modules[mod_name]

def _import_package_from_path(package_name, package_path):
original_sys_path = sys.path.copy()
original_sys_meta_path = sys.meta_path.copy()
for finder_name in sys.meta_path:
if package_name in str(finder_name):
sys.meta_path.remove(finder_name)
sys.path.insert(0, package_path.as_posix())
module = importlib.import_module(package_name)
sys.path = original_sys_path
sys.meta_path = original_sys_meta_path
return module
Loading

0 comments on commit 9397a02

Please sign in to comment.