1
0
mirror of https://github.com/msberends/AMR.git synced 2026-06-29 14:56:24 +02:00

(v3.0.1.9075) another go

This commit is contained in:
2026-06-26 12:06:12 +02:00
parent 3c17679382
commit 5f6372342e
3 changed files with 186 additions and 73 deletions

View File

@@ -1,5 +1,5 @@
Package: AMR Package: AMR
Version: 3.0.1.9074 Version: 3.0.1.9075
Date: 2026-06-26 Date: 2026-06-26
Title: Antimicrobial Resistance Data Analysis Title: Antimicrobial Resistance Data Analysis
Description: Functions to simplify and standardise antimicrobial resistance (AMR) Description: Functions to simplify and standardise antimicrobial resistance (AMR)

View File

@@ -1,4 +1,4 @@
# AMR 3.0.1.9074 # AMR 3.0.1.9075
Planned as v3.1.0, end of June 2026. Planned as v3.1.0, end of June 2026.

View File

@@ -33,18 +33,20 @@
rm -rf ../PythonPackage/AMR/* rm -rf ../PythonPackage/AMR/*
mkdir -p ../PythonPackage/AMR/AMR mkdir -p ../PythonPackage/AMR/AMR
# Output Python file # Output files
setup_file="../PythonPackage/AMR/setup.py" setup_file="../PythonPackage/AMR/setup.py"
functions_file="../PythonPackage/AMR/AMR/functions.py"
datasets_file="../PythonPackage/AMR/AMR/datasets.py"
init_file="../PythonPackage/AMR/AMR/__init__.py" init_file="../PythonPackage/AMR/AMR/__init__.py"
engine_file="../PythonPackage/AMR/AMR/_engine.py"
datasets_file="../PythonPackage/AMR/AMR/datasets.py"
functions_file="../PythonPackage/AMR/AMR/functions.py"
beta_file="../PythonPackage/AMR/AMR/beta.py"
description_file="../DESCRIPTION" description_file="../DESCRIPTION"
# Write header to the datasets Python file, including the convert_to_python function # ---- _engine.py: R environment setup and installation logic ---- #
cat <<EOL > "$datasets_file"
cat <<'EOL' > "$engine_file"
import os import os
import sys import sys
import pandas as pd
import importlib.metadata as metadata import importlib.metadata as metadata
# Get the path to the virtual environment # Get the path to the virtual environment
@@ -56,45 +58,47 @@ os.makedirs(r_lib_path, exist_ok=True)
os.environ['R_LIBS_SITE'] = r_lib_path os.environ['R_LIBS_SITE'] = r_lib_path
from rpy2 import robjects from rpy2 import robjects
from rpy2.robjects.conversion import localconverter from rpy2.robjects.vectors import StrVector
from rpy2.robjects import default_converter, numpy2ri, pandas2ri
from rpy2.robjects.packages import importr, isinstalled from rpy2.robjects.packages import importr, isinstalled
# Import base and utils # Import base and utils once
base = importr('base') base = importr('base')
utils = importr('utils') utils = importr('utils')
base.options(warn=-1) # Silence R console output entirely
robjects.r('suppressMessages(suppressWarnings(sink(tempfile())))')
# Ensure library paths explicitly
base._libPaths(r_lib_path) base._libPaths(r_lib_path)
# Check if the AMR package is installed in R _installed_source = None
if not isinstalled('AMR', lib_loc=r_lib_path):
print(f"AMR: Installing latest AMR R package to {r_lib_path}...", flush=True)
utils.install_packages(
StrVector(['remotes', 'desc']),
repos='https://cloud.r-project.org',
lib=r_lib_path,
quiet=True
)
remotes = importr('remotes', lib_loc=r_lib_path)
remotes.install_github('msberends/AMR', lib=r_lib_path, quiet=True)
# Retrieve Python AMR version def _r_version():
try: """Return the currently installed AMR R package version, or None."""
python_amr_version = str(metadata.version('AMR'))
except metadata.PackageNotFoundError:
python_amr_version = str('')
# Retrieve R AMR version
r_amr_version = robjects.r(f'as.character(packageVersion("AMR", lib.loc = "{r_lib_path}"))')
r_amr_version = str(r_amr_version[0])
# Compare R and Python package versions
if r_amr_version != python_amr_version:
try: try:
print(f"AMR: Updating AMR package in {r_lib_path}...", flush=True) return str(robjects.r(
f'as.character(packageVersion("AMR", lib.loc = "{r_lib_path}"))')[0])
except Exception:
return None
def _py_version():
"""Return the Python AMR package version from metadata, or empty string."""
try:
return str(metadata.version('AMR'))
except metadata.PackageNotFoundError:
return ''
def _install_cran():
"""Install AMR from CRAN into the isolated library."""
print("AMR: Installing from CRAN...", flush=True)
utils.install_packages(
'AMR',
repos='https://cloud.r-project.org',
lib=r_lib_path,
quiet=True
)
def _install_github():
"""Install AMR development version from GitHub into the isolated library."""
print("AMR: Installing development version from GitHub...", flush=True)
utils.install_packages( utils.install_packages(
StrVector(['remotes', 'desc']), StrVector(['remotes', 'desc']),
repos='https://cloud.r-project.org', repos='https://cloud.r-project.org',
@@ -103,18 +107,78 @@ if r_amr_version != python_amr_version:
) )
remotes = importr('remotes', lib_loc=r_lib_path) remotes = importr('remotes', lib_loc=r_lib_path)
remotes.install_github('msberends/AMR', lib=r_lib_path, quiet=True) remotes.install_github('msberends/AMR', lib=r_lib_path, quiet=True)
r_amr_version = robjects.r(f'as.character(packageVersion("AMR", lib.loc = "{r_lib_path}"))')
r_amr_version = str(r_amr_version[0]) def ensure_amr(source="cran"):
"""Ensure AMR is installed from the requested source. Idempotent per source."""
global _installed_source
if _installed_source == source:
return
install_fn = _install_github if source == "github" else _install_cran
if not isinstalled('AMR', lib_loc=r_lib_path):
install_fn()
else:
# Check for version mismatch and update if needed
r_ver = _r_version()
py_ver = _py_version()
if r_ver != py_ver:
try:
install_fn()
except Exception as e: except Exception as e:
print(f"AMR: Could not update: {e}", flush=True) print(f"AMR: Could not update ({e})", flush=True)
print(f"AMR: R package version {r_amr_version} loaded.", flush=True) print(f"AMR: R package version {_r_version()} ready.", flush=True)
print(f"AMR: Setting up R environment and AMR datasets...", flush=True) _installed_source = source
# Activate the automatic conversion between R and pandas DataFrames def restore_sink():
with localconverter(default_converter + numpy2ri.converter + pandas2ri.converter): """Restore R console output after setup is complete."""
# example_isolates try:
example_isolates = robjects.r(''' robjects.r('sink()')
except Exception:
pass
EOL
# ---- datasets.py: only dataset loading ---- #
cat <<'EOL' > "$datasets_file"
import pandas as pd
from rpy2 import robjects
from rpy2.robjects.conversion import localconverter
from rpy2.robjects import default_converter, numpy2ri, pandas2ri
from ._engine import ensure_amr, restore_sink
_cache = {}
_loaded_source = None
def _load_datasets(source="cran"):
"""Load all AMR datasets into the module cache."""
global _loaded_source
if _cache and _loaded_source == source:
return
if _cache and _loaded_source != source:
_cache.clear()
ensure_amr(source)
with localconverter(default_converter + numpy2ri.converter + pandas2ri.converter):
_cache['example_isolates'] = _load_example_isolates()
_cache['microorganisms'] = robjects.r(
'AMR::microorganisms[, !sapply(AMR::microorganisms, is.list)]')
_cache['antimicrobials'] = robjects.r(
'AMR::antimicrobials[, !sapply(AMR::antimicrobials, is.list)]')
_cache['clinical_breakpoints'] = robjects.r(
'AMR::clinical_breakpoints[, !sapply(AMR::clinical_breakpoints, is.list)]')
restore_sink()
_loaded_source = source
def _load_example_isolates():
df = robjects.r('''
df <- AMR::example_isolates df <- AMR::example_isolates
df[] <- lapply(df, function(x) { df[] <- lapply(df, function(x) {
if (inherits(x, c("Date", "POSIXt", "factor"))) { if (inherits(x, c("Date", "POSIXt", "factor"))) {
@@ -126,26 +190,72 @@ with localconverter(default_converter + numpy2ri.converter + pandas2ri.converter
df <- df[, !sapply(df, is.list)] df <- df[, !sapply(df, is.list)]
df df
''') ''')
example_isolates['date'] = pd.to_datetime(example_isolates['date']) df['date'] = pd.to_datetime(df['date'])
return df
# microorganisms def get(name, source="cran"):
microorganisms = robjects.r('AMR::microorganisms[, !sapply(AMR::microorganisms, is.list)]') """Retrieve a dataset by name, installing AMR if needed."""
antimicrobials = robjects.r('AMR::antimicrobials[, !sapply(AMR::antimicrobials, is.list)]') _load_datasets(source)
clinical_breakpoints = robjects.r('AMR::clinical_breakpoints[, !sapply(AMR::clinical_breakpoints, is.list)]') return _cache[name]
base.options(warn = 0)
print(f"AMR: Done.", flush=True)
EOL EOL
echo "from .datasets import example_isolates" >> $init_file # ---- __init__.py: lazy module, CRAN by default ---- #
echo "from .datasets import microorganisms" >> $init_file
echo "from .datasets import antimicrobials" >> $init_file
echo "from .datasets import clinical_breakpoints" >> $init_file
cat <<'EOL' > "$init_file"
import sys
# Write header to the functions Python file, including the convert_to_python function _DATASETS = frozenset({
cat <<EOL > "$functions_file" 'example_isolates', 'microorganisms',
'antimicrobials', 'clinical_breakpoints'
})
class _AMRModule(type(sys.modules[__name__])):
"""Lazy-loading module: nothing runs until an attribute is accessed."""
def __getattr__(self, name):
if name in _DATASETS:
from .datasets import get
return get(name, source="cran")
try:
from . import functions
return getattr(functions, name)
except AttributeError:
raise AttributeError(
f"module 'AMR' has no attribute '{name}'")
sys.modules[__name__].__class__ = _AMRModule
EOL
# ---- beta.py: GitHub development version ---- #
cat <<'EOL' > "$beta_file"
import sys
_DATASETS = frozenset({
'example_isolates', 'microorganisms',
'antimicrobials', 'clinical_breakpoints'
})
class _BetaModule(type(sys.modules[__name__])):
"""Lazy-loading module: installs AMR from GitHub on first access."""
def __getattr__(self, name):
if name in _DATASETS:
from .datasets import get
return get(name, source="github")
try:
from . import functions
return getattr(functions, name)
except AttributeError:
raise AttributeError(
f"module 'AMR.beta' has no attribute '{name}'")
sys.modules[__name__].__class__ = _BetaModule
EOL
# ---- functions.py: R-to-Python wrapper functions ---- #
cat <<'EOL' > "$functions_file"
import functools import functools
import rpy2.robjects as robjects import rpy2.robjects as robjects
from rpy2.robjects.packages import importr from rpy2.robjects.packages import importr
@@ -155,7 +265,10 @@ from rpy2.robjects import default_converter, numpy2ri, pandas2ri
import pandas as pd import pandas as pd
import numpy as np import numpy as np
# Import the AMR R package from ._engine import ensure_amr
# Ensure AMR is available before importing it in R
ensure_amr("cran")
amr_r = importr('AMR') amr_r = importr('AMR')
def convert_to_r(value): def convert_to_r(value):
@@ -221,12 +334,11 @@ def r_to_python(r_func):
return wrapper return wrapper
EOL EOL
# Directory where the .Rd files are stored # ---- Generate wrapper functions from .Rd files ---- #
rd_dir="../man" rd_dir="../man"
# Iterate through each .Rd file in the man directory
for rd_file in "$rd_dir"/*.Rd; do for rd_file in "$rd_dir"/*.Rd; do
# Extract function names and their arguments from the .Rd files
awk ' awk '
BEGIN { BEGIN {
usage_started = 0 usage_started = 0
@@ -309,18 +421,19 @@ for rd_file in "$rd_dir"/*.Rd; do
' "$rd_file" ' "$rd_file"
done done
# Output completion message
echo "Python wrapper functions generated in $functions_file." echo "Python wrapper functions generated in $functions_file."
echo "Python wrapper functions listed in $init_file." echo "Python wrapper functions listed in $init_file."
# ---- README ---- #
cp ../vignettes/AMR_for_Python.Rmd ../PythonPackage/AMR/README.md cp ../vignettes/AMR_for_Python.Rmd ../PythonPackage/AMR/README.md
sed -i '1,/^# Introduction$/d' ../PythonPackage/AMR/README.md sed -i '1,/^# Introduction$/d' ../PythonPackage/AMR/README.md
echo "README copied" echo "README copied."
# ---- setup.py ---- #
# Extract the relevant fields from DESCRIPTION
version=$(grep "^Version:" "$description_file" | awk '{print $2}') version=$(grep "^Version:" "$description_file" | awk '{print $2}')
# Write the setup.py file
cat <<EOL > "$setup_file" cat <<EOL > "$setup_file"
from setuptools import setup, find_packages from setuptools import setup, find_packages
@@ -351,10 +464,10 @@ setup(
) )
EOL EOL
# Output completion message echo "setup.py generated."
echo "setup.py has been generated in $setup_file."
# ---- Build ---- #
cd ../PythonPackage/AMR cd ../PythonPackage/AMR
pip3 install build pip3 install build
python3 -m build python3 -m build
# python3 setup.py sdist bdist_wheel