<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Reem Hamraz</title>
    <description>The latest articles on DEV Community by Reem Hamraz (@reemhamraz).</description>
    <link>https://dev.to/reemhamraz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3833351%2F77914e3e-884c-4fc1-8fd3-2c53b8e91097.jpeg</url>
      <title>DEV Community: Reem Hamraz</title>
      <link>https://dev.to/reemhamraz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reemhamraz"/>
    <language>en</language>
    <item>
      <title>Learning to Speak C &amp; Cython: My GSoC Summer with Astropy</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Sun, 16 Aug 2026 15:35:15 +0000</pubDate>
      <link>https://dev.to/reemhamraz/learning-to-speak-c-cython-my-gsoc-summer-with-astropy-1fh0</link>
      <guid>https://dev.to/reemhamraz/learning-to-speak-c-cython-my-gsoc-summer-with-astropy-1fh0</guid>
      <description>&lt;p&gt;The summer is officially over. I am staring at a remarkably clean Git branch, my laptop didn't literally take off into orbit (though the CPU fans certainly tried a few times during local CI builds), and I somehow know what &lt;code&gt;git rebase -i&lt;/code&gt; does without having to Google it in a cold sweat.&lt;/p&gt;

&lt;p&gt;If you'd asked me back in May what I was going to be doing, I would have confidently told you I was going to "write tests for Astropy's C extensions." It sounded so neat. So contained. But open source doesn't really work like that. I came in thinking I was just going to write tests, and somewhere along the way, I ended up learning how the actual machinery underneath the Python abstraction works, how maintainers think about architecture, and how to safely catch C-level memory panics without taking down the entire interpreter.&lt;/p&gt;

&lt;p&gt;So, here is the real story of what I did for the last few months, what broke, how we fixed it, and where the project stands now.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what was I actually supposed to do?
&lt;/h2&gt;

&lt;p&gt;Astropy is a beast of a library. The Python-facing API is incredibly robust and beautifully documented. But underneath all those pretty Python classes is a complex, mixed-language architecture. The library relies heavily on compiled C, and Cython extensions to handle the performance-critical hot-paths.&lt;/p&gt;

&lt;p&gt;The problem? That compiled layer was a massive testing blind spot. Before this summer, these performance-critical extensions were almost entirely tested indirectly, meaning they were only validated by calling the high-level Python wrappers. That is a risky abstraction. If a regression happens deep inside the C code, the Python layer sitting above it can accidentally mask it. You wouldn't know something was fundamentally broken until a downstream package started acting weird.&lt;/p&gt;

&lt;p&gt;My project goal was to build a dedicated, de novo test suite that bypassed the public API completely and exercised each compiled extension module directly. This wasn't just for code coverage. It was an absolute prerequisite for three massive Astropy milestones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The APE Split:&lt;/strong&gt; Proving the extensions are stable enough to live in a completely separate package.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Meson Migration:&lt;/strong&gt; An opportunity to try Meson out during a potential package split without bothering the many developers that are already watching the main repo.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Python 3.15 Limited API:&lt;/strong&gt; Safely testing internal C-refactors for the upcoming free-threaded builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mission was to harden Astropy's core stability. The reality was peeling away layers of Python abstractions until I was staring at raw memory buffers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turns out, "testing C extensions" is not quite as simple as it sounds
&lt;/h2&gt;

&lt;p&gt;I started my summer in &lt;code&gt;astropy/table&lt;/code&gt;, which my mentors flagged as a solid starting point. My first target was &lt;code&gt;test_np_utils.py&lt;/code&gt;, writing a low-level suite for the &lt;code&gt;join_inner&lt;/code&gt; Cython extension.&lt;/p&gt;

&lt;p&gt;The thing is, Cython extensions don't want your high-level &lt;code&gt;Table&lt;/code&gt; objects. They want raw data. I had to build a helper function, &lt;code&gt;_make_join_inputs&lt;/code&gt;, which acted as a pre-processor. It took two arrays, concatenated them, sorted them with &lt;code&gt;argsort(kind="stable")&lt;/code&gt;, and used a boolean diffs array to find the exact boundaries of unique keys. It output the exact &lt;code&gt;np.intp&lt;/code&gt; bindings that Cython demanded.&lt;/p&gt;

&lt;p&gt;Once I could feed the raw extension, I built strictly typed dataclasses (&lt;code&gt;ArrayMaskPair&lt;/code&gt; and &lt;code&gt;ExpectedResults&lt;/code&gt;) to hold the outputs without making the pytest matrices unreadable with massive tuples. I tested standard overlaps, but the real fun was the Cartesian edge cases. I threw O(N²) expansions at the extension, where duplicate keys joined with duplicate keys, just to prove the C extension could handle the explosive memory allocation without crashing. I also verified that it properly handles &lt;code&gt;np.nan&lt;/code&gt; as a unique entity (since &lt;code&gt;nan != nan&lt;/code&gt; in Python).&lt;/p&gt;

&lt;p&gt;And all this stands as proof that I can actually write code that works, phew!&lt;/p&gt;

&lt;h2&gt;
  
  
  The C-Slots
&lt;/h2&gt;

&lt;p&gt;Things got significantly more complicated when I moved to &lt;code&gt;test_column_mixins.py&lt;/code&gt;. The objective here was to test the Cython &lt;code&gt;__getitem__&lt;/code&gt; routing in pure isolation.&lt;/p&gt;

&lt;p&gt;I couldn't just pass a standard array. I had to create a "Shim Strategy" (so fancy), so I wrote &lt;code&gt;MinimalColumn&lt;/code&gt; and &lt;code&gt;MinimalMaskedColumn&lt;/code&gt; classes that inherited directly from the Cython mixins (&lt;code&gt;_ColumnGetitemShim&lt;/code&gt;, &lt;code&gt;_MaskedColumnGetitemShim&lt;/code&gt;) and mapped them directly onto raw NumPy arrays using &lt;code&gt;.view()&lt;/code&gt; casting. By overriding the &lt;code&gt;.data&lt;/code&gt; property to return a pure &lt;code&gt;ndarray&lt;/code&gt; view, I stopped the Cython extension from returning raw memoryview buffers, which would have hard-crashed the tests. We were hitting the &lt;code&gt;tp_as_mapping-&amp;gt;mp_subscript&lt;/code&gt; C-slot directly.&lt;/p&gt;

&lt;p&gt;But this is where I hit my first major hurdle.&lt;/p&gt;

&lt;p&gt;While I was writing a test for structured arrays, indexing it with a single string, the structured dtype suddenly just dropped, and it returned a 1D array of the underlying field's type. I spent hours going down this rabbit hole, utterly convinced my test was wrong. Eventually, I found the machinery underneath: a literal trapdoor in &lt;code&gt;base_getitem&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dtype_kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;V&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;STRING_TYPES&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it accessed the raw &lt;code&gt;.data&lt;/code&gt; array, it inherited NumPy's default behavior and lost the structure. Rather than cementing this unintended behavior by writing a passing test for it, I dropped the test case and opened Issue #19827.&lt;/p&gt;

&lt;p&gt;This was my first realization that tests aren't just a safety net; when written at this level, they act like a microscope.&lt;/p&gt;

&lt;h2&gt;
  
  
  And then things started breaking in interesting ways
&lt;/h2&gt;

&lt;p&gt;Open source isn't just writing code, getting a green checkmark, and merging. The messy middle is where the actual work happens. I mean everyone knows that right? Yeah no sometimes people get a reality check; people is me, I am people.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Masked Array &lt;code&gt;isinstance&lt;/code&gt; Trap:&lt;/strong&gt; I proudly swapped &lt;code&gt;isinstance()&lt;/code&gt; checks for strict &lt;code&gt;type(result) is ...&lt;/code&gt; assertions, which instantly caused my masked array tests to fail. It turns out standard ndarray slicing strips subclasses, but &lt;code&gt;np.ma.MaskedArray.__getitem__&lt;/code&gt; inherently preserves them. The C-slot was actively returning the &lt;code&gt;MinimalMaskedColumn&lt;/code&gt; shim.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The CI Gatekeeper:&lt;/strong&gt; While working on the mixins, I used an &lt;code&gt;@override&lt;/code&gt; decorator. Python 3.12 CI matrices immediately failed because &lt;code&gt;typing_extensions&lt;/code&gt; wasn't in the base test environment. Instead of bloating &lt;code&gt;pyproject.toml&lt;/code&gt; with a new dependency just for one test file, I made the architectural choice to drop the decorator completely. CI went green.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Git History looking like a crime scene:&lt;/strong&gt; At one point, a &lt;code&gt;uv.lock&lt;/code&gt; file triggered a &lt;code&gt;check-added-large-files&lt;/code&gt; pre-commit hook failure. My branch had some incredibly messy commits. So I took a couple of deep breaths, paced around, and after a midly dramatic breakdown, I ran &lt;code&gt;git restore --staged&lt;/code&gt;, and fired up a local interactive rebase (&lt;code&gt;git rebase -i HEAD~6&lt;/code&gt;) via Nano to squash the chaos into a single, clean production commit (see told ya it was simple :) )&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Somewhere along the way, I stopped just writing tests
&lt;/h2&gt;

&lt;p&gt;As the summer progressed, the project evolved. The APE split required not just test coverage, but rigorous structural boundaries. I started writing &lt;code&gt;.pyi&lt;/code&gt; type stubs to define the Python-facing boundaries of compiled extensions so static type checkers could understand them without executing the C extension, not to forget the Proof of Concept, all in a days work (I'm lying, I almost cried trying the machete strategy).&lt;/p&gt;

&lt;h3&gt;
  
  
  The XML C-Extension Stubs (&lt;code&gt;iterparser_iterparser.pyi&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;When I looked at the C struct for the &lt;code&gt;_iterparser&lt;/code&gt; extension, it looked like the parser accepted &lt;code&gt;fd&lt;/code&gt;, &lt;code&gt;buffersize&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, and &lt;code&gt;buffer&lt;/code&gt;. But looking at the C struct is like looking at a map and realizing the map is wrong.&lt;/p&gt;

&lt;p&gt;By analyzing the C-API execution (&lt;code&gt;PyArg_ParseTupleAndKeywords&lt;/code&gt;), I discovered that &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;buffer&lt;/code&gt; were strictly internal C-state variables generated after crossing the Python boundary. The Python &lt;code&gt;__init__&lt;/code&gt; only actually takes &lt;code&gt;fd&lt;/code&gt; and &lt;code&gt;buffersize&lt;/code&gt;. Because the C extension didn't natively support keyword argument routing here, I had to enforce positional-only arguments using the PEP 570 &lt;code&gt;/&lt;/code&gt; marker in Python.&lt;/p&gt;

&lt;p&gt;Even better, we had a friendly discussion about the &lt;code&gt;fd.read&lt;/code&gt; callable. My mentor pointed out &lt;code&gt;read(self-&amp;gt;file, self-&amp;gt;buffer...)&lt;/code&gt; in the C code. But tracing the Python C-API showed a &lt;code&gt;Py_BuildValue("(n)", buffersize)&lt;/code&gt; call directly before &lt;code&gt;PyObject_CallObject&lt;/code&gt;. The C extension was packing a C &lt;code&gt;Py_ssize_t&lt;/code&gt; into a 1-element Python tuple. We proved the Python callable only ever receives one integer, finalizing the strict protocol signature as &lt;code&gt;Callable[[int], bytes]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dimensionality Locking in &lt;code&gt;_convolve&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;_convolve.pyx&lt;/code&gt;, we needed to ensure that arrays passed to the C extension matched perfectly before runtime. I used bounded TypeVar aliases (&lt;code&gt;_D1&lt;/code&gt;, &lt;code&gt;_D2&lt;/code&gt;, &lt;code&gt;_D3&lt;/code&gt;) to enforce a strict mathematical rule at the static analysis level. The result, &lt;code&gt;array_to_convolve&lt;/code&gt;, and &lt;code&gt;kernel&lt;/code&gt; arrays can be 1D, 2D, or 3D, but type checkers now can statically flag improper use, mixing dimensionalities.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;unit_list_proxy.c&lt;/code&gt; Dependency Injection
&lt;/h3&gt;

&lt;p&gt;This was easily one of the coolest things I worked on. &lt;code&gt;astropy/wcs/src/unit_list_proxy.c&lt;/code&gt; had a massive circular dependency problem. It was relying on &lt;code&gt;astropy.units.UnitBase&lt;/code&gt; via &lt;code&gt;PyImport_ImportModule&lt;/code&gt; at runtime.&lt;/p&gt;

&lt;p&gt;We discovered that &lt;code&gt;cunit&lt;/code&gt; wasn't a normal attribute; it was a C-level mutable proxy array. Standard Python &lt;code&gt;@property&lt;/code&gt; wrappers broke the memory linkage, meaning the C-array never received updates.&lt;/p&gt;

&lt;p&gt;Following my mentor Clément's strategy, we scrapped the Python wrappers and built a Dependency Injection architecture. I implemented a &lt;code&gt;_setup_unit_class(PyObject* unit_class)&lt;/code&gt; function, dynamically exposed to &lt;code&gt;_wcs&lt;/code&gt;. It runs exactly once during Python initialization to cache the Unit class pointer in a static C variable, completely severing the heavy &lt;code&gt;PyImport&lt;/code&gt; dependency. We updated the getitem C-slot to natively yield &lt;code&gt;Unit&lt;/code&gt; objects (like &lt;code&gt;CompositeUnit&lt;/code&gt;) back to Python, and made the setitem slot use duck-typing (&lt;code&gt;parse_strict="warn"&lt;/code&gt;) so it gracefully emits a &lt;code&gt;UnitsWarning&lt;/code&gt; for garbage FITS strings instead of hard-crashing. I was super elated when I finally succeeded and to see the CI checks green? Goodness was that rewarding, so I treated myself to an ice-cream sundae!&lt;/p&gt;

&lt;h3&gt;
  
  
  Numerical sanity and the "Grid Trap"
&lt;/h3&gt;

&lt;p&gt;I also wrote isolated suites for the &lt;code&gt;astropy.timeseries&lt;/code&gt; periodogram C-extensions. Bypassing the high-level &lt;code&gt;BoxLeastSquares&lt;/code&gt; API, I fed raw &lt;code&gt;np.float64&lt;/code&gt; memory buffers directly into the Cython boundary (&lt;code&gt;_impl.pyx&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I built a &lt;code&gt;SyntheticTransit&lt;/code&gt; fixture to inject a perfect transit dip into a simulated light curve to prove the C extension's &lt;code&gt;best_objective&lt;/code&gt; algorithm peaked accurately.&lt;/p&gt;

&lt;p&gt;But math is cruel :(&lt;/p&gt;

&lt;p&gt;My tests were failing to recover a mathematically perfect period of 2.0. Why? Because I generated my frequency grid using &lt;code&gt;np.linspace(1.5, 2.5, 100)&lt;/code&gt;. If you do the floating-point math, the step size is ~0.0101. The number 2.0 literally did not exist in the array (the closest it got was 2.005). I was stuck fighting my own numerical grid. I changed it to 101 steps, and voila it hit the target perfectly!&lt;/p&gt;

&lt;p&gt;We also dealt with a fascinating accuracy tradeoff. When using a coarse grid (1,000 points, &lt;code&gt;oversample=10&lt;/code&gt;), the discrete phase-binning of the BLS algorithm caused the edges of the transit to smear, changing the recovered depth from a perfect 0.5 to 0.478. My mentor challenged the 5% tolerance. By fixing the linspace bug and adjusting the points, we tightened the mathematical accuracy to 1e-4 while balancing the matrix to keep the isolated test executing in under 300ms so we didn't anger the CI limits.&lt;/p&gt;

&lt;p&gt;I ran into a similar trapdoor testing the Lomb-Scargle Cython implementation (&lt;code&gt;cython_impl.pyx&lt;/code&gt;). I tried to test the Cython-level &lt;code&gt;t.ndim != 1&lt;/code&gt; error. But when I passed a 2D array for &lt;code&gt;t&lt;/code&gt; and 1D arrays for &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;np.broadcast_arrays&lt;/code&gt; panicked and raised a Python &lt;code&gt;ValueError&lt;/code&gt; before the C extension ever saw it. The fix? I deliberately passed identical 2D arrays for all parameters so it survived the Python broadcasting check and successfully crashed directly on the C-boundary. Hurrayy!&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that didn't go according to plan
&lt;/h2&gt;

&lt;p&gt;Of course, not everything matched the original proposal. My initial timeline had me delivering tests for &lt;code&gt;astropy/erfa&lt;/code&gt; and &lt;code&gt;astropy.coordinates&lt;/code&gt; towards the end of the summer (Deliverables 5 and 6).&lt;/p&gt;

&lt;p&gt;The reality of open-source struck: &lt;code&gt;astropy/erfa&lt;/code&gt; had already been spun out into the standalone &lt;code&gt;pyerfa&lt;/code&gt; package, and &lt;code&gt;astropy.coordinates&lt;/code&gt; was delegating its math to that external library. You can't write isolated C-extension tests for an architecture that doesn't live in your repository anymore.&lt;/p&gt;

&lt;p&gt;Rather than viewing this as a failure, I contacted my mentors, pivoted, and spent that time significantly deepening the coverage and architectural stability of the &lt;code&gt;.pyi&lt;/code&gt; stubs and the XML parsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, where did we end up? (Current State)
&lt;/h2&gt;

&lt;p&gt;The goal was to make the compiled layer testable, and I am leaving behind an architecture to ensure it stays that way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merged work &amp;amp; contributions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Table C-extensions:&lt;/strong&gt; merged isolated tests for &lt;code&gt;join_inner&lt;/code&gt; (PR #19458), documented jointype integer mappings (PR #19468), and added direct C-slot tests for &lt;code&gt;_column_mixins&lt;/code&gt; (PR #19806).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;XML iterparser:&lt;/strong&gt; built structural 4-tuple boundary tests for the &lt;code&gt;_iterparser&lt;/code&gt; C-extension and caught memory buffer behaviors (PR #19922).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Type stubs:&lt;/strong&gt; defined the static type boundaries for &lt;code&gt;_iterparser.pyi&lt;/code&gt; (PR #20006, positional-only) and &lt;code&gt;_convolve&lt;/code&gt; (PR #20093, dimensionality locking).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Time parse extension:&lt;/strong&gt; overhauled the &lt;code&gt;_parse_times.c&lt;/code&gt; boundary test using strict &lt;code&gt;type(parser) is np.ufunc&lt;/code&gt; assertions and byte arrays ("S24") (PR #19875).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Quantity unit extraction:&lt;/strong&gt; fixed unit extraction in &lt;code&gt;structured_to_unstructured&lt;/code&gt; for &lt;code&gt;StructuredQuantity&lt;/code&gt; (PR #19106).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;APE split expansion:&lt;/strong&gt; contributed to the proof of concept for the APE split, expanding the implementation section in Clement's fork (PR #3) to define what the low-level tests look like.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Still open, waiting on review&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;WCS dependency injection:&lt;/strong&gt; refactored &lt;code&gt;unit_list_proxy.c&lt;/code&gt; to cache the Unit class pointer, severing the &lt;code&gt;PyImport&lt;/code&gt; overhead (PR #20072).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Timeseries math engines:&lt;/strong&gt; built exact synthetic recovery matrices for Box Least Squares (&lt;code&gt;bls.c&lt;/code&gt; / &lt;code&gt;_impl.pyx&lt;/code&gt;, PR #20151) and Lomb-Scargle (&lt;code&gt;cython_impl.pyx&lt;/code&gt;, PR #20222).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Developer Guide
&lt;/h2&gt;

&lt;p&gt;Perhaps the most lasting piece of work I did was writing and merging &lt;code&gt;testing_extensions.rst&lt;/code&gt; (PR #20239) into Astropy's core documentation. This document establishes the architectural standards for making low-level compiled modules testable. It outlines the rules for bypassing Python wrappers, strictly avoiding &lt;code&gt;isinstance()&lt;/code&gt; at the compiled boundary, structural validation, safe exception handling using &lt;code&gt;pytest.raises&lt;/code&gt; substring matching, and &lt;code&gt;.pyi&lt;/code&gt; architecture. If anyone has any questions regarding the project I think this would serve as a good starting point (before the actual coding part of course).&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left after I leave?
&lt;/h2&gt;

&lt;p&gt;The work on &lt;code&gt;astropy/erfa&lt;/code&gt; was pivoted to the external &lt;code&gt;pyerfa&lt;/code&gt; repository, so testing that boundary remains a future task for whoever manages that standalone package.&lt;/p&gt;

&lt;p&gt;Beyond that, the primary future work is maintenance. When a new Cython extension is updated or a C-boundary changes, these isolated tests will immediately flag it. The &lt;code&gt;testing_extensions.rst&lt;/code&gt; guide exists specifically so future contributors don't have to stumble through the same trapdoors I did. Future contributors can use this suite as a blueprint to expand coverage into newer subpackages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sidenote: I plan on staying (long time). I'm not going to disappear just because GSoC is over but rather continue to contribute to this epic organization. I'll keep working, raising PRs, beefing with GitHub and most importantly, I'll help guide all those who stumble across Astropy just as Clément and Nathaniel were always there for me!!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took away from this
&lt;/h2&gt;

&lt;p&gt;I came into GSoC thinking I was going to write tests. Somewhere along the way, I ended up learning how to actually reason about a mature codebase.&lt;/p&gt;

&lt;p&gt;I learned that you have to read the C code before writing the Python test. I learned to question unexpected behavior rather than blindly cementing it into a test. I learned that CI is a judgmental gatekeeper, Git history tells a story, and numerical tolerances matter a whole lot more than just getting the test to pass. I learned that maybe breaking a huge problem into separate sections wasn't such a bad idea after all and it's this lesson that I'll be applying to my life as well.&lt;/p&gt;

&lt;p&gt;Most importantly, I learned how maintainer-level review actually works. The feedback I got wasn't just "fix this bug," it was "let's structure this so it's statically typed, mathematically locked, and future-proof." And it really shaped my journey. I mean the Reem from December 2025 decided to open her very first PR and make huge changes in 4 different files, which ultimately was pretty overwhelming and she closed the PR. My point is that I have grown as a contributor and that makes me so very proud.&lt;/p&gt;

&lt;h2&gt;
  
  
  A HUGE thank you
&lt;/h2&gt;

&lt;p&gt;All this is just because I had the BEST mentors EVER, Clément Robert (@neutrinoceros), and Nathaniel Starkman (@nstarman).&lt;/p&gt;

&lt;p&gt;Clément, thank you for never once making me feel dumb for asking the same question twice, and for having the patience to let me actually sit with a hard bug instead of just handing me the fix. For taking time out from your busy schedule every week, just so that I could ask you all the questions I saved, and to go through my code, suggest changes and help me understand all the whys. You didn't just correct my code, you really changed how I think about coding. I came into this summer knowing how to write Python and I'm ending it knowing how to reason about the internals underneath.&lt;/p&gt;

&lt;p&gt;Nathaniel, thank you for being just as generous with your time, and just as willing to slow down and explain something properly even when the answer must have felt obvious from where you were standing.&lt;/p&gt;

&lt;p&gt;Having two mentors who both cared this much about getting it right, and not just getting it merged, made all the difference. And I couldn't be more grateful!&lt;/p&gt;

&lt;p&gt;To the OpenAstronomy and Astropy community: thank you for letting me poke around the raw internals of your library.&lt;/p&gt;

&lt;p&gt;Until next time,&lt;br&gt;
Reem &amp;lt;3&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>gsoc26</category>
      <category>openastronomy</category>
      <category>astropy</category>
    </item>
    <item>
      <title>Wiring the Abyss</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Thu, 30 Jul 2026 16:22:22 +0000</pubDate>
      <link>https://dev.to/reemhamraz/wiring-the-abyss-1l6a</link>
      <guid>https://dev.to/reemhamraz/wiring-the-abyss-1l6a</guid>
      <description>&lt;p&gt;I can see the finish line. Send help, or snacks, or both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;unit_list_proxy.c&lt;/code&gt; Exorcism
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;unit_list_proxy.c&lt;/code&gt; is in review. That circular dependency on &lt;code&gt;astropy.units.UnitBase&lt;/code&gt; was a nightmare: it kept re-importing the entire units module at runtime just to check if something looked vaguely unit-shaped. So I ripped out &lt;code&gt;PyImport_ImportModule&lt;/code&gt; completely and built a proper Dependency Injection bridge instead: &lt;code&gt;astropy.units.Unit&lt;/code&gt; now gets handed straight to the C-layer during module init [no more knocking on Python's door mid-runtime, it just lives there now]. &lt;code&gt;setitem&lt;/code&gt; uses that injected class to enforce FITS formatting, and it's smarter than a blunt gatekeeper about it. Feed it something like &lt;code&gt;"bananas // sekonds"&lt;/code&gt; and it doesn't slam the &lt;code&gt;TypeError&lt;/code&gt; hammer down; it shrugs, emits a &lt;code&gt;UnitsWarning&lt;/code&gt;, and keeps the string around for backward compatibility [petty tolerance for legacy nonsense, but tolerance nonetheless]. It only throws a hard &lt;code&gt;TypeError&lt;/code&gt; when the input is genuinely un-parseable garbage. And &lt;code&gt;getitem&lt;/code&gt;, this is the actual win, stopped handing back plain strings altogether. It now returns real &lt;code&gt;Unit&lt;/code&gt; objects, like &lt;code&gt;CompositeUnit&lt;/code&gt;, straight across the C boundary, which is what fixed the sequence math bug that started this whole mess. Tests skip the &lt;code&gt;astropy.wcs&lt;/code&gt; wrappers entirely and hit &lt;code&gt;_wcs.Wcsprm&lt;/code&gt;'s C-slots directly to prove the whole thing is airtight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;bls&lt;/code&gt; Extension, Numerically Unbothered
&lt;/h2&gt;

&lt;p&gt;Also finished the Box Least Squares (&lt;code&gt;bls&lt;/code&gt;) extension, feeding typed &lt;code&gt;np.float64&lt;/code&gt; buffers straight into &lt;code&gt;bls_impl&lt;/code&gt;/&lt;code&gt;run_bls&lt;/code&gt; and bypassing the high-level API entirely. Built a &lt;code&gt;SyntheticTransit&lt;/code&gt; fixture with a fake star, a fake dip, a fake planet doing exactly what it's told, and confirmed the C engine recovers period, depth, duration correctly. First pass, I hit some phase-binning drift and figured I'd just loosen &lt;code&gt;rtol&lt;/code&gt; to &lt;code&gt;5e-2&lt;/code&gt; and call it a day [the coward's tolerance]. Mentor feedback said otherwise. Cranked the fixture to 10,000 points, hunted down a sneaky &lt;code&gt;np.linspace&lt;/code&gt; grid-spacing bug, and tightened &lt;code&gt;rtol&lt;/code&gt; all the way to a genuinely crisp &lt;code&gt;1e-4&lt;/code&gt;. Turns out the drift was never the model's fault, it was mine. Also confirmed the early-exit flags (1 and 2) raise real &lt;code&gt;ValueError&lt;/code&gt;s instead of just disappearing without a word.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Being a Translator at the Cython Border
&lt;/h2&gt;

&lt;p&gt;This Cython boundary work is basically being a translator between two people who are both right and also both furious. C doesn't warn you before it breaks. Python will forgive almost anything. I'm just standing in the middle going "she just needs a &lt;code&gt;float64&lt;/code&gt;, please don't segfault."&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Left
&lt;/h2&gt;

&lt;p&gt;One thing left: &lt;code&gt;lombscargle&lt;/code&gt;. Then I'm done.&lt;/p&gt;

&lt;p&gt;Tired. Wired. Diving back in for one more compile.&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>gsoc26</category>
      <category>astropy</category>
      <category>openastronomy</category>
    </item>
    <item>
      <title>Coming up for Air</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Fri, 10 Jul 2026 14:53:14 +0000</pubDate>
      <link>https://dev.to/reemhamraz/coming-up-for-air-587f</link>
      <guid>https://dev.to/reemhamraz/coming-up-for-air-587f</guid>
      <description>&lt;p&gt;After the absolute gauntlet of the past few weeks, this week finally offered a little bit of breathing room. I'm not going to lie, I needed it. But "lighter" in the open-source world doesn't mean stopping; it just means you finally have the time to sit back and actually think before you code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing .pyi Stub Files
&lt;/h2&gt;

&lt;p&gt;A good chunk of my time went into writing &lt;code&gt;.pyi&lt;/code&gt; stub files. It sounds mundane on the surface, like just writing Python type hints, BUT it's actually this bizarre, highly precise exercise in translation. You are basically acting as a diplomat between the ruthless C-engine and Python's static type checkers. It's microscopic work. Every single argument has to align perfectly with how the C-level memory actually allocates it, otherwise the whole illusion shatters. It requires a sort of discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Main Target
&lt;/h2&gt;

&lt;p&gt;But my main focus, and what I'm gearing up to dive back into, is the &lt;code&gt;unit_list_proxy.c&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;There is a nasty runtime dependency lurking in there. Right now, the C-code aggressively tries to import a massive Python library the second it gets created, locking everything into this heavy, tangled cycle. For a minute, I was dreading having to do a complete, tear-it-down-to-the-studs refactor just to untangle it.&lt;/p&gt;

&lt;p&gt;Thankfully, I pitched an alternative to my mentor, Clément, and he gave me the green light to run with it. We'll see how that goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix?
&lt;/h2&gt;

&lt;p&gt;Instead of a massive rewrite, we are going to use &lt;strong&gt;structural sub-typing&lt;/strong&gt; which is a more specific form of &lt;strong&gt;duck typing&lt;/strong&gt; (I guess don't come at me if I'm wrong) right at the C-level. Rather than forcing the C-engine to check an object's exact, official DNA (which requires importing the whole Python library), we are just going to check its behavior. Does it have the method we need? If it walks like a duck and quacks like a duck, the C-engine will blindly trust that it's a duck and process it.&lt;/p&gt;

&lt;p&gt;It is such an elegant workaround. It completely severs the nasty dependency chain without us having to burn the house down to fix the plumbing (I was pretty proud of actually thinking about it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Thoughts..
&lt;/h2&gt;

&lt;p&gt;It's a smarter, cleaner way forward. The C-layer is still intimidating, but for the first time in a while, I feel like I'm actually outsmarting the machine instead of just wrestling with it.&lt;/p&gt;

&lt;p&gt;Back into the dark I go.&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>gsoc26</category>
      <category>openastronomy</category>
      <category>astropy</category>
    </item>
    <item>
      <title>Comfort is a Trap</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Wed, 01 Jul 2026 22:24:13 +0000</pubDate>
      <link>https://dev.to/reemhamraz/comfort-is-a-trap-oa0</link>
      <guid>https://dev.to/reemhamraz/comfort-is-a-trap-oa0</guid>
      <description>&lt;p&gt;I've realized something over these last few weeks. Comfort is a trap.&lt;/p&gt;

&lt;p&gt;In Python, everything is comfortable. You have these beautiful, high-level wrappers that hide the ugly reality of how the machine actually works. But if I want to do work that matters—if I want to build things that last—I can't stay in the comfortable layers. I have to go down to where it's dark and unforgiving.&lt;/p&gt;

&lt;p&gt;Lately, that means staring down the &lt;code&gt;_iterparser&lt;/code&gt; C-extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Raw Machine
&lt;/h2&gt;

&lt;p&gt;I decided to completely bypass the Python safety nets. The goal was to mathematically prove that the underlying &lt;code&gt;libexpat&lt;/code&gt; state machine could handle brutally malformed XML byte streams without breaking.&lt;/p&gt;

&lt;p&gt;When you dig this deep, you realize the C-engine doesn't care about being polite. High-level code gives you clean outputs, but down here? Closing tags don't yield nice, empty strings. To save precious CPU cycles on memory reallocation, the engine just yields the closing flag along with whatever lingering text is still bleeding over in the shared memory buffer. It's messy. It's raw. But it is beautifully, ruthlessly efficient.&lt;/p&gt;

&lt;p&gt;The standard for this kind of open-source architecture is completely unforgiving. You can't just casually check if data matches; I had to explicitly enforce &lt;code&gt;strict=True&lt;/code&gt; on my iterables just to guarantee identical lengths and prevent silent failures.&lt;/p&gt;

&lt;p&gt;It forces a kind of discipline I didn't know I had in me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ghosts in the Infrastructure
&lt;/h2&gt;

&lt;p&gt;And then, of course, the infrastructure decides to humble you.&lt;/p&gt;

&lt;p&gt;I spent hours losing my mind over a corrupted Windows virtual environment that was failing my GitHub Actions for no apparent reason. The fix? Literally just clearing the cache to force a fresh dependency rebuild. Add in Ruff pre-commit hooks aborting my commits to protect the working directory, and my patience was practically non-existent.&lt;/p&gt;

&lt;p&gt;But I finally conquered the interactive rebase against upstream targets (&lt;code&gt;git rebase -i upstream/main&lt;/code&gt;). I managed to squash my messy, chaotic review iterations into a single, clean production commit. I might still despise GitHub squash and rebase, but at least now I know how to wield them.&lt;/p&gt;

&lt;p&gt;I'd be lying if i said that it isn't tiring at times. It's exhausting. It's isolating. BUT when I force the C-engine to crash on a truncated byte stream, safely catch the error, and watch the internal 4-tuple align flawlessly across the Python-C boundary...&lt;/p&gt;

&lt;p&gt;Yeah. That's the feeling. That's why I love this.&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>gsoc2026</category>
      <category>openastronomy</category>
      <category>astropy</category>
    </item>
    <item>
      <title>Biting Into It: My Astropy Summer (Weeks 1-3)</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Mon, 15 Jun 2026 09:48:44 +0000</pubDate>
      <link>https://dev.to/reemhamraz/biting-into-it-my-astropy-summer-weeks-1-3-5g2l</link>
      <guid>https://dev.to/reemhamraz/biting-into-it-my-astropy-summer-weeks-1-3-5g2l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"I really want to be something in life. I don't want to be forgotten."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a heavy thought for a Tuesday night, but it's been bouncing around my head a lot lately. I read this Sartre quote recently about living a "toothless life", just waiting around, reserving yourself for later, and then suddenly realizing your teeth are gone. I decided a while ago that I'm not doing that. If I'm going to leave something behind, it has to be through work that actually matters.&lt;/p&gt;

&lt;p&gt;Right now, that work looks like deliberately bypassing high-level Python APIs to aggressively test raw, compiled C-engines for Astropy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Weekly Grind
&lt;/h2&gt;

&lt;p&gt;Juggling CS coursework while trying to wrangle Cython for Google Summer of Code is a lot. But honestly? I've been loving every damn second of it. Reading documentation in a vacuum is one thing, actually hashing out C-level memory bindings is where the reality sets in. My mentors don't just let me write code; they force me to hold my work to strict architectural standards, which honestly gives me an immense sense of accomplishment once I clear them.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;test_np_utils.py&lt;/code&gt;: First Blood
&lt;/h2&gt;

&lt;p&gt;Getting this merged was my first real bite into the codebase. Instead of playing it safe with high-level Astropy &lt;code&gt;Table&lt;/code&gt; objects, I stripped away the Python wrapper to test the raw C-engine underneath. I threw every Cartesian edge case at the &lt;code&gt;join_inner&lt;/code&gt; function just to see if the explosive $O(N^2)$ memory allocations would break it.&lt;/p&gt;

&lt;p&gt;They didn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;test_column_mixins.py&lt;/code&gt;: The Beast
&lt;/h2&gt;

&lt;p&gt;Absolute beast of a PR. I had to write &lt;code&gt;MinimalColumn&lt;/code&gt; shims and cast them directly onto raw NumPy arrays just to isolate the Cython &lt;code&gt;__getitem__&lt;/code&gt; routing. Somewhere in there I found a legitimate C-level trapdoor — indexing a structured array with a single string was silently dropping the dtype entirely. That became Issue #19827, which I later learned was actually expected behavior, so I closed it. End of story.&lt;/p&gt;

&lt;p&gt;But the real boss fight wasn't even the code (funny, right?!). It was Git. I ran a terrifying interactive rebase in Nano to squash six messy commits before force-pushing to my branch. Truly character-building.&lt;/p&gt;

&lt;p&gt;If it isn't clear by now, let me spell it out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I absolutely despise GitHub squash and rebase. Actually, I don't think we (me and GitHub) can ever be on amicable terms."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  APE Split &amp;amp; &lt;code&gt;_parse_times.c&lt;/code&gt;: Standing on Shoulders
&lt;/h2&gt;

&lt;p&gt;Lately I've been laying the tracks for the APE split, writing isolated tests for the &lt;code&gt;_parse_times.c&lt;/code&gt; extension. This was mostly building on another contributor's accepted PR, but it was enlightening to understand how other brains work. I'll be honest, I spent a good chunk of time just deciphering the existing test cases before touching anything, making sure I didn't alter their original essence.&lt;/p&gt;




&lt;p&gt;That's weeks 1–3, folks.&lt;/p&gt;

&lt;p&gt;It's a blur of memory buffers, strict type checking, and fighting CI matrices. But when those green checkmarks come in, and especially when I see those purple merged PRs, I know I'm actually building something.&lt;/p&gt;

&lt;p&gt;I'm biting into it. I'm not waiting anymore.&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>gsoc2026</category>
      <category>astropy</category>
      <category>openastronomy</category>
    </item>
    <item>
      <title>Beyond the Abstraction</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Sun, 31 May 2026 20:14:31 +0000</pubDate>
      <link>https://dev.to/reemhamraz/beyond-the-abstraction-17na</link>
      <guid>https://dev.to/reemhamraz/beyond-the-abstraction-17na</guid>
      <description>&lt;p&gt;The glow of the screen hits different when you're reading an email that dictates your entire summer. Getting accepted into Google Summer of Code 2026 programme for Astropy, under the OpenAstronomy umbrella, was one of those really defining moments; all I could do was stare at the screen in disbelief (I'd done it). Then, the abstraction fades away, and suddenly, you need to get down to the real work.&lt;/p&gt;

&lt;p&gt;This post, is the first of many. I plan to keep these updates transparent and real—documenting, so it's really not just going to be about the code that ships, but rather the (not-so)basic commands I need to google, or the dumb mistakes, or the long hours spent scouring the internet for things that developers ought to know (but I don't), and the brutal reality of open-source development or at least the reality from the perspective of a first-timer (that would be me needing to google how to squash and rebase, haha fun times:| or not)&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's break down my project
&lt;/h3&gt;

&lt;p&gt;This summer, my life will revolve around Astropy's low-level test suite, or more formally speaking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hardening Astropy's Core Stability" &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am skipping the wrappers and going straight for the raw C-extensions. Which, when you think about it, there is a kind of profound honesty in testing the core engine. It forces transparency. It demands technical accountability (excuse my philosophical analysis). What I meant to say is that no one can fake it you know? By the end of the day you really gotta understand what you're doing because otherwise you'll be lost as hell. So yeah, this is what I plan to do all summer and I can't wait for it! So totafreakingly excited!!&lt;/p&gt;

&lt;h3&gt;
  
  
  Speaking of being lost
&lt;/h3&gt;

&lt;p&gt;My greatest advice to anyone starting would be to find your mentors, and I will swear upon this, that you really need to find your perfect organization and your perfect mentors! I wouldn't have even started down this path if it weren't for @neutrinoceros. It was a fateful email, and one that led to many many more and here I am. So, I'd really like to give a huge shoutout to my mentors for this project, @nstarman and @neutrinoceros. Open-source can be so intimidating, but having mentors who are just straight up transparent and value direct communication makes it all so much more manageable, and I'm pretty grateful that I have that experience. &lt;/p&gt;

&lt;p&gt;And moreover, I am not starting from absolute zero either. We actually got some real momentum going recently. I finally got PR #19458 merged! The one adding direct tests for join_inner in table/_np_utils Cython extension. IT GOT MERGED. Though, ideally this would've been my first PR right after the community bonding period, I tackled it before-hand and well it paid off (proof that I can actually write code that works, phew). &lt;/p&gt;

&lt;p&gt;A lil side note: I'm writing this post after the community bonding period but I'm starting from the very start so bear with me please. The next post will deep dive into my the coding details of my first and second set of test cases, mentor meetings, Astropy's dev telecon and all the wonderful stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  The next 2 weeks
&lt;/h3&gt;

&lt;p&gt;Now that the official coding phase is here, my life for the next fortnight is basically going to be staring at architecture maps. I need get going with the test coverage for these Cython extensions. Plus, I really have to optimize my local setup so my laptop doesn't literally take off into orbit while running these low-level builds. Although @neutrinoceros did have a meeting with me, to help set me with the local setup, GitHub CLI and UV tools, plus a bunch of cool commands that have literally helped me a tonne.&lt;/p&gt;

&lt;p&gt;I know, that it is going to be a grind, but like, a really good grind. I'll be back here in, whenever I feel like it, (though most probably within the next 2 weeks) to let you all in on more cool things I learnt over the course of these weeks. Check back then for more raw updates and probably more stories of me overcomplicating basic git commands. &lt;/p&gt;

&lt;p&gt;Toodles!&lt;/p&gt;

</description>
      <category>astropy</category>
      <category>openastronomy</category>
      <category>gsoc</category>
      <category>gsoc2026</category>
    </item>
    <item>
      <title>Applying to GSoC 2026 with Astropy</title>
      <dc:creator>Reem Hamraz</dc:creator>
      <pubDate>Thu, 19 Mar 2026 08:26:02 +0000</pubDate>
      <link>https://dev.to/reemhamraz/applying-to-gsoc-2026-with-astropy-4389</link>
      <guid>https://dev.to/reemhamraz/applying-to-gsoc-2026-with-astropy-4389</guid>
      <description>&lt;p&gt;I'm working on submitting my Google Summer of Code 2026 proposal to OpenAstronomy this week, and I wanted to write a quick post about it.&lt;/p&gt;

&lt;p&gt;The project is called &lt;strong&gt;Hardening Astropy's Core Stability&lt;/strong&gt;. The short version: Astropy has C and Cython extensions powering its most performance-critical code, and none of them have direct tests. They're only tested indirectly through the public Python API, which means a bug deep in the compiled layer can sit undetected for a long time. My project is to build a test suite that targets that layer directly.&lt;/p&gt;

&lt;p&gt;It's also groundwork for something bigger — there's a proposal in the Astropy community to eventually split the compiled extensions into a separate package. That can't happen safely without standalone tests first.&lt;/p&gt;

&lt;p&gt;I've been contributing to Astropy since December 2025 — trying to fix the core and complex issues. That work gave me enough codebase context to write a proposal grounded in real problems I already ran into, not just ideas on paper.&lt;/p&gt;

&lt;p&gt;If it goes well, I'll be spending the summer reading C source code, mapping calling conventions, and making a part of Astropy that almost nobody sees a lot more reliable. That sounds exactly like the kind of work I want to be doing.&lt;/p&gt;

&lt;p&gt;Fingers crossed. 🤞&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
