Repository: jdrouet/mrml (language: rust)

Issue:
Python 3.13 issue
Heyya, I'm trying to update my site to Python 3.13.1 from 3.12 and the mrml Python package is giving me an error on install:

```
            "__Py_FalseStruct", referenced from:
                pyo3_ffi::boolobject::Py_False::h6e9f567c960b65c2 in mrml.6n2q40yqp9ohjfvdd900z9xpa.rcgu.o
            "__Py_NoneStruct", referenced from:
                pyo3_ffi::object::Py_None::h859c5a506aff8790 in mrml.33p6zkgdavws1digwyigb597b.rcgu.o
                pyo3_ffi::object::Py_None::hed4c9bb7100ba418 in libpyo3-074370a080eda9c5.rlib[8](pyo3-074370a080eda9c5.pyo3.c60c8efc02a4e87-cgu.05.rcgu.o)
            "__Py_NotImplementedStruct", referenced from:
                pyo3_ffi::object::Py_NotImplemented::h8bd06342b95028c5 in mrml.33p6zkgdavws1digwyigb597b.rcgu.o
            "__Py_TrueStruct", referenced from:
                pyo3_ffi::boolobject::Py_True::h30b83a93710f16f2 in mrml.6n2q40yqp9ohjfvdd900z9xpa.rcgu.o
                pyo3_ffi::boolobject::Py_True::h32f7809a6a402da3 in libpyo3-074370a080eda9c5.rlib[15](pyo3-074370a080eda9c5.pyo3.c60c8efc02a4e87-cgu.12.rcgu.o)
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)


warning: `mrml-python` (lib) generated 5 warnings
error: could not compile `mrml-python` (lib) due to 1 previous error; 5 warnings emitted
```
Maybe py03 needs to be updated?

Thank you!


Discussion / hints:
This is on an M1 Mac, with the latest OS.

Fix patch:
diff --git a/.github/workflows/mrml-python-main.yml b/.github/workflows/mrml-python-main.yml
index 315e7670..a1badbee 100644
--- a/.github/workflows/mrml-python-main.yml
+++ b/.github/workflows/mrml-python-main.yml
@@ -5,18 +5,18 @@ on:
     branches:
       - main
     paths:
-      - '.github/workflows/mrml-python-main.yml'
-      - 'Cargo.lock'
-      - 'Cargo.toml'
-      - 'packages/mrml-core/**'
-      - 'packages/mrml-python/**'
+      - ".github/workflows/mrml-python-main.yml"
+      - "Cargo.lock"
+      - "Cargo.toml"
+      - "packages/mrml-core/**"
+      - "packages/mrml-python/**"
   pull_request:
     paths:
-      - '.github/workflows/mrml-python-main.yml'
-      - 'Cargo.lock'
-      - 'Cargo.toml'
-      - 'packages/mrml-core/**'
-      - 'packages/mrml-python/**'
+      - ".github/workflows/mrml-python-main.yml"
+      - "Cargo.lock"
+      - "Cargo.toml"
+      - "packages/mrml-core/**"
+      - "packages/mrml-python/**"
   workflow_dispatch:
 
 permissions:
@@ -26,16 +26,30 @@ jobs:
   testing:
     runs-on: ubuntu-latest
 
+    strategy:
+      matrix:
+        python-version: ["3.10", "3.13"]
+
     concurrency:
-      group: ${{ github.ref }}-mrml-python
+      group: ${{ github.ref }}-mrml-python-${{ matrix.python-version }}
       cancel-in-progress: true
 
     steps:
       - uses: actions/checkout@v4
 
-      - uses: actions/setup-python@v5
+      - name: setup python
+        if: matrix.python-version != '3.13'
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      # Use deadsnakes until https://github.com/actions/setup-python/issues/771 is closed
+      - name: setup python
+        if: matrix.python-version == '3.13'
+        uses: deadsnakes/action@v3.2.0
         with:
-          python-version: '3.10'
+          python-version: ${{ matrix.python-version }}
+          nogil: true
 
       - name: install cargo toolchain
         uses: actions-rs/toolchain@v1
@@ -43,15 +57,12 @@ jobs:
           toolchain: stable
           profile: minimal
 
-      - name: install maturin
-        run: cargo install maturin --locked
-      
       - name: init python venv
         run: |
-          python3 -m pip install --upgrade pip
+          pip install --upgrade pip
           python3 -m venv env
           source env/bin/activate
-          python3 -m pip install pytest
+          python3 -m pip install pytest maturin
           maturin develop
           python3 -m pytest
         working-directory: packages/mrml-python
diff --git a/.github/workflows/mrml-python-release.yml b/.github/workflows/mrml-python-release.yml
index 32f64be2..71128871 100644
--- a/.github/workflows/mrml-python-release.yml
+++ b/.github/workflows/mrml-python-release.yml
@@ -37,9 +37,12 @@ jobs:
             manylinux: "auto"
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v5
+      # Use deadsnakes until https://github.com/actions/setup-python/issues/771 is closed
+      - name: setup python
+        uses: deadsnakes/action@v3.2.0
         with:
-          python-version: "3.10"
+          python-version: "3.13"
+          nogil: true"
       - name: build wheels
         uses: PyO3/maturin-action@v1
         with:
@@ -61,9 +64,12 @@ jobs:
         target: [x64, x86]
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v5
+      # Use deadsnakes until https://github.com/actions/setup-python/issues/771 is closed
+      - name: setup python
+        uses: deadsnakes/action@v3.2.0
         with:
-          python-version: "3.10"
+          python-version: "3.13"
+          nogil: true"
           architecture: ${{ matrix.target }}
       - name: build wheels
         uses: PyO3/maturin-action@v1
@@ -85,9 +91,12 @@ jobs:
         target: [x86_64, aarch64]
     steps:
       - uses: actions/checkout@v4
-      - uses: actions/setup-python@v5
+      # Use deadsnakes until https://github.com/actions/setup-python/issues/771 is closed
+      - name: setup python
+        uses: deadsnakes/action@v3.2.0
         with:
-          python-version: "3.10"
+          python-version: "3.13"
+          nogil: true"
       - name: build wheels
         uses: PyO3/maturin-action@v1
         with:
diff --git a/Cargo.lock b/Cargo.lock
index bc0a5b4d..6402621a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -123,7 +123,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -134,7 +134,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -176,7 +176,7 @@ dependencies = [
  "serde_json",
  "serde_path_to_error",
  "serde_urlencoded",
- "sync_wrapper 1.0.2",
+ "sync_wrapper",
  "tokio",
  "tower",
  "tower-layer",
@@ -199,7 +199,7 @@ dependencies = [
  "mime",
  "pin-project-lite",
  "rustversion",
- "sync_wrapper 1.0.2",
+ "sync_wrapper",
  "tower-layer",
  "tower-service",
  "tracing",
@@ -213,7 +213,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -242,7 +242,7 @@ dependencies = [
  "miniz_oxide",
  "object",
  "rustc-demangle",
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -259,9 +259,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
 
 [[package]]
 name = "bstr"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
+checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
 dependencies = [
  "memchr",
  "regex-automata 0.4.9",
@@ -294,9 +294,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.2.3"
+version = "1.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
+checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
 dependencies = [
  "shlex",
 ]
@@ -371,7 +371,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -388,12 +388,12 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
 name = "colored"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
 dependencies = [
  "lazy_static",
- "windows-sys 0.48.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -403,19 +403,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d"
 dependencies = [
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
 name = "console"
-version = "0.15.8"
+version = "0.15.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
 dependencies = [
  "encode_unicode",
- "lazy_static",
  "libc",
- "windows-sys 0.52.0",
+ "once_cell",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -504,9 +504,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-deque"
-version = "0.8.5"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
 dependencies = [
  "crossbeam-epoch",
  "crossbeam-utils",
@@ -523,9 +523,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
 [[package]]
 name = "crunchy"
@@ -570,7 +570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
 dependencies = [
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -587,7 +587,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -613,9 +613,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
 
 [[package]]
 name = "encode_unicode"
-version = "0.3.6"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
 
 [[package]]
 name = "enum-as-inner"
@@ -626,7 +626,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -638,14 +638,14 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
 name = "env_filter"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
 dependencies = [
  "log",
  "regex",
@@ -653,9 +653,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.11.5"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
+checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
 dependencies = [
  "anstream",
  "anstyle",
@@ -894,9 +894,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
 name = "hyper"
-version = "1.5.1"
+version = "1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
+checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -915,9 +915,9 @@ dependencies = [
 
 [[package]]
 name = "hyper-rustls"
-version = "0.27.3"
+version = "0.27.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
+checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
 dependencies = [
  "futures-util",
  "http",
@@ -1065,7 +1065,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -1171,9 +1171,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.167"
+version = "0.2.169"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
 
 [[package]]
 name = "lightningcss"
@@ -1280,9 +1280,9 @@ dependencies = [
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.0"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
 dependencies = [
  "adler2",
 ]
@@ -1403,9 +1403,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.36.5"
+version = "0.36.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
 dependencies = [
  "memchr",
 ]
@@ -1464,7 +1464,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -1525,7 +1525,7 @@ dependencies = [
  "phf_shared",
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -1609,9 +1609,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
+checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15"
 dependencies = [
  "cfg-if",
  "indoc",
@@ -1627,9 +1627,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
+checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b"
 dependencies = [
  "once_cell",
  "target-lexicon",
@@ -1637,9 +1637,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
+checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -1647,27 +1647,27 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
+checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.22.6"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
+checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d"
 dependencies = [
  "heck",
  "proc-macro2",
  "pyo3-build-config",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -1683,7 +1683,7 @@ dependencies = [
  "rustc-hash",
  "rustls",
  "socket2",
- "thiserror 2.0.5",
+ "thiserror 2.0.9",
  "tokio",
  "tracing",
 ]
@@ -1702,7 +1702,7 @@ dependencies = [
  "rustls",
  "rustls-pki-types",
  "slab",
- "thiserror 2.0.5",
+ "thiserror 2.0.9",
  "tinyvec",
  "tracing",
  "web-time",
@@ -1710,9 +1710,9 @@ dependencies = [
 
 [[package]]
 name = "quinn-udp"
-version = "0.5.7"
+version = "0.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da"
+checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
 dependencies = [
  "cfg_aliases",
  "libc",
@@ -1783,9 +1783,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.7"
+version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
 dependencies = [
  "bitflags",
 ]
@@ -1865,7 +1865,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_urlencoded",
- "sync_wrapper 1.0.2",
+ "sync_wrapper",
  "tokio",
  "tokio-rustls",
  "tower-service",
@@ -1906,9 +1906,9 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
 
 [[package]]
 name = "rustls"
-version = "0.23.19"
+version = "0.23.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
+checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
 dependencies = [
  "log",
  "once_cell",
@@ -1930,9 +1930,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-pki-types"
-version = "1.10.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
 dependencies = [
  "web-time",
 ]
@@ -1983,9 +1983,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.215"
+version = "1.0.216"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
+checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
 dependencies = [
  "serde_derive",
 ]
@@ -2003,13 +2003,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.215"
+version = "1.0.216"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
+checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2020,14 +2020,14 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.133"
+version = "1.0.134"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
+checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
 dependencies = [
  "itoa",
  "memchr",
@@ -2169,21 +2169,15 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.90"
+version = "2.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
+checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
-[[package]]
-name = "sync_wrapper"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
-
 [[package]]
 name = "sync_wrapper"
 version = "1.0.2"
@@ -2201,7 +2195,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2221,11 +2215,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.5"
+version = "2.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643caef17e3128658ff44d85923ef2d28af81bb71e0d67bbfe1d76f19a73e053"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
 dependencies = [
- "thiserror-impl 2.0.5",
+ "thiserror-impl 2.0.9",
 ]
 
 [[package]]
@@ -2236,18 +2230,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.5"
+version = "2.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "995d0bbc9995d1f19d28b7215a9352b0fc3cd3a2d2ec95c2cadc485cdedbcdde"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2282,9 +2276,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.8.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -2321,7 +2315,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2373,14 +2367,14 @@ dependencies = [
 
 [[package]]
 name = "tower"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
 dependencies = [
  "futures-core",
  "futures-util",
  "pin-project-lite",
- "sync_wrapper 0.1.2",
+ "sync_wrapper",
  "tokio",
  "tower-layer",
  "tower-service",
@@ -2419,7 +2413,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2489,7 +2483,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde_derive_internals",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2619,7 +2613,7 @@ dependencies = [
  "log",
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
  "wasm-bindgen-shared",
 ]
 
@@ -2654,7 +2648,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -2687,7 +2681,7 @@ checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -2758,7 +2752,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
 dependencies = [
  "windows-result",
  "windows-strings",
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -2767,7 +2761,7 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
 dependencies = [
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -2777,16 +2771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
 dependencies = [
  "windows-result",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets",
 ]
 
 [[package]]
@@ -2795,7 +2780,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -2804,22 +2789,7 @@ version = "0.59.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
 dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
+ "windows-targets",
 ]
 
 [[package]]
@@ -2828,46 +2798,28 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
 ]
 
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
 [[package]]
 name = "windows_i686_gnu"
 version = "0.52.6"
@@ -2880,48 +2832,24 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
 [[package]]
 name = "windows_i686_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.52.6"
@@ -2960,7 +2888,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
  "synstructure",
 ]
 
@@ -2982,7 +2910,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
 
 [[package]]
@@ -3002,7 +2930,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
  "synstructure",
 ]
 
@@ -3031,5 +2959,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.90",
+ "syn 2.0.91",
 ]
diff --git a/packages/mrml-python/Cargo.toml b/packages/mrml-python/Cargo.toml
index 6f7d1fb9..e6f4a417 100644
--- a/packages/mrml-python/Cargo.toml
+++ b/packages/mrml-python/Cargo.toml
@@ -20,4 +20,4 @@ mrml = { version = "4.0.1", path = "../mrml-core", features = [
     "http-loader-ureq",
     "local-loader",
 ] }
-pyo3 = { version = "0.22", features = ["extension-module"] }
+pyo3 = { version = "0.23", features = ["extension-module"] }
diff --git a/packages/mrml-python/mrml.pyi b/packages/mrml-python/mrml.pyi
index e68440f9..13d71b5b 100644
--- a/packages/mrml-python/mrml.pyi
+++ b/packages/mrml-python/mrml.pyi
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Optional, Set, Union, List
+from typing import Any, Dict, List, Optional, Set, Union
 
 class NoopIncludeLoaderOptions:
     """No-operation loader options class, which requires no specific configuration."""
@@ -67,9 +67,15 @@ class RenderOptions:
     def __init__(
         self,
         disable_comments: bool = False,
-        social_icon_origin: Optional[str] = None,
-        fonts: Optional[Dict[str, str]] = None,
+        social_icon_origin: str | None = None,
+        fonts: Dict[str, str] | None = None,
     ) -> None: ...
+    @property
+    def disable_comments(self) -> bool: ...
+    @property
+    def social_icon_origin(self) -> str | None: ...
+    @property
+    def fonts(self) -> Dict[str, str] | None: ...
 
 class Warning:
     @property
@@ -86,7 +92,8 @@ class Output:
     @property
     def content(self) -> str: ...
     @property
-    def warnings(self) -> List[Warning]
+    def warnings(self) -> List[Warning]: ...
+    def __init__(self) -> None: ...
 
 def to_html(
     input: str,
diff --git a/packages/mrml-python/pyproject.toml b/packages/mrml-python/pyproject.toml
index 2af64b0a..ed99a869 100644
--- a/packages/mrml-python/pyproject.toml
+++ b/packages/mrml-python/pyproject.toml
@@ -4,13 +4,14 @@ build-backend = "maturin"
 
 [project]
 name = "mrml"
+version = "0.1.15"
 description = "A Python wrapper for MRML (Rust port of MJML)."
 readme = "readme.md"
 requires-python = ">=3.7"
 classifiers = [
-  "Programming Language :: Rust",
-  "Programming Language :: Python :: Implementation :: CPython",
-  "Programming Language :: Python :: Implementation :: PyPy",
+    "Programming Language :: Rust",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
 ]
 
 [project.urls]
diff --git a/packages/mrml-python/src/lib.rs b/packages/mrml-python/src/lib.rs
index 094f48d7..f74a84ad 100644
--- a/packages/mrml-python/src/lib.rs
+++ b/packages/mrml-python/src/lib.rs
@@ -10,19 +10,19 @@ use mrml::prelude::parser::noop_loader::NoopIncludeLoader;
 use pyo3::exceptions::PyIOError;
 use pyo3::prelude::*;
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct NoopIncludeLoaderOptions;
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct MemoryIncludeLoaderOptions(HashMap<String, String>);
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct LocalIncludeLoaderOptions(PathBuf);
 
-#[pyclass(eq, eq_int)]
+#[pyclass(frozen, eq, eq_int)]
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum HttpIncludeLoaderOptionsMode {
     Allow,
@@ -35,15 +35,15 @@ impl Default for HttpIncludeLoaderOptionsMode {
     }
 }
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct HttpIncludeLoaderOptions {
     mode: HttpIncludeLoaderOptionsMode,
     list: HashSet<String>,
 }
 
-// #[pyclass]
-#[derive(FromPyObject, Clone, Debug)]
+#[pyclass(frozen)]
+#[derive(Clone, Debug)]
 pub enum ParserIncludeLoaderOptions {
     Noop(NoopIncludeLoaderOptions),
     Memory(MemoryIncludeLoaderOptions),
@@ -79,17 +79,6 @@ impl ParserIncludeLoaderOptions {
     }
 }
 
-impl IntoPy<PyObject> for ParserIncludeLoaderOptions {
-    fn into_py(self, py: Python<'_>) -> PyObject {
-        match self {
-            Self::Noop(inner) => inner.into_py(py),
-            Self::Memory(inner) => inner.into_py(py),
-            Self::Local(inner) => inner.into_py(py),
-            Self::Http(inner) => inner.into_py(py),
-        }
-    }
-}
-
 #[pyfunction]
 #[pyo3(name = "noop_loader")]
 pub fn noop_loader() -> ParserIncludeLoaderOptions {
@@ -130,10 +119,10 @@ pub fn http_loader(
     })
 }
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct ParserOptions {
-    #[pyo3(get, set)]
+    #[pyo3(get)]
     pub include_loader: ParserIncludeLoaderOptions,
 }
 
@@ -155,22 +144,31 @@ impl From<ParserOptions> for mrml::prelude::parser::ParserOptions {
     }
 }
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct RenderOptions {
-    #[pyo3(get, set)]
+    #[pyo3(get)]
     pub disable_comments: bool,
-    #[pyo3(get, set)]
+    #[pyo3(get)]
     pub social_icon_origin: Option<String>,
-    #[pyo3(get, set)]
+    #[pyo3(get)]
     pub fonts: Option<HashMap<String, String>>,
 }
 
 #[pymethods]
 impl RenderOptions {
     #[new]
-    pub fn new() -> Self {
-        Self::default()
+    #[pyo3(signature = (disable_comments=false, social_icon_origin=None, fonts=None))]
+    pub fn new(
+        disable_comments: bool,
+        social_icon_origin: Option<String>,
+        fonts: Option<HashMap<String, String>>,
+    ) -> Self {
+        Self {
+            disable_comments,
+            social_icon_origin,
+            fonts,
+        }
     }
 }
 
@@ -193,10 +191,10 @@ impl From<RenderOptions> for mrml::prelude::render::RenderOptions {
     }
 }
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct Warning {
-    #[pyo3(get, set)]
+    #[pyo3(get)]
     pub origin: Option<String>,
     #[pyo3(get)]
     pub kind: &'static str,
@@ -226,7 +224,7 @@ impl From<mrml::prelude::parser::Warning> for Warning {
     }
 }
 
-#[pyclass]
+#[pyclass(frozen)]
 #[derive(Clone, Debug, Default)]
 pub struct Output {
     #[pyo3(get)]
@@ -275,5 +273,6 @@ fn register(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
     m.add_function(wrap_pyfunction!(local_loader, m)?)?;
     m.add_function(wrap_pyfunction!(http_loader, m)?)?;
     m.add_function(wrap_pyfunction!(memory_loader, m)?)?;
+    m.gil_used(false)?;
     Ok(())
 }


Test patch:
diff --git a/packages/mrml-python/tests/test_thread_safety.py b/packages/mrml-python/tests/test_thread_safety.py
new file mode 100644
index 00000000..4c8bcc72
--- /dev/null
+++ b/packages/mrml-python/tests/test_thread_safety.py
@@ -0,0 +1,149 @@
+import concurrent.futures
+
+import mrml
+import pytest
+
+
+def test_concurrent_simple_template():
+    def worker():
+        result = mrml.to_html("<mjml></mjml>")
+        assert result.content.startswith("<!doctype html>")
+        assert len(result.warnings) == 0
+        return True
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+        futures = [executor.submit(worker) for _ in range(100)]
+        results = [f.result() for f in futures]
+        assert all(results)
+
+
+def test_concurrent_memory_loader():
+    parser_options = mrml.ParserOptions(
+        include_loader=mrml.memory_loader(
+            {
+                "hello-world.mjml": "<mj-text>Hello World!</mj-text>",
+            }
+        )
+    )
+
+    def worker():
+        result = mrml.to_html(
+            '<mjml><mj-body><mj-include path="hello-world.mjml" /></mj-body></mjml>',
+            parser_options=parser_options,
+        )
+        assert result.content.startswith("<!doctype html>")
+        assert len(result.warnings) == 0
+        return True
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+        futures = [executor.submit(worker) for _ in range(100)]
+        results = [f.result() for f in futures]
+        assert all(results)
+
+
+def test_concurrent_local_loader():
+    parser_options = mrml.ParserOptions(
+        include_loader=mrml.local_loader("./resources/partials")
+    )
+
+    def worker():
+        result = mrml.to_html(
+            '<mjml><mj-body><mj-include path="file:///hello-world.mjml" /></mj-body></mjml>',
+            parser_options=parser_options,
+        )
+        assert result.content.startswith("<!doctype html>")
+        assert len(result.warnings) == 0
+        return True
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+        futures = [executor.submit(worker) for _ in range(100)]
+        results = [f.result() for f in futures]
+        assert all(results)
+
+
+def test_concurrent_http_loader():
+    http_loader = mrml.http_loader(
+        mode=mrml.HttpIncludeLoaderOptionsMode.Allow,
+        list=set(["https://gist.githubusercontent.com"]),
+    )
+    parser_options = mrml.ParserOptions(include_loader=http_loader)
+
+    def worker():
+        result = mrml.to_html(
+            """<mjml>
+      <mj-body>
+        <mj-include
+          path="https://gist.githubusercontent.com/jdrouet/b0ac80fa08a3e7262bd4c94fc8865a87/raw/ec8771f4804a6c38427ed2a9f5937e11ec2b8c27/hello-world.mjml"
+        />
+      </mj-body>
+    </mjml>""",
+            parser_options=parser_options,
+        )
+        assert result.content.startswith("<!doctype html>")
+        assert len(result.warnings) == 0
+        return True
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+        futures = [
+            executor.submit(worker) for _ in range(20)
+        ]  # Fewer iterations for HTTP test
+        results = [f.result() for f in futures]
+        assert all(results)
+
+
+def test_concurrent_mixed_operations():
+    """Test different MRML operations running concurrently"""
+    memory_parser_options = mrml.ParserOptions(
+        include_loader=mrml.memory_loader(
+            {
+                "hello-world.mjml": "<mj-text>Hello World!</mj-text>",
+            }
+        )
+    )
+
+    local_parser_options = mrml.ParserOptions(
+        include_loader=mrml.local_loader("./resources/partials")
+    )
+
+    def worker_simple():
+        result = mrml.to_html("<mjml></mjml>")
+        assert result.content.startswith("<!doctype html>")
+        return "simple"
+
+    def worker_memory():
+        result = mrml.to_html(
+            '<mjml><mj-body><mj-include path="hello-world.mjml" /></mj-body></mjml>',
+            parser_options=memory_parser_options,
+        )
+        assert result.content.startswith("<!doctype html>")
+        return "memory"
+
+    def worker_local():
+        result = mrml.to_html(
+            '<mjml><mj-body><mj-include path="file:///hello-world.mjml" /></mj-body></mjml>',
+            parser_options=local_parser_options,
+        )
+        assert result.content.startswith("<!doctype html>")
+        return "local"
+
+    workers = [worker_simple, worker_memory, worker_local]
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+        task_count = 100
+        futures = []
+        for _ in range(task_count):  # 30 total operations
+            for worker in workers:
+                futures.append(executor.submit(worker))
+
+        results = [f.result() for f in futures]
+        assert len(results) == task_count * len(workers)
+        assert all(r in ["simple", "memory", "local"] for r in results)
+
+
+def test_render_options_thread_safety():
+    """Test mutation throws AttributeError"""
+    render_options = mrml.RenderOptions(disable_comments=True)
+    assert render_options.disable_comments
+    assert render_options.social_icon_origin is None
+    with pytest.raises(AttributeError) as _:
+        render_options.disable_comments = False