TemplateClass.py#

The following is a template to illustrate formatting and style when writing code within Scarabaeus. For an in-depth explanation of the file, see Scarabaeus coding conventions

# SPDX-FileCopyrightText: 2026 Orbital Research Cluster for Celestial Applications (ORCCA) Lab, University of Colorado at Boulder
# SPDX-License-Identifier: ISC
from scarabaeus import (    # import all necessary SCB classes
    constants,
    Units,
    ArrayWUnits,
    Frame,
    ArrayWFrame,
    EpochArray
)

from pathlib import Path    # import additional libraries with a break between SCB imports
import numpy as np

# define units and frames
km, kg, sec = Units.get_units(['km', 'kg', 'sec'])
J2000 = Frame('J2000')

# class definition
class TemplateClass():
    """ Template class for Scarabaeus developers.

        An example Python class to illustrate the style guide adopted by 
        Scarabaeus (SCB) developers and provide a starting point when creating 
        new modules.

        This is the second description paragraph. This template provides 
        examples for each docstring section, although not every section must always 
        be included. See the Notes section for an overview of the docstring 
        structure in order and recomendations for when and when not to include 
        them.

        Parameters
        ----------
        input_a : float
            The first input, not optional. We noted its type with the : float 
            after its name.
        input_b : str, optional
            A second optional input. Make sure to note what the default 
            value is using double back ticks. Defaults to ``None``.

        Raises
        ------
        CustomError
            Raised when we trigger the custom error. Only need to fill this 
            section out if we've created a custom error, otherwise this should 
            be omitted from the docstring.

        See Also
        --------
        scarabaeus.Units : provide a small reason to see the also.

        Notes
        -----
        The format of the class docstring follows the order:

        * name of the class
        * high-level description of the class in a sentence or two
        * if necessary, provide a more detailed description of the class in the
          second paragraph
        * Parameters section
        * Raises section - optional, usually not included. Only necessary 
          if the class contains custom errors
        * See Also section - optional, only included if you want to point to 
          any other SCB classes that are relevant
        * Notes section - optional, only included if you want to provide 
          additional information that would make the general description overly 
          long or complicated.
        * References section - optional, only included if the class 
          uses concepts from a source that needs to be cited
        * Examples - provide an example or two of using the class if it's small. 
          For large/complex classes, omit this section but make sure that you 
          provide examples in function docstrings.

        References
        ----------
        .. [1] Numpydoc,
          https://numpydoc.readthedocs.io/en/latest/example.html
            
        Examples
        --------
        Written in doctest format:
        
        >>> import scarabaeus as scb
        >>> example = scb.TemplateClass(1.0)
        >>> print(example)
        SCB Template Class with input a: 1.0 and input b: RECEIVED NONE FOR b
    """
    # region Constructor
    def __init__(self, input_a: float, input_b: str = None):
        # NOTE: we don't put a docstring on the init, otherwise it will override 
        #       the class docstring.
        ## save inputs
        # here we are performing input validation by passing them to their 
        # setters before actually assigning the property (no _)
        self._validate_a(input_a)
        self._validate_and_condition_b(input_b)

        ## save additional parameters if necessary
        # since we're setting these, we can set their private attributes directly
        # with the _
        self._public_prop = 2.0     # this property is readable by users
        self._priv_att    = False   # this attribute is private

    # region Properties
    @property
    def input_a(self) -> float:
        """ Explain what the property is. """
        return self._input_a
    
    def _validate_a(self, a) -> None:
        """ Internal method to validate input_a. """
        # must be of type float
        if not isinstance(a, float):
            raise TypeError(f'Received invalid type {type(a)} for input_a. '
                            'Must be of float.')
        
        # not doing anymore checking/conditioning -> set property
        self._input_a = a

    @property
    def input_b(self) -> str | None:
        """ Explain what the property is.
            
            For properties that need more than one line, make sure 
            to leave a vertical space between the short and long description. 
            Also note that we use the -> pointer to hint what type the property 
            is. This is important for documentation as well as for inline 
            autocompletion.
        """
        return self._input_b
    
    def _validate_and_condition_b(self, b) -> None:
        """ Internal method to validate and condition input_b. 
            In addition to checking the type, this will condition 
            the input depending on the received type.
        """
        match b:
            case str():
                # received string input -> more input handling
                if b < 0.0:
                    # negative -> must be positive
                    raise ValueError('Received a negative value for input_b.')
                
                if b == 5.2:
                    # special custom error
                    err_str = ('To illustrate a custom error, cannot accept '
                               'an input_b value of 5.2.')
                    raise type("CustomError", (Exception,), {})(err_str)

                # valid input -> assign property
                self._input_b = b
            case None:
                # received None -> assuming default
                self._input_b = 'RECEIVED NONE FOR b'
            case _:
                # didn't receive string or None -> bad type
                raise TypeError(f'Received invalid type {type(b)} for input_b. '
                                'Must be str or None.')

    @property
    def public_prop(self) -> float:
        """ Some additional public property of the class. 
            
            Even if it doesn't have a setter, still need to provide 
            a description of it if we want it visible to the user.
        """
        return self.public_prop
    
    # NOTE: do not assign a @property decorator to private attributes, for example 
    #       self._priv_att
    
    # endregion Properties

    # region Operators
    def __repr__(self):
        # most other operators don't need to be overridden. __repr__ 
        # almost always should be though so that printing the class can 
        # give us more info. We can see in the Examples section of the 
        # class docstring that printing TemplateClass will return info 
        # about its input variables, so let's do that here
        return f'SCB Template Class with input a: {self._input_a} and input b: None'

    # endregion Operators

    # region Methods
    def template_method(self, input_a: ArrayWUnits) -> float:
        """ Multiplies an :class:`~scarabaeus.units.ArrayWUnits` and returns its 
            values multiplied by the internal property :attr:`public_prop`.
        
            This is an example method for the example class TemplateClass.
            It takes 

            Parameters
            ----------
            input_a : :class:`~scarabaeus.units.ArrayWUnits`
                The ArrayWUnits to multiply by.

            Returns
            -------
            multiplied: float
                The given multiplied result.

            Notes
            -----
            Notes go here if you have them.

            Examples
            --------
            Here is an example:

            >>> import scarabaeus as scb
            >>> kg = scb.Units.get_units('kg')
            >>> a = scb.ArrayWUnits(10, kg)
            >>> print(a)
            10 kg
            >>> template = scb.TemplateClass(5.0)
            >>> print(template.public_prop)
            2.0
            >>> ans = template.template_method(a)
            >>> print(ans)
            20.0
        """
        # input handling
        if not isinstance(input_a, ArrayWUnits):
            raise TypeError(f'Received invalid type {type(input_a)} for input_a. '
                            'Must be ArrayWUnits.')
        
        # multiply value by property and return
        return input_a.values * self._public_prop

heres more