Repository: sunpy/sunpy

Issue:
Automatically include Map docstring when displaying help for subclasses?
Originally raised in #396. Ideally, the `GenericMap` docstring should also be included when using one of the source-specific maps. For example, when calling `help(sunpy.map.sources.AIAMap)`, I should see the docstring for `GenericMap` and `AIAMap`.


Discussion:
Hi 👋 I think I can tackle this issue, just to be sure I was thinking of implementing the following code:
```
class Foo(object):
    """
    Foo Class.
    This class foos around.
    """
    pass

class Bar(Foo):
    """
    Bar class, children of Foo
    Use this when you want to Bar around.
    parent:
    """ 
    __doc__ += Foo.__doc__
    pass
```

We could do this for all the sources. 
(Apologies if something I said doesn't make sense, I am new to open source and still learning! :) ) 
Should work.
@nabobalis should I start to work on it then?

Go ahead.
I recommend implementing this in `GenericMap.__init_subclass__()` so that it's automatic for any subclass and doesn't have to be manually added.

Resolving patch:
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -68,6 +68,52 @@
 to query how the metadata has been modified.
 """
 
+# The notes live here so we can reuse it in the source maps
+_notes_doc = """
+
+Notes
+-----
+
+A number of the properties of this class are returned as two-value named
+tuples that can either be indexed by position ([0] or [1]) or be accessed
+by the names (.x and .y) or (.axis1 and .axis2). Things that refer to pixel
+axes use the ``.x``, ``.y`` convention, where x and y refer to the FITS
+axes (x for columns y for rows). Spatial axes use ``.axis1`` and ``.axis2``
+which correspond to the first and second axes in the header. ``axis1``
+corresponds to the coordinate axis for ``x`` and ``axis2`` corresponds to
+``y``.
+
+This class makes some assumptions about the WCS information contained in
+the meta data. The first and most extensive assumption is that it is
+FITS-like WCS information as defined in the FITS WCS papers.
+
+Within this scope it also makes some other assumptions.
+
+* In the case of APIS convention headers where the CROTAi/j arguments are
+    provided it assumes that these can be converted to the standard PCi_j
+    notation using equations 32 in Thompson (2006).
+
+* If a CDi_j matrix is provided it is assumed that it can be converted to a
+    PCi_j matrix and CDELT keywords as described in
+    `Greisen & Calabretta (2002) <https://doi.org/10.1051/0004-6361:20021327>`_
+
+* The 'standard' FITS keywords that are used by this class are the PCi_j
+    matrix and CDELT, along with the other keywords specified in the WCS
+    papers. All subclasses of this class must convert their header
+    information to this formalism. The CROTA to PCi_j conversion is done in
+    this class.
+
+.. warning::
+    This class currently assumes that a header with the CDi_j matrix
+    information also includes the CDELT keywords, without these keywords
+    this class will not process the WCS.
+    Also the rotation_matrix does not work if the CDELT1 and CDELT2
+    keywords are exactly equal.
+    Also, if a file with more than two dimensions is feed into the class,
+    only the first two dimensions (NAXIS1, NAXIS2) will be loaded and the
+    rest will be discarded.
+"""
+
 __all__ = ['GenericMap']
 
 
@@ -132,50 +178,7 @@ class GenericMap(NDData):
     >>> aia.spatial_units   # doctest: +REMOTE_DATA
     SpatialPair(axis1=Unit("arcsec"), axis2=Unit("arcsec"))
     >>> aia.peek()   # doctest: +SKIP
-
-    Notes
-    -----
-
-    A number of the properties of this class are returned as two-value named
-    tuples that can either be indexed by position ([0] or [1]) or be accessed
-    by the names (.x and .y) or (.axis1 and .axis2). Things that refer to pixel
-    axes use the ``.x``, ``.y`` convention, where x and y refer to the FITS
-    axes (x for columns y for rows). Spatial axes use ``.axis1`` and ``.axis2``
-    which correspond to the first and second axes in the header. ``axis1``
-    corresponds to the coordinate axis for ``x`` and ``axis2`` corresponds to
-    ``y``.
-
-    This class makes some assumptions about the WCS information contained in
-    the meta data. The first and most extensive assumption is that it is
-    FITS-like WCS information as defined in the FITS WCS papers.
-
-    Within this scope it also makes some other assumptions.
-
-    * In the case of APIS convention headers where the CROTAi/j arguments are
-      provided it assumes that these can be converted to the standard PCi_j
-      notation using equations 32 in Thompson (2006).
-
-    * If a CDi_j matrix is provided it is assumed that it can be converted to a
-      PCi_j matrix and CDELT keywords as described in
-      `Greisen & Calabretta (2002) <https://doi.org/10.1051/0004-6361:20021327>`_
-
-    * The 'standard' FITS keywords that are used by this class are the PCi_j
-      matrix and CDELT, along with the other keywords specified in the WCS
-      papers. All subclasses of this class must convert their header
-      information to this formalism. The CROTA to PCi_j conversion is done in
-      this class.
-
-    .. warning::
-        This class currently assumes that a header with the CDi_j matrix
-        information also includes the CDELT keywords, without these keywords
-        this class will not process the WCS.
-        Also the rotation_matrix does not work if the CDELT1 and CDELT2
-        keywords are exactly equal.
-        Also, if a file with more than two dimensions is feed into the class,
-        only the first two dimensions (NAXIS1, NAXIS2) will be loaded and the
-        rest will be discarded.
     """
-
     _registry = dict()
     # This overrides the default doc for the meta attribute
     meta = MetaData(doc=_meta_doc, copy=False)
@@ -190,6 +193,7 @@ def __init_subclass__(cls, **kwargs):
         This is then passed into the Map Factory so we can register them.
         """
         super().__init_subclass__(**kwargs)
+        cls.__doc__ += textwrap.indent(_notes_doc, "    ")
         if hasattr(cls, 'is_datasource_for'):
             cls._registry[cls] = cls.is_datasource_for
 
@@ -2566,6 +2570,9 @@ def _check_axes(self, axes, allow_non_wcsaxes=False, warn_different_wcs=False):
         return axes
 
 
+GenericMap.__doc__ += textwrap.indent(_notes_doc, "    ")
+
+
 class InvalidHeaderInformation(ValueError):
     """Exception to raise when an invalid header tag value is encountered for a
     FITS/JPEG 2000 file."""


Test patch: