Thursday, February 16, 2017

Templating Other Entities

Today's post will be long from a scrolling standpoint, but since there's not much meat to the template-code, it should go pretty quickly. The goal for today is to generate final templates/snippets for interface, abstract class, class and final class constructs that can be used as starting-points for defining those types in project-code without having to start from scratch every single time. If you cringed, or started to think out a non-Pythonic-themed comment when you saw final, stop right now, and read this first.

I'm going to tackle them in the order noted above.

The Interface Template

These cannot be instantiated, and must be extended into concrete implementations. In most languages that I'm aware of, an interface definition cannot have properties (not even abstract ones), and cannot have concrete method-implementations. Python doesn't have that constraint (it doesn't have a formal interface-construct, but allows for abstract property- and method-definition through the abc module, using its ABCMeta as a __metaclass__ specification to define a class that has some abstract members, and the @abstractmethod and @abstractproperty decorator-functions to define which members are abstract.

@describe.InitClass()
class InterfaceName( object ):
    """TODO: Document InterfaceName
Provides interface requirements and type-identity for objects that can 
REPRESENT SOMETHING"""

    #-----------------------------------#
    # Abstraction through abc.ABCMeta   #
    #-----------------------------------#
    __metaclass__ = abc.ABCMeta

    #-----------------------------------#
    # Static interface attributes (and  #
    # default values?)                  #
    #-----------------------------------#

    #-----------------------------------#
    # Abstract Properties               #
    #-----------------------------------#

#    PropertyName = abc.abstractproperty()

    #-----------------------------------#
    # Instance Initializer              #
    #-----------------------------------#
    @describe.AttachDocumentation()
    @describe.todo( 'Document __init__' )
    @describe.todo( 'Implement __init__' )
    def __init__( self ):
        """
Instance initializer"""
        # InterfaceName is intended to be an interface,
        # and is NOT intended to be instantiated. Alter at your own risk!
        if self.__class__ == InterfaceName:
            raise NotImplementedError( 'InterfaceName is '
                'intended to be an interface, NOT to be instantiated.' )
        # Call parent initializers, if applicable.

    #-----------------------------------#
    # Instance Garbage Collection       #
    #-----------------------------------#

    #-----------------------------------#
    # Abstract Instance Methods         #
    #-----------------------------------#

#    @abc.abstractmethod
#    def RequiredMethod( arg1, arg2=None, *args, **kwargs ):
#        raise NotImplementedError( '%s.RequiredMethod is not implemented as '
#            'required by InterfaceName' % self.__class__.__name__ )

    #-----------------------------------#
    # Abstract Class Methods            #
    #-----------------------------------#

    #-----------------------------------#
    # Static Class Methods              #
    #-----------------------------------#

#---------------------------------------#
# Append to __all__                     #
#---------------------------------------#
__all__.append( 'InterfaceName' )

Like the module- and package-templates, a lot of this is just structural, organizational comment-blocks, indicating where to put verious types of members. After the initial declaration of the __metaclass__ for the interface, there are seven major blocks:

  • A place to define static(-ish) interface-level attributes/constants;
  • A place to define abstract (required) properties;
  • A concrete __init__, more on that shortly;
  • A block for a __del__ method, if one needs to be required in derived classes;
  • A block for defining abstract instance-methods;
  • A block for defining abstract class-methods; and
  • A block for defining static methods.

The check for the InterfaceName is in place so that an interface could be made more-or-less operational on Python versions that predate the abc module (sometime in the 2.6.x versions). It'd be kinda tedious, but all that would have to be done would be to remove all of the abc.[whatever] calls and references, and the interface would still be non-instantiable. A similar pattern is also in place for the templated abstract-method for the same reason.

The Abstract Class Template

These also cannot be instantiated, but can contain concrete implementations of properties and methods both, as well as abstract requirements for properties and methods. Much of it is very similar to the Interface template, for much the same reasons, but there are a couple additional sections:

  • Blocks for collecting the (usually protected) property-getter, -setter and -deleter methods for any concrete property implementations; and
  • A concrete- and abstract-property location;

@describe.InitClass()
class AbstractClassName( object ):
    """TODO: Document AbstractClassName
Provides baseline functionality, interface requirements and type-identity for 
objects that can REPRESENT SOMETHING"""

    #-----------------------------------#
    # Abstraction through abc.ABCMeta   #
    #-----------------------------------#
    __metaclass__ = abc.ABCMeta

    #-----------------------------------#
    # Class attributes (and instance-   #
    # attribute default values)         #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-getter methods  #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-setter methods  #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-deleter methods #
    #-----------------------------------#

    #-----------------------------------#
    # Instance Properties (abstract OR  #
    # concrete!)                        #
    #-----------------------------------#

#    PropertyName = abc.abstractproperty()
#    PropertyName = describe.makeProperty()

    #-----------------------------------#
    # Instance Initializer              #
    #-----------------------------------#
    @describe.AttachDocumentation()
    @describe.todo( 'Document __init__' )
    @describe.todo( 'Implement __init__' )
    def __init__( self ):
        """
Instance initializer"""
        # AbstractClassName is intended to be an abstract class,
        # and is NOT intended to be instantiated. Alter at your own risk!
        if self.__class__ == AbstractClassName:
            raise NotImplementedError( 'AbstractClassName is '
                'intended to be an abstract class, NOT to be instantiated.' )
        # Call parent initializers, if applicable.
        # Set default instance property-values with _Del... methods as needed.
        # Set instance property values from arguments if applicable.

    #-----------------------------------#
    # Instance Garbage Collection       #
    #-----------------------------------#

    #-----------------------------------#
    # Instance Methods                  #
    #-----------------------------------#

#    @abc.abstractmethod
#    def RequiredMethod( arg1, arg2=None, *args, **kwargs ):
#        raise NotImplementedError( '%s.RequiredMethod is not implemented as '
#            'required by InterfaceName' % self.__class__.__name__ )

    #-----------------------------------#
    # Class Methods                     #
    #-----------------------------------#

    #-----------------------------------#
    # Static Class Methods              #
    #-----------------------------------#

#---------------------------------------#
# Append to __all__                     #
#---------------------------------------#
__all__.append( 'AbstractClassName' )

There are a few more prompt-comments in the __init__ method as well, keeping with my normal programming structure: I explicitly delete all of an instance's properties during initialization so that they will always have a value after instances are first created.

The Class Template

Classes are intended to be instantiated, and can be extended freely. This template doesn't have anything new, that I haven't shown before, so there's not much that can be said about it.

@describe.InitClass()
class ClassName( object ):
    """TODO: Document ClassName
Represents SOMETHING"""
    #-----------------------------------#
    # Class attributes (and instance-   #
    # attribute default values)         #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-getter methods  #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-setter methods  #
    #-----------------------------------#

    #-----------------------------------#
    # Instance property-deleter methods #
    #-----------------------------------#

    #-----------------------------------#
    # Instance Properties               #
    #-----------------------------------#

    #-----------------------------------#
    # Instance Initializer              #
    #-----------------------------------#
    @describe.AttachDocumentation()
    @describe.todo( 'Document __init__' )
    @describe.todo( 'Implement __init__' )
    def __init__( self ):
        """
Instance initializer"""
        # Call parent initializers, if applicable.
        # Set default instance property-values with _Del... methods as needed.
        # Set instance property values from arguments if applicable.
        pass # TODO: Remove this line after __init__ is implemented

    #-----------------------------------#
    # Instance Garbage Collection       #
    #-----------------------------------#

    #-----------------------------------#
    # Instance Methods                  #
    #-----------------------------------#

    #-----------------------------------#
    # Class Methods                     #
    #-----------------------------------#

    #-----------------------------------#
    # Static Class Methods              #
    #-----------------------------------#

#---------------------------------------#
# Append to __all__                     #
#---------------------------------------#
__all__.append( 'ClassName' )

The Final Class Template

A final class is intended to be instantiated, but not extended. The only significant difference between this template and the Class template above is how the __init__ handles extension-attempts:

    #-----------------------------------#
    # Instance Initializer              #
    #-----------------------------------#
    @describe.AttachDocumentation()
    @describe.todo( 'Document __init__' )
    @describe.todo( 'Implement __init__' )
    def __init__( self ):
        """
Instance initializer"""
        # FinalClassName is intended to be a nominally-final class
        # and is NOT intended to be extended. Alter at your own risk!
        #---------------------------------------------------------------------#
        # TODO: Explain WHY it's nominally final!                             #
        #---------------------------------------------------------------------#
        if self.__class__ != FinalClassName:
            raise NotImplementedError( 'FinalClassName is '
                'intended to be a nominally-final class, NOT to be extended.' )
        # Call parent initializers, if applicable.
        # Set default instance property-values with _Del... methods as needed.
        # Set instance property values from arguments if applicable.

After taking some time to roll the doc_metadata.py file into the new standard template, I can (finally) make good on my promise to make it available for download, as well as the collection of template-files:

109.6kB

It's been fourteen posts now, and just over a month since I started in on the documentation-process. I'm not sure exactly where I want to go next on the Python side of things, though I have a couple of ideas, so I think for my next post, I'll play around a bit with implementing some design-patterns in JavaScript. If nothing else, that will give me some time to ponder what the next logical step is.

No comments:

Post a Comment