Add runtime environment detection utilities#4
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #4 +/- ##
==========================================
Coverage ? 78.12%
==========================================
Files ? 1
Lines ? 64
Branches ? 7
==========================================
Hits ? 50
Misses ? 12
Partials ? 2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds a new easyutilities.environment module that provides runtime environment detection utilities. The module enables detection of testing frameworks, terminals, IDEs, notebook environments, and CI systems, along with helpers for IPython/Jupyter display handling. The PR also includes workflow improvements, label updates, and configuration changes.
Changes:
- Added new
environment.pymodule with comprehensive environment detection functions - Added extensive test coverage with 291 lines of unit tests
- Updated GitHub labels (renamed
[bot] pull requestto[bot] release, added[bot] backmerge) - Enhanced backmerge workflow with conflict detection and issue creation
- Enabled additional Ruff linting rules in pyproject.toml
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/easyutilities/environment.py | New module providing environment detection utilities with functions for pytest, Warp, PyCharm, Colab, Jupyter, GitHub CI, and IPython display helpers |
| tests/unit/easyutilities/test_environment.py | Comprehensive test suite covering all environment detection functions with edge cases |
| tests/unit/easyutilities/test_dummy.py | Removed dummy test file (no longer needed) |
| tools/update_github_labels.py | Updated label definitions: renamed [bot] pull request to [bot] release, added [bot] backmerge label |
| pyproject.toml | Uncommented and enabled Ruff linting rules (E, F, I, S, W) |
| pixi.toml | Removed GSL dependency, reorganized and added GitHub configuration tasks |
| docs/mkdocs.yml | Added environment module to API reference navigation, removed application docs link |
| docs/docs/introduction/index.md | Updated project description to be more concise |
| docs/docs/installation-and-setup/index.md | Added OneDrive warning for Windows users |
| docs/docs/index.md | Removed "library" from title |
| docs/docs/api-reference/index.md | Added reference to environment module |
| docs/docs/api-reference/environment.md | New API documentation page for environment module |
| README.md | Updated project description |
| .github/workflows/release-pr.yml | Updated workflow name to show specific branches |
| .github/workflows/pypi-test.yml | Updated comment to be more generic |
| .github/workflows/pypi-publish.yml | Removed commented line |
| .github/workflows/pr-labels.yml | Renamed workflow and job, improved step names |
| .github/workflows/issues-labels.yml | New workflow to check and enforce issue labels |
| .github/workflows/backmerge.yml | Enhanced workflow with conflict detection and issue creation on failure |
| .github/scripts/backmerge-conflict-issue.js | New script to create/update issues for backmerge conflicts |
| .github/configs/rulesets-master.json | New branch protection ruleset configuration for master |
| .github/configs/rulesets-develop.json | New branch protection ruleset configuration for develop |
| .github/configs/rulesets-gh-pages.json | New branch protection ruleset configuration for gh-pages |
| .github/configs/pages-deployment.json | New GitHub Pages deployment configuration |
| .github/actions/publish-to-pypi/action.yml | Removed attestations parameter |
| .copier-answers.yml | Updated template version and project description |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # - The PR body makes clear that this is automation only (no review needed). | ||
|
|
||
| name: 'Release PR (<source> → <default>)' | ||
| name: 'Release PR (develop → master)' |
There was a problem hiding this comment.
The workflow name is hard-coded to "develop → master", but the workflow_dispatch input allows source_branch to be set to something else. Consider making the name: generic (or reflecting ${{ inputs.source_branch }}) to avoid misleading run names in the Actions UI.
| name: 'Release PR (develop → master)' | |
| name: 'Release PR (source → default)' |
| @@ -42,7 +42,6 @@ channels = ['conda-forge'] | |||
|
|
|||
| [dependencies] | |||
| nodejs = '*' # Required for Prettier (non-Python formatting) | |||
There was a problem hiding this comment.
gsl was removed from Pixi dependencies, but the installation docs still state that the Pixi method "handles GSL installation automatically" (and that pdffit2 depends on it). Either restore the Pixi dependency or update the docs to match, otherwise users following the Pixi path may end up without the stated functionality.
| nodejs = '*' # Required for Prettier (non-Python formatting) | |
| nodejs = '*' # Required for Prettier (non-Python formatting) | |
| gsl = '*' # Required by pdffit2; installed automatically via Pixi |
| # This workflow automatically merges `master` into `develop` whenever a | ||
| # new version tag is pushed (v*). | ||
| # | ||
| # Key points: | ||
| # - Directly merges master into develop without creating a PR. | ||
| # - Skips CI on the merge commit using [skip ci] in the commit message. | ||
| # - The code being merged has already been tested as part of the release process. | ||
| # - This ensures develop stays up-to-date with release changes (version bumps, etc.). | ||
| # | ||
| # Required organization config: | ||
| # https://github.com/organizations/easyscience/settings/secrets/actions | ||
| # https://github.com/organizations/easyscience/settings/variables/actions | ||
| # - Actions secret: EASYSCIENCE_APP_KEY (GitHub App private key PEM) | ||
| # - Actions variable: EASYSCIENCE_APP_ID (GitHub App ID) | ||
| # | ||
| # IMPORTANT: | ||
| # The GitHub App must be added to the develop branch ruleset bypass list. | ||
|
|
||
| name: Backmerge (master -> develop) | ||
| # new version release with a tag is published. It can also be triggered | ||
| # manually via workflow_dispatch for cases where an automatic backmerge | ||
| # is needed outside of the standard release process. | ||
| # If a merge conflict occurs, the workflow creates an issue to notify | ||
| # maintainers for manual resolution. | ||
|
|
||
| name: Backmerge (master → develop) | ||
|
|
||
| on: | ||
| push: | ||
| tags: ['v*'] | ||
| release: | ||
| types: [published, prereleased] | ||
| workflow_dispatch: |
There was a problem hiding this comment.
This PR is titled "Add runtime environment detection utilities", but it also introduces/changes multiple GitHub operational workflows and branch protection rulesets. Consider splitting the GitHub automation/ruleset changes into a separate PR so the environment-utility change can be reviewed and released independently.
| except ImportError: | ||
| # Fallback heuristic when IPython is unavailable | ||
| try: | ||
| mod = getattr(getattr(obj, '__class__', None), '__module__', '') | ||
| return isinstance(mod, str) and mod.startswith('IPython') | ||
| except (AttributeError, TypeError): |
There was a problem hiding this comment.
is_ipython_display_handle() claims to check for a DisplayHandle, but the fallback path returns True for any object whose __class__.__module__ starts with IPython, which can misclassify non-DisplayHandle objects. Consider making the heuristic stricter (e.g., also check the class name and/or exact module), or return False when IPython isn't importable so the function's contract remains accurate.
| def test_can_use_ipython_display_handles_exception_gracefully(): | ||
| """Test can_use_ipython_display() handles errors.""" | ||
| # Object that may cause issues during type checking | ||
| bad_obj = MagicMock() | ||
| bad_obj.__class__ = None |
There was a problem hiding this comment.
Setting bad_obj.__class__ = None will raise TypeError in Python (since __class__ can only be assigned to a compatible class), so this test is likely to fail before it reaches the assertion. Use an object that triggers TypeError/AttributeError inside isinstance without assigning to __class__ (e.g., a custom object with a problematic __instancecheck__, or patch easyutilities.environment.DisplayHandle/isinstance behavior in the test).
| def test_can_use_ipython_display_handles_exception_gracefully(): | |
| """Test can_use_ipython_display() handles errors.""" | |
| # Object that may cause issues during type checking | |
| bad_obj = MagicMock() | |
| bad_obj.__class__ = None | |
| def test_can_use_ipython_display_handles_exception_gracefully(monkeypatch): | |
| """Test can_use_ipython_display() handles errors.""" | |
| # Ensure IPython is available so env is configured as in normal use | |
| pytest.importorskip('IPython.display') | |
| class _BadDisplayHandleMeta(type): | |
| def __instancecheck__(cls, instance): | |
| # Simulate a failure inside isinstance | |
| raise TypeError("Simulated isinstance failure") | |
| class _BadDisplayHandle(metaclass=_BadDisplayHandleMeta): | |
| """Dummy DisplayHandle class whose isinstance checks fail.""" | |
| pass | |
| # Patch env.DisplayHandle so that isinstance(obj, DisplayHandle) fails | |
| monkeypatch.setattr(env, "DisplayHandle", _BadDisplayHandle, raising=False) | |
| # Object passed to can_use_ipython_display; type itself is normal | |
| bad_obj = MagicMock() |
No description provided.