From 1b96123c697e47f5956e52b9edf56bad27eed0ba Mon Sep 17 00:00:00 2001 From: Yusuke Watanabe Date: Mon, 27 Apr 2026 05:46:26 +0900 Subject: [PATCH] refactor(os): extract scitex.os into standalone scitex-os package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host check (is_host / check_host / verify_host) + safe file mv now live in the standalone scitex-os package (https://github.com/ywatanabe1989/scitex-os). scitex.os/__init__.py becomes a sys.modules alias. The [os] extra is updated to depend on scitex-os>=0.1.0. Pure stdlib — zero deps. 7/7 smoke tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 3 +- src/scitex/os/__init__.py | 32 +++++++----- src/scitex/os/_check_host.py | 33 ------------- src/scitex/os/_mv.py | 50 ------------------- src/scitex/os/_skills/SKILL.md | 23 --------- src/scitex/os/_skills/check-host.md | 77 ----------------------------- src/scitex/os/_skills/mv.md | 53 -------------------- tests/scitex/os/test__mv.py | 66 ------------------------- 8 files changed, 22 insertions(+), 315 deletions(-) delete mode 100755 src/scitex/os/_check_host.py delete mode 100755 src/scitex/os/_mv.py delete mode 100644 src/scitex/os/_skills/SKILL.md delete mode 100644 src/scitex/os/_skills/check-host.md delete mode 100644 src/scitex/os/_skills/mv.md delete mode 100644 tests/scitex/os/test__mv.py diff --git a/pyproject.toml b/pyproject.toml index bdfecd610..3f2de9670 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -490,7 +490,8 @@ notification = [ # OS Module - OS utilities # Use: pip install scitex[os] -os = [] +# Real implementation lives in the standalone scitex-os package. +os = ["scitex-os>=0.1.0"] # Parallel Module - Parallel processing # Use: pip install scitex[parallel] diff --git a/src/scitex/os/__init__.py b/src/scitex/os/__init__.py index c5b79edc8..8f6fad80f 100755 --- a/src/scitex/os/__init__.py +++ b/src/scitex/os/__init__.py @@ -1,12 +1,20 @@ -#!/usr/bin/env python3 -"""Scitex os module.""" - -from ._check_host import check_host, is_host, verify_host -from ._mv import mv - -__all__ = [ - "check_host", - "is_host", - "mv", - "verify_host", -] +"""SciTeX os — thin compatibility shim for scitex-os. + +Aliases ``scitex.os`` to the standalone ``scitex_os`` package via ``sys.modules``. +``scitex.os is scitex_os``. + +Install: ``pip install scitex[os]`` (or ``pip install scitex-os``). +See: https://github.com/ywatanabe1989/scitex-os +""" + +import sys as _sys + +try: + import scitex_os as _real +except ImportError as _e: # pragma: no cover + raise ImportError( + "scitex.os requires the 'scitex-os' package. " + "Install with: pip install scitex[os] (or: pip install scitex-os)" + ) from _e + +_sys.modules[__name__] = _real diff --git a/src/scitex/os/_check_host.py b/src/scitex/os/_check_host.py deleted file mode 100755 index 56b9eaa3c..000000000 --- a/src/scitex/os/_check_host.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 -# Time-stamp: "2024-11-02 13:43:36 (ywatanabe)" -# File: /home/ywatanabe/proj/scitex-python/src/scitex/os/_check_host.py - -import socket -import sys - - -def check_host(keyword): - """Check if the current hostname contains the given keyword.""" - return keyword in socket.gethostname() - - -is_host = check_host - - -def verify_host(keyword): - if is_host(keyword): - print(f"Host verification successed for keyword: {keyword}") - return - else: - print(f"Host verification failed for keyword: {keyword}") - sys.exit(1) - - -if __name__ == "__main__": - # check_host("ywata") - verify_host("titan") - verify_host("ywata") - verify_host("crest") - - -# EOF diff --git a/src/scitex/os/_mv.py b/src/scitex/os/_mv.py deleted file mode 100755 index 582182f3f..000000000 --- a/src/scitex/os/_mv.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Time-stamp: "2024-04-06 09:00:45 (ywatanabe)" - -# import os -# import shutil - -# def mv(src, tgt): -# successful = True -# os.makedirs(tgt, exist_ok=True) - -# if os.path.isdir(src): -# # Iterate over the items in the directory -# for item in os.listdir(src): -# item_path = os.path.join(src, item) -# # Check if the item is a file -# if os.path.isfile(item_path): -# try: -# shutil.move(item_path, tgt) -# print(f"\nMoved file from {item_path} to {tgt}") -# except OSError as e: -# print(f"\nError: {e}") -# successful = False -# else: -# print(f"\nSkipped directory {item_path}") -# else: -# # If src is a file, just move it -# try: -# shutil.move(src, tgt) -# print(f"\nMoved from {src} to {tgt}") -# except OSError as e: -# print(f"\nError: {e}") -# successful = False - -# return successful - - -def mv(src, tgt): - import os - import shutil - - successful = True - os.makedirs(tgt, exist_ok=True) - - try: - shutil.move(src, tgt) - print(f"\nMoved from {src} to {tgt}") - except OSError as e: - print(f"\nError: {e}") - successful = False diff --git a/src/scitex/os/_skills/SKILL.md b/src/scitex/os/_skills/SKILL.md deleted file mode 100644 index 1e7b1cdeb..000000000 --- a/src/scitex/os/_skills/SKILL.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: stx.os -description: OS-level utilities for hostname-based guards and file moving. Use when scripts must run only on a specific machine or when moving files to auto-created destination directories. -user-invocable: false ---- - -# stx.os — OS Utilities - -Thin OS-level helpers that complement Python's standard `os` module. Accessed via `import scitex as stx` then `stx.os.`. - -**Public API** - -```python -from scitex.os import check_host, is_host, verify_host, mv -``` - -## Sub-skills - -### Host Checking -- [check-host.md](check-host.md) — `check_host`, `is_host`, `verify_host`: substring-match the current hostname and optionally exit the process on mismatch - -### File Moving -- [mv.md](mv.md) — `mv`: move a file or directory to a destination, auto-creating the destination directory tree diff --git a/src/scitex/os/_skills/check-host.md b/src/scitex/os/_skills/check-host.md deleted file mode 100644 index 7d63904a3..000000000 --- a/src/scitex/os/_skills/check-host.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -description: Hostname-based guards — check_host / is_host return a boolean, verify_host exits the process when the current machine does not match. ---- - -# Host Checking - -`stx.os` provides three functions built on `socket.gethostname()` for asserting or querying which machine the current process is running on. All three accept a plain keyword string and perform a substring match against the full hostname. - -## Functions - -```python -check_host(keyword: str) -> bool -is_host(keyword: str) -> bool # alias for check_host -verify_host(keyword: str) -> None # exits with sys.exit(1) on mismatch -``` - -### check_host / is_host - -Return `True` when `keyword` appears anywhere in the current hostname, `False` otherwise. `is_host` is a direct alias — they are the same object. - -```python -check_host(keyword) -> bool -is_host(keyword) -> bool -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `keyword` | `str` | Substring to search for inside `socket.gethostname()` | - -**Returns** `bool` - -### verify_host - -Print a success or failure message, then call `sys.exit(1)` if the hostname does not contain `keyword`. Use this at the top of scripts that must only run on a designated machine (e.g. a specific HPC node). - -```python -verify_host(keyword) -> None -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `keyword` | `str` | Substring that must appear in `socket.gethostname()` | - -**Side effects** -- Prints `"Host verification successed for keyword: "` on match (note: original source spells "successed") -- Prints `"Host verification failed for keyword: "` and calls `sys.exit(1)` on mismatch - -## Examples - -```python -import scitex as stx - -# Boolean query — safe, never exits -if stx.os.is_host("titan"): - print("Running on the titan cluster") - -if stx.os.check_host("laptop"): - # development-only code path - debug_mode = True - -# Hard guard — process exits immediately if not on crest -stx.os.verify_host("crest") - -# Typical pattern: gate expensive setup behind host check -if stx.os.is_host("gpu-node"): - import torch - device = torch.device("cuda") -else: - device = "cpu" -``` - -## Behaviour Details - -- Match is a **substring check** (`keyword in socket.gethostname()`), not an exact match - - `is_host("titan")` returns `True` for hostname `"titan01.hpc.example.com"` -- Case-sensitive: `is_host("TITAN")` will not match `"titan01"` -- `verify_host` calls `sys.exit(1)` — the process terminates with a non-zero exit code, which is visible to shell scripts and CI pipelines diff --git a/src/scitex/os/_skills/mv.md b/src/scitex/os/_skills/mv.md deleted file mode 100644 index ce955aa5a..000000000 --- a/src/scitex/os/_skills/mv.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -description: Move a file or directory to a destination path, auto-creating the destination directory tree if it does not exist. ---- - -# File Moving — mv - -`stx.os.mv` is a thin wrapper around `shutil.move` that also calls `os.makedirs` on the target path before moving, so the destination directory is always created automatically. - -## Function - -```python -mv(src: str, tgt: str) -> None -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `src` | `str` | Source path — file or directory to move | -| `tgt` | `str` | Destination **directory** path; created with `exist_ok=True` if absent | - -**Returns** `None` - -**Side effects** -- Calls `os.makedirs(tgt, exist_ok=True)` unconditionally before the move -- On success: prints `"\nMoved from to "` -- On `OSError`: prints `"\nError: "` and sets an internal `successful = False` flag (the flag is not returned) - -> Note: `tgt` is treated as a **directory** — `os.makedirs(tgt)` is always called on it. `shutil.move` then places `src` inside that directory. If you pass a full file path as `tgt`, the final component becomes a sub-directory, not the new filename. - -## Examples - -```python -import scitex as stx - -# Move a single file into an existing or new directory -stx.os.mv("results/run_01/data.csv", "archive/2024/") -# Prints: "\nMoved from results/run_01/data.csv to archive/2024/" -# File lands at: archive/2024/data.csv - -# Move an entire directory subtree -stx.os.mv("experiments/tmp_run", "experiments/completed/") -# experiments/completed/tmp_run/ is created - -# Destination is auto-created — no need to mkdir beforehand -stx.os.mv("output.pkl", "long/nested/new/dir/") -# long/nested/new/dir/ is created, then output.pkl is moved inside it -``` - -## Behaviour Details - -- `os.makedirs(tgt, exist_ok=True)` is called even when `tgt` already exists — this is safe and has no side effects in that case -- The underlying `shutil.move` handles both same-filesystem renames (fast) and cross-filesystem copies+deletes (slower) transparently -- On `OSError` the function does **not** re-raise; it prints the error message and continues — callers cannot detect failure via return value or exception. If failure detection is needed, check for the file's presence at the destination after calling `mv` -- Silent failure is the current behaviour; do not rely on `mv` returning a success indicator diff --git a/tests/scitex/os/test__mv.py b/tests/scitex/os/test__mv.py deleted file mode 100644 index 0540dd8fc..000000000 --- a/tests/scitex/os/test__mv.py +++ /dev/null @@ -1,66 +0,0 @@ -# Add your tests here - -if __name__ == "__main__": - import os - - import pytest - - pytest.main([os.path.abspath(__file__)]) - -# -------------------------------------------------------------------------------- -# Start of Source Code from: /home/ywatanabe/proj/scitex-code/src/scitex/os/_mv.py -# -------------------------------------------------------------------------------- -# #!/usr/bin/env python3 -# # -*- coding: utf-8 -*- -# # Time-stamp: "2024-04-06 09:00:45 (ywatanabe)" -# -# # import os -# # import shutil -# -# # def mv(src, tgt): -# # successful = True -# # os.makedirs(tgt, exist_ok=True) -# -# # if os.path.isdir(src): -# # # Iterate over the items in the directory -# # for item in os.listdir(src): -# # item_path = os.path.join(src, item) -# # # Check if the item is a file -# # if os.path.isfile(item_path): -# # try: -# # shutil.move(item_path, tgt) -# # print(f"\nMoved file from {item_path} to {tgt}") -# # except OSError as e: -# # print(f"\nError: {e}") -# # successful = False -# # else: -# # print(f"\nSkipped directory {item_path}") -# # else: -# # # If src is a file, just move it -# # try: -# # shutil.move(src, tgt) -# # print(f"\nMoved from {src} to {tgt}") -# # except OSError as e: -# # print(f"\nError: {e}") -# # successful = False -# -# # return successful -# -# -# def mv(src, tgt): -# import os -# import shutil -# -# successful = True -# os.makedirs(tgt, exist_ok=True) -# -# try: -# shutil.move(src, tgt) -# print(f"\nMoved from {src} to {tgt}") -# except OSError as e: -# print(f"\nError: {e}") -# successful = False - -# -------------------------------------------------------------------------------- -# End of Source Code from: /home/ywatanabe/proj/scitex-code/src/scitex/os/_mv.py -# --------------------------------------------------------------------------------