Rename test modules based on sentinel metadata and optional import-following logic
Project description
splurge-test-namer
A small tool to rename test modules based on module-level sentinel lists (e.g. DOMAINS = ['core','utils']).
Usage
-- Dry run: python -m splurge_test_namer.cli --test-root tests
-- Apply renames: python -m splurge_test_namer.cli --test-root tests --apply
-- Follow imports under a root package: python -m splurge_test_namer.cli --test-root tests --import-root splurge_sql_tool --repo-root /path/to/repo
Note: This release adds --prefix and --fallback flags to the CLI to control generated filename prefixes and the fallback domain used when no sentinel is present.
See docs/README-DETAILS.md for full API and design notes.
Features
-
Rename test modules using in-file sentinel lists like
DOMAINS = ['core', 'utils']. -
Support for dynamic import detection (string literals, simple f-strings and concatenations) so the tool can follow imports to locate sentinel-defining modules.
-
Member-fallback resolution for imports that reference attributes (e.g.
from pkg import names-> resolvespkg.names). -
CLI flags:
--test-root: directory containing test modules to analyze (default:tests).--import-root/--repo-root: follow imports under a root package in your repository.--exclude: file/directory patterns to ignore when searching for tests or imports.--apply: actually rename files; omit to run a dry-run preview.--debug: enable verbose debug logging to troubleshoot resolution and import-following behavior.
Sentinel name (default)
By default the tool looks for a module-level sentinel list named
DOMAINS. This default is configurable via the--sentinelCLI flag (seetools/rename_tests_by_sentinels.pyfor a small helper script that demonstrates this flag). The lookup behavior is:- Primary source: a
DOMAINSlist defined in the test module itself. If present, that list is used as the authoritative set of domain slugs for renaming. - Fallback: if the test module lacks a sentinel, the tool optionally walks the import chain (see "How renaming works") to find a
DOMAINSlist defined in imported modules. A sentinel discovered via import-walking is treated as a fallback source.
Quick start
- Dry-run (preview what would be renamed):
python -m splurge_test_namer.cli --test-root tests
- Apply renames for real:
python -m splurge_test_namer.cli --test-root tests --apply
- To follow imports from a package in your repo (so sentinels defined in other modules are discovered):
python -m splurge_test_namer.cli --test-root tests --import-root my_package --repo-root /path/to/repo
Run the installed console script
After installing the package (for example pip install . or pip install splurge-test-namer), the project installs a console script named splurge-test-namer (see [project.scripts] in pyproject.toml). You can run the same commands via that script:
# dry-run using installed script
splurge-test-namer --test-root tests
# apply renames
splurge-test-namer --test-root tests --apply
# follow imports from a package under your repo
splurge-test-namer --test-root tests --import-root my_package --repo-root /path/to/repo
Examples
- Rename tests that declare
DOMAINSat module level to include domain slugs in filenames. - Exclude files or directories during analysis:
python -m splurge_test_namer.cli --test-root tests --exclude tests/e2e --exclude tools
Before / After example
Given a test module with no descriptive name:
Before:
tests/unit/test_0001.py
And the file contains a sentinel list at module-level like:
# tests/unit/test_0001.py
DOMAINS = ['core', 'utils']
def test_feature():
assert True
After running with --apply, the file will be renamed to follow the pattern:
tests/unit/test_core_utils_0001.py
Filename pattern
- Renamed test files follow the pattern:
test_[domains]_nnnn.pywhere: [domains]is an underscore-separated (snake_case) list of domain slugs taken from the sentinel (order is preserved from the list).nnnnis the original numerical suffix retained to avoid breaking test discovery order (zero-padded as in the original name when present).
Sequence numbering behavior
- Sequence numbers are assigned per test sub-folder under the provided
--test-root. That is, numbering resets for each directory relative to--test-rootso you may have files with the same sequence in different sub-folders (for example,tests/unit/test_cli_0001.pyandtests/e2e/test_cli_0001.py). - If a file isn't located under the provided
--test-rootthe sequence grouping falls back to the file's parent directory path.
Limits
To keep generated filenames predictable and safe across platforms, the tool enforces a few limits (these are conservative and intended to avoid cross-platform filename issues):
Sentinel & filename limits
The tool enforces conservative, portable limits and sanitization rules to keep generated filenames safe across platforms:
- Sentinel token length: each sentinel value (each item in
DOMAINSor your configured sentinel list) must be at most 64 characters. Values longer than 64 characters will cause the tool to raiseSplurgeTestNamerErrorwith a message identifying the offending token. - Fallback token length: the
--fallbackvalue (or the default fallback used when no sentinel is found) must also be at most 64 characters; an oversized fallback raisesSplurgeTestNamerError. - Proposed filename length: the resulting proposed filename (for example
<PREFIX>_[domains]_nnnn.py) is validated to be at most 240 characters. If a proposed filename would exceed this limit aSplurgeTestNamerErroris raised explaining the offending proposed name and the original path.
Sanitization details (summary):
- Allowed token characters after sanitization: ASCII letters, digits and underscore.
- Repeated underscores collapse to single underscore; leading/trailing underscores removed.
- Case is preserved (tokens are case-sensitive).
Sanitization and patterns
- Allowed token characters: after sanitization tokens will contain only letters (A-Z, a-z), digits (0-9) and underscores. Any other characters are replaced with an underscore. Repeated underscores are collapsed to a single underscore and leading/trailing underscores are removed.
- Case preservation: token case is preserved (the tool does not lowercase tokens). Filenames are therefore case-sensitive and tokens are delimited with underscores.
- Prefix: use the
--prefixCLI flag to set the leading filename prefix (default:test). The prefix must match the regex^[A-Za-z][A-Za-z0-9_]*$and may be up to 64 characters.
Examples:
- Valid sentinel:
['Core', 'utils']→ domains slugCore_utils(OK, case preserved) - Too-long sentinel:
['this_is_a_very_long_sentinel_value_exceeding_sixty_four_characters_...']→ error
These limits are conservative and help avoid platform-specific filename failures. If you need different limits, consider pre-processing sentinel values before invoking the tool or open an issue to discuss configurability.
How renaming works (import-chain walking)
- Parse test modules to find a module-level sentinel (by default
DOMAINS, but the parser can be adapted). - If a sentinel is present in the test module, use it directly to compute the new filename.
- If a sentinel is not present, the tool optionally follows imports from the test module to locate sentinel definitions:
- The tool performs conservative static analysis on imports using the AST. It resolves:
- Static imports (e.g.
from pkg import names,import pkg.module). - Simple dynamic import expressions where the module name is a string literal, a constant NAME (simple assignment), a small concatenation (BinOp with strings), or a simple f-string / JoinedStr that resolves to a literal at parse time.
- Static imports (e.g.
- When the import references a member (e.g.
from pkg import names), the tool attempts a member-fallback resolution (look forpkg.namesas a module or an attribute inpkg). - The resolver maps import names to file paths under the provided
--repo-rootand--import-rootboundaries. It avoids following external packages (only repo-scoped modules are searched).
- The tool performs conservative static analysis on imports using the AST. It resolves:
- If multiple sentinel sources are found while walking imports, the tool aggregates them (the first found per module chain is used by default; aggregation behavior can be configured in advanced options).
Edge cases & assumptions
- The import-walking is conservative: it intentionally avoids executing project code and only considers static patterns that can be resolved safely.
- Complex runtime constructions (e.g. names built from environment variables, network calls, or loops) are not evaluated.
- If no sentinel is found after import-walking, the default behavior is to rename the test using a misc fallback: the domain portion becomes
miscand the file is renamed totest_misc_nnnn.py. This keeps naming consistent for tests that lack sentinel metadata. You can change this behavior via configuration or by providing a custom sentinel name with--sentinel.
Links
- CHANGELOG: see
CHANGELOG.mdfor release notes and history. - Full documentation:
docs/README-DETAILS.md(design notes, API, internals)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file splurge_test_namer-2025.1.0.tar.gz.
File metadata
- Download URL: splurge_test_namer-2025.1.0.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be544638a78de826ed640a5327f8456192934d646d290e6e063ff5137b821fa4
|
|
| MD5 |
dd8bd491257fd1f84ed428e2bc7f3a47
|
|
| BLAKE2b-256 |
c3f73c0a915272db3596214abd9d225b68f09b39941112a84d896dc229fad2e5
|
File details
Details for the file splurge_test_namer-2025.1.0-py3-none-any.whl.
File metadata
- Download URL: splurge_test_namer-2025.1.0-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c5704f29fedd82a3d23dc45ed72b152e5bd3256a899356e0aff82d5557baaad
|
|
| MD5 |
a9eca4a9fd126d50aeb79fdd81dca88c
|
|
| BLAKE2b-256 |
a3106c18fa815e2afeab53ffdec1bb0589e61e404a37d5545c69bfe8e6843fa3
|