From 2105dadf65570a40696560b98c1ebb22df2ed4c0 Mon Sep 17 00:00:00 2001 From: Larry Bradley Date: Thu, 8 Aug 2024 18:24:10 -0400 Subject: [PATCH 1/2] Fix docstrings --- dev/regions_parse.py | 2 +- regions/_utils/examples.py | 5 +++ regions/core/attributes.py | 8 ++++ regions/core/core.py | 55 +++++++++++++++++++++++ regions/core/metadata.py | 8 ++++ regions/core/regions.py | 1 + regions/core/registry.py | 88 +++++++++++++++++++++++++++++++++++++ regions/shapes/annulus.py | 31 +++++++++++-- regions/shapes/ellipse.py | 10 +++-- regions/shapes/rectangle.py | 6 ++- 10 files changed, 204 insertions(+), 10 deletions(-) diff --git a/dev/regions_parse.py b/dev/regions_parse.py index 4fbcc7e4..49ee3690 100644 --- a/dev/regions_parse.py +++ b/dev/regions_parse.py @@ -16,7 +16,7 @@ @click.group() def cli(): """ - astropy.regions parser debugging tool. + Region parser debugging tool. """ pass diff --git a/regions/_utils/examples.py b/regions/_utils/examples.py index 91d35ebc..40515fa4 100644 --- a/regions/_utils/examples.py +++ b/regions/_utils/examples.py @@ -67,6 +67,11 @@ def make_example_dataset(data='simulated', config=None): class ExampleDataset: """ Base class for an example dataset. + + Parameters + ---------- + config : dict or None + Configuration options. """ def __init__(self, config=None): diff --git a/regions/core/attributes.py b/regions/core/attributes.py index f2095f48..a05f21cd 100644 --- a/regions/core/attributes.py +++ b/regions/core/attributes.py @@ -157,6 +157,14 @@ def _validate(self, value): class RegionType(RegionAttribute): """ Descriptor class to check the region type of value. + + Parameters + ---------- + name : str + The name of the attribute. + + regionclass : `~regions.Region` + The region class to check. """ def __init__(self, name, regionclass): diff --git a/regions/core/core.py b/regions/core/core.py index c1cef9d3..5b9ea305 100644 --- a/regions/core/core.py +++ b/regions/core/core.py @@ -23,6 +23,11 @@ class Region(abc.ABC): def copy(self, **changes): """ Make an independent (deep) copy. + + Parameters + ---------- + **changes : dict + Changes to make to the region parameters. """ fields = list(self._params) + ['meta', 'visual'] @@ -97,6 +102,11 @@ def __eq__(self, other): def __ne__(self, other): """ Inequality operator for Region. + + Parameters + ---------- + other : `Region` + The other region that will be compared. """ return not (self == other) @@ -105,6 +115,11 @@ def intersection(self, other): """ Return a region representing the intersection of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the intersection. """ raise NotImplementedError @@ -113,6 +128,11 @@ def symmetric_difference(self, other): """ Return the union of the two regions minus any areas contained in the intersection of the two regions. + + Parameters + ---------- + other : `Region` + The other region to use for the symmetric difference. """ raise NotImplementedError @@ -121,6 +141,11 @@ def union(self, other): """ Return a region representing the union of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the union. """ raise NotImplementedError @@ -218,6 +243,11 @@ def intersection(self, other): """ Return a region representing the intersection of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the intersection. """ from regions.core.compound import CompoundPixelRegion return CompoundPixelRegion(region1=self, region2=other, @@ -227,6 +257,11 @@ def symmetric_difference(self, other): """ Return the union of the two regions minus any areas contained in the intersection of the two regions. + + Parameters + ---------- + other : `Region` + The other region to use for the symmetric difference. """ from regions.core.compound import CompoundPixelRegion return CompoundPixelRegion(region1=self, region2=other, @@ -236,6 +271,11 @@ def union(self, other): """ Return a region representing the union of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the union. """ from regions.core.compound import CompoundPixelRegion return CompoundPixelRegion(region1=self, region2=other, @@ -418,6 +458,11 @@ def intersection(self, other): """ Return a region representing the intersection of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the intersection. """ from regions.core.compound import CompoundSkyRegion return CompoundSkyRegion(region1=self, region2=other, @@ -427,6 +472,11 @@ def symmetric_difference(self, other): """ Return the union of the two regions minus any areas contained in the intersection of the two regions. + + Parameters + ---------- + other : `Region` + The other region to use for the symmetric difference. """ from regions.core.compound import CompoundSkyRegion return CompoundSkyRegion(region1=self, region2=other, @@ -436,6 +486,11 @@ def union(self, other): """ Return a region representing the union of this region with ``other``. + + Parameters + ---------- + other : `Region` + The other region to use for the union. """ from regions.core.compound import CompoundSkyRegion return CompoundSkyRegion(region1=self, region2=other, diff --git a/regions/core/metadata.py b/regions/core/metadata.py index cfab7e5f..09553151 100644 --- a/regions/core/metadata.py +++ b/regions/core/metadata.py @@ -10,6 +10,14 @@ class Meta(dict): """ A base class for region metadata. + + Parameters + ---------- + seq : dict-like, optional + A dictionary or other mapping object to initialize the metadata. + + **kwargs + Additional keyword arguments to initialize the metadata. """ valid_keys = [] diff --git a/regions/core/regions.py b/regions/core/regions.py index 20ee93b3..c60f9a2b 100644 --- a/regions/core/regions.py +++ b/regions/core/regions.py @@ -115,6 +115,7 @@ def pop(self, index=-1): Returns ------- result : `~regions.Region` + The removed region. """ return self.regions.pop(index) diff --git a/regions/core/registry.py b/regions/core/registry.py index faf15fb9..50d3737a 100644 --- a/regions/core/registry.py +++ b/regions/core/registry.py @@ -64,6 +64,26 @@ def identify_format(cls, filename, classobj, methodname): def read(cls, filename, classobj, format=None, **kwargs): """ Read in a regions file. + + Parameters + ---------- + filename : str + The file to read from. + + classobj : class + The class to read the regions into. + + format : str, optional + The format of the file. If not provided, the format will be + identified based on the file name or contents. + + **kwargs : dict, optional + Additional keyword arguments to pass to the reader. + + Returns + ------- + regions : classobj + The regions object read from the file. """ if format is None: format = cls.identify_format(filename, classobj, 'read') @@ -83,6 +103,26 @@ def read(cls, filename, classobj, format=None, **kwargs): def parse(cls, data, classobj, format=None, **kwargs): """ Parse a regions string or table. + + Parameters + ---------- + data : str, Table + The data to parse. + + classobj : class + The class to parse the regions into. + + format : str, optional + The format of the data. If not provided, an error will be + raised. + + **kwargs : dict, optional + Additional keyword arguments to pass to the parser. + + Returns + ------- + regions : classobj + The regions object parsed from the data. """ if format is None: cls._no_format_error(classobj) @@ -102,6 +142,24 @@ def parse(cls, data, classobj, format=None, **kwargs): def write(cls, regions, filename, classobj, format=None, **kwargs): """ Write to a regions file. + + Parameters + ---------- + regions : classobj + The regions object to write. + + filename : str + The file to write to. + + classobj : class + The class of the regions object. + + format : str, optional + The format of the file. If not provided, an error will be + raised. + + **kwargs : dict, optional + Additional keyword arguments to pass to the writer. """ if format is None: format = cls.identify_format(filename, classobj, 'write') @@ -121,6 +179,26 @@ def write(cls, regions, filename, classobj, format=None, **kwargs): def serialize(cls, regions, classobj, format=None, **kwargs): """ Serialize to a regions string or table. + + Parameters + ---------- + regions : classobj + The regions object to serialize. + + classobj : class + The class of the regions object. + + format : str, optional + The format of the data. If not provided, an error will be + raised. + + **kwargs : dict, optional + Additional keyword arguments to pass to the serializer. + + Returns + ------- + data : str, Table + The data serialized from the regions object. """ if format is None: cls._no_format_error(classobj) @@ -140,6 +218,16 @@ def serialize(cls, regions, classobj, format=None, **kwargs): def get_formats(cls, classobj): """ Get the registered I/O formats as a Table. + + Parameters + ---------- + classobj : class + The class to get the formats for. + + Returns + ------- + tbl : Table + The table of formats. """ filetypes = list({key[2] for key in cls.registry if key[0] == classobj}) diff --git a/regions/shapes/annulus.py b/regions/shapes/annulus.py index 16b9ca85..22e72956 100644 --- a/regions/shapes/annulus.py +++ b/regions/shapes/annulus.py @@ -230,6 +230,11 @@ class AsymmetricAnnulusPixelRegion(AnnulusPixelRegion): The rotation angle of the annulus, measured anti-clockwise. If set to zero (the default), the width axis is lined up with the x axis. + meta : `~regions.RegionMeta` or `dict`, optional + A dictionary that stores the meta attributes of the region. + visual : `~regions.RegionVisual` or `dict`, optional + A dictionary that stores the visual meta attributes of the + region. """ _params = ('center', 'inner_width', 'outer_width', 'inner_height', @@ -313,7 +318,7 @@ class AsymmetricAnnulusSkyRegion(SkyRegion): angle : `~astropy.units.Quantity`, optional The rotation angle of the annulus, measured anti-clockwise. If set to zero (the default), the width axis is lined up with the - longitude axis of the celestial coordinates + longitude axis of the celestial coordinates. meta : `~regions.RegionMeta` or `dict`, optional A dictionary that stores the meta attributes of the region. visual : `~regions.RegionVisual` or `dict`, optional @@ -443,6 +448,11 @@ class EllipseAnnulusPixelRegion(AsymmetricAnnulusPixelRegion): _component_class = EllipsePixelRegion + def __init__(self, center, inner_width, outer_width, inner_height, + outer_height, angle=0 * u.deg, meta=None, visual=None): + super().__init__(center, inner_width, outer_width, inner_height, + outer_height, angle, meta, visual) + def to_sky(self, wcs): return EllipseAnnulusSkyRegion(*self.to_sky_args(wcs), meta=self.meta.copy(), @@ -472,7 +482,7 @@ class EllipseAnnulusSkyRegion(AsymmetricAnnulusSkyRegion): angle : `~astropy.units.Quantity`, optional The rotation angle of the elliptical annulus, measured anti-clockwise. If set to zero (the default), the width axis is - lined up with the longitude axis of the celestial coordinates + lined up with the longitude axis of the celestial coordinates. meta : `~regions.RegionMeta` or `dict`, optional A dictionary that stores the meta attributes of the region. visual : `~regions.RegionVisual` or `dict`, optional @@ -496,6 +506,11 @@ class EllipseAnnulusSkyRegion(AsymmetricAnnulusSkyRegion): _component_class = EllipseSkyRegion + def __init__(self, center, inner_width, outer_width, inner_height, + outer_height, angle=0 * u.deg, meta=None, visual=None): + super().__init__(center, inner_width, outer_width, inner_height, + outer_height, angle, meta, visual) + def to_pixel(self, wcs): return EllipseAnnulusPixelRegion(*self.to_pixel_args(wcs), meta=self.meta.copy(), @@ -574,6 +589,11 @@ class RectangleAnnulusPixelRegion(AsymmetricAnnulusPixelRegion): _component_class = RectanglePixelRegion + def __init__(self, center, inner_width, outer_width, inner_height, + outer_height, angle=0 * u.deg, meta=None, visual=None): + super().__init__(center, inner_width, outer_width, inner_height, + outer_height, angle, meta, visual) + def to_sky(self, wcs): return RectangleAnnulusSkyRegion(*self.to_sky_args(wcs), meta=self.meta.copy(), @@ -604,7 +624,7 @@ class RectangleAnnulusSkyRegion(AsymmetricAnnulusSkyRegion): angle : `~astropy.units.Quantity`, optional The rotation angle of the rectangular annulus, measured anti-clockwise. If set to zero (the default), the width axis is - lined up with the longitude axis of the celestial coordinates + lined up with the longitude axis of the celestial coordinates. meta : `~regions.RegionMeta` or `dict`, optional A dictionary that stores the meta attributes of the region. visual : `~regions.RegionVisual` or `dict`, optional @@ -628,6 +648,11 @@ class RectangleAnnulusSkyRegion(AsymmetricAnnulusSkyRegion): _component_class = RectangleSkyRegion + def __init__(self, center, inner_width, outer_width, inner_height, + outer_height, angle=0 * u.deg, meta=None, visual=None): + super().__init__(center, inner_width, outer_width, inner_height, + outer_height, angle, meta, visual) + def to_pixel(self, wcs): return RectangleAnnulusPixelRegion(*self.to_pixel_args(wcs), meta=self.meta.copy(), diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index 570c7f96..c4bfca37 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -34,9 +34,9 @@ class EllipsePixelRegion(PixelRegion): center : `~regions.PixCoord` The position of the center of the ellipse. width : float - The width of the ellipse (before rotation) in pixels + The width of the ellipse (before rotation) in pixels. height : float - The height of the ellipse (before rotation) in pixels + The height of the ellipse (before rotation) in pixels. angle : `~astropy.units.Quantity`, optional The rotation angle of the ellipse, measured anti-clockwise. If set to zero (the default), the width axis is lined up with the x @@ -220,7 +220,7 @@ def _update_from_mpl_selector(self, *args, **kwargs): self._mpl_selector_callback(self) def as_mpl_selector(self, ax, active=True, sync=True, callback=None, - **kwargs): + drag_from_anywhere=False, **kwargs): """ Return a matplotlib editable widget for this region (`matplotlib.widgets.EllipseSelector`). @@ -282,7 +282,9 @@ def sync_callback(*args, **kwargs): rectprops.update(kwargs.pop('props', dict())) kwargs.update({'props': rectprops}) - self._mpl_selector = EllipseSelector(ax, sync_callback, interactive=True, **kwargs) + self._mpl_selector = EllipseSelector( + ax, sync_callback, interactive=True, + drag_from_anywhere=drag_from_anywhere, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 87a8edfd..773d9711 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -216,7 +216,7 @@ def _update_from_mpl_selector(self, *args, **kwargs): self._mpl_selector_callback(self) def as_mpl_selector(self, ax, active=True, sync=True, callback=None, - **kwargs): + drag_from_anywhere=False, **kwargs): """ Return a matplotlib editable widget for the region (`matplotlib.widgets.RectangleSelector`). @@ -279,7 +279,9 @@ def sync_callback(*args, **kwargs): rectprops.update(kwargs.pop('props', dict())) kwargs.update({'props': rectprops}) - self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, **kwargs) + self._mpl_selector = RectangleSelector( + ax, sync_callback, interactive=True, + drag_from_anywhere=drag_from_anywhere, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, From 3e1c2a54b3317342d73ac7873fb580307042f1ae Mon Sep 17 00:00:00 2001 From: Larry Bradley Date: Thu, 8 Aug 2024 18:24:29 -0400 Subject: [PATCH 2/2] Add numpydoc validation to pre-commit --- .pre-commit-config.yaml | 8 ++++++-- pyproject.toml | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5da2e1de..6fbbe27a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,8 +57,7 @@ repos: rev: "v0.4.7" hooks: - id: ruff - # args: ["--fix", "--show-fixes"] - args: ["--show-fixes"] + args: ["--fix", "--show-fixes"] - repo: https://github.com/asottile/pyupgrade rev: v3.17.0 @@ -102,6 +101,11 @@ repos: additional_dependencies: - tomli + - repo: https://github.com/numpy/numpydoc + rev: v1.8.0rc2 + hooks: + - id: numpydoc-validation + - repo: https://github.com/PyCQA/docformatter rev: v1.7.5 hooks: diff --git a/pyproject.toml b/pyproject.toml index 5e4b0509..683d8098 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -192,6 +192,28 @@ exclude_dirs = ['*/tests/test_casa_mask.py'] [tool.bandit.assert_used] skips = ['*_test.py', '*/test_*.py', '*/tests/helpers.py'] +[tool.numpydoc_validation] +checks = [ + 'all', # report on all checks, except the below + 'ES01', # missing extended summary + 'EX01', # missing "Examples" + 'GL08', # missing docstring + 'SA01', # missing "See Also" + 'SA04', # missing "See Also" description + 'RT02', # only type in "Returns" section (no name) + 'SS06', # single-line summary + 'RT01', # do not require return type for lazy properties +] + +# don't report on objects that match any of these regex; +# remember to use single quotes for regex in TOML +exclude = [ + '\._.*', # private functions/methods + '^test_*', # test code + '^conftest.*$', # pytest configuration + '^helpers.*$', # helper modules +] + [tool.docformatter] wrap-summaries = 72 pre-summary-newline = true