Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

jobs:
precommit:
name: pre-commit hooks
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Run pre-commit (skip pytest-smoke here)
uses: pre-commit/action@v3.0.1
env:
SKIP: pytest-smoke
with:
extra_args: --all-files --hook-stage pre-push

smoke:
name: smoke import test
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up micromamba environment
uses: mamba-org/setup-micromamba@v2
with:
environment-file: environment.yml
environment-name: ci
cache-environment: true
condarc: |
channels:
- conda-forge
- defaults

- name: Run smoke tests
shell: micromamba-shell {0}
run: |
pytest -q -m smoke -x -ra
24 changes: 16 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
*checkpoints
*egg_info/*
.gitingore
photozpy.egg-info/
*__pycache__
*.DS_Store*
tests/
src/photozpy/calibration/testing_files/
# Jupyter / notebooks
.ipynb_checkpoints/

# Python caches
__pycache__/
*.py[cod]

# pytest
.pytest_cache/

# packaging artifacts
*.egg-info/
build/
dist/

# macOS
.DS_Store
90 changes: 90 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# .pre-commit-config.yaml (example with exclude)
default_install_hook_types: [pre-push]
default_stages: [pre-push] # 影响的是没有内置 stages、且你自己没写 stages 的那些 hook
# 对于有内置stages,这个默认stage不会起效,需要在下面的hook
# 中显式定义,比如trailing-whitespace
default_language_version:
python: python3.11

exclude: |
(?x)^(
docs/_build/|
build/|
dist/|
\.venv/|
\.mypy_cache/|
\.pytest_cache/|
__pycache__/|
.*\.ipynb
)$



# Categories:
# - Format: keep code style consistent
# - Lint: catch bugs and style issues early
# - Typing (optional): static type checks
# - Hygiene/Safety: catch common repo issues
# - Notebooks (optional): keep outputs clean or skip notebooks
# - Pre-push smoke tests (optional): run a tiny subset before pushing

repos:

# ---------- Lint (ruff lint only, no formatter) ----------
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.1
hooks:
- id: ruff
stages: [pre-push]
args: ["--select", "E9,F"] # only check: E9=syntax/parse errors,
# F=pyflakes correctness (undefined/unused),
# S=security (bandit); avoid style rules


# ---------- Hygiene / Safety ----------
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
stages: [pre-push]
files: &code_files '\.(py|md|rst|yml|yaml|toml|json)$'
- id: end-of-file-fixer # 确保文件末尾只有 1 个换行
stages: [pre-push]
files: *code_files
- id: check-merge-conflict # prevent committing conflict markers
stages: [pre-push]
- id: check-yaml # validate .yml/.yaml
stages: [pre-push]
- id: check-json # validate JSON files
stages: [pre-push]
- id: check-toml # validate TOML files (if any)
stages: [pre-push]
- id: check-case-conflict # warn about case-insensitive FS collisiois
stages: [pre-push]
- id: mixed-line-ending # 统一换行风格
stages: [pre-push]
files: *code_files
args: ["--fix=lf"] # 统一 LF(推荐 macOS/Linux)
- id: check-added-large-files # 给「大文件」阈值一个你能接受的上限(默认 5MB)
stages: [pre-push]
args: ["--maxkb=10000"] # 10MB;或换成你项目想要的大小

- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
files: \.(py|md|rst|yml|yaml|toml)$
args: ["--ignore-words-list", "nd,teh,mjd,ra,dec,fwhm,psf,skycoords,skycoord"]


# ---------- Pre-push smoke tests (optional) ----------
# This does NOT replace full CI tests; it just runs a tiny, fast subset before push.
# You can comment this whole block out if you don't want tests in pre-commit.
- repo: local
hooks:
- id: pytest-smoke
name: "pytest -m smoke (pre-push)"
entry: bash -c 'pytest -q -m smoke -x --disable-warnings'
language: system
pass_filenames: false
always_run: true
29 changes: 29 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: researchcode
channels:
- conda-forge
dependencies:
- python=3.11
- pip

# notebooks
- jupyterlab
- ipykernel

# core scientific stack
- numpy
- scipy
- pandas
- matplotlib

# astronomy stack (按你需求可删减)
- astropy
- astroquery

# dev tools
- pytest
- pre-commit
- ruff

# optional: nice-to-have
- rich
- tqdm
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool.pytest.ini_options]
minversion = "6.0"
markers = [
"smoke: fast checks to run before push",
]
addopts = "-ra"
testpaths = ["tests"]
55 changes: 55 additions & 0 deletions tests/test_smoke_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[1]
PROJECTS_ROOT = REPO_ROOT / "Projects"

# 你可以按需要继续加
SKIP_DIRS = {
"__pycache__",
".ipynb_checkpoints",
".pytest_cache",
".mypy_cache",
}

@pytest.mark.smoke
def test_import_all_py_files_under_projects():
assert PROJECTS_ROOT.exists(), f"Expected folder not found: {PROJECTS_ROOT}"

# 让 Projects 内部如果有绝对导入(比如 import some_utils)更容易找到
sys.path.insert(0, str(PROJECTS_ROOT))
sys.path.insert(0, str(REPO_ROOT))

py_files = []
for p in PROJECTS_ROOT.rglob("*.py"):
parts = set(p.relative_to(PROJECTS_ROOT).parts)
if parts & SKIP_DIRS:
continue
py_files.append(p)

failures = []
for p in sorted(py_files):
# 给每个文件一个唯一的“假模块名”,避免冲突
mod_name = (
"smoke_projects_"
+ p.relative_to(PROJECTS_ROOT).with_suffix("").as_posix().replace("/", "_").replace("-", "_")
)

try:
spec = importlib.util.spec_from_file_location(mod_name, p)
if spec is None or spec.loader is None:
raise RuntimeError(f"Cannot create import spec for {p}")
mod = importlib.util.module_from_spec(spec)
sys.modules[mod_name] = mod
spec.loader.exec_module(mod)
except Exception as e:
failures.append((str(p.relative_to(PROJECTS_ROOT)), repr(e)))

if failures:
msg = "\n".join([f"- {relpath}: {err}" for relpath, err in failures])
raise AssertionError(f"Import smoke failed for some .py files under Projects:\n{msg}\n")