From 983020e6f72d401db4c14f466783f8c95ead5a15 Mon Sep 17 00:00:00 2001 From: Daniel Pressler Date: Wed, 4 Feb 2026 08:01:56 -0700 Subject: [PATCH] add numpydoc linting --- Makefile | 1 + jbpy/core.py | 234 ++++++++++++++++------------------ jbpy/extensions/tre/EXOPTA.py | 4 +- jbpy/image_data.py | 41 +++--- pyproject.toml | 11 ++ 5 files changed, 144 insertions(+), 147 deletions(-) diff --git a/Makefile b/Makefile index 769bfac..c18826c 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ format: lint: ruff check ruff format --diff + numpydoc lint jbpy/*.py mypy jbpy build: diff --git a/jbpy/core.py b/jbpy/core.py index a0207a4..5dc2d9a 100644 --- a/jbpy/core.py +++ b/jbpy/core.py @@ -47,21 +47,19 @@ def write(self, __data: bytes) -> int: ... class SubFile: - """File-like object mapping to a contiguous subset of another file-like object""" + """File-like object mapping to a contiguous subset of another file-like object - def __init__(self, file: Any, start: int, length: int): - """ - Initialize a SubFile view. + Parameters + ---------- + file : file-like + An open file object. Must be binary. + start : int + Start byte offset of the subfile + length : int + Number of bytes to expose from the start + """ - Arguments - --------- - file : file-like - An open file object. Must be binary. - start : int - Start byte offset of the subfile - length : int - Number of bytes to expose from the start - """ + def __init__(self, file: Any, start: int, length: int): self._file = file self._start = start self._length = length @@ -71,8 +69,8 @@ def seek(self, offset: int, whence: int = 0) -> int: """ Seek to a position within the subfile. - Arguments - --------- + Parameters + ---------- offset : int Offset to seek whence : int @@ -106,8 +104,8 @@ def read(self, size: int = -1) -> bytes: """ Read data from the subfile. - Arguments - --------- + Parameters + ---------- size : int Number of bytes to read, or -1 for all remaining """ @@ -220,8 +218,8 @@ def from_bytes_impl(self, encoded_value: bytes) -> str: class StringISO8859_1(PythonConverter): # noqa: N801 """Convert to/from an ISO 8859-1 str - Note - ---- + Notes + ----- JBP-2025.1 Table D-1 specifies the full ECS-A character set, which happens to match ISO 8859 part 1. """ @@ -339,13 +337,12 @@ def isvalid(self, value: Any) -> bool: class MinMax(RangeCheck): """Field has a minimum and/or maximum value - Args - ---- - minimum: + Parameters + ---------- + minimum Minimum value. A value of 'None' indicates no minimum. - maximum: + maximum Maximum value. A value of 'None' indicates no maximum. - """ def __init__(self, minimum: int | float | None, maximum: int | float | None): @@ -394,11 +391,10 @@ def isvalid(self, value: Any) -> bool: class AnyOf(RangeCheck): """Field value must match at least one of many different RangeChecks - Args - ---- + Parameters + ---------- *ranges: RangeCheck RangeCheck objects to check against - """ def __init__(self, *ranges: RangeCheck): @@ -412,11 +408,10 @@ def isvalid(self, value: Any) -> bool: class AllOf(RangeCheck): """Field value must match all of many different RangeChecks - Args - ---- + Parameters + ---------- *ranges: RangeCheck RangeCheck objects to check against - """ def __init__(self, *ranges: RangeCheck): @@ -466,15 +461,15 @@ def __init__(self, name: str): def load(self, fd: BinaryFile_R) -> Self: """Read from a file descriptor - Args - ---- - fd: file-like + + Parameters + ---------- + fd : file-like Binary file-like object to read from Returns ------- A reference to self - """ try: self._load_impl(fd) @@ -485,11 +480,12 @@ def load(self, fd: BinaryFile_R) -> Self: def dump(self, fd: BinaryFile_RW, seek_first: bool = False) -> int: """Write to a file descriptor - Args - ---- - fd: file-like + + Parameters + ---------- + fd : file-like Binary file-like object to write to - seek_first: bool + seek_first : bool Seek to the components offset before writing Returns @@ -525,8 +521,9 @@ def get_size(self) -> int: def as_json(self, full: bool = False) -> str: """Return a JSON representation of the component - Args - ---- + + Parameters + ---------- full : bool Include additional details such as offset and length """ @@ -548,8 +545,8 @@ def finalize(self): def as_filelike(self, file: Any) -> SubFile: """Create file object containing just this component - Arguments - --------- + Parameters + ---------- file : file-like File object for entire file @@ -565,27 +562,27 @@ class Field(JbpIOComponent): """JBP Field containing a single value. Intended to have 1:1 mapping to rows in JBP-2025.1 header tables. - Args - ---- - name: str + Parameters + ---------- + name : str Name of this field - description: str + description : str Text description of the field - size: int + size : int Size in bytes of the field - charset: str or None, optional + charset : str or None, optional regex expression matching a single character. If ``None``, character set check is skipped. - encoded_range: RangeCheck or None, optional + encoded_range : RangeCheck or None, optional Checker for the encoded value. If ``None``, encoded validation is skipped. - decoded_range: RangeCheck or None, optional + decoded_range : RangeCheck or None, optional Checker for the decoded value. If ``None``, decoded validation is skipped. - converter: PythonConverter + converter : PythonConverter Object to use for converting to/from python data types - default: any + default : any Initial python value of the field - setter_callback: callable or None, optional + setter_callback : callable or None, optional function to call if the field's value changes - nullable: bool, optional + nullable : bool, optional ``True`` if BCS-A spaces are allowed for entire field (often denoted with "<>" in JBP Field Type). When ``True``, charset, range checks, conversion, etc. are bypassed when the python-typed value is ``None``. @@ -756,7 +753,6 @@ class BinaryPlaceholder(JbpIOComponent): """Represents a block of large binary data. This class does not actually read, write or store data, only seek past it. - """ def __init__(self, name: str, size: int): @@ -874,11 +870,10 @@ class Group(ComponentCollection, collections.abc.Mapping): """ A Collection of JBP fields. Indexed by JBP short names. - Args - ---- - name: str + Parameters + ---------- + name : str Name to give the group of fields - """ def __init__(self, name): @@ -909,8 +904,9 @@ def _insert_after( def find_all(self, pattern: str) -> Iterator[JbpIOComponent]: """Find child components with names matching a regex pattern - Args - ---- + + Parameters + ---------- pattern : str Regex pattern @@ -963,17 +959,16 @@ class SecurityFields(Group): """ JBP security header/subheader fields - Args - ---- - name: str + Parameters + ---------- + name : str Name to give this component - x: str + x : str Value to replace leading "x" of Short Name in fields - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.10-1 and Table 5.10-2 - """ def __init__(self, name: str, x: str): @@ -1166,37 +1161,36 @@ class FileHeader(Group): """ JBP File Header - Args - ---- - name: str + Parameters + ---------- + name : str Name to give the object - numi_callback: callable + numi_callback : callable Function to call when NUMI changes - lin_callback: callable + lin_callback : callable Function to call when LIn changes - nums_callback: callable + nums_callback : callable Function to call when NUMS changes - lsn_callback: callable + lsn_callback : callable Function to call when LSn changes - numt_callback: callable + numt_callback : callable Function to call when NUMT changes - ltn_callback: callable + ltn_callback : callable Function to call when LTn changes - numdes_callback: callable + numdes_callback : callable Function to call when NUMDES changes - ldn_callback: callable + ldn_callback : callable Function to call when LDn changes - numres_callback: callable + numres_callback : callable Function to call when NUMRES changes - lreshn_callback: callable + lreshn_callback : callable Function to call when LRESHn changes - lren_callback: callable + lren_callback : callable Function to call when LREn changes - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.11-1 - """ def __init__( @@ -1726,15 +1720,14 @@ class ImageSubheader(Group): """ Image Subheader fields - Args - ---- - name: str + Parameters + ---------- + name : str Name to give this component - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.13-1 - """ def __init__(self, name: str): @@ -2360,15 +2353,14 @@ class GraphicSubheader(Group): """ Graphic Subheader fields - Args - ---- - name: str + Parameters + ---------- + name : str Name to give this component - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.15-1 - """ def __init__(self, name: str): @@ -2568,15 +2560,14 @@ class TextSubheader(Group): """ Text Subheader fields - Args - ---- - name: str + Parameters + ---------- + name : str Name to give this component - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.17-1 - """ def __init__(self, name: str): @@ -2732,9 +2723,9 @@ class DataExtensionSubheader(Group): """ Data Extension Segment (DES) Subheader with unrecognized user-defined subheader fields - Args - ---- - name: str + Parameters + ---------- + name : str Name to give this component desid_constraint : RangeCheck or None, optional Decoded range check for 'DESID' @@ -2743,10 +2734,9 @@ class DataExtensionSubheader(Group): desshl_constraint : RangeCheck or None, optional Decoded range check for 'DESSHL' - Note - ---- + Notes + ----- See JBP-2025.1 Table 5.18-1 - """ def __init__( @@ -2898,8 +2888,8 @@ def des_subheader_factory( ) -> DataExtensionSubheader: """Create a Data Extension Segment (DES) subheader - Args - ---- + Parameters + ---------- desid : str Unique DES type identifier desver : int @@ -2979,7 +2969,6 @@ class Jbp(Group): * TextSegments * DataExtensionSegments * ReservedExtensionSegments - """ def __init__(self): @@ -3384,13 +3373,12 @@ class TreSequence(ComponentCollection, collections.abc.MutableSequence): Intended for use as the user defined and/or extended data fields. See Section 5.9.3. - Arguments - --------- - name: str + Parameters + ---------- + name : str Name to give the field - length: int + length : int Initial length in bytes - """ def __init__(self, name, length): @@ -3439,8 +3427,8 @@ class Tre(Group): Includes the TRETAG and TREL tags. - Arguments - --------- + Parameters + ---------- identifier : str identifier of the TRE. Must be 1-6 characters. tretag_rename : str @@ -3450,8 +3438,8 @@ class Tre(Group): length_constraint : RangeCheck or None Decoded range check for 'TREL' field. Defaults to MinMax(1, 99985) - Note - ---- + Notes + ----- BIIF and JBP define TREs as having 3 fields, TRETAG, TREL, and. TREDATA. However, TREs commonly rename TRETAG and TREL and define their own fields as replacing TREDATA. """ @@ -3554,8 +3542,8 @@ def available_tres() -> dict[str, Callable[[], Tre]]: def tre_factory(tretag: str) -> Tre: """Create a TRE instance - Arguments - --------- + Parameters + ---------- tretag : str The 1-6 character name of the TRE diff --git a/jbpy/extensions/tre/EXOPTA.py b/jbpy/extensions/tre/EXOPTA.py index 573e960..78eb350 100644 --- a/jbpy/extensions/tre/EXOPTA.py +++ b/jbpy/extensions/tre/EXOPTA.py @@ -15,8 +15,8 @@ class EXOPTA(core.Tre): """Exploitation Usability Optical Information Extension Format See STDI-0002 Volume 1 App E, Table E-10 - Note - ---- + Notes + ----- Very similar to USE00A... """ diff --git a/jbpy/image_data.py b/jbpy/image_data.py index 20f4192..6077711 100644 --- a/jbpy/image_data.py +++ b/jbpy/image_data.py @@ -16,8 +16,8 @@ def array_protocol_typestr(pvtype: str, nbpp: int) -> str: """Generate a NumPy array interface protocol typestr describing a NITF pixel - Arguments - --------- + Parameters + ---------- pvtype : str Image subheader Pixel Value Type (PVTYPE) nbpp : int @@ -53,8 +53,8 @@ def from_bytes_impl(self, encoded_value: bytes) -> int: class MaskTable(jbpy.core.Group): """JBP Image Data Mask Table - Arguments - --------- + Parameters + ---------- name : str Name to give this group image_subheader : jbpy.core.ImageSubheader @@ -63,7 +63,6 @@ class MaskTable(jbpy.core.Group): Notes ----- image_subheader must not change after initializing this class. - """ def __init__(self, name: str, image_subheader: jbpy.core.ImageSubheader): @@ -189,8 +188,8 @@ def _handle_tpxcdlnth(self, field): def bmr_name(block_index: int, band_index: int) -> str: """Generate the expected name for BMRnBNDm given indices - Arguments - --------- + Parameters + ---------- block_index : int Linear index of the block (zero-based). "n" band_index : int @@ -200,7 +199,6 @@ def bmr_name(block_index: int, band_index: int) -> str: ------- str Field name - """ return f"BMR{block_index:08d}BND{band_index:05d}" @@ -208,8 +206,8 @@ def bmr_name(block_index: int, band_index: int) -> str: def tmr_name(block_index: int, band_index: int) -> str: """Generate the expected name for TMRnBNDm given indices - Arguments - --------- + Parameters + ---------- block_index : int Linear index of the block (one-based). "n" band_index : int @@ -219,7 +217,6 @@ def tmr_name(block_index: int, band_index: int) -> str: ------- str Field name - """ return f"TMR{block_index:08d}BND{band_index:05d}" @@ -229,9 +226,9 @@ def read_mask_table( ) -> MaskTable: """Read an image segment's mask table - Arguments - --------- - image_segment: ImageSegment + Parameters + ---------- + image_segment : ImageSegment Which image segment's mask table to read file : file-like JBP file containing the image_segment @@ -271,8 +268,8 @@ def image_array_description( Always describes a 3D shape with one axis being the bands. Axis containing the bands is determined by the IMODE field. - Arguments - --------- + Parameters + ---------- image_segment : jbpy.core.ImageSegment The image segment to describe @@ -366,8 +363,8 @@ def block_info_uncompressed( """ Describe the blocks comprising an uncompressed image segment - Arguments - --------- + Parameters + ---------- image_segment : ImageSegment Which image segment to describe file : file-like @@ -394,8 +391,8 @@ def block_info_uncompressed( def nominal_block_info(image_subheader: jbpy.core.ImageSubheader) -> list[BlockInfo]: """Create a list of block information assuming an image is uncompressed and unmasked (IC=NC) - Arguments - --------- + Parameters + ---------- image_subheader : jbpy.core.ImageSubheader Subheader of the image to describe @@ -565,8 +562,8 @@ def apply_mask_table_to_block_info( ) -> list[BlockInfo]: """Return a copy of a block_info list with information from a mask table applied - Arguments - --------- + Parameters + ---------- image_subheader : jbpy.core.ImageSubheader Subheader of the image to describe block_info : list of BlockInfo diff --git a/pyproject.toml b/pyproject.toml index 50ce266..e5b3cb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,16 @@ select = [ ] preview = true +[tool.numpydoc_validation] +checks = [ + "GL02", # Closing quotes should be placed in the line after the last text in the docstring (do not close the quotes in the same line as the text, or leave a blank line between the last text and the quotes) + "GL03", # Double line break found; please use only one blank line to separate sections or paragraphs, and do not leave blank lines at the end of docstrings + "GL06", # Found unknown section "{section}". Allowed sections are: "{allowed_sections}" + "GL07", # Sections are in the wrong order. Correct order is: {correct_sections} + "PR03", # Wrong parameters order + "PR10", # Parameter "{param_name}" requires a space before the colon +] + [project] dynamic = ["version"] requires-python = ">= 3.11" @@ -54,6 +64,7 @@ dev = [ "pytest", "smart_open[http]", "numpy", + "numpydoc>=1.10.0", {include-group = "examples"}, ]