Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates from the package template (#260) #262

Merged
merged 4 commits into from
Nov 11, 2024
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
5 changes: 3 additions & 2 deletions .cruft.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"template": "https://github.com/sunpy/package-template",
"commit": "aec53b81aed2e7e534045e59303d82712fe82fb1",
"commit": "4ca8e60aac805d5f736de80c45ae0aba96b4cb85",
"checkout": null,
"context": {
"cookiecutter": {
Expand All @@ -16,7 +16,8 @@
"enable_dynamic_dev_versions": "y",
"include_example_code": "n",
"include_cruft_update_github_workflow": "y",
"_sphinx_theme": "alabaster",
"use_extended_ruff_linting": "y",
"_sphinx_theme": "sunpy",
"_parent_project": "",
"_install_requires": "",
"_copy_without_render": [
Expand Down
48 changes: 31 additions & 17 deletions .github/workflows/sub_package_update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
- add-paths: .
body: apply the changes to this repo.
branch: cruft/update
commit-message: "Automatic package template update"
title: Updates from the package template

steps:
- uses: actions/checkout@v4

Expand All @@ -55,25 +47,47 @@ jobs:
echo "has_changes=$CHANGES" >> "$GITHUB_OUTPUT"

- name: Run update if available
id: cruft_update
if: steps.check.outputs.has_changes == '1'
run: |
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
git config --global user.name "${{ github.actor }}"

cruft update --skip-apply-ask --refresh-private-variables
cruft_output=$(cruft update --skip-apply-ask --refresh-private-variables)
echo $cruft_output
git restore --staged .

- name: Create pull request
if [[ "$cruft_output" == *"Failed to cleanly apply the update, there may be merge conflicts."* ]]; then
echo merge_conflicts=1 >> $GITHUB_OUTPUT
else
echo merge_conflicts=0 >> $GITHUB_OUTPUT
fi

- name: Check if only .cruft.json is modified
id: cruft_json
if: steps.check.outputs.has_changes == '1'
run: |
git status --porcelain=1
if [[ "$(git status --porcelain=1)" == " M .cruft.json" ]]; then
echo "Only .cruft.json is modified. Exiting workflow early."
echo "has_changes=0" >> "$GITHUB_OUTPUT"
else
echo "has_changes=1" >> "$GITHUB_OUTPUT"
fi

- name: Create pull request
if: steps.cruft_json.outputs.has_changes == '1'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: ${{ matrix.add-paths }}
commit-message: ${{ matrix.commit-message }}
branch: ${{ matrix.branch }}
add-paths: "."
commit-message: "Automatic package template update"
branch: "cruft/update"
delete-branch: true
branch-suffix: timestamp
title: ${{ matrix.title }}
draft: ${{ steps.cruft_update.outputs.merge_conflicts == '1' }}
title: "Updates from the package template"
body: |
This is an autogenerated PR, which will ${{ matrix.body }}.
[Cruft](https://cruft.github.io/cruft/) has detected updates from the Package Template
This is an autogenerated PR, which will applies the latest changes from the [SunPy Package Template](https://github.com/sunpy/package-template).
If this pull request has been opened as a draft there are conflicts which need fixing.

**To run the CI on this pull request you will need to close it and reopen it.**
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
repos:
# This should be before any formatting hooks like isort
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.7.1"
hooks:
- id: ruff
args: ["--fix"]
- repo: https://github.com/PyCQA/autoflake
rev: v2.3.1
hooks:
Expand Down
69 changes: 57 additions & 12 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
target-version = "py310"
line-length = 110
line-length = 120
exclude = [
".git,",
"__pycache__",
Expand All @@ -8,30 +8,75 @@ exclude = [
]

[lint]
select = ["E", "F", "W", "UP", "PT"]
select = [
"E",
"F",
"W",
"UP",
"PT",
"BLE",
"A",
"C4",
"INP",
"PIE",
"T20",
"RET",
"TID",
"PTH",
"PD",
"PLC",
"PLE",
"FLY",
"NPY",
"PERF",
"RUF",
]
extend-ignore = [
# pycodestyle (E, W)
"E501", # LineTooLong # TODO! fix
"E501", # ignore line length will use a formatter instead
# pyupgrade (UP)
"UP038", # Use | in isinstance - not compatible with models and is slower
# pytest (PT)
"PT001", # Always use pytest.fixture()
"PT004", # Fixtures which don't return anything should have leading _
"PT007", # Parametrize should be lists of tuples # TODO! fix
"PT011", # Too broad exception assert # TODO! fix
"PT023", # Always use () on pytest decorators
# flake8-pie (PIE)
"PIE808", # Disallow passing 0 as the first argument to range
# flake8-use-pathlib (PTH)
"PTH123", # open() should be replaced by Path.open()
# Ruff (RUF)
"RUF003", # Ignore ambiguous quote marks, doesn't allow ' in comments
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
"RUF013", # PEP 484 prohibits implicit `Optional`
"RUF015", # Prefer `next(iter(...))` over single element slice
]

[lint.per-file-ignores]
# Part of configuration, not a package.
"setup.py" = ["INP001"]
"conftest.py" = ["INP001"]
"setup.py" = [
"INP001", # File is part of an implicit namespace package.
]
"conftest.py" = [
"INP001", # File is part of an implicit namespace package.
]
"docs/conf.py" = [
"E402" # Module imports not at top of file
"E402" # Module imports not at top of file
]
"docs/*.py" = [
"INP001", # Implicit-namespace-package. The examples are not a package.
"INP001", # File is part of an implicit namespace package.
]
"examples/**.py" = [
"T201", # allow use of print in examples
"INP001", # File is part of an implicit namespace package.
]
"__init__.py" = [
"E402", # Module level import not at top of cell
"F401", # Unused import
"F403", # from {name} import * used; unable to detect undefined names
"F405", # {name} may be undefined, or defined from star imports
]
"test_*.py" = [
"E402", # Module level import not at top of cell
]
"__init__.py" = ["E402", "F401", "F403"]
"test_*.py" = ["B011", "D", "E402", "PGH001", "S101"]

[lint.pydocstyle]
convention = "numpy"
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
automodapi_toctreedirnm = "generated/api"

# Add any paths that contain templates here, relative to this directory.
# templates_path = ["_templates"] # NOQA: ERA001
# templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand Down Expand Up @@ -103,7 +103,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ["_static"] # NOQA: ERA001
# html_static_path = ["_static"]

# By default, when rendering docstrings for classes, sphinx.ext.autodoc will
# make docs with the class-level docstring and the class-method docstrings,
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ tests = [
]
docs = [
"sphinx",
"sphinx-automodapi",
"sunpy-sphinx-theme",
"packaging",
"sphinx_automodapi",
"sphinx-changelog",
"sphinx-gallery",
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ filterwarnings =
ignore: invalid value encountered in true_divide
# https://github.com/pytest-dev/pytest-cov/issues/557
ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning
ignore:Please use astropy.wcs.wcsapi.high_level_api.values_to_high_level_objects:DeprecationWarning
4 changes: 2 additions & 2 deletions sunraster/_dev/scm_version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Try to use setuptools_scm to get the current version; this is only used
# in development installations from the git repository.
import os.path
from pathlib import Path

try:
from setuptools_scm import get_version

version = get_version(root=os.path.join("..", ".."), relative_to=__file__)
version = get_version(root=Path("../.."), relative_to=__file__)
except ImportError:
raise
except Exception as e:
Expand Down
97 changes: 48 additions & 49 deletions sunraster/extern/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(self, header=None, comments=None, axes=None, data_shape=None):
axes = dict(axes)
if not set(axes.keys()).issubset(set(header_keys)):
raise ValueError("All axes must correspond to a value in header under the same key.")
self._axes = dict([(key, self._sanitize_axis_value(axis, header[key], key)) for key, axis in axes.items()])
self._axes = {key: self._sanitize_axis_value(axis, header[key], key) for key, axis in axes.items()}

def _sanitize_axis_value(self, axis, value, key):
if axis is None:
Expand All @@ -100,7 +100,7 @@ def _sanitize_axis_value(self, axis, value, key):
shape_error_msg = f"{key} must have shape {tuple(self.shape[axis])} as it is associated with axes {axis}"
if len(axis) == 1 and not hasattr(value, "__len__") or len(axis) != 1 and not hasattr(value, "shape"):
raise TypeError(shape_error_msg)
elif len(axis) == 1:
if len(axis) == 1:
meta_shape = (len(value),)
else:
meta_shape = value.shape
Expand Down Expand Up @@ -189,51 +189,50 @@ def __getitem__(self, item):
# If item is single string, slicing is simple.
if isinstance(item, str):
return super().__getitem__(item)
elif self.shape is None:
if self.shape is None:
raise TypeError("Meta object does not have a shape and so cannot be sliced.")
else:
new_meta = copy.deepcopy(self)
# Convert item to array of ints and slices for consistent behaviour.
if isinstance(item, (numbers.Integral, slice)):
item = [item]
item = np.array(list(item) + [slice(None)] * (len(self.shape) - len(item)), dtype=object)

# Edit data shape and calculate which axis will be dropped.
dropped_axes = np.zeros(len(self.shape), dtype=bool)
new_shape = new_meta.shape
for i, axis_item in enumerate(item):
if isinstance(axis_item, numbers.Integral):
dropped_axes[i] = True
elif isinstance(axis_item, slice):
start = axis_item.start
if start is None:
start = 0
if start < 0:
start = self.shape[i] - start
stop = axis_item.stop
if stop is None:
stop = self.shape[i]
if stop < 0:
stop = self.shape[i] - stop
new_shape[i] = stop - start
else:
raise TypeError("Unrecognized slice type. " "Must be an int, slice and tuple of the same.")
new_meta._data_shape = new_shape[np.invert(dropped_axes)]

# Calculate the cumulative number of dropped axes.
cumul_dropped_axes = np.cumsum(dropped_axes)

# Slice all metadata associated with axes.
for key, value in self.items():
axis = self.axes.get(key, None)
if axis is not None:
new_item = tuple(item[axis])
new_value = value[new_item[0]] if len(new_item) == 1 else value[new_item]
new_axis = np.array([-1 if isinstance(i, numbers.Integral) else a for i, a in zip(new_item, axis)])
new_axis -= cumul_dropped_axes[axis]
new_axis = new_axis[new_axis >= 0]
if len(new_axis) == 0:
new_axis = None
new_meta.add(key, new_value, self.comments.get(key, None), new_axis, overwrite=True)

return new_meta
new_meta = copy.deepcopy(self)
# Convert item to array of ints and slices for consistent behaviour.
if isinstance(item, (numbers.Integral, slice)):
item = [item]
item = np.array(list(item) + [slice(None)] * (len(self.shape) - len(item)), dtype=object)

# Edit data shape and calculate which axis will be dropped.
dropped_axes = np.zeros(len(self.shape), dtype=bool)
new_shape = new_meta.shape
for i, axis_item in enumerate(item):
if isinstance(axis_item, numbers.Integral):
dropped_axes[i] = True
elif isinstance(axis_item, slice):
start = axis_item.start
if start is None:
start = 0
if start < 0:
start = self.shape[i] - start
stop = axis_item.stop
if stop is None:
stop = self.shape[i]
if stop < 0:
stop = self.shape[i] - stop
new_shape[i] = stop - start
else:
raise TypeError("Unrecognized slice type. " "Must be an int, slice and tuple of the same.")
new_meta._data_shape = new_shape[np.invert(dropped_axes)]

# Calculate the cumulative number of dropped axes.
cumul_dropped_axes = np.cumsum(dropped_axes)

# Slice all metadata associated with axes.
for key, value in self.items():
axis = self.axes.get(key, None)
if axis is not None:
new_item = tuple(item[axis])
new_value = value[new_item[0]] if len(new_item) == 1 else value[new_item]
new_axis = np.array([-1 if isinstance(i, numbers.Integral) else a for i, a in zip(new_item, axis)])
new_axis -= cumul_dropped_axes[axis]
new_axis = new_axis[new_axis >= 0]
if len(new_axis) == 0:
new_axis = None
new_meta.add(key, new_value, self.comments.get(key, None), new_axis, overwrite=True)

return new_meta
Loading