|
29 | 29 | "a Python object equivalent to the one it started out as.\n", |
30 | 30 | "\n", |
31 | 31 | "Note: The object to be serialized to ASDF must consist\n", |
32 | | - "of only elements that ADSF knows how to serialize. ASDF does\n", |
| 32 | + "of only elements that asdf knows how to serialize. asdf does\n", |
33 | 33 | "know how to serialize numpy arrays and all standard \n", |
34 | 34 | "Python primative types (e.g., strings, numbers, booleans,\n", |
35 | 35 | "lists and dictionaries), as well as objects that have \n", |
|
54 | 54 | "--------------\n", |
55 | 55 | "\n", |
56 | 56 | "For ASDF to be aware of the converters, it is necessary \n", |
57 | | - "to include in the setup.cfg file information for an entry\n", |
| 57 | + "to configure an entry\n", |
58 | 58 | "point for ASDF. Entry points are\n", |
59 | | - "a very useful Python tool for making plug-ins for packages\n", |
60 | | - "easy for users of the plug-in to use, both in the installation\n", |
| 59 | + "a useful Python tool for making plug-ins easy for users, both in the installation\n", |
61 | 60 | "and usage aspect.\n", |
62 | 61 | "Entry points remove the need for the core package to be continually \n", |
63 | 62 | "updated with new extension packages that it has to be aware of.\n", |
64 | 63 | "\n", |
65 | 64 | "This information is provided in the converter package's\n", |
66 | | - "setup.cfg (it could be in\n", |
67 | | - "setup.py, but the .cfg file is the usual place to put this\n", |
68 | | - "information). What happens when the package is installed is\n", |
| 65 | + "configuration. What happens when the package is installed is\n", |
69 | 66 | "that information about entry points is saved by Python. Python\n", |
70 | 67 | "provides an API to the core package for it to discover what \n", |
71 | 68 | "entry points have been designated for that package so that it can\n", |
|
80 | 77 | "We will name the converter package mfconverter (for My First\n", |
81 | 78 | "Converter) so this will create a package of that name in\n", |
82 | 79 | "the current directory. This tutorial will have the bare bones\n", |
83 | | - "files needed for that. The following will generate\n", |
84 | | - "such a barebones directory structure.\n", |
85 | | - "\n", |
86 | | - "Since this tutorial effectively is editing files and needs\n", |
87 | | - "to move between different directories, it is more awkward than\n", |
88 | | - "usual for a notebook\n", |
89 | | - "\n", |
90 | | - "Required Software\n", |
91 | | - "----------------------\n", |
92 | | - "\n", |
93 | | - "- numpy\n", |
94 | | - "- asdf v2.8 or higher\n", |
95 | | - "\n", |
96 | | - "Let's begin\n", |
97 | | - "-------------\n", |
98 | | - "\n", |
99 | | - "This will create a converter_tutorial directory in your home directory.\n", |
100 | | - "If you wish it to be elsewhere, make the appropriate changes to the\n", |
101 | | - "dependent commands, but if there is no strong reason to change it,\n", |
102 | | - "leave it be." |
103 | | - ] |
104 | | - }, |
105 | | - { |
106 | | - "cell_type": "code", |
107 | | - "execution_count": null, |
108 | | - "id": "5fec10da", |
109 | | - "metadata": {}, |
110 | | - "outputs": [], |
111 | | - "source": [ |
112 | | - "mkdir ~/converter_tutorial" |
113 | | - ] |
114 | | - }, |
115 | | - { |
116 | | - "cell_type": "code", |
117 | | - "execution_count": null, |
118 | | - "id": "3b1e163f", |
119 | | - "metadata": {}, |
120 | | - "outputs": [], |
121 | | - "source": [ |
122 | | - "cd ~/converter_tutorial" |
123 | | - ] |
124 | | - }, |
125 | | - { |
126 | | - "cell_type": "code", |
127 | | - "execution_count": null, |
128 | | - "id": "06a42933", |
129 | | - "metadata": {}, |
130 | | - "outputs": [], |
131 | | - "source": [ |
132 | | - "# Record current directory for restarts\n", |
133 | | - "import os\n", |
134 | | - "curdir = os.getcwd()\n", |
135 | | - "print(curdir)" |
136 | | - ] |
137 | | - }, |
138 | | - { |
139 | | - "cell_type": "code", |
140 | | - "execution_count": null, |
141 | | - "id": "0ae4a184", |
142 | | - "metadata": {}, |
143 | | - "outputs": [], |
144 | | - "source": [ |
145 | | - "mkdir mfconverter" |
146 | | - ] |
147 | | - }, |
148 | | - { |
149 | | - "cell_type": "code", |
150 | | - "execution_count": null, |
151 | | - "id": "46d22a46", |
152 | | - "metadata": {}, |
153 | | - "outputs": [], |
154 | | - "source": [ |
155 | | - "cd mfconverter" |
156 | | - ] |
157 | | - }, |
158 | | - { |
159 | | - "cell_type": "code", |
160 | | - "execution_count": null, |
161 | | - "id": "a980c3f9", |
162 | | - "metadata": {}, |
163 | | - "outputs": [], |
164 | | - "source": [ |
165 | | - "mkdir src" |
166 | | - ] |
167 | | - }, |
168 | | - { |
169 | | - "cell_type": "code", |
170 | | - "execution_count": null, |
171 | | - "id": "8dbb76d3", |
172 | | - "metadata": {}, |
173 | | - "outputs": [], |
174 | | - "source": [ |
175 | | - "mkdir src/mfconverter" |
| 80 | + "files needed for that." |
176 | 81 | ] |
177 | 82 | }, |
178 | 83 | { |
179 | 84 | "cell_type": "markdown", |
180 | 85 | "id": "ede348bd", |
181 | 86 | "metadata": {}, |
182 | 87 | "source": [ |
183 | | - "And now we will create a module that has a very, very simple photo ID class and add a package `__init__.py`" |
184 | | - ] |
185 | | - }, |
186 | | - { |
187 | | - "cell_type": "code", |
188 | | - "execution_count": null, |
189 | | - "id": "f045f111", |
190 | | - "metadata": {}, |
191 | | - "outputs": [], |
192 | | - "source": [ |
193 | | - "%%writefile src/mfconverter/photo_id.py\n", |
194 | | - "class PhotoID:\n", |
195 | | - " \"Holds Photo ID information\"\n", |
196 | | - "\n", |
197 | | - " def __init__(self, last_name, first_name, photo):\n", |
198 | | - " \"expects a monochromatic numpy array for photo\"\n", |
199 | | - " self.last_name = last_name\n", |
200 | | - " self.first_name = first_name\n", |
201 | | - " self.photo = photo\n", |
202 | | - " \n", |
203 | | - " def name(self):\n", |
204 | | - " return self.last_name + ', ' + self.first_name" |
205 | | - ] |
206 | | - }, |
207 | | - { |
208 | | - "cell_type": "code", |
209 | | - "execution_count": null, |
210 | | - "id": "ad205d2e", |
211 | | - "metadata": {}, |
212 | | - "outputs": [], |
213 | | - "source": [ |
214 | | - "%%writefile src/__init__.py\n", |
215 | | - "\n" |
216 | | - ] |
217 | | - }, |
218 | | - { |
219 | | - "cell_type": "markdown", |
220 | | - "id": "09ee1385", |
221 | | - "metadata": {}, |
222 | | - "source": [ |
223 | | - "Next we create the file that contains the converter code" |
224 | | - ] |
225 | | - }, |
226 | | - { |
227 | | - "cell_type": "code", |
228 | | - "execution_count": null, |
229 | | - "id": "aa345e79", |
230 | | - "metadata": {}, |
231 | | - "outputs": [], |
232 | | - "source": [ |
233 | | - "%%writefile src/mfconverter/converter.py\n", |
234 | | - "from asdf.extension import Converter\n", |
235 | | - "\n", |
236 | | - "class PhotoIDConverter(Converter):\n", |
237 | | - " tags = [\"asdf://stsci.edu/example-project/tags/photo_id-*\"]\n", |
238 | | - " types = [\"mfconverter.photo_id.PhotoID\"]\n", |
239 | | - " # The above registers the tag that the converter is used for, as well as\n", |
240 | | - " # associating the class that the converter is used for.\n", |
241 | | - " \n", |
242 | | - " # This method converts from the Python object to yaml\n", |
243 | | - " def to_yaml_tree(self, obj, tags, ctx):\n", |
244 | | - " # The yaml conversion expects a dictionary returned\n", |
245 | | - " node = {}\n", |
246 | | - " node['first_name'] = obj.first_name\n", |
247 | | - " node['last_name'] = obj.last_name\n", |
248 | | - " node['photo'] = obj.photo\n", |
249 | | - " return node\n", |
250 | | - " \n", |
251 | | - " # This method converts from yaml to the Python object\n", |
252 | | - " def from_yaml_tree(self, node, tag, ctx):\n", |
253 | | - " from .photo_id import PhotoID # Deferred import to avoid always importing \n", |
254 | | - " # when ASDF gets entry points.\n", |
255 | | - " return PhotoID(node['last_name'],\n", |
256 | | - " node['first_name'],\n", |
257 | | - " node['photo'])" |
| 88 | + "The files for `mfconverter` are located in the `Your_first_ASDF_converter` directory alongside this notebook. Please review the files. A few specific details will be highlighted below." |
258 | 89 | ] |
259 | 90 | }, |
260 | 91 | { |
261 | 92 | "cell_type": "markdown", |
262 | 93 | "id": "55ca30cd", |
263 | 94 | "metadata": {}, |
264 | 95 | "source": [ |
265 | | - "In the above example, the class attribute `types` is a list since it is\n", |
| 96 | + "In `PhotoIDConverter` defined in `converter.py`, the class attribute `types` is a list since it is\n", |
266 | 97 | "possible for multiple types to use the same converter. Secondly, it is\n", |
267 | 98 | "possible to supply the type as either a string (as done above) as the \n", |
268 | 99 | "actual class itself. The former is preferred for peformance reasons as\n", |
269 | 100 | "using the actual class itself forces the ASDF package to import the\n", |
270 | 101 | "module containing the class, even if it is never used in the program\n", |
271 | | - "using ASDF.\n", |
| 102 | + "using asdf.\n", |
272 | 103 | "\n", |
273 | 104 | "The wildcard for the tag entry indicates that this converter will work\n", |
274 | 105 | "with all versions of the tag. It isn't strictly needed here, but generally \n", |
275 | 106 | "a good practice if one wants the converter code to handle multiple versions.\n", |
276 | | - "(How to handle versioning is a topic in its own right and not covered here)\n", |
277 | | - "\n", |
278 | | - "We need to create a module for the entry point handling" |
279 | | - ] |
280 | | - }, |
281 | | - { |
282 | | - "cell_type": "code", |
283 | | - "execution_count": null, |
284 | | - "id": "f9c03910", |
285 | | - "metadata": {}, |
286 | | - "outputs": [], |
287 | | - "source": [ |
288 | | - "%%writefile src/mfconverter/extensions.py\n", |
289 | | - "from asdf.extension import Extension\n", |
290 | | - "from .converter import PhotoIDConverter\n", |
291 | | - "\n", |
292 | | - "class MFExtension(Extension):\n", |
293 | | - " extension_uri = \"asdf://stsci.edu/example-project/photo_id-1.0.0\"\n", |
294 | | - " tags = [\"asdf://stsci.edu/example-project/tags/photo_id-1.0.0\"]\n", |
295 | | - " converters = [PhotoIDConverter()]\n", |
296 | | - "\n", |
297 | | - "# The following will be called by ASDF when looking for ASDF entry points \n", |
298 | | - "def get_extensions():\n", |
299 | | - " return [MFExtension()]" |
| 107 | + "(How to handle versioning is a topic in its own right and not covered here)" |
300 | 108 | ] |
301 | 109 | }, |
302 | 110 | { |
303 | 111 | "cell_type": "markdown", |
304 | 112 | "id": "412c3e4f", |
305 | 113 | "metadata": {}, |
306 | 114 | "source": [ |
307 | | - "And finally, the entry point reference in setup.cfg, provided here as a whole file." |
308 | | - ] |
309 | | - }, |
310 | | - { |
311 | | - "cell_type": "code", |
312 | | - "execution_count": null, |
313 | | - "id": "feb206d7", |
314 | | - "metadata": {}, |
315 | | - "outputs": [], |
316 | | - "source": [ |
317 | | - "%%writefile pyproject.toml\n", |
318 | | - "[project]\n", |
319 | | - "name = \"mfconverter\"\n", |
320 | | - "description = \"TODO\"\n", |
321 | | - "version='0.1.0'\n", |
322 | | - "requires-python = \">=3.9\"\n", |
323 | | - "dependencies = [\n", |
324 | | - " \"jsonschema >=3.0.2\",\n", |
325 | | - " \"asdf >=2.8\",\n", |
326 | | - " \"psutil >=5.7.2\",\n", |
327 | | - " \"numpy>=1.16\",\n", |
328 | | - "]\n", |
329 | | - "\n", |
330 | | - "[build-system]\n", |
331 | | - "requires = [\"setuptools >=61\", \"setuptools_scm[toml] >=3.4\"]\n", |
332 | | - "build-backend = \"setuptools.build_meta\"\n", |
| 115 | + "As mentioned above `mfconverter` defines an entry point in the `pyproject.toml` configurate file that is used to register `PhotoIDConverter` with asdf.\n", |
333 | 116 | "\n", |
334 | | - "[project.entry-points.\"asdf.extensions\"]\n", |
335 | | - "mfconverter = \"mfconverter.extensions:get_extensions\"\n", |
336 | | - "\n", |
337 | | - "[tool.setuptools.packages.find]\n", |
338 | | - "where = [\"src\"]" |
339 | | - ] |
340 | | - }, |
341 | | - { |
342 | | - "cell_type": "markdown", |
343 | | - "id": "8d0e3882", |
344 | | - "metadata": {}, |
345 | | - "source": [ |
346 | 117 | "If you wish to learn more about entry points, see:\n", |
347 | | - "https://packaging.python.org/guides/creating-and-discovering-plugins/\n", |
348 | | - "\n", |
349 | | - "We need the setup.py file too" |
350 | | - ] |
351 | | - }, |
352 | | - { |
353 | | - "cell_type": "code", |
354 | | - "execution_count": null, |
355 | | - "id": "3ffb22d6", |
356 | | - "metadata": {}, |
357 | | - "outputs": [], |
358 | | - "source": [ |
359 | | - "%%writefile setup.py\n", |
360 | | - "#!/usr/bin/env python3\n", |
361 | | - "from setuptools import setup\n", |
362 | | - "\n", |
363 | | - "setup()" |
| 118 | + "https://packaging.python.org/guides/creating-and-discovering-plugins/" |
364 | 119 | ] |
365 | 120 | }, |
366 | 121 | { |
|
369 | 124 | "metadata": {}, |
370 | 125 | "source": [ |
371 | 126 | "Install package\n", |
372 | | - "------------------\n", |
373 | | - "\n", |
374 | | - "This is best done from a terminal window using the command\n", |
375 | | - "\n", |
376 | | - "`pip install --editable .`\n", |
377 | | - "\n", |
378 | | - "But this notebook will do that, but a consequence is that if the reinstallation must be done, the Jupyter kernel must be restarted to pick up the new installation." |
| 127 | + "------------------" |
379 | 128 | ] |
380 | 129 | }, |
381 | 130 | { |
|
385 | 134 | "metadata": {}, |
386 | 135 | "outputs": [], |
387 | 136 | "source": [ |
388 | | - "!pip install --editable ." |
| 137 | + "!pip install ./Your_first_ASDF_converter" |
389 | 138 | ] |
390 | 139 | }, |
391 | 140 | { |
|
539 | 288 | "name": "python", |
540 | 289 | "nbconvert_exporter": "python", |
541 | 290 | "pygments_lexer": "ipython3", |
542 | | - "version": "3.10.10" |
| 291 | + "version": "3.13.4" |
543 | 292 | } |
544 | 293 | }, |
545 | 294 | "nbformat": 4, |
|
0 commit comments