Repository: pydicom/pydicom

Issue:
Request: read file position and value length of a tag without also reading the actual value
**Is your feature request related to a problem? Please describe.**
I am unable to read the file position and value length of a tag without also reading the actual value. See an example code at https://stackoverflow.com/q/76897860/880783.

**Describe the solution you'd like**
I am looking for a way to access `_dict[tag].value_tell` and `_dict[tag].length` through a public API - namely, without acessing/decoding the data. Extending `get_item` to allow that would be one solution, as would be adding another getter method.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pydicom/pydicom/pull/1990?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`76c565d`)](https://app.codecov.io/gh/pydicom/pydicom/commit/76c565d5582cb44eb975e020f25b9f306b4177f6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) 97.93% compared to head [(`68524d0`)](https://app.codecov.io/gh/pydicom/pydicom/pull/1990?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) 97.93%.


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #1990   +/-   ##
=======================================
  Coverage   97.93%   97.93%           
=======================================
  Files          63       63           
  Lines       10816    10816           
=======================================
  Hits        10593    10593           
  Misses        223      223           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pydicom/pydicom/pull/1990?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).


Patch:
diff --git a/doc/release_notes/v3.0.0.rst b/doc/release_notes/v3.0.0.rst
index 99a1a6f19..6dc971e70 100644
--- a/doc/release_notes/v3.0.0.rst
+++ b/doc/release_notes/v3.0.0.rst
@@ -134,6 +134,9 @@ Enhancements
   syntaxes to :class:`~pydicom.uid.UID` via the :meth:`~pydicom.uid.UID.set_private_encoding`
   method
 * Warning messages are also sent to the pydicom logger (:issue:`1529`)
+* Added the `keep_deferred` keyword argument to :meth:`Dataset.get_item()
+  <pydicom.dataset.Dataset.get_item>` to allow accessing the file offset and
+  element length without having to read the element value. (:issue:`1873`)
 
 
 Fixes
diff --git a/src/pydicom/dataset.py b/src/pydicom/dataset.py
index feb7ed942..d99f0694f 100644
--- a/src/pydicom/dataset.py
+++ b/src/pydicom/dataset.py
@@ -1197,15 +1197,18 @@ class Dataset:
         return self.__getitem__(block.get_tag(element_offset))
 
     @overload
-    def get_item(self, key: slice) -> "Dataset":
+    def get_item(self, key: slice, *, keep_deferred: bool = ...) -> "Dataset":
         pass  # pragma: no cover
 
     @overload
-    def get_item(self, key: TagType) -> DataElement:
+    def get_item(self, key: TagType, *, keep_deferred: bool = ...) -> DataElement:
         pass  # pragma: no cover
 
     def get_item(
-        self, key: "slice | TagType"
+        self,
+        key: "slice | TagType",
+        *,
+        keep_deferred: bool = False,
     ) -> "Dataset | DataElement | RawDataElement | None":
         """Return the raw data element if possible.
 
@@ -1213,6 +1216,10 @@ class Dataset:
         own value. Note if the data element is a deferred-read element,
         then it is read and converted before being returned.
 
+        .. versionchanged: 3.0
+
+            Added the `keep_deferred` keyword argument.
+
         Parameters
         ----------
         key
@@ -1220,10 +1227,14 @@ class Dataset:
             :func:`~pydicom.tag.Tag` such as ``[0x0010, 0x0010]``,
             ``(0x10, 0x10)``, ``0x00100010``, etc. May also be a :class:`slice`
             made up of DICOM tags.
+        keep_deferred : bool, optional
+            If ``True`` then when returning :class:`~pydicom.dataelem.RawDataElement`
+            do not perform the deferred read of the element's value (accessing
+            the value will return ``None`` instead). Default ``False``.
 
         Returns
         -------
-        dataelem.DataElement
+        dataelem.DataElement | dataelem.RawDataElement
             The corresponding element.
         """
         if isinstance(key, slice):
@@ -1231,7 +1242,11 @@ class Dataset:
 
         elem = self._dict.get(Tag(key))
         # If a deferred read, return using __getitem__ to read and convert it
-        if isinstance(elem, RawDataElement) and elem.value is None:
+        if (
+            isinstance(elem, RawDataElement)
+            and not keep_deferred
+            and elem.value is None
+        ):
             return self[key]
 
         return elem


Test patch:
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index 8a26eb350..4f91285a2 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -1024,6 +1024,18 @@ class TestDataset:
         # Slice all items - should return original dataset
         assert ds == ds.get_item(slice(None, None))
 
+    def test_getitem_deferred(self):
+        """Test get_item(..., keep_deferred)"""
+        test_file = get_testdata_file("MR_small.dcm")
+        ds = dcmread(test_file, force=True, defer_size="0.8 kB")
+        elem = ds.get_item("PixelData", keep_deferred=True)
+        assert isinstance(elem, RawDataElement)
+        assert elem.value is None
+
+        elem = ds.get_item("PixelData", keep_deferred=False)
+        assert isinstance(elem, DataElement)
+        assert elem.value is not None
+
     def test_get_private_item(self):
         ds = Dataset()
         ds.add_new(0x00080005, "CS", "ISO_IR 100")