Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-gwcs for openSUSE:Factory 
checked in at 2023-01-14 00:03:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-gwcs (Old)
 and      /work/SRC/openSUSE:Factory/.python-gwcs.new.32243 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-gwcs"

Sat Jan 14 00:03:32 2023 rev:10 rq:1058263 version:0.18.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-gwcs/python-gwcs.changes  2022-10-28 
19:30:41.858982562 +0200
+++ /work/SRC/openSUSE:Factory/.python-gwcs.new.32243/python-gwcs.changes       
2023-01-14 00:04:18.338091106 +0100
@@ -1,0 +2,16 @@
+Fri Jan 13 13:47:14 UTC 2023 - Ben Greiner <c...@bnavigator.de>
+
+- Update to 0.18.3
+  * Fix pixel scale formula used by iterative inverse by @mcara in
+    #423
+  * refactor for Tox 4 by @zacharyburnett in #428
+  * Improve stability of SIP fitting. Fix constant term - CRPIX -
+    in SIP by @mcara in #427
+  * use canonical name of RTD config and enable nitpicky mode to
+    catch broken links in docs by @zacharyburnett in #430
+  * add downstream tests for JWST and Roman calibration pipelines
+    by @zacharyburnett in #415
+  * fix CRDS_PATH in CI when running downstream tests by
+    @zacharyburnett in #432
+
+-------------------------------------------------------------------

Old:
----
  gwcs-0.18.2.tar.gz

New:
----
  gwcs-0.18.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-gwcs.spec ++++++
--- /var/tmp/diff_new_pack.m2U3MI/_old  2023-01-14 00:04:18.870094205 +0100
+++ /var/tmp/diff_new_pack.m2U3MI/_new  2023-01-14 00:04:18.878094251 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-gwcs
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,12 +17,13 @@
 
 
 Name:           python-gwcs
-Version:        0.18.2
+Version:        0.18.3
 Release:        0
 Summary:        Generalized World Coordinate System
 License:        BSD-3-Clause
 Group:          Productivity/Scientific/Astronomy
 URL:            https://gwcs.readthedocs.io/en/latest/
+# SourceRepository: https://github.com/spacetelescope/gwcs
 Source:         
https://files.pythonhosted.org/packages/source/g/gwcs/gwcs-%{version}.tar.gz
 BuildRequires:  %{python_module asdf >= 2.8.1}
 BuildRequires:  %{python_module asdf-astropy >= 0.2.0}
@@ -30,9 +31,11 @@
 BuildRequires:  %{python_module astropy >= 5.1}
 BuildRequires:  %{python_module base >= 3.8}
 BuildRequires:  %{python_module numpy}
+BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module scipy}
 BuildRequires:  %{python_module setuptools_scm}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-asdf >= 2.8.1
@@ -56,10 +59,10 @@
 %setup -q -n gwcs-%{version}
 
 %build
-%python_build
+%pyproject_wheel
 
 %install
-%python_install
+%pyproject_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check

++++++ gwcs-0.18.2.tar.gz -> gwcs-0.18.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/.github/workflows/ci.yml 
new/gwcs-0.18.3/.github/workflows/ci.yml
--- old/gwcs-0.18.2/.github/workflows/ci.yml    2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/.github/workflows/ci.yml    2022-12-23 20:01:15.000000000 
+0100
@@ -12,66 +12,91 @@
     # * is a special character in YAML so you have to quote this string
     - cron: '0 9 * * 1'
 
-
 jobs:
-  tox:
-    name: ${{ matrix.name }}
-    runs-on: ${{ matrix.runs-on }}
-    env:
-      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  check:
+    name: ${{ matrix.toxenv }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        toxenv: [ check-style, check-security, check-build ]
+        python-version: [ '3.11' ]
+        os: [ ubuntu-latest ]
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: 'pip'
+          cache-dependency-path: setup.cfg
+      - run: pip install "tox>=4.0"
+      - run: tox -e ${{ matrix.toxenv }}
+  test:
+    name: ${{ matrix.toxenv }} (Python ${{ matrix.python-version }}, ${{ 
matrix.os }})
+    runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
       matrix:
+        toxenv: [ test ]
+        python-version: [ '3.8', '3.9', '3.10', '3.11' ]
+        os: [ ubuntu-latest, macos-latest ]
         include:
-          - name: Python 3.10.0
-            runs-on: ubuntu-latest
-            python-version: '3.10.0'
-            toxenv: py310
-
-          - name: Python 3.9
-            runs-on: ubuntu-latest
-            python-version: 3.9
-            toxenv: py39
-
-          - name: Python 3.8 with coverage
-            runs-on: ubuntu-latest
-            python-version: 3.8
-            toxenv: py38-cov
-
-          - name: Code style checks
-            runs-on: ubuntu-latest
-            python-version: 3.8
-            toxenv: style
-
-          - name: Bandit security audit
-            runs-on: ubuntu-latest
-            python-version: 3.8
-            toxenv: bandit
-
-          - name: Python 3.8 with dev dependencies
-            runs-on: ubuntu-latest
-            python-version: 3.8
-            toxenv: py38-dev
+          - toxenv: test-dev
+            os: ubuntu-latest
+            python-version: '3.x'
+          - toxenv: test-pyargs
+            os: ubuntu-latest
+            python-version: '3.11'
+          - toxenv: test-cov
+            os: ubuntu-latest
+            python-version: '3.11'
+          - toxenv: test-numpy122
+            os: ubuntu-latest
+            python-version: '3.10'
+          - toxenv: test-numpy121
+            os: ubuntu-latest
+            python-version: '3.10'
+          - toxenv: test-numpy122
+            os: ubuntu-latest
+            python-version: '3.9'
+          - toxenv: test-numpy121
+            os: ubuntu-latest
+            python-version: '3.9'
+          - toxenv: test-numpy120
+            os: ubuntu-latest
+            python-version: '3.9'
+          - toxenv: test-numpy122
+            os: ubuntu-latest
+            python-version: '3.8'
+          - toxenv: test-numpy121
+            os: ubuntu-latest
+            python-version: '3.8'
+          - toxenv: test-numpy120
+            os: ubuntu-latest
+            python-version: '3.8'
+          - toxenv: test-jwst-xdist
+            os: ubuntu-latest
+            python-version: '3.x'
+          - toxenv: test-romancal-xdist
+            os: ubuntu-latest
+            python-version: '3.x'
     steps:
-      - uses: actions/checkout@v2
+      - run: echo "HOME=$HOME" >> $GITHUB_ENV
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 0
-
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+      - uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
-
-      - name: Install tox
-        run: |
-          python -m pip install --upgrade pip
-          pip install tox
-
-      - name: Run tox
-        run: tox -e ${{ matrix.toxenv }}
-
-      - name: Upload coverage to codecov
-        if: ${{ contains(matrix.toxenv,'-cov') }}
-        uses: codecov/codecov-action@v2
+          cache: 'pip'
+          cache-dependency-path: setup.cfg
+      - run: pip install "tox>=4.0"
+      - run: tox -e ${{ matrix.toxenv }}
+      - if: ${{ contains(matrix.toxenv,'-cov') }}
+        uses: codecov/codecov-action@v3
         with:
           file: ./coverage.xml
+          flags: unit
+          fail_ci_if_error: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/.readthedocs.yaml 
new/gwcs-0.18.3/.readthedocs.yaml
--- old/gwcs-0.18.2/.readthedocs.yaml   1970-01-01 01:00:00.000000000 +0100
+++ new/gwcs-0.18.3/.readthedocs.yaml   2022-12-23 20:01:15.000000000 +0100
@@ -0,0 +1,21 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+version: 2
+
+sphinx:
+  builder: html
+  configuration: docs/conf.py
+  fail_on_warning: true
+
+# Set the version of Python and requirements required to build your docs
+python:
+  version: 3.8
+  system_packages: true
+  install:
+    - method: pip
+      path: .
+      extra_requirements:
+        - docs
+
+# Don't build any extra formats
+formats: []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/CHANGES.rst new/gwcs-0.18.3/CHANGES.rst
--- old/gwcs-0.18.2/CHANGES.rst 2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/CHANGES.rst 2022-12-23 20:01:15.000000000 +0100
@@ -1,3 +1,15 @@
+0.18.3 (2022-12-23)
+-------------------
+Bug Fixes
+^^^^^^^^^
+
+- Fixed a bug in the estimate of pixel scale in the iterative inverse
+  code. [#423]
+
+- Fixed constant term in the polynomial used for SIP fitting.
+  Improved stability and accuracy of the SIP fitting code. [#427]
+
+
 0.18.2 (2022-09-07)
 -------------------
 Bug Fixes
@@ -222,6 +234,7 @@
 
 - Fixed WCS API issues when ``output_frame`` is 1D, e.g. ``Spectral`` only. 
[#232]
 
+
 0.10.0 (12/20/2018)
 -------------------
 
@@ -239,6 +252,7 @@
 - Added a ``wcs_from_[points`` function which creates a WCS object
   two matching sets of points ``(x,y)`` and ``(ra, dec)``. [#42]
 
+
 0.9.0 (2018-05-23)
 ------------------
 
@@ -280,6 +294,7 @@
 - Fixed a bug in ``grid_from_bounding_box`` which caused the grid to be larger 
than
   the image in cases when the bounding box is on the edges of an image. [#121]
 
+
 0.8.0 (2017-11-02)
 ------------------
 
@@ -292,7 +307,7 @@
 - Replace ``domain`` with ``bounding_box``. [#74]
 
 - Added a ``LabelMapper`` model where ``mapper`` is an instance of
-  `~astropy.modeling.core.Model`. [#78]
+  `~astropy.modeling.Model`. [#78]
 
 - Evaluating a WCS with bounding box was moved to ``astropy.modeling``. [#86]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/PKG-INFO new/gwcs-0.18.3/PKG-INFO
--- old/gwcs-0.18.2/PKG-INFO    2022-09-07 19:01:34.642974100 +0200
+++ new/gwcs-0.18.3/PKG-INFO    2022-12-23 20:01:27.781919200 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: gwcs
-Version: 0.18.2
+Version: 0.18.3
 Summary: Generalized World Coordinate System
 Home-page: https://github.com/spacetelescope/gwcs
 Author: gwcs developers
@@ -10,6 +10,7 @@
 Project-URL: Documentation, https://gwcs.readthedocs.io/en/stable/
 Project-URL: Source Code, https://github.com/spacetelescope/jwst
 Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
 Provides-Extra: docs
 Provides-Extra: test
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/docs/conf.py new/gwcs-0.18.3/docs/conf.py
--- old/gwcs-0.18.2/docs/conf.py        2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/docs/conf.py        2022-12-23 20:01:15.000000000 +0100
@@ -165,3 +165,12 @@
 
 sys.path.insert(0, os.path.join(os.path.dirname('__file__'), 'sphinxext'))
 extensions += ['sphinx_asdf']
+
+# Enable nitpicky mode - which ensures that all references in the docs resolve.
+nitpicky = True
+nitpick_ignore = [
+    ('py:class', 'gwcs.api.GWCSAPIMixin'),
+    ('py:obj', 'astropy.modeling.projections.projcodes'),
+    ('py:attr', 'gwcs.WCS.bounding_box'),
+    ('py:meth', 'gwcs.WCS.footprint')
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/docs/gwcs/points_to_wcs.rst 
new/gwcs-0.18.3/docs/gwcs/points_to_wcs.rst
--- old/gwcs-0.18.2/docs/gwcs/points_to_wcs.rst 2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/docs/gwcs/points_to_wcs.rst 2022-12-23 20:01:15.000000000 
+0100
@@ -12,7 +12,7 @@
 This example shows how to use the `~gwcs.wcstools.wcs_from_points` tool to fit 
a WCS to a matched set of 
 pixel and sky positions.  Along with arrays of the (x,y) pixel position in the 
image and the matched sky coordinates,
 the fiducial point for the projection must be supplied as a 
`~astropy.coordinates.SkyCoord` object. Additionally,
-the projection type must be specified from the available projections in 
`~astropy.modeling.projections.projcode`.
+the projection type must be specified from the available projections in 
`~astropy.modeling.projections.projcodes`.
 
 Geometric distortion can also be fit to the input coordinates - the distortion 
type (2D polynomial, chebyshev, legendre) and 
 the degree can be supplied to fit this component of the model.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/docs/gwcs/using_wcs.rst 
new/gwcs-0.18.3/docs/gwcs/using_wcs.rst
--- old/gwcs-0.18.2/docs/gwcs/using_wcs.rst     2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/docs/gwcs/using_wcs.rst     2022-12-23 20:01:15.000000000 
+0100
@@ -145,7 +145,7 @@
 
 In order to catch these kind of errors that can occur during numerical
 inversion, we need to turn off ``quiet`` mode and be prepared to catch
-`gwcs.wcs.WCS.NoConvergence` exceptions. In the next example, let's also add a
+`gwcs.wcs.NoConvergence` exceptions. In the next example, let's also add a
 point far away from the image for which numerical inverse fails.
 
 .. doctest-skip::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/docs/index.rst 
new/gwcs-0.18.3/docs/index.rst
--- old/gwcs-0.18.2/docs/index.rst      2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/docs/index.rst      2022-12-23 20:01:15.000000000 +0100
@@ -297,8 +297,8 @@
 
 
 
-Using `gwcs`
-------------
+Using ``gwcs``
+--------------
 
 .. toctree::
   :maxdepth: 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/coordinate_frames.py 
new/gwcs-0.18.3/gwcs/coordinate_frames.py
--- old/gwcs-0.18.2/gwcs/coordinate_frames.py   2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/gwcs/coordinate_frames.py   2022-12-23 20:01:15.000000000 
+0100
@@ -94,7 +94,7 @@
     reference_frame : astropy.coordinates.builtin_frames
         Reference frame (usually used with output_frame to convert to world 
coordinate objects).
     reference_position : str
-        Reference position - one of `STANDARD_REFERENCE_POSITION`
+        Reference position - one of ``STANDARD_REFERENCE_POSITION``
     unit : list of astropy.units.Unit
         Unit for each axis.
     axes_names : list
@@ -431,7 +431,7 @@
     name : str
         Name for this frame.
     reference_position : str
-        Reference position - one of `STANDARD_REFERENCE_POSITION`
+        Reference position - one of ``STANDARD_REFERENCE_POSITION``
 
     """
 
@@ -481,7 +481,7 @@
     reference_frame : `~astropy.time.Time`
         A Time object which holds the time scale and format.
         If data is provided, it is the time zero point.
-        To not set a zero point for the frame initialize `reference_frame`
+        To not set a zero point for the frame initialize ``reference_frame``
         with an empty list.
     unit : str or `~astropy.units.Unit`
         Time unit.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/selector.py 
new/gwcs-0.18.3/gwcs/selector.py
--- old/gwcs-0.18.2/gwcs/selector.py    2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/gwcs/selector.py    2022-12-23 20:01:15.000000000 +0100
@@ -13,7 +13,7 @@
   - A mapping of inputs to labels - "label_mapper"
   - A mapping of labels to transforms - "transform_selector"
 
-A "label_mapper" is also a transform, a subclass of 
`astropy.modeling.core.Model`,
+A "label_mapper" is also a transform, a subclass of `astropy.modeling.Model`,
 which returns the labels corresponding to the inputs.
 
 An instance of a ``LabelMapper`` class is passed to `RegionsSelector`.
@@ -504,7 +504,7 @@
     selector : dict
         Mapping of region labels to transforms.
         Labels can be of type int or str, transforms are of type
-        `~astropy.modeling.core.Model`.
+        `~astropy.modeling.Model`.
     label_mapper : a subclass of `~gwcs.selector._LabelMapper`
         A model which maps locations to region labels.
     undefined_transform_value : float, np.nan (default)
@@ -651,7 +651,7 @@
 
     Parameters
     ----------
-    mapper : `~astropy.modeling.core.Model`
+    mapper : `~astropy.modeling.Model`
         A function which returns a region.
     no_label : str or int
         "" or 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/spectroscopy.py 
new/gwcs-0.18.3/gwcs/spectroscopy.py
--- old/gwcs-0.18.2/gwcs/spectroscopy.py        2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/gwcs/spectroscopy.py        2022-12-23 20:01:15.000000000 
+0100
@@ -189,7 +189,7 @@
         Iterable of size 3 containing B coefficients.
     C_coef : ndarray
         Iterable of size 3 containing c coefficients in
-        units of `u.um**2`.
+        units of ``u.um**2``.
 
     Returns
     -------
@@ -264,9 +264,9 @@
     Parameters
     ----------
     temperature : float
-        Temperature of the material in `u.Kelvin`.
+        Temperature of the material in ``u.Kelvin``.
     ref_temperature : float
-        Reference emperature of the glass in `u.Kelvin`.
+        Reference emperature of the glass in ``u.Kelvin``.
     ref_pressure : float
         Reference pressure in ATM.
     pressure : float
@@ -275,7 +275,7 @@
         Iterable of size 3 containing B coefficients.
     C_coef : ndarray
         Iterable of size 3 containing C coefficients in
-        units of `u.um**2`.
+        units of ``u.um**2``.
     D_coef : ndarray
         Iterable of size 3 containing constants to describe the
         behavior of the material.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/tests/test_wcs.py 
new/gwcs-0.18.3/gwcs/tests/test_wcs.py
--- old/gwcs-0.18.2/gwcs/tests/test_wcs.py      2022-09-07 19:01:21.000000000 
+0200
+++ new/gwcs-0.18.3/gwcs/tests/test_wcs.py      2022-12-23 20:01:15.000000000 
+0100
@@ -19,6 +19,7 @@
 from .. import coordinate_frames as cf
 from .. import utils
 from ..utils import CoordinateFrameError
+from .utils import _gwcs_from_hst_fits_wcs
 import asdf
 
 
@@ -605,8 +606,8 @@
         assert_allclose(footprint, fits_footprint)
 
     def test_inverse(self):
-        sky_coord = self.wcs(1, 2, with_units=True)
-        assert np.allclose(self.wcs.invert(sky_coord), (1, 2))
+        sky_coord = self.wcs(10, 20, with_units=True)
+        assert np.allclose(self.wcs.invert(sky_coord), (10, 20))
 
     def test_back_coordinates(self):
         sky_coord = self.wcs(1, 2, with_units=True)
@@ -636,9 +637,9 @@
     af = asdf.open(get_pkg_data_filename('data/miriwcs.asdf'))
     miriwcs = af.tree['wcs']
     bounding_box = ((0, 1024), (0, 1024))
-    mirisip = miriwcs.to_fits_sip(bounding_box, max_inv_pix_error=0.1)
+    mirisip = miriwcs.to_fits_sip(bounding_box, max_inv_pix_error=0.1, 
verbose=True)
     fitssip = astwcs.WCS(mirisip)
-    fitsvalx, fitsvaly = fitssip.all_pix2world(xflat+1, yflat+1, 1)
+    fitsvalx, fitsvaly = fitssip.all_pix2world(xflat + 1, yflat + 1, 1)
     gwcsvalx, gwcsvaly = miriwcs(xflat, yflat)
     assert_allclose(gwcsvalx, fitsvalx, atol=1e-10, rtol=0)
     assert_allclose(gwcsvaly, fitsvaly, atol=1e-10, rtol=0)
@@ -648,7 +649,7 @@
 
     mirisip = miriwcs.to_fits_sip(bounding_box=None, max_inv_pix_error=0.1)
     fitssip = astwcs.WCS(mirisip)
-    fitsvalx, fitsvaly = fitssip.all_pix2world(xflat+1, yflat+1, 1)
+    fitsvalx, fitsvaly = fitssip.all_pix2world(xflat + 1, yflat + 1, 1)
     assert_allclose(gwcsvalx, fitsvalx, atol=4e-11, rtol=0)
     assert_allclose(gwcsvaly, fitsvaly, atol=4e-11, rtol=0)
 
@@ -699,7 +700,7 @@
         crpix=None,
         projection='TAN',
         matrix_type=matrix_type,
-        verbose=False
+        verbose=True
     )
     fitssip = astwcs.WCS(fits_wcs)
 
@@ -1224,3 +1225,38 @@
     with warnings.catch_warnings():
         warnings.simplefilter("error")
         wcs.WCS(pipeline)
+
+
+def test_sip_roundtrip():
+    hdr = fits.Header.fromtextfile(get_pkg_data_filename("data/acs.hdr"),
+                                   endcard=False)
+    nx = ny = 1024
+    hdr['naxis'] = 2
+    hdr['naxis1'] = nx
+    hdr['naxis2'] = ny
+    gw = _gwcs_from_hst_fits_wcs(hdr)
+    hdr_back = gw.to_fits_sip(
+        max_pix_error=1e-6,
+        max_inv_pix_error=None,
+        npoints=64,
+        crpix=(hdr['crpix1'], hdr['crpix2'])
+    )
+
+    for k in ['naxis', 'naxis1', 'naxis2', 'ctype1', 'ctype2', 'a_order', 
'b_order']:
+        assert hdr[k] == hdr_back[k]
+
+    for k in ['cd1_1', 'cd1_2', 'cd2_1', 'cd2_2']:
+        assert np.allclose(hdr[k], hdr_back[k], atol=0, rtol=1e-9)
+
+    for t in ('a', 'b'):
+        order = hdr[f'{t}_order']
+        for i in range(order + 1):
+            for j in range(order + 1):
+                if 1 < i + j <= order:
+                    k = f'{t}_{i}_{j}'
+                    assert np.allclose(
+                        hdr[k],
+                        hdr_back[k],
+                        atol=0.0,
+                        rtol=1.0e-8 * 10**(i + j)
+                    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/tests/utils.py 
new/gwcs-0.18.3/gwcs/tests/utils.py
--- old/gwcs-0.18.2/gwcs/tests/utils.py 1970-01-01 01:00:00.000000000 +0100
+++ new/gwcs-0.18.3/gwcs/tests/utils.py 2022-12-23 20:01:15.000000000 +0100
@@ -0,0 +1,59 @@
+import numpy as np
+
+from astropy.modeling.models import (
+    Shift, Polynomial2D, Pix2Sky_TAN, RotateNative2Celestial, Mapping
+)
+
+from astropy import coordinates as coord
+from astropy import units
+from astropy import wcs as fits_wcs
+
+from .. wcs import WCS
+from .. import coordinate_frames as cf
+
+
+def _gwcs_from_hst_fits_wcs(header, hdu=None):
+    # NOTE: this function ignores table distortions
+    def coeffs_to_poly(mat, degree):
+        pol = Polynomial2D(degree=degree)
+        for i in range(mat.shape[0]):
+            for j in range(mat.shape[1]):
+                if 0 < i + j <= degree:
+                    setattr(pol, f'c{i}_{j}', mat[i, j])
+        return pol
+
+    w = fits_wcs.WCS(header, hdu)
+    ny, nx = w.pixel_shape
+    x0, y0 = w.wcs.crpix - 1
+
+    cd = w.wcs.piximg_matrix
+
+    cfx, cfy = np.dot(cd, [w.sip.a.ravel(), w.sip.b.ravel()])
+    a = np.reshape(cfx, w.sip.a.shape)
+    b = np.reshape(cfy, w.sip.b.shape)
+    a[1, 0] = cd[0, 0]
+    a[0, 1] = cd[0, 1]
+    b[1, 0] = cd[1, 0]
+    b[0, 1] = cd[1, 1]
+
+    polx = coeffs_to_poly(a, w.sip.a_order)
+    poly = coeffs_to_poly(b, w.sip.b_order)
+
+    # construct GWCS:
+    det2sky = (
+        (Shift(-x0) & Shift(-y0)) | Mapping((0, 1, 0, 1)) | (polx & poly) |
+        Pix2Sky_TAN() | RotateNative2Celestial(*w.wcs.crval, 180)
+    )
+
+    detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"),
+                                unit=(units.pix, units.pix))
+    sky_frame = cf.CelestialFrame(
+        reference_frame=getattr(coord, w.wcs.radesys).__call__(),
+        name=w.wcs.radesys,
+        unit=(units.deg, units.deg)
+    )
+    pipeline = [(detector_frame, det2sky), (sky_frame, None)]
+    gw = WCS(pipeline)
+    gw.bounding_box = ((-0.5, nx - 0.5), (-0.5, ny - 0.5))
+
+    return gw
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/wcs.py new/gwcs-0.18.3/gwcs/wcs.py
--- old/gwcs-0.18.2/gwcs/wcs.py 2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/gwcs/wcs.py 2022-12-23 20:01:15.000000000 +0100
@@ -4,7 +4,7 @@
 import warnings
 import numpy as np
 import numpy.linalg as npla
-from scipy import optimize
+from scipy import optimize, linalg
 from astropy import units as u
 from astropy.modeling.core import Model
 from astropy.modeling.models import (
@@ -12,7 +12,6 @@
     Sky2Pix_TAN, RotateCelestial2Native
 )
 from astropy.modeling import projections, fix_inputs
-from astropy.modeling.fitting import LinearLSQFitter
 import astropy.io.fits as fits
 from astropy.wcs.utils import celestial_frame_to_wcs, proj_plane_pixel_scales
 
@@ -199,9 +198,9 @@
 
         Parameters
         ----------
-        from_frame : str or `~gwcs.coordinate_frame.CoordinateFrame`
+        from_frame : str or `~gwcs.coordinate_frames.CoordinateFrame`
             Initial coordinate frame name of object.
-        to_frame : str, or instance of `~gwcs.cordinate_frames.CoordinateFrame`
+        to_frame : str, or instance of 
`~gwcs.coordinate_frames.CoordinateFrame`
             End coordinate frame name or object.
 
         Returns
@@ -237,9 +236,9 @@
 
         Parameters
         ----------
-        from_frame : str or `~gwcs.coordinate_frame.CoordinateFrame`
+        from_frame : str or `~gwcs.coordinate_frames.CoordinateFrame`
             Initial coordinate frame.
-        to_frame : str, or instance of `~gwcs.cordinate_frames.CoordinateFrame`
+        to_frame : str, or instance of 
`~gwcs.coordinate_frames.CoordinateFrame`
             End coordinate frame.
         transform : `~astropy.modeling.Model`
             Transform between ``from_frame`` and ``to_frame``.
@@ -871,10 +870,8 @@
         l2, phi2 = np.deg2rad(self.__call__(*(crpix + [-0.5, 0.5])))
         l3, phi3 = np.deg2rad(self.__call__(*(crpix + 0.5)))
         l4, phi4 = np.deg2rad(self.__call__(*(crpix + [0.5, -0.5])))
-        area = np.abs(0.5 * ((l4 - l2) * np.sin(phi1) +
-                             (l1 - l3) * np.sin(phi2) +
-                             (l2 - l4) * np.sin(phi3) +
-                             (l3 - l2) * np.sin(phi4)))
+        area = np.abs(0.5 * ((l4 - l2) * (np.sin(phi1) - np.sin(phi3)) +
+                             (l1 - l3) * (np.sin(phi2) - np.sin(phi4))))
         inv_pscale = 1 / np.rad2deg(np.sqrt(area))
 
         # form equation:
@@ -1117,7 +1114,7 @@
         ----------
         from_frame : str or `~gwcs.coordinate_frames.CoordinateFrame`
             Initial coordinate frame.
-        to_frame : str, or instance of `~gwcs.cordinate_frames.CoordinateFrame`
+        to_frame : str, or instance of 
`~gwcs.coordinate_frames.CoordinateFrame`
             Coordinate frame into which to transform.
         args : float or array-like
             Inputs in ``from_frame``, separate inputs for each dimension.
@@ -1182,7 +1179,7 @@
 
         Parameters
         ----------
-        frame : str or `~gwcs.coordinate_frame.CoordinateFrame`
+        frame : str or `~gwcs.coordinate_frames.CoordinateFrame`
             Coordinate frame which sets the point of insertion.
         transform : `~astropy.modeling.Model`
             New transform to be inserted in the pipeline
@@ -1204,17 +1201,17 @@
         Insert a new frame into an existing pipeline. This frame must be
         anchored to a frame already in the pipeline by a transform. This
         existing frame is identified solely by its name, although an entire
-        `~gwcs.coordinate_frame.CoordinateFrame` can be passed (e.g., the
+        `~gwcs.coordinate_frames.CoordinateFrame` can be passed (e.g., the
         `input_frame` or `output_frame` attribute). This frame is never
         modified.
 
         Parameters
         ----------
-        input_frame : str or `~gwcs.coordinate_frame.CoordinateFrame`
+        input_frame : str or `~gwcs.coordinate_frames.CoordinateFrame`
             Coordinate frame at start of new transform
         transform : `~astropy.modeling.Model`
             New transform to be inserted in the pipeline
-        output_frame: str or `~gwcs.coordinate_frame.CoordinateFrame`
+        output_frame: str or `~gwcs.coordinate_frames.CoordinateFrame`
             Coordinate frame at end of new transform
         """
         input_name, input_frame_obj = self._get_frame_name(input_frame)
@@ -1420,12 +1417,12 @@
         Parameters
         ----------
         bounding_box : tuple of floats: (start, stop)
-            `prop: bounding_box`
+            ``prop: bounding_box``
         center : bool
             If `True` use the center of the pixel, otherwise use the corner.
         axis_type : str
-            A supported ``output_frame.axes_type`` or "all" (default).
-            One of ['spatial', 'spectral', 'temporal'] or a custom type.
+            A supported ``output_frame.axes_type`` or ``"all"`` (default).
+            One of [``'spatial'``, ``'spectral'``, ``'temporal'``] or a custom 
type.
 
         Returns
         -------
@@ -1483,12 +1480,12 @@
         Parameters
         ----------
         fixed : dict
-            Keyword arguments with fixed values corresponding to 
`self.selector`.
+            Keyword arguments with fixed values corresponding to 
``self.selector``.
 
         Returns
         -------
         new_wcs : `WCS`
-            A new unique WCS corresponding to the values in `fixed`.
+            A new unique WCS corresponding to the values in ``fixed``.
 
         Examples
         --------
@@ -1528,7 +1525,8 @@
             Maximum allowed error over the domain of the pixel array. This
             error is the equivalent pixel error that corresponds to the maximum
             error in the output coordinate resulting from the fit based on
-            a nominal plate scale.
+            a nominal plate scale. Ignored when ``degree`` is an integer or
+            a list with a single degree.
 
         degree : int, iterable, None, optional
             Degree of the SIP polynomial. Default value `None` indicates that
@@ -1544,7 +1542,8 @@
 
         max_inv_pix_error : float, optional
             Maximum allowed inverse error over the domain of the pixel array
-            in pixel units. If None, no inverse is generated.
+            in pixel units. If None, no inverse is generated. Ignored when
+            ``degree`` is an integer or a list with a single degree.
 
         inv_degree : int, iterable, None, optional
             Degree of the SIP polynomial. Default value `None` indicates that
@@ -1829,8 +1828,9 @@
         plate_scale = np.sqrt(pixarea)
 
         # The fitting section.
+        if verbose:
+            print("\nFitting forward SIP ...")
         fit_poly_x, fit_poly_y, max_resid = _fit_2D_poly(
-            ntransform, npoints,
             degree, max_pix_error, plate_scale,
             u, v, undist_x, undist_y,
             ud, vd, undist_xd, undist_yd,
@@ -1849,12 +1849,15 @@
         Vd = (-cdmat[1][0] * undist_xd + cdmat[0][0] * undist_yd) / detd
 
         if max_inv_pix_error:
-            fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = 
_fit_2D_poly(ntransform,
-                                                            npoints, 
inv_degree,
-                                                            max_inv_pix_error, 
1,
-                                                            U, V, u-U, v-V,
-                                                            Ud, Vd, ud-Ud, 
vd-Vd,
-                                                            verbose=verbose)
+            if verbose:
+                print("\nFitting inverse SIP ...")
+            fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = _fit_2D_poly(
+                inv_degree,
+                max_inv_pix_error, 1,
+                U, V, u-U, v-V,
+                Ud, Vd, ud-Ud, vd-Vd,
+                verbose=verbose
+        )
 
         # create header with WCS info:
         w = celestial_frame_to_wcs(frame.reference_frame, 
projection=projection)
@@ -2178,8 +2181,8 @@
             does not match the number of image axes.
 
         RuntimeError
-            If the number of image axes (`~gwcs.WCS.pixel_n_dim`) is larger
-            than the number of world axes (`~gwcs.WCS.world_n_dim`).
+            If the number of image axes (``~gwcs.WCS.pixel_n_dim``) is larger
+            than the number of world axes (``~gwcs.WCS.world_n_dim``).
 
         """
         if bounding_box is None:
@@ -2359,8 +2362,8 @@
             does not match the number of image axes.
 
         RuntimeError
-            If the number of image axes (`~gwcs.WCS.pixel_n_dim`) is larger
-            than the number of world axes (`~gwcs.WCS.world_n_dim`).
+            If the number of image axes (``~gwcs.WCS.pixel_n_dim``) is larger
+            than the number of world axes (``~gwcs.WCS.world_n_dim``).
 
         """
         if bounding_box is None:
@@ -2541,8 +2544,8 @@
         TypeError
 
         RuntimeError
-            If the number of image axes (`~gwcs.WCS.pixel_n_dim`) is larger
-            than the number of world axes (`~gwcs.WCS.world_n_dim`).
+            If the number of image axes (``~gwcs.WCS.pixel_n_dim``) is larger
+            than the number of world axes (``~gwcs.WCS.world_n_dim``).
 
         """
         if isinstance(bin_ext, str):
@@ -2740,8 +2743,7 @@
         undist_xd, undist_yd = ntransform(ud, vd)
 
         fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = _fit_2D_poly(
-            ntransform,
-            npoints, None,
+            None,
             max_inv_pix_error, 1,
             undist_x, undist_y, u, v,
             undist_xd, undist_yd, ud, vd,
@@ -2754,7 +2756,106 @@
                                 (Shift(crpix[0]) & Shift(crpix[1])))
 
 
-def _fit_2D_poly(ntransform, npoints, degree, max_error, plate_scale,
+def _poly_fit_lu(xin, yin, xout, yout, degree, coord_pow=None):
+    # This function fits 2D polynomials to data by writing the normal system
+    # of equations and solving it using LU-decomposition. In theory this
+    # should be less stable than the SVD method used by numpy's lstsq or
+    # astropy's LinearLSQFitter because the condition of the normal matrix
+    # is squared compared to the direct matrix. However, in practice,
+    # in our (Mihai Cara) tests of fitting WCS distortions, solving the
+    # normal system proved to be significantly more accurate, efficient,
+    # and stable than SVD.
+    #
+    # coord_pow - a dictionary used to store powers of coordinate arrays
+    #    of the form x**p * y**q used to build the pseudo-Vandermonde matrix.
+    #    This improves efficiency especially when fitting multiple degrees
+    #    on the same coordinate grid in _fit_2D_poly by reusing computed
+    #    powers.
+    powers = [
+        (i, j)
+        for i in range(degree + 1) for j in range(degree + 1 - i) if i + j > 0
+    ]
+    if coord_pow is None:
+        coord_pow = {}
+
+    nterms = len(powers)
+
+    flt_type = np.longdouble
+
+    # allocate array for the coefficients of the system of equations (a*x=b):
+    a = np.empty((nterms, nterms), dtype=flt_type)
+    bx = np.empty(nterms, dtype=flt_type)
+    by = np.empty(nterms, dtype=flt_type)
+
+    xout = xout.ravel()
+    yout = yout.ravel()
+
+    x = np.asarray(xin.ravel(), dtype=flt_type)
+    y = np.asarray(yin.ravel(), dtype=flt_type)
+
+    # pseudo_vander - a reduced Vandermonde matrix for 2D polynomials
+    # that has only terms x^i * y^j with powers i, j that satisfy:
+    # 0 < i + j <= degree.
+    pseudo_vander = np.empty((x.size, nterms), dtype=float)
+
+    def pow2(p, q):
+        # computes product of powers of coordinate arrays (x**p) * (y**q)
+        # in an efficient way avoiding unnecessary array copying
+        # and/or raising to power
+        if (p, q) in coord_pow:
+            return coord_pow[(p, q)]
+        if p == 0:
+            arr = y**q if q > 1 else y
+        elif q == 0:
+            arr = x**p if p > 1 else x
+        else:
+            xp = x if p == 1 else x**p
+            yq = y if q == 1 else y**q
+            arr = xp * yq
+        coord_pow[(p, q)] = arr
+        return arr
+
+    for i in range(nterms):
+        pi, qi = powers[i]
+        coord_pq = pow2(pi, qi)
+        pseudo_vander[:, i] = coord_pq
+        bx[i] = np.sum(xout * coord_pq, dtype=flt_type)
+        by[i] = np.sum(yout * coord_pq, dtype=flt_type)
+
+        for j in range(i, nterms):
+            pj, qj = powers[j]
+            coord_pq = pow2(pi + pj, qi + qj)
+            a[i, j] = np.sum(coord_pq, dtype=flt_type)
+            a[j, i] = a[i, j]
+
+    with warnings.catch_warnings(record=True):
+        warnings.simplefilter('error', category=linalg.LinAlgWarning)
+        try:
+            lu_piv = linalg.lu_factor(a)
+            poly_coeff_x = linalg.lu_solve(lu_piv, bx).astype(float)
+            poly_coeff_y = linalg.lu_solve(lu_piv, by).astype(float)
+        except (ValueError, linalg.LinAlgWarning, np.linalg.LinAlgError) as e:
+            raise np.linalg.LinAlgError(
+                f"Failed to fit SIP. Reported error:\n{e.args[0]}"
+            )
+
+    if not np.all(np.isfinite([poly_coeff_x, poly_coeff_y])):
+        raise np.linalg.LinAlgError(
+            "Failed to fit SIP. Computed coefficients are not finite."
+        )
+
+    cond = np.linalg.cond(a.astype(float))
+
+    fitx = np.dot(pseudo_vander, poly_coeff_x)
+    fity = np.dot(pseudo_vander, poly_coeff_y)
+
+    dist = np.sqrt((xout - fitx)**2 + (yout - fity)**2)
+    max_resid = dist.max()
+
+    return poly_coeff_x, poly_coeff_y, max_resid, powers, cond
+
+
+def _fit_2D_poly(degree, max_error, plate_scale,
                  xin, yin, xout, yout,
                  xind, yind, xoutd, youtd,
                  verbose=False):
@@ -2762,14 +2863,12 @@
     Fit a pair of ordinary 2D polynomials to the supplied transform.
 
     """
-    llsqfitter = LinearLSQFitter()
-
     # The case of one pass with the specified polynomial degree
     if degree is None:
-        deglist = range(1, 10)
+        deglist = list(range(1, 10))
     elif hasattr(degree, '__iter__'):
         deglist = sorted(map(int, degree))
-        if set(deglist).difference(range(1, 10)):
+        if deglist[0] < 1 or deglist[-1] > 9:
             raise ValueError("Allowed values for SIP degree are [1...9]")
     else:
         degree = int(degree)
@@ -2777,44 +2876,116 @@
             raise ValueError("Allowed values for SIP degree are [1...9]")
         deglist = [degree]
 
-    prev_max_error = float(np.inf)
-    if verbose:
-        print(f'Maximum_specified_error: {max_error}')
+    single_degree = len(deglist) == 1
+
+    fit_error = np.inf
+    if verbose and not single_degree:
+        print(f'Maximum specified SIP approximation error: {max_error}')
     max_error *= plate_scale
 
+    fit_warning_msg = "Failed to achieve requested SIP approximation accuracy."
+
+    # Fit lowest degree SIP first.
+    coord_pow = {}  # hold coordinate arrays powers for optimization purpose
     for deg in deglist:
-        poly_x = Polynomial2D(degree=deg)
-        poly_y = Polynomial2D(degree=deg)
-        fit_poly_x = llsqfitter(poly_x, xin, yin, xout)
-        fit_poly_y = llsqfitter(poly_y, xin, yin, yout)
-        max_resid = _compute_distance_residual(xout, yout,
-                                               fit_poly_x(xin, yin),
-                                               fit_poly_y(xin, yin))
-        if max_resid > prev_max_error:
-            raise RuntimeError('Failed to achieve required error tolerance')
-        prev_max_error = max_resid
+        try:
+            cfx_i, cfy_i, fit_error_i, powers_i, cond = _poly_fit_lu(
+                xin, yin, xout, yout, degree=deg, coord_pow=coord_pow
+            )
+            if verbose and not single_degree:
+                print(
+                    f"   - SIP degree: {deg}. "
+                    f"Maximum residual: {fit_error_i / plate_scale:.5g}"
+                )
 
-        if verbose:
-            print(f'Degree = {deg}, max_resid = {max_resid / plate_scale}')
-        if max_resid < max_error:
-            # Check to see if double sampling meets error requirement.
-            max_resid = _compute_distance_residual(xoutd, youtd,
-                                                   fit_poly_x(xind, yind),
-                                                   fit_poly_y(xind, yind))
-            if verbose:
-                print(f'Double sampling check: maximum residual={max_resid / 
plate_scale}')
-            if max_resid < max_error:
-                if verbose:
-                    print('Terminating condition met')
+        except np.linalg.LinAlgError as e:
+            if single_degree:
+                # Nothing to do if failure is for the lowest degree
+                raise e
+            else:
+                # Keep results from the previous iteration. Discard current fit
                 break
 
-    return fit_poly_x, fit_poly_y, max_resid / plate_scale
+        if not np.isfinite(cond):
+            # Ill-conditioned system
+            if single_degree:
+                warnings.warn("The fit may be poorly conditioned.")
+                cfx = cfx_i
+                cfy = cfy_i
+                fit_error = fit_error_i
+                powers = powers_i
+            break
+
+        if fit_error_i >= fit_error:
+            # Accuracy does not improve. Likely ill-conditioned system
+            break
+
+        cfx = cfx_i
+        cfy = cfy_i
+        powers = powers_i
+
+        fit_error = fit_error_i
+
+        if fit_error <= max_error:
+            # Requested accuracy has been achieved
+            fit_warning_msg = None
+            break
+
+        # Continue to the next degree
+
+    fit_poly_x = Polynomial2D(degree=deg, c0_0=0.0)
+    fit_poly_y = Polynomial2D(degree=deg, c0_0=0.0)
+    for cx, cy, (p, q) in zip(cfx, cfy, powers):
+        setattr(fit_poly_x, f'c{p:1d}_{q:1d}', cx)
+        setattr(fit_poly_y, f'c{p:1d}_{q:1d}', cy)
+
+    if fit_warning_msg:
+        warnings.warn(fit_warning_msg, linalg.LinAlgWarning)
+
+    if fit_error <= max_error or single_degree:
+        # Check to see if double sampling meets error requirement.
+        max_resid = _compute_distance_residual(
+            xoutd,
+            youtd,
+            fit_poly_x(xind, yind),
+            fit_poly_y(xind, yind)
+        )
+        if verbose:
+            print(
+                "* Maximum residual, double sampled grid: "
+                f"{max_resid / plate_scale:.5g}"
+            )
+
+        if max_resid > min(5.0 * fit_error, max_error):
+            warnings.warn(
+                "Double sampling check FAILED: Sampling may be too coarse for "
+                "the distortion model being fitted."
+            )
+
+        # Residuals on the double-dense grid may be better estimates
+        # of the accuracy of the fit. So we report the largest of
+        # the residuals (on single- and double-sampled grid) as the fit error:
+        fit_error = max(max_resid, fit_error)
+
+    if verbose:
+        if single_degree:
+            print(
+                f"Maximum residual: {fit_error / plate_scale:.5g}"
+            )
+        else:
+            print(
+                f"* Final SIP degree: {deg}. "
+                f"Maximum residual: {fit_error / plate_scale:.5g}"
+            )
+
+    return fit_poly_x, fit_poly_y, fit_error / plate_scale
 
 
 def _make_sampling_grid(npoints, bounding_box, crpix):
     step = np.subtract.reduce(bounding_box, axis=1) / (1.0 - npoints)
     crpix = np.asanyarray(crpix)[:, None, None]
-    return grid_from_bounding_box(bounding_box, step=step, center=False) - 
crpix
+    x, y = grid_from_bounding_box(bounding_box, step=step, center=False) - 
crpix
+    return x.flatten(), y.flatten()
 
 
 def _compute_distance_residual(undist_x, undist_y, fit_poly_x, fit_poly_y):
@@ -2913,9 +3084,9 @@
     ----------
     frame : `~gwcs.coordinate_frames.CoordinateFrame`
         A gwcs coordinate frame object.
-    transform : `~astropy.modeling.core.Model` or None
+    transform : `~astropy.modeling.Model` or None
         A transform from this step's frame to next step's frame.
-        The transform of the last step should be ``None``.
+        The transform of the last step should be `None`.
     """
     def __init__(self, frame, transform=None):
         self.frame = frame
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs/wcstools.py 
new/gwcs-0.18.3/gwcs/wcstools.py
--- old/gwcs-0.18.2/gwcs/wcstools.py    2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/gwcs/wcstools.py    2022-12-23 20:01:15.000000000 +0100
@@ -46,7 +46,7 @@
     bounding_box : tuple
         The bounding box over which the WCS is valid.
         It is a tuple of tuples of size 2 where each tuple
-        represents a range of (low, high) values. The `bounding_box` is in the 
order of
+        represents a range of (low, high) values. The ``bounding_box`` is in 
the order of
         the axes, `~gwcs.coordinate_frames.CoordinateFrame.axes_order`.
         For two inputs and axes_order(0, 1) the bounding box is ((xlow, 
xhigh), (ylow, yhigh)).
     input_frame : ~gwcs.coordinate_frames.CoordinateFrame`
@@ -247,8 +247,8 @@
         be passed in.
     projection : `~astropy.modeling.projections.Projection`
         A projection type. One of the projections in
-        `~astropy.modeling.projections.projcode`. Defaults to TAN projection
-        (`projections.Sky2Pix_TAN()`).
+        `~astropy.modeling.projections.projcodes`. Defaults to TAN projection
+        (`astropy.modeling.projections.Sky2Pix_TAN`).
     poly_degree : int
         Degree of polynomial model to be fit to data. Defaults to 4.
     polynomial_type : str
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs.egg-info/PKG-INFO 
new/gwcs-0.18.3/gwcs.egg-info/PKG-INFO
--- old/gwcs-0.18.2/gwcs.egg-info/PKG-INFO      2022-09-07 19:01:34.000000000 
+0200
+++ new/gwcs-0.18.3/gwcs.egg-info/PKG-INFO      2022-12-23 20:01:27.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: gwcs
-Version: 0.18.2
+Version: 0.18.3
 Summary: Generalized World Coordinate System
 Home-page: https://github.com/spacetelescope/gwcs
 Author: gwcs developers
@@ -10,6 +10,7 @@
 Project-URL: Documentation, https://gwcs.readthedocs.io/en/stable/
 Project-URL: Source Code, https://github.com/spacetelescope/jwst
 Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
 Provides-Extra: docs
 Provides-Extra: test
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/gwcs.egg-info/SOURCES.txt 
new/gwcs-0.18.3/gwcs.egg-info/SOURCES.txt
--- old/gwcs-0.18.2/gwcs.egg-info/SOURCES.txt   2022-09-07 19:01:34.000000000 
+0200
+++ new/gwcs-0.18.3/gwcs.egg-info/SOURCES.txt   2022-12-23 20:01:27.000000000 
+0100
@@ -1,5 +1,6 @@
 .bandit.yaml
 .gitignore
+.readthedocs.yaml
 CHANGES.rst
 CODE_OF_CONDUCT.md
 CONTRIBUTING.md
@@ -8,7 +9,6 @@
 conftest.py
 convert_schemas.py
 pyproject.toml
-readthedocs.yml
 requirements-dev.txt
 setup.cfg
 setup.py
@@ -69,6 +69,7 @@
 gwcs/tests/test_spectroscopy_models.py
 gwcs/tests/test_utils.py
 gwcs/tests/test_wcs.py
+gwcs/tests/utils.py
 gwcs/tests/data/acs.hdr
 gwcs/tests/data/acs_wfc.hdr
 gwcs/tests/data/miri_lrs_wcs.asdf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/readthedocs.yml 
new/gwcs-0.18.3/readthedocs.yml
--- old/gwcs-0.18.2/readthedocs.yml     2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/readthedocs.yml     1970-01-01 01:00:00.000000000 +0100
@@ -1,21 +0,0 @@
-# Read the Docs configuration file
-# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
-version: 2
-
-sphinx:
-  builder: html
-  configuration: docs/conf.py
-  fail_on_warning: true
-
-# Set the version of Python and requirements required to build your docs
-python:
-  version: 3.8
-  system_packages: true
-  install:
-    - method: pip
-      path: .
-      extra_requirements:
-        - docs
-
-# Don't build any extra formats
-formats: []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/setup.cfg new/gwcs-0.18.3/setup.cfg
--- old/gwcs-0.18.2/setup.cfg   2022-09-07 19:01:34.642974100 +0200
+++ new/gwcs-0.18.3/setup.cfg   2022-12-23 20:01:27.781919200 +0100
@@ -2,6 +2,7 @@
 name = gwcs
 description = Generalized World Coordinate System
 long_description = Tools for managing the WCS of astronomical observations in 
a general (non-FITS) way
+long_description_content_type = text/x-rst
 author = gwcs developers
 author_email = h...@stsci.edu
 license = BSD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gwcs-0.18.2/tox.ini new/gwcs-0.18.3/tox.ini
--- old/gwcs-0.18.2/tox.ini     2022-09-07 19:01:21.000000000 +0200
+++ new/gwcs-0.18.3/tox.ini     2022-12-23 20:01:15.000000000 +0100
@@ -1,55 +1,104 @@
 [tox]
-envlist= {py38,py39, py310}-test
-    py38-dev
-    py38-test-numpy{119,120}
-    style
-    bandit
-    py38-cov
-
-requires =
-    setuptools >= 30.3.0
-    pip >= 19.3.1
-isolated_build = true
+envlist =
+    check-{style,security,build}
+    test{,-dev}{,-pyargs,-cov}
+    test-numpy{120,121,122}
+    build-{docs,dist}
+
+# tox environments are constructed with so-called 'factors' (or terms)
+# separated by hyphens, e.g. test-devdeps-cov. Lines below starting with 
factor:
+# will only take effect if that factor is included in the environment name. To
+# see a list of example environments that can be run, along with a description,
+# run:
+#
+#     tox -l -v
+#
+
+[testenv:check-style]
+description = check code style, e.g. with flake8
+skip_install = true
+deps =
+    flake8
+commands =
+    flake8 . {posargs}
 
-[testenv]
-passenv= HOME GITHUB_* TOXENV CI CODECOV_* DISPLAY
+[testenv:check-security]
+description = run bandit to check security compliance
+skip_install = true
+deps =
+    bandit>=1.7
+commands =
+    bandit -r -ll -c .bandit.yaml gwcs
+
+[testenv:check-build]
+description = check build sdist/wheel and a strict twine check for metadata
+skip_install = true
+deps =
+    twine>=3.3
+    build
+commands =
+    python -m build .
+    twine check --strict dist/*
 
+[testenv]
 description =
     run tests
+    jwst: of JWST pipeline
+    romancal: of Romancal pipeline
     dev: with the latest developer version of key dependencies
-    cov: and test coverage
-    numpy120: with numpy 1.20.*
-    numpy119: with numpy 1.19.*
-
-deps=
-    dev: -rrequirements-dev.txt
-
-    cov: coverage
-
-    numpy120: numpy==1.20.*
-    numpy119: numpy==1.19.*
-
+    pyargs: with --pyargs on installed package
+    warnings: treating warnings as errors
+    cov: with coverage
+    xdist: using parallel processing
+passenv =
+    HOME
+    GITHUB_*
+    TOXENV
+    CI
+    CODECOV_*
+    DISPLAY
+args_are_paths = false
+change_dir = pyargs: {homedir}
 extras =
     test
-
-commands =
+    alldeps: all
+deps =
+    xdist: pytest-xdist
+    dev: -rrequirements-dev.txt
+    cov: pytest-cov
+    jwst: jwst[test] @ git+https://github.com/spacetelescope/jwst.git
+    romancal: romancal[test] @ 
git+https://github.com/spacetelescope/romancal.git
+    numpy120: numpy==1.20.*
+    numpy121: numpy==1.21.*
+    numpy122: numpy==1.22.*
+set_env =
+    jwst: CRDS_SERVER_URL=https://jwst-crds.stsci.edu
+    romancal: CRDS_SERVER_URL=https://roman-crds.stsci.edu
+    jwst,romancal: CRDS_PATH=$HOME/crds_cache
+    jwst,romancal: CRDS_CLIENT_RETRY_COUNT=3
+    jwst,romancal: CRDS_CLIENT_RETRY_DELAY_SECONDS=20
+commands_pre =
     pip freeze
-    !cov: pytest {posargs}
-    cov: pytest --cov-report xml --cov {posargs}
-
-[testenv:pyargs]
-changedir = {homedir}
 commands =
-     pyargs: pytest {toxinidir}/docs --pyargs {posargs}
-
-[testenv:style]
-deps=
-    flake8
-commands=
-    flake8 {posargs}
+    pytest \
+    warnings: -W error \
+    xdist: -n auto \
+    pyargs: {toxinidir}/docs --pyargs gwcs \
+    jwst: --pyargs jwst --ignore-glob=timeconversion 
--ignore-glob=associations \
+    romancal: --pyargs romancal \
+    cov: --cov=. --cov-config=setup.cfg --cov-report=term-missing 
--cov-report=xml \
+    {posargs}
+
+[testenv:build-docs]
+description = invoke sphinx-build to build the HTML docs
+extras = docs
+commands =
+    sphinx-build -W docs docs/_build
 
-[testenv:bandit]
-deps=
-    bandit
-commands=
-    bandit -r -ll -c .bandit.yaml gwcs
+[testenv:build-dist]
+description = build wheel and sdist
+skip_install = true
+deps =
+    build
+commands =
+    python -m build .

Reply via email to