Skip to content

Commit 6cb2046

Browse files
[ENH] feat :replacing print() statements with Python logging module. (#477)
#### Reference Issues/PRs Fixes #416. #### What does this implement/fix? Explain your changes. This PR migrates the codebase from using `print()` statements for progress and debug reporting to the standard Python `logging` module. This allows library consumers to control log verbosity programmatically and prevents cluttering `stdout` in production environments. **Key Changes:** * **Module-level Loggers:** Added `logger = logging.getLogger(__name__)` to `mcts/_algorithm.py`, `aptatrans/_pipeline.py`, and `aptatrans/_model.py`. * **Logging levels:** * Used `logger.debug()` for high-frequency MCTS round updates and internal search progress. * Used `logger.info()` for lifecycle events like model weight loading, downloads, and final candidate recommendations. * **Performance Optimization:** Implemented `logger.isEnabledFor(logging.DEBUG)` guards in the MCTS hot loop (`run` method) to avoid unnecessary string formatting overhead when debug logging is disabled. * **Ruff Enforcement:** Updated `pyproject.toml` to include the `T20` (flake8-print) rule set in the Ruff configuration. This will cause linting to fail if `print()` or `pprint()` statements are accidentally reintroduced in the future. #### What should a reviewer concentrate their feedback on? * Verify that the chosen log levels (`DEBUG` vs `INFO`) are appropriate for the respective output. * Check the implementation of the performance guards in `pyaptamer/mcts/_algorithm.py`. #### Did you add any tests for the change? The changes were verified by running `ruff check pyaptamer/ --select T20` to ensure that no `print()` statements remain in the library code and that the new linting rule is active. Syntax correctness was verified with a full AST parse check of the modified files. #### PR checklist - [x] The PR title starts with either [ENH], [MNT], [DOC], or [BUG]. - [ ] Added/modified tests - [x] Used pre-commit hooks when committing to ensure that code is compliant with hooks.
1 parent e975325 commit 6cb2046

6 files changed

Lines changed: 48 additions & 12 deletions

File tree

pyaptamer/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33
from importlib.metadata import version
44

5+
from pyaptamer._logger import logger
6+
57
__version__ = version("pyaptamer")
8+
9+
__all__ = ["__version__", "logger"]

pyaptamer/_logger.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Centralized logger for pyaptamer."""
2+
3+
import logging
4+
5+
# Create the one global logger
6+
logger = logging.getLogger("pyaptamer")
7+
logger.setLevel(logging.INFO)
8+
9+
# Format it to show the calling module so we know where logs come from
10+
formatter = logging.Formatter("[%(levelname)s] [%(module)s] %(message)s")
11+
12+
# Prevent adding multiple handlers if the module is reloaded
13+
if not logger.handlers:
14+
handler = logging.StreamHandler()
15+
handler.setFormatter(formatter)
16+
logger.addHandler(handler)
17+
18+
# Prevent log propagation to the root logger to avoid duplicate logs
19+
logger.propagate = False

pyaptamer/aptatrans/_model.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import torch.nn as nn
1212
from torch import Tensor
1313

14+
from pyaptamer import logger
1415
from pyaptamer.aptatrans.layers._convolutional import ConvBlock
1516
from pyaptamer.aptatrans.layers._encoder import (
1617
EncoderPredictorConfig,
@@ -270,10 +271,10 @@ def load_pretrained_weights(self) -> None:
270271
)
271272

272273
if os.path.exists(path):
273-
print(f"Loading pretrained weights from {path}...")
274+
logger.info("Loading pretrained weights from %s...", path)
274275
state_dict = torch.load(path, map_location=torch.device("cpu"))
275276
else:
276-
print("Downloading best weights from hugging face...")
277+
logger.info("Downloading best weights from hugging face...")
277278
url = (
278279
"https://huggingface.co/gcos/pyaptamer-aptatrans/resolve/main/"
279280
"pretrained.pt"

pyaptamer/aptatrans/_pipeline.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
__author__ = ["nennomp"]
77
__all__ = ["AptaTransPipeline"]
88

9+
910
import torch
1011
from torch import Tensor
1112

13+
from pyaptamer import logger
1214
from pyaptamer.aptatrans import AptaTrans
1315
from pyaptamer.experiments import AptamerEvalAptaTrans
1416
from pyaptamer.mcts import MCTS
@@ -246,8 +248,11 @@ def recommend(
246248

247249
if verbose:
248250
for candidate, sequence, score in candidates.values():
249-
print(
250-
f"Candidate: {candidate}, Sequence: {sequence}, Score: {score:.4f}"
251+
logger.info(
252+
"Candidate: %s, Sequence: %s, Score: %.4f",
253+
candidate,
254+
sequence,
255+
score,
251256
)
252257

253258
return set(candidates.values())

pyaptamer/mcts/_algorithm.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
__author__ = ["nennomp"]
44
__all__ = ["MCTS"]
55

6+
import logging
67
import random
78

89
import numpy as np
910
from skbase.base import BaseObject
1011

12+
from pyaptamer import logger
13+
1114

1215
class MCTS(BaseObject):
1316
"""
@@ -296,8 +299,8 @@ def run(self, verbose: bool = True) -> dict:
296299
# continue until we reach the target sequence length (i.e, depth * 2)
297300
round_count = 0
298301
while len(self.base) < self.depth * 2:
299-
if verbose:
300-
print(f"\n ----- Round: {round_count + 1} -----")
302+
if verbose and logger.isEnabledFor(logging.DEBUG):
303+
logger.debug("Round: %d", round_count + 1)
301304

302305
for _ in range(self.n_iterations):
303306
# selection
@@ -315,11 +318,12 @@ def run(self, verbose: bool = True) -> dict:
315318

316319
self.base = self._find_best_subsequence()
317320

318-
if verbose:
319-
print("#" * 50)
320-
print(f"Best subsequence: {self.base}")
321-
print(f"Depth: {len(self.base) // 2}")
322-
print("#" * 50)
321+
if verbose and logger.isEnabledFor(logging.DEBUG):
322+
logger.debug(
323+
"Best subsequence: %s | Depth: %d",
324+
self.base,
325+
len(self.base) // 2,
326+
)
323327

324328
# reset for next iteration
325329
self.root = TreeNode(

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ target-version = "py310"
3939
exclude = ["__pycache__", ".venv", "build", "dist", ".git"]
4040

4141
[tool.ruff.lint]
42-
select = ["E", "F", "B", "I", "UP", "N", "C4"]
42+
select = ["E", "F", "B", "I", "UP", "N", "C4", "T20"]
4343
ignore = ["N803", "N806", "N812"]
4444

4545
[tool.ruff.format]
@@ -56,3 +56,6 @@ include-package-data = true
5656

5757
[tool.setuptools.package-data]
5858
pyaptamer = ["datasets/data/*.csv", "datasets/data/*.pdb"]
59+
60+
[tool.ruff.lint.per-file-ignores]
61+
"examples/*.ipynb" = ["T201"]

0 commit comments

Comments
 (0)