Repository: omni-us/jsonargparse

Issue:
`MappingProxyType` not accepted as `Mapping`
## 🐛 Bug report

Using a [`MappingProxyType`](https://docs.python.org/3/library/types.html#types.MappingProxyType) as a read only default value for a `Mapping` parameter fails with

```
Expected a <class 'collections.abc.Mapping'>. Got value: {'parameter': 1}
```

### To reproduce

```python
# /// script
# dependencies = [
#   "jsonargparse[ruyaml,signatures]"
# ]
# ///


from collections.abc import Mapping
from types import MappingProxyType
from typing import Any

from jsonargparse import CLI

DEFAULTS = MappingProxyType({"parameter": 1})


def func(arg: Mapping[str, Any] = DEFAULTS):
    print(arg)


if __name__ == "__main__":
    assert isinstance(DEFAULTS, Mapping)

    CLI([func])
```

Running this script fails with
```command
$> python mwe.py                                                                   
usage: mwe.py [-h] [--config CONFIG] [--print_config[=flags]] [--arg ARG]
error: Validation failed: Parser key "arg":
  Expected a <class 'collections.abc.Mapping'>. Got value: {'parameter': 1}
```

### Expected behavior

Running as normal, which jsonargparse does once the `MappingProxyType` is replaced by a normal `dict`.

### Environment

- jsonargparse version: 4.31.0
- Python version: 3.10.12
- How jsonargparse was installed: in a fresh isolated environment (`hatch run <script name>`)
- OS: Ubuntu 22.04.4 LTS


Discussion:
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=540) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=540&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=540&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=540&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=540&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=540&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=540)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`9feda6f`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/9feda6f8951b17a7abc32b74d39dcb2f238afecd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`2bf0ff4`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/2bf0ff4281e754cd7baca03c206b5fd6613c083e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

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


```diff
@@            Coverage Diff            @@
##              main      #540   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines         6414      6421    +7     
=========================================
+ Hits          6414      6421    +7     
```

| [Flag](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [py3.10](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `85.68% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.10_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.10_pydantic1](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.40% <44.44%> (-0.03%)` | :arrow_down: |
| [py3.10_pydantic2](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.18% <44.44%> (-0.03%)` | :arrow_down: |
| [py3.10_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.70% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `85.66% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.11_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.70% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `85.80% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.12_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.70% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `86.14% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.7_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.21% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.26% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `86.33% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.8_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.37% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.40% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `86.21% <100.00%> (+0.01%)` | :arrow_up: |
| [py3.9_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.28% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.29% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/540?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
: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=omni-us).

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=540) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=540&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=540&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=540&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=540&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=540&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=540)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 99a4845..dcb418c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,6 +12,15 @@ The semantic versioning only considers the public API as described in
 paths are considered internals and can change in minor and patch releases.
 
 
+v4.32.0 (2024-07-??)
+--------------------
+
+Added
+^^^^^
+- Support for ``MappingProxyType`` as a type and as default for mapping types
+  (`#540 <https://github.com/omni-us/jsonargparse/pull/540>`__).
+
+
 v4.31.0 (2024-06-27)
 --------------------
 
diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst
index cc56c13..c9e7699 100644
--- a/DOCUMENTATION.rst
+++ b/DOCUMENTATION.rst
@@ -400,8 +400,9 @@ Some notes about this support are:
   :ref:`restricted-strings` and paths and URLs as explained in sections
   :ref:`parsing-paths` and :ref:`parsing-urls`.
 
-- ``Dict``, ``Mapping``, ``MutableMapping``, and ``TypedDict`` are supported but
-  only with ``str`` or ``int`` keys. For more details see :ref:`dict-items`.
+- ``Dict``, ``Mapping``, ``MutableMapping``, ``MappingProxyType``, and
+  ``TypedDict`` are supported but only with ``str`` or ``int`` keys. For more
+  details see :ref:`dict-items`.
 
 - ``Tuple``, ``Set`` and ``MutableSet`` are supported even though they can't be
   represented in json distinguishable from a list. Each ``Tuple`` element
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index 5d25709..6016382 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -10,7 +10,7 @@ from contextvars import ContextVar
 from copy import deepcopy
 from enum import Enum
 from functools import partial
-from types import FunctionType
+from types import FunctionType, MappingProxyType
 from typing import (
     Any,
     Callable,
@@ -145,7 +145,7 @@ sequence_origin_types = {
     abc.Sequence,
     abc.MutableSequence,
 }
-mapping_origin_types = {Dict, dict, Mapping, MutableMapping, abc.Mapping, abc.MutableMapping}
+mapping_origin_types = {Dict, dict, Mapping, MappingProxyType, MutableMapping, abc.Mapping, abc.MutableMapping}
 callable_origin_types = {Callable, abc.Callable}
 
 literal_types = {Literal}
@@ -872,6 +872,8 @@ def adapt_typehints(
                 val = {**prev_val, val.key: val.val}
             else:
                 val = {val.key: val.val}
+        elif isinstance(val, MappingProxyType):
+            val = dict(val)
         elif not isinstance(val, dict):
             raise_unexpected_value(f"Expected a {typehint_origin}", val)
         if subtypehints is not None:
@@ -902,6 +904,8 @@ def adapt_typehints(
                 raise_unexpected_value(f"Unexpected keys: {extra_keys}", val)
             for k, v in val.items():
                 val[k] = adapt_typehints(v, typehint.__annotations__[k], **adapt_kwargs)
+        if typehint_origin == MappingProxyType and not serialize:
+            val = MappingProxyType(val)
 
     # Callable
     elif typehint_origin in callable_origin_types or typehint in callable_origin_types:


Test patch:
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index 4210083..dd107a3 100644
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -8,6 +8,7 @@ import uuid
 from calendar import Calendar, TextCalendar
 from enum import Enum
 from pathlib import Path
+from types import MappingProxyType
 from typing import (
     Any,
     Callable,
@@ -545,6 +546,23 @@ def test_typeddict_with_args_ntotal(parser):
     ctx.match("Expected a <class 'dict'>")
 
 
+def test_mapping_proxy_type(parser):
+    parser.add_argument("--mapping", type=MappingProxyType)
+    cfg = parser.parse_args(['--mapping={"x":1}'])
+    assert isinstance(cfg.mapping, MappingProxyType)
+    assert cfg.mapping == {"x": 1}
+    assert parser.dump(cfg, format="json") == '{"mapping":{"x":1}}'
+
+
+def test_mapping_default_mapping_proxy_type(parser):
+    mapping_proxy = MappingProxyType({"x": 1})
+    parser.add_argument("--mapping", type=Mapping[str, int], default=mapping_proxy)
+    cfg = parser.parse_args([])
+    assert isinstance(cfg.mapping, Mapping)
+    assert mapping_proxy == cfg.mapping
+    assert parser.dump(cfg, format="json") == '{"mapping":{"x":1}}'
+
+
 # union tests