1.. _io_registry:
2
3************************************
4I/O Registry (`astropy.io.registry`)
5************************************
6
7.. note::
8
9   The I/O registry is only meant to be used directly by users who want to
10   define their own custom readers/writers. Users who want to find out more
11   about what built-in formats are supported by :class:`~astropy.table.Table`
12   by default should see :ref:`table_io`.
13   Likewise :ref:`cosmology_io` for built-in formats supported by
14   :class:`~astropy.cosmology.Cosmology`.
15   No built-in formats are currently defined for
16   :class:`~astropy.nddata.NDData`, but this will be added in future.
17
18Introduction
19============
20
21The I/O registry is a submodule used to define the readers/writers available
22for the :class:`~astropy.table.Table`, :class:`~astropy.nddata.NDData`,
23and :class:`~astropy.cosmology.Cosmology` classes.
24
25
26Custom Read/Write Functions
27===========================
28
29This section demonstrates how to create a custom reader/writer. A reader is
30written as a function that can take any arguments except ``format`` (which is
31needed when manually specifying the format — see below) and returns an
32instance of the :class:`~astropy.table.Table` or
33:class:`~astropy.nddata.NDData` classes (or subclasses).
34
35
36Examples
37--------
38
39..
40  EXAMPLE START
41  Using astropy.io.registry to Create a Custom Reader/Writer
42
43Here we assume that we are trying to write a reader/writer for the
44:class:`~astropy.table.Table` class::
45
46    >>> from astropy.table import Table
47
48    >>> def my_table_reader(filename, some_option=1):
49    ...     # Read in the table by any means necessary
50    ...     return table  # should be an instance of Table
51
52Such a function can then be registered with the I/O registry::
53
54    from astropy.io import registry
55    registry.register_reader('my-table-format', Table, my_table_reader)
56
57where the first argument is the name of the format, the second argument is the
58class that the function returns an instance for, and the third argument is the
59reader itself.
60
61We can then read in a table with::
62
63    d = Table.read('my_table_file.mtf', format='my-table-format')
64
65In practice, it would be nice to have the ``read`` method automatically
66identify that this file is in the ``my-table-format`` format, so we can
67construct a function that can recognize these files, which we refer to here as
68an *identifier* function.
69
70An identifier function should take a first argument that is a string
71which indicates whether the identifier is being called from ``read`` or
72``write``, and should then accept an arbitrary number of positional and keyword
73arguments via ``*args`` and ``**kwargs``, which are the arguments passed to
74the ``read`` method.
75
76In the above case, we can write a function that only looks at
77filenames (but in practice, this function could even look at the first few
78bytes of the file, for example). The only requirement for the identifier
79function is that it return a boolean indicating whether the input matches that
80expected for the format. In our example, we want to automatically recognize
81files with filenames ending in ``.mtf`` as being in the ``my-table-format``
82format::
83
84    import os
85
86    def identify_mtf(origin, *args, **kwargs):
87        return (isinstance(args[0], str) and
88                os.path.splitext(args[0].lower())[1] == '.mtf')
89
90.. note::
91
92    Identifier functions should be prepared for arbitrary input — in
93    particular, the first argument may not be a filename or file object, so it
94    should not assume that this is the case.
95
96We then register this identifier function, similarly to the reader function::
97
98    registry.register_identifier('my-table-format', Table, identify_mtf)
99
100Having registered this function, we can then do::
101
102    t = Table.read('catalog.mtf')
103
104If multiple formats match the current input, then an exception is
105raised, and similarly if no format matches the current input. In that
106case, the format should be explicitly given with the ``format=``
107keyword argument.
108
109It is also possible to create custom writers. To go with our custom reader
110above, we can write a custom writer::
111
112   def my_table_writer(table, filename, overwrite=False):
113       ...  # Write the table out to a file
114       return ...  # generally None, but other values are not forbidden.
115
116Writer functions should take a dataset object (either an instance of the
117:class:`~astropy.table.Table` or :class:`~astropy.nddata.NDData`
118classes or subclasses), and any number of subsequent positional and keyword
119arguments — although as for the reader, the ``format`` keyword argument cannot
120be used.
121
122We then register the writer::
123
124   registry.register_writer('my-custom-format', Table, my_table_writer)
125
126We can write the table out to a file::
127
128   t.write('catalog_new.mtf', format='my-table-format')
129
130Since we have already registered the identifier function, we can also do::
131
132   t.write('catalog_new.mtf')
133
134..
135  EXAMPLE END
136
137
138Registries, local and default
139=============================
140
141.. versionchanged:: 5.0
142
143As of Astropy 5.0 the I/O registry submodule has switched to a class-based
144architecture, allowing for the creation of custom registries.
145The three supported registry types are read-only --
146:class:`~astropy.io.registry.UnifiedInputRegistry` --
147write-only -- :class:`~astropy.io.registry.UnifiedOutputRegistry` --
148and read/write -- :class:`~astropy.io.registry.UnifiedIORegistry`.
149
150    >>> from astropy.io.registry import UnifiedIORegistry
151    >>> example_reg = UnifiedIORegistry()
152    >>> print([m for m in dir(example_reg) if not m.startswith("_")])
153    ['available_registries', 'delay_doc_updates', 'get_formats', 'get_reader',
154     'get_writer', 'identify_format', 'read', 'register_identifier',
155     'register_reader', 'register_writer', 'unregister_identifier',
156     'unregister_reader', 'unregister_writer', 'write']
157
158For backward compatibility all the methods on this registry have corresponding
159module-level functions, which work with the default global read/write registry.
160These functions were used in the previous examples. This new registry is empty.
161
162    >>> example_reg.get_formats()
163    <Table length=0>
164    Data class  Format   Read   Write  Auto-identify
165     float64   float64 float64 float64    float64
166    ---------- ------- ------- ------- -------------
167
168We can register read / write / identify methods with this registry object:
169
170    >>> example_reg.register_reader('my-table-format', Table, my_table_reader)
171    >>> example_reg.get_formats()
172    <Table length=1>
173    Data class      Format     Read Write Auto-identify
174       str5         str15      str3  str2      str2
175    ---------- --------------- ---- ----- -------------
176         Table my-table-format  Yes    No            No
177
178
179What is the use of a custom registries?
180
181    1. To make read-only or write-only registries.
182    2. To allow for different readers for the same format.
183    3. To allow for an object to have different *kinds* of readers and writers.
184       E.g. |Cosmology| which supports both file I/O and object conversion.
185
186
187Reference/API
188=============
189
190.. automodapi:: astropy.io.registry
191