Repository: lightkurve/lightkurve

Issue:
Incorrect UserWarning in assigning LightCurve.meta
<!-- Fill in the information below before opening an issue. -->

#### Problem description
<!-- Provide a clear and concise description of the issue. -->

1. When user assigns value to `lc.meta`, an UserWarning` is issued. It should not be there.
2. The URL in the UserWarning is outdated. It is now https://docs.lightkurve.org/reference/api/lightkurve.LightCurve.html


#### Example
<!-- Provide a link or minimal code snippet that demonstrates the issue. -->

The warning should not appear in the following code.
```python
import lightkurve as lk
>>> lc = lk.LightCurve(time=[1, 2, 3, 4], flux=[1, 1, 1, 1])
>>> lc.meta = {'TELESCOP': 'Kepler'}
<stdin>:1: UserWarning: Lightkurve doesn't allow columns or meta values to be created via a new attribute name.A new attribute is created. It will not be carried over when the object is copied. - see https://docs.lightkurve.org/api/lightkurve.lightcurve.LightCurve.html
```

The `UserWarning` appeared **whenever** `lc.meta` is assigned, not just the initial assignment in the above code.

#### Environment

-  lightkurve version (e.g. 1.0b6): 2.0.9 (should affect all older 2.0.x)
-  installation method (e.g. pip, conda, source): pip


Discussion:

Patch:
diff --git a/docs/source/conf.py b/docs/source/conf.py
index e8b19226..5c5feec9 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -19,6 +19,7 @@ extensions = [
     'sphinx.ext.autosummary',
     'sphinx.ext.mathjax',
     'sphinx.ext.intersphinx',
+    'sphinx.ext.viewcode',
     'nbsphinx',
     'numpydoc',
     'sphinxcontrib.rawfiles']
diff --git a/docs/source/tutorials/1-getting-started/what-are-targetpixelfile-objects.ipynb b/docs/source/tutorials/1-getting-started/what-are-targetpixelfile-objects.ipynb
index 79e30060..77fbf60e 100644
--- a/docs/source/tutorials/1-getting-started/what-are-targetpixelfile-objects.ipynb
+++ b/docs/source/tutorials/1-getting-started/what-are-targetpixelfile-objects.ipynb
@@ -97,7 +97,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "You can find the full list of properties in the [API documentation](https://docs.lightkurve.org/api/lightkurve.targetpixelfile.KeplerTargetPixelFile.html#lightkurve.targetpixelfile.KeplerTargetPixelFile) on this object."
+    "You can find the full list of properties in the [API documentation](https://docs.lightkurve.org/reference/api/lightkurve.targetpixelfile.KeplerTargetPixelFile.html#lightkurve.targetpixelfile.KeplerTargetPixelFile) on this object."
    ]
   },
   {
diff --git a/docs/source/tutorials/2-creating-light-curves/2-1-combining-multiple-quarters.ipynb b/docs/source/tutorials/2-creating-light-curves/2-1-combining-multiple-quarters.ipynb
index 487f3237..880e8ce3 100644
--- a/docs/source/tutorials/2-creating-light-curves/2-1-combining-multiple-quarters.ipynb
+++ b/docs/source/tutorials/2-creating-light-curves/2-1-combining-multiple-quarters.ipynb
@@ -311,7 +311,7 @@
     "id": "vf6NDWeqk-4g"
    },
    "source": [
-    "Let's first have a look at how these observations differ from one another. We can plot the simple aperture photometry (SAP) flux of all of the observations in the [`LightCurveCollection`](https://docs.lightkurve.org/api/lightkurve.collections.LightCurveCollection.html#lightkurve.collections.LightCurveCollection) to see how they compare."
+    "Let's first have a look at how these observations differ from one another. We can plot the simple aperture photometry (SAP) flux of all of the observations in the [`LightCurveCollection`](https://docs.lightkurve.org/reference/api/lightkurve.collections.LightCurveCollection.html#lightkurve.collections.LightCurveCollection) to see how they compare."
    ]
   },
   {
diff --git a/docs/source/tutorials/2-creating-light-curves/2-2-kepler-noise-2-spurious-signals-and-time-sampling-effects.ipynb b/docs/source/tutorials/2-creating-light-curves/2-2-kepler-noise-2-spurious-signals-and-time-sampling-effects.ipynb
index d36dd912..f026f5fa 100644
--- a/docs/source/tutorials/2-creating-light-curves/2-2-kepler-noise-2-spurious-signals-and-time-sampling-effects.ipynb
+++ b/docs/source/tutorials/2-creating-light-curves/2-2-kepler-noise-2-spurious-signals-and-time-sampling-effects.ipynb
@@ -263,7 +263,7 @@
     "\n",
     "Like *Kepler*, the *K2* mission used guide stars to assist with pointing as part of the FGS. Where *Kepler* used 40 guide stars throughout the whole mission, in *K2* it was harder to find suitable guide stars in *K2*'s visually crowded fields along the ecliptic plane, and so four stars were used in each campaign. This predictably reduced pointing stability, as each guide star in *K2* contributed more to the overall pointing.\n",
     "\n",
-    "The majority of *K2* guide stars were stable. However, one of the Campaign 6 guide stars was an eclipsing binary with a period of 0.6046 days. By [folding](https://docs.lightkurve.org/api/lightkurve.lightcurve.LightCurve.html#lightkurve.lightcurve.LightCurve.fold) an affected light curve on this period, we can see the impact of the eclipsing binary on an otherwise non-variable target.\n",
+    "The majority of *K2* guide stars were stable. However, one of the Campaign 6 guide stars was an eclipsing binary with a period of 0.6046 days. By [folding](https://docs.lightkurve.org/reference/api/lightkurve.lightcurve.LightCurve.html#lightkurve.lightcurve.LightCurve.fold) an affected light curve on this period, we can see the impact of the eclipsing binary on an otherwise non-variable target.\n",
     "\n",
     "The example below is from the [*K2* Data Release Notes 35](https://archive.stsci.edu/missions/k2/doc/drn/KSCI-19156-001_K2-DRN35_C6.pdf) on the reprocessing of Campaign 6:"
    ]
diff --git a/docs/source/tutorials/3-science-examples/asteroseismology-estimating-mass-and-radius.ipynb b/docs/source/tutorials/3-science-examples/asteroseismology-estimating-mass-and-radius.ipynb
index 92a2dbae..24bf0006 100644
--- a/docs/source/tutorials/3-science-examples/asteroseismology-estimating-mass-and-radius.ipynb
+++ b/docs/source/tutorials/3-science-examples/asteroseismology-estimating-mass-and-radius.ipynb
@@ -125,7 +125,7 @@
     "id": "83w84X7O-cFt"
    },
    "source": [
-    "In one of the companion tutorials, we already identified the location of the modes of oscillation, so we'll zoom in on that region right away when creating the periodogram. We also set the [`normalization='psd'`](https://docs.lightkurve.org/api/lightkurve.periodogram.LombScarglePeriodogram.html#lightkurve.periodogram.LombScarglePeriodogram.from_lightcurve) keyword argument, to match the common conventions in asteroseismology of solar-like oscillators."
+    "In one of the companion tutorials, we already identified the location of the modes of oscillation, so we'll zoom in on that region right away when creating the periodogram. We also set the [`normalization='psd'`](https://docs.lightkurve.org/reference/api/lightkurve.periodogram.LombScarglePeriodogram.html#lightkurve.periodogram.LombScarglePeriodogram.from_lightcurve) keyword argument, to match the common conventions in asteroseismology of solar-like oscillators."
    ]
   },
   {
diff --git a/docs/source/tutorials/3-science-examples/periodograms-measuring-a-rotation-period.ipynb b/docs/source/tutorials/3-science-examples/periodograms-measuring-a-rotation-period.ipynb
index 7e4bc4ab..334cb5a2 100644
--- a/docs/source/tutorials/3-science-examples/periodograms-measuring-a-rotation-period.ipynb
+++ b/docs/source/tutorials/3-science-examples/periodograms-measuring-a-rotation-period.ipynb
@@ -103,7 +103,7 @@
     "\n",
     "We're going to explore a star named KIC 2157356, which is part of the cohort of rotating stars that were studied by *Kepler* and analyzed in a research paper by [McQuillan et al. (2014)](https://arxiv.org/abs/1402.5694).\n",
     "\n",
-    "Because rotation variability tends to happen on relatively long time scales (for instance, the Sun rotates every 25 days), we will start by downloading three ~90-day long quarters of *Kepler* data and then combine them using Lightkurve's [`stitch()`](https://docs.lightkurve.org/api/lightkurve.collections.LightCurveCollection.html#lightkurve.collections.LightCurveCollection.stitch) feature."
+    "Because rotation variability tends to happen on relatively long time scales (for instance, the Sun rotates every 25 days), we will start by downloading three ~90-day long quarters of *Kepler* data and then combine them using Lightkurve's [`stitch()`](https://docs.lightkurve.org/reference/api/lightkurve.collections.LightCurveCollection.html#lightkurve.collections.LightCurveCollection.stitch) feature."
    ]
   },
   {
diff --git a/docs/source/tutorials/3-science-examples/periodograms-verifying-the-location-of-a-signal.ipynb b/docs/source/tutorials/3-science-examples/periodograms-verifying-the-location-of-a-signal.ipynb
index a1fe16b0..bfb82650 100644
--- a/docs/source/tutorials/3-science-examples/periodograms-verifying-the-location-of-a-signal.ipynb
+++ b/docs/source/tutorials/3-science-examples/periodograms-verifying-the-location-of-a-signal.ipynb
@@ -449,7 +449,7 @@
    "source": [
     "In this case, we can clearly see an eclipse to the left of our target. The eclipsing binary is outside of the pipeline aperture, but the signal is strong enough to have contaminated it. And sure enough, if we check on the [NASA Exoplanet Archive](https://exoplanetarchive.ipac.caltech.edu/cgi-bin/DisplayOverview/nph-DisplayOverview?objname=K00311.01&type=KEPLER_CANDIDATE), KOI 311 was designated a false positive in the first data release it was included in.\n",
     "\n",
-    "We can double-check this by using Lightkurve's [cone search](https://docs.lightkurve.org/api/lightkurve.search.search_lightcurvefile.html#lightkurve.search.search_lightcurvefile) to look for the eclipsing binary in question. So long as there's a light curve for this contaminant — and we can reasonably expect one, for such a star — we'll be able to find it with Lightkurve's search function.\n",
+    "We can double-check this by using Lightkurve's [cone search](https://docs.lightkurve.org/reference/api/lightkurve.search.search_lightcurvefile.html#lightkurve.search.search_lightcurvefile) to look for the eclipsing binary in question. So long as there's a light curve for this contaminant — and we can reasonably expect one, for such a star — we'll be able to find it with Lightkurve's search function.\n",
     "\n",
     "(As an aside, if you run this notebook yourself, you can use the [interact_sky()](https://docs.lightkurve.org/reference/api/lightkurve.KeplerTargetPixelFile.interact_sky.html?highlight=interact_sky#lightkurve.KeplerTargetPixelFile.interact_sky) function, which returns an interactive TPF widget with targets labelled by _Gaia_ ID. This includes many targets which were not collected by _Kepler_ or _K2_.)\n",
     "\n",
diff --git a/src/lightkurve/lightcurve.py b/src/lightkurve/lightcurve.py
index d1337a73..f352f089 100644
--- a/src/lightkurve/lightcurve.py
+++ b/src/lightkurve/lightcurve.py
@@ -362,12 +362,13 @@ class LightCurve(QTimeSeries):
                 name not in self.__dict__
                 and not name.startswith("_")
                 and not self._new_attributes_relax
+                and name != 'meta'
             ):
                 warnings.warn(
                     (
                         "Lightkurve doesn't allow columns or meta values to be created via a new attribute name."
                         "A new attribute is created. It will not be carried over when the object is copied."
-                        " - see https://docs.lightkurve.org/api/lightkurve.lightcurve.LightCurve.html"
+                        " - see https://docs.lightkurve.org/reference/api/lightkurve.LightCurve.html"
                     ),
                     UserWarning,
                     stacklevel=2,
diff --git a/src/lightkurve/targetpixelfile.py b/src/lightkurve/targetpixelfile.py
index c73e6d3f..8ea983c4 100644
--- a/src/lightkurve/targetpixelfile.py
+++ b/src/lightkurve/targetpixelfile.py
@@ -66,7 +66,11 @@ class HduToMetaMapping(collections.abc.Mapping):
     """Provides a read-only view of HDU header in `astropy.timeseries.TimeSeries.meta` format"""
 
     def __init__(self, hdu):
-        self._dict = {}
+        # use OrderedDict rather than simple dict for 2 reasons:
+        # 1. more friendly __repr__ and __str__
+        # 2. make the behavior between a TPF and a LC is more consistent.
+        #    (LightCurve.meta is an OrderedDict)
+        self._dict = collections.OrderedDict()
         self._dict.update(hdu.header)
 
     def __getitem__(self, key):
@@ -78,6 +82,12 @@ class HduToMetaMapping(collections.abc.Mapping):
     def __iter__(self):
         return iter(self._dict)
 
+    def __repr__(self):
+        return self._dict.__repr__()
+
+    def __str__(self):
+        return self._dict.__str__()
+
 
 class TargetPixelFile(object):
     """Abstract class representing FITS files which contain time series imaging data.


Test patch:
diff --git a/tests/test_lightcurve.py b/tests/test_lightcurve.py
index ad2260a4..9fbdea5a 100644
--- a/tests/test_lightcurve.py
+++ b/tests/test_lightcurve.py
@@ -1362,6 +1362,30 @@ def test_attr_access_meta():
     assert lc.keycase == "value lower"  # the meta entry with exact case is retrieved
 
 
+@pytest.mark.parametrize(
+    "lc",
+    [
+        LightCurve(time=[1, 2, 3], flux=[4, 5, 6], meta={'SECTOR': 5}),
+        LightCurve(time=[1, 2, 3], flux=[4, 5, 6]),
+    ],
+)
+def test_meta_assignment(lc):
+    """Test edge cases in trying to assign meta (#1046)"""
+
+    # ensure lc.meta assignment does not emit any warnings.
+    meta_new = {'TSTART': 123456789.0}
+    with pytest.warns(None) as record:
+        lc.meta = meta_new
+
+    if (len(record) > 0):
+        pytest.fail(f"{len(record)} unexpected warning: {record[0]}")
+
+    # for the case existing meta is not empty
+    # ensure the assignment overwrites it
+    # (rather than just copying the values over to the existing one)
+    assert lc.meta == meta_new
+
+
 def test_attr_access_others():
     """Test accessing attributes, misc. boundary cases"""
     u_e_s = u.electron / u.second
diff --git a/tests/test_targetpixelfile.py b/tests/test_targetpixelfile.py
index f7e5df0a..cfd22fa0 100644
--- a/tests/test_targetpixelfile.py
+++ b/tests/test_targetpixelfile.py
@@ -1,3 +1,4 @@
+import collections
 import os
 import tempfile
 import warnings
@@ -842,6 +843,11 @@ def test_tpf_meta():
         tpf.meta["CHANNEL"] = 44
     with pytest.raises(TypeError):
         tpf.meta["KEY-NEW"] = 44
+    # needed for current internal HduToMetaMapping-based meta, ensuring it has
+    # a friendly __repr__() and __str__()
+    expected = collections.OrderedDict(tpf.meta).__repr__()
+    assert tpf.meta.__repr__() == expected
+    assert tpf.meta.__str__() == expected
 
 
 def test_estimate_background():