diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 11d8699e4..97e352f51 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
os: [windows, ubuntu, macos]
- python: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+ python: ["3.7", "3.8", "3.9", "3.10", "3.11"]
platform: [x64, x86]
exclude:
- os: ubuntu
@@ -54,15 +54,17 @@ jobs:
run: |
pip install -v .
- - name: Set Python DLL path (non Windows)
+ - name: Set Python DLL path and PYTHONHOME (non Windows)
if: ${{ matrix.os != 'windows' }}
run: |
- python -m pythonnet.find_libpython --export >> $GITHUB_ENV
+ echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV
+ echo PYTHONHOME=$(python -c 'import sys; print(sys.prefix)') >> $GITHUB_ENV
- - name: Set Python DLL path (Windows)
+ - name: Set Python DLL path and PYTHONHOME (Windows)
if: ${{ matrix.os == 'windows' }}
run: |
- python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONNET_PYDLL=$(python -m find_libpython)"
+ Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONHOME=$(python -c 'import sys; print(sys.prefix)')"
- name: Embedding tests
run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/
diff --git a/.gitignore b/.gitignore
index 6159b1b14..7e94c38a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,10 @@
*.pdb
*.deps.json
+# Ignore package builds
+*.nupkg
+*.snupkg
+
### JetBrains ###
.idea/
diff --git a/AUTHORS.md b/AUTHORS.md
index 92f1a4a97..9edd75517 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -29,6 +29,7 @@
- Christoph Gohlke ([@cgohlke](https://github.com/cgohlke))
- Christopher Bremner ([@chrisjbremner](https://github.com/chrisjbremner))
- Christopher Pow ([@christopherpow](https://github.com/christopherpow))
+- Colton Sellers ([@C-SELLERS](https://github.com/C-SELLERS))
- Daniel Abrahamsson ([@danabr](https://github.com/danabr))
- Daniel Fernandez ([@fdanny](https://github.com/fdanny))
- Daniel Santana ([@dgsantana](https://github.com/dgsantana))
@@ -52,6 +53,7 @@
- Luke Stratman ([@lstratman](https://github.com/lstratman))
- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy))
- Matthias Dittrich ([@matthid](https://github.com/matthid))
+- Martin Molinero ([@Martin-Molinero](https://github.com/Martin-Molinero))
- Meinrad Recheis ([@henon](https://github.com/henon))
- Mohamed Koubaa ([@koubaa](https://github.com/koubaa))
- Patrick Stewart ([@patstew](https://github.com/patstew))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e706b866..83d72a3a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@ details about the cause of the failure
able to access members that are part of the implementation class, but not the
interface. Use the new `__implementation__` or `__raw_implementation__` properties to
if you need to "downcast" to the implementation class.
+
- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition
to the regular method return value (unless they are passed with `ref` or `out` keyword).
- BREAKING: Drop support for the long-deprecated CLR.* prefix.
diff --git a/Directory.Build.props b/Directory.Build.props
index 496060877..d724e41e7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,18 +4,6 @@
Copyright (c) 2006-2021 The Contributors of the Python.NET Project
pythonnet
Python.NET
- 10.0
false
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
diff --git a/README.rst b/README.rst
index d5b280bfa..72f800b7a 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,6 @@
pythonnet - Python.NET
===========================
-
+
|Join the chat at https://gitter.im/pythonnet/pythonnet| |stackexchange shield|
|gh shield|
diff --git a/pyproject.toml b/pyproject.toml
index b6df82f71..6151e3fff 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,55 @@
requires = ["setuptools>=42", "wheel", "pycparser"]
build-backend = "setuptools.build_meta"
+[project]
+name = "pythonnet"
+description = ".NET and Mono integration for Python"
+license = {text = "MIT"}
+
+readme = "README.rst"
+
+dependencies = [
+ "clr_loader>=0.2.2,<0.3.0"
+]
+
+requires-python = ">=3.7, <3.12"
+
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: C#",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: MacOS :: MacOS X",
+]
+
+dynamic = ["version"]
+
+[[project.authors]]
+name = "The Contributors of the Python.NET Project"
+email = "pythonnet@python.org"
+
+[project.urls]
+Homepage = "https://pythonnet.github.io/"
+Sources = "https://github.com/pythonnet/pythonnet"
+
+[tool.setuptools]
+zip-safe = false
+py-modules = ["clr"]
+
+[tool.setuptools.dynamic.version]
+file = "version.txt"
+
+[tool.setuptools.packages.find]
+include = ["pythonnet*"]
+
[tool.pytest.ini_options]
xfail_strict = true
testpaths = [
diff --git a/pythonnet.sln b/pythonnet.sln
index eb97cfbd0..f1ddac929 100644
--- a/pythonnet.sln
+++ b/pythonnet.sln
@@ -12,8 +12,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test", "src\testing\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PerformanceTests", "src\perf_tests\Python.PerformanceTests.csproj", "{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.DomainReloadTests", "tests\domain_tests\Python.DomainReloadTests.csproj", "{F2FB6DA3-318E-4F30-9A1F-932C667E38C5}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -42,11 +40,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{BC426F42
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PythonTestsRunner", "src\python_tests_runner\Python.PythonTestsRunner.csproj", "{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{142A6752-C2C2-4F95-B982-193418001B65}"
- ProjectSection(SolutionItems) = preProject
- Directory.Build.props = Directory.Build.props
- EndProjectSection
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -72,30 +65,27 @@ Global
{4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.Release|x64.Build.0 = Release|Any CPU
{4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.Release|x86.ActiveCfg = Release|Any CPU
{4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.Release|x86.Build.0 = Release|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|Any CPU.ActiveCfg = TraceAlloc|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|Any CPU.Build.0 = TraceAlloc|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x64.ActiveCfg = Debug|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x64.Build.0 = Debug|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x86.ActiveCfg = Debug|Any CPU
- {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x86.Build.0 = Debug|Any CPU
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|Any CPU.ActiveCfg = Debug|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|Any CPU.Build.0 = Debug|x64
+ {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|Any CPU.ActiveCfg = Release|Any CPU
+ {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x64.ActiveCfg = Release|Any CPU
+ {4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}.TraceAlloc|x86.ActiveCfg = Release|Any CPU
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|x64.ActiveCfg = Debug|x64
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|x64.Build.0 = Debug|x64
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|x86.ActiveCfg = Debug|x86
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Debug|x86.Build.0 = Debug|x86
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|Any CPU.ActiveCfg = Release|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|Any CPU.Build.0 = Release|x64
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|Any CPU.Build.0 = Release|Any CPU
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|x64.ActiveCfg = Release|x64
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|x64.Build.0 = Release|x64
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|x86.ActiveCfg = Release|x86
{E6B01706-00BA-4144-9029-186AC42FBE9A}.Release|x86.Build.0 = Release|x86
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|Any CPU.ActiveCfg = Debug|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|Any CPU.Build.0 = Debug|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x64.ActiveCfg = Debug|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x64.Build.0 = Debug|x64
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x86.ActiveCfg = Debug|x86
- {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x86.Build.0 = Debug|x86
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|Any CPU.ActiveCfg = Release|Any CPU
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|Any CPU.Build.0 = Release|Any CPU
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x64.ActiveCfg = Release|x64
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x64.Build.0 = Release|x64
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x86.ActiveCfg = Release|x86
+ {E6B01706-00BA-4144-9029-186AC42FBE9A}.TraceAlloc|x86.Build.0 = Release|x86
{819E089B-4770-400E-93C6-4F7A35F0EA12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{819E089B-4770-400E-93C6-4F7A35F0EA12}.Debug|Any CPU.Build.0 = Debug|Any CPU
{819E089B-4770-400E-93C6-4F7A35F0EA12}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -126,48 +116,30 @@ Global
{14EF9518-5BB7-4F83-8686-015BD2CC788E}.Release|x64.Build.0 = Release|Any CPU
{14EF9518-5BB7-4F83-8686-015BD2CC788E}.Release|x86.ActiveCfg = Release|Any CPU
{14EF9518-5BB7-4F83-8686-015BD2CC788E}.Release|x86.Build.0 = Release|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|Any CPU.ActiveCfg = Debug|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|Any CPU.Build.0 = Debug|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x64.ActiveCfg = Debug|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x64.Build.0 = Debug|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x86.ActiveCfg = Debug|Any CPU
- {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x86.Build.0 = Debug|Any CPU
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|Any CPU.ActiveCfg = Debug|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|Any CPU.Build.0 = Debug|x64
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|Any CPU.ActiveCfg = Release|Any CPU
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|Any CPU.Build.0 = Release|Any CPU
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x64.ActiveCfg = Release|Any CPU
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x64.Build.0 = Release|Any CPU
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x86.ActiveCfg = Release|Any CPU
+ {14EF9518-5BB7-4F83-8686-015BD2CC788E}.TraceAlloc|x86.Build.0 = Release|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|x64.ActiveCfg = Debug|x64
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|x64.Build.0 = Debug|x64
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|x86.ActiveCfg = Debug|x86
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Debug|x86.Build.0 = Debug|x86
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|Any CPU.ActiveCfg = Release|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|Any CPU.Build.0 = Release|x64
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|Any CPU.Build.0 = Release|Any CPU
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x64.ActiveCfg = Release|x64
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x64.Build.0 = Release|x64
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.ActiveCfg = Release|x86
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.Build.0 = Release|x86
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|Any CPU.ActiveCfg = Debug|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|Any CPU.Build.0 = Debug|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x64.ActiveCfg = Debug|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x64.Build.0 = Debug|x64
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x86.ActiveCfg = Debug|x86
- {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x86.Build.0 = Debug|x86
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.Build.0 = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.Build.0 = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.Build.0 = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.ActiveCfg = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.Build.0 = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.ActiveCfg = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.Build.0 = Release|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|Any CPU.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|Any CPU.Build.0 = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|x64.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|x64.Build.0 = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|x86.ActiveCfg = Debug|Any CPU
- {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.TraceAlloc|x86.Build.0 = Debug|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|Any CPU.ActiveCfg = Release|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|Any CPU.Build.0 = Release|Any CPU
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x64.ActiveCfg = Release|x64
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x64.Build.0 = Release|x64
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x86.ActiveCfg = Release|x86
+ {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.TraceAlloc|x86.Build.0 = Release|x86
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x64.ActiveCfg = Debug|Any CPU
diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py
index 10dc403e4..5c1ca108a 100644
--- a/pythonnet/__init__.py
+++ b/pythonnet/__init__.py
@@ -1,60 +1,168 @@
+"""Python.NET runtime loading and configuration"""
+
import sys
+from pathlib import Path
+from typing import Dict, Optional, Union, Any
import clr_loader
-_RUNTIME = None
-_LOADER_ASSEMBLY = None
-_FFI = None
-_LOADED = False
+__all__ = ["set_runtime", "set_runtime_from_env", "load", "unload", "get_runtime_info"]
+
+_RUNTIME: Optional[clr_loader.Runtime] = None
+_LOADER_ASSEMBLY: Optional[clr_loader.Assembly] = None
+_LOADED: bool = False
+
+
+def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None:
+ """Set up a clr_loader runtime without loading it
+ :param runtime:
+ Either an already initialised `clr_loader` runtime, or one of netfx,
+ coreclr, mono, or default. If a string parameter is given, the runtime
+ will be created.
+ """
-def set_runtime(runtime):
global _RUNTIME
if _LOADED:
- raise RuntimeError("The runtime {} has already been loaded".format(_RUNTIME))
+ raise RuntimeError(f"The runtime {_RUNTIME} has already been loaded")
- _RUNTIME = runtime
+ if isinstance(runtime, str):
+ runtime = _create_runtime_from_spec(runtime, params)
+ _RUNTIME = runtime
-def set_default_runtime() -> None:
- if sys.platform == "win32":
- set_runtime(clr_loader.get_netfx())
- else:
- set_runtime(clr_loader.get_mono())
+def get_runtime_info() -> Optional[clr_loader.RuntimeInfo]:
+ """Retrieve information on the configured runtime"""
-def load():
- global _FFI, _LOADED, _LOADER_ASSEMBLY
+ if _RUNTIME is None:
+ return None
+ else:
+ return _RUNTIME.info()
+
+
+def _get_params_from_env(prefix: str) -> Dict[str, str]:
+ from os import environ
+
+ full_prefix = f"PYTHONNET_{prefix.upper()}_"
+ len_ = len(full_prefix)
+
+ env_vars = {
+ (k[len_:].lower()): v
+ for k, v in environ.items()
+ if k.upper().startswith(full_prefix)
+ }
+
+ return env_vars
+
+
+def _create_runtime_from_spec(
+ spec: str, params: Optional[Dict[str, Any]] = None
+) -> clr_loader.Runtime:
+ was_default = False
+ if spec == "default":
+ was_default = True
+ if sys.platform == "win32":
+ spec = "netfx"
+ else:
+ spec = "mono"
+
+ params = params or _get_params_from_env(spec)
+
+ try:
+ if spec == "netfx":
+ return clr_loader.get_netfx(**params)
+ elif spec == "mono":
+ return clr_loader.get_mono(**params)
+ elif spec == "coreclr":
+ return clr_loader.get_coreclr(**params)
+ else:
+ raise RuntimeError(f"Invalid runtime name: '{spec}'")
+ except Exception as exc:
+ if was_default:
+ raise RuntimeError(
+ f"""Failed to create a default .NET runtime, which would
+ have been "{spec}" on this system. Either install a
+ compatible runtime or configure it explicitly via
+ `set_runtime` or the `PYTHONNET_*` environment variables
+ (see set_runtime_from_env)."""
+ ) from exc
+ else:
+ raise RuntimeError(
+ f"""Failed to create a .NET runtime ({spec}) using the
+ parameters {params}."""
+ ) from exc
+
+
+def set_runtime_from_env() -> None:
+ """Set up the runtime using the environment
+
+ This will use the environment variable PYTHONNET_RUNTIME to decide the
+ runtime to use, which may be one of netfx, coreclr or mono. The parameters
+ of the respective clr_loader.get_ functions can also be given as
+ environment variables, named `PYTHONNET__`. In
+ particular, to use `PYTHONNET_RUNTIME=coreclr`, the variable
+ `PYTHONNET_CORECLR_RUNTIME_CONFIG` has to be set to a valid
+ `.runtimeconfig.json`.
+
+ If no environment variable is specified, a globally installed Mono is used
+ for all environments but Windows, on Windows the legacy .NET Framework is
+ used.
+ """
+ from os import environ
+
+ spec = environ.get("PYTHONNET_RUNTIME", "default")
+ runtime = _create_runtime_from_spec(spec)
+ set_runtime(runtime)
+
+
+def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> None:
+ """Load Python.NET in the specified runtime
+
+ The same parameters as for `set_runtime` can be used. By default,
+ `set_default_runtime` is called if no environment has been set yet and no
+ parameters are passed.
+
+ After a successful call, further invocations will return immediately."""
+ global _LOADED, _LOADER_ASSEMBLY
if _LOADED:
return
- from os.path import join, dirname
+ if _RUNTIME is None:
+ if runtime is None:
+ set_runtime_from_env()
+ else:
+ set_runtime(runtime, **params)
if _RUNTIME is None:
- # TODO: Warn, in the future the runtime must be set explicitly, either
- # as a config/env variable or via set_runtime
- set_default_runtime()
+ raise RuntimeError("No valid runtime selected")
- dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll")
+ dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll"
- _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path)
+ _LOADER_ASSEMBLY = assembly = _RUNTIME.get_assembly(str(dll_path))
+ func = assembly.get_function("Python.Runtime.Loader.Initialize")
- func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"]
if func(b"") != 0:
raise RuntimeError("Failed to initialize Python.Runtime.dll")
+
+ _LOADED = True
import atexit
atexit.register(unload)
-def unload():
- global _RUNTIME
+def unload() -> None:
+ """Explicitly unload a loaded runtime and shut down Python.NET"""
+
+ global _RUNTIME, _LOADER_ASSEMBLY
if _LOADER_ASSEMBLY is not None:
- func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"]
+ func = _LOADER_ASSEMBLY.get_function("Python.Runtime.Loader.Shutdown")
if func(b"full_shutdown") != 0:
raise RuntimeError("Failed to call Python.NET shutdown")
+ _LOADER_ASSEMBLY = None
+
if _RUNTIME is not None:
- # TODO: Add explicit `close` to clr_loader
+ _RUNTIME.shutdown()
_RUNTIME = None
diff --git a/src/console/Console.csproj b/src/console/Console.csproj
index bcbc1292b..418179393 100644
--- a/src/console/Console.csproj
+++ b/src/console/Console.csproj
@@ -1,7 +1,6 @@
- net472;net6.0
- x64;x86
+ net10.0
Exe
nPython
Python.Runtime
@@ -9,19 +8,10 @@
python-clear.ico
-
-
-
- Python.Runtime.dll
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
+
diff --git a/src/embed_tests/ClassManagerTests.cs b/src/embed_tests/ClassManagerTests.cs
index 72025a28b..2fd38f272 100644
--- a/src/embed_tests/ClassManagerTests.cs
+++ b/src/embed_tests/ClassManagerTests.cs
@@ -1,3 +1,9 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
using NUnit.Framework;
using Python.Runtime;
@@ -24,6 +30,1245 @@ public void NestedClassDerivingFromParent()
var f = new NestedTestContainer().ToPython();
f.GetAttr(nameof(NestedTestContainer.Bar));
}
+
+ #region Snake case naming tests
+
+ public enum SnakeCaseEnum
+ {
+ EnumValue1,
+ EnumValue2,
+ EnumValue3
+ }
+
+ public class SnakeCaseNamesTesClass
+ {
+ // Purposely long names to test snake case conversion
+
+ public string PublicStringField = "public_string_field";
+ public const string PublicConstStringField = "public_const_string_field";
+ public readonly string PublicReadonlyStringField = "public_readonly_string_field";
+ public static string PublicStaticStringField = "public_static_string_field";
+ public static readonly string PublicStaticReadonlyStringField = "public_static_readonly_string_field";
+
+ public static string SettablePublicStaticStringField = "settable_public_static_string_field";
+
+ public string PublicStringProperty { get; set; } = "public_string_property";
+ public string PublicStringGetOnlyProperty { get; } = "public_string_get_only_property";
+ public static string PublicStaticStringProperty { get; set; } = "public_static_string_property";
+ public static string PublicStaticReadonlyStringGetterOnlyProperty { get; } = "public_static_readonly_string_getter_only_property";
+ public static string PublicStaticReadonlyStringPrivateSetterProperty { get; private set; } = "public_static_readonly_string_private_setter_property";
+ public static string PublicStaticReadonlyStringProtectedSetterProperty { get; protected set; } = "public_static_readonly_string_protected_setter_property";
+ public static string PublicStaticReadonlyStringInternalSetterProperty { get; internal set; } = "public_static_readonly_string_internal_setter_property";
+ public static string PublicStaticReadonlyStringProtectedInternalSetterProperty { get; protected internal set; } = "public_static_readonly_string_protected_internal_setter_property";
+ public static string PublicStaticReadonlyStringExpressionBodiedProperty => "public_static_readonly_string_expression_bodied_property";
+
+ protected string ProtectedStringGetOnlyProperty { get; } = "protected_string_get_only_property";
+ protected static string ProtectedStaticStringProperty { get; set; } = "protected_static_string_property";
+ protected static string ProtectedStaticReadonlyStringGetterOnlyProperty { get; } = "protected_static_readonly_string_getter_only_property";
+ protected static string ProtectedStaticReadonlyStringPrivateSetterProperty { get; private set; } = "protected_static_readonly_string_private_setter_property";
+ protected static string ProtectedStaticReadonlyStringExpressionBodiedProperty => "protected_static_readonly_string_expression_bodied_property";
+
+ public event EventHandler PublicStringEvent;
+ public static event EventHandler PublicStaticStringEvent;
+
+ public SnakeCaseEnum EnumValue = SnakeCaseEnum.EnumValue2;
+
+ public void InvokePublicStringEvent(string value)
+ {
+ PublicStringEvent?.Invoke(this, value);
+ }
+
+ public static void InvokePublicStaticStringEvent(string value)
+ {
+ PublicStaticStringEvent?.Invoke(null, value);
+ }
+
+ public int AddNumbersAndGetHalf(int a, int b)
+ {
+ return (a + b) / 2;
+ }
+
+ public static int AddNumbersAndGetHalf_Static(int a, int b)
+ {
+ return (a + b) / 2;
+ }
+
+ public string JoinToString(string thisIsAStringParameter,
+ char thisIsACharParameter,
+ int thisIsAnIntParameter,
+ float thisIsAFloatParameter,
+ double thisIsADoubleParameter,
+ decimal? thisIsADecimalParameter,
+ bool thisIsABoolParameter,
+ DateTime thisIsADateTimeParameter = default)
+ {
+ // Join all parameters into a single string separated by "-"
+ return string.Join("-", thisIsAStringParameter, thisIsACharParameter, thisIsAnIntParameter, thisIsAFloatParameter,
+ thisIsADoubleParameter, thisIsADecimalParameter ?? 123.456m, thisIsABoolParameter, string.Format("{0:MMddyyyy}", thisIsADateTimeParameter));
+ }
+
+ public static Action StaticReadonlyActionProperty { get; } = () => Throw();
+ public static Action StaticReadonlyActionWithParamsProperty { get; } = (i) => Throw();
+ public static Func StaticReadonlyFuncProperty { get; } = () =>
+ {
+ Throw();
+ return 42;
+ };
+ public static Func StaticReadonlyFuncWithParamsProperty { get; } = (i) =>
+ {
+ Throw();
+ return i * 2;
+ };
+
+ public static Action StaticReadonlyExpressionBodiedActionProperty => () => Throw();
+ public static Action StaticReadonlyExpressionBodiedActionWithParamsProperty => (i) => Throw();
+ public static Func StaticReadonlyExpressionBodiedFuncProperty => () =>
+ {
+ Throw();
+ return 42;
+ };
+ public static Func StaticReadonlyExpressionBodiedFuncWithParamsProperty => (i) =>
+ {
+ Throw();
+ return i * 2;
+ };
+
+ public static readonly Action StaticReadonlyActionField = () => Throw();
+ public static readonly Action StaticReadonlyActionWithParamsField = (i) => Throw();
+ public static readonly Func StaticReadonlyFuncField = () =>
+ {
+ Throw();
+ return 42;
+ };
+ public static readonly Func StaticReadonlyFuncWithParamsField = (i) =>
+ {
+ Throw();
+ return i * 2;
+ };
+
+ public static readonly Action StaticReadonlyExpressionBodiedActionField = () => Throw();
+ public static readonly Action StaticReadonlyExpressionBodiedActionWithParamsField = (i) => Throw();
+ public static readonly Func StaticReadonlyExpressionBodiedFuncField = () =>
+ {
+ Throw();
+ return 42;
+ };
+ public static readonly Func StaticReadonlyExpressionBodiedFuncWithParamsField = (i) =>
+ {
+ Throw();
+ return i * 2;
+ };
+
+ private static void Throw() => throw new Exception("Pepe");
+
+ public static string GenericMethodBindingStatic(int arg1, SnakeCaseEnum enumValue)
+ {
+ return "GenericMethodBindingStatic";
+ }
+
+ public string GenericMethodBinding(int arg1, SnakeCaseEnum enumValue = SnakeCaseEnum.EnumValue3)
+ {
+ return "GenericMethodBinding" + arg1;
+ }
+ }
+
+ [TestCase("generic_method_binding_static", "GenericMethodBindingStatic")]
+ [TestCase("generic_method_binding", "GenericMethodBinding1")]
+ [TestCase("generic_method_binding2", "GenericMethodBinding2")]
+ [TestCase("generic_method_binding3", "GenericMethodBinding3")]
+ public void GenericMethodBinding(string targetMethod, string expectedReturn)
+ {
+ using (Py.GIL())
+ {
+ var module = PyModule.FromString("module", $@"
+from clr import AddReference
+AddReference(""Python.EmbeddingTest"")
+
+from Python.EmbeddingTest import *
+
+def generic_method_binding_static(value):
+ return ClassManagerTests.SnakeCaseNamesTesClass.generic_method_binding_static[bool](1, enum_value=ClassManagerTests.SnakeCaseEnum.EnumValue1)
+
+def generic_method_binding(value):
+ return value.generic_method_binding[bool](1, enum_value=ClassManagerTests.SnakeCaseEnum.EnumValue1)
+
+def generic_method_binding2(value):
+ return value.generic_method_binding[bool](2, ClassManagerTests.SnakeCaseEnum.EnumValue1)
+
+def generic_method_binding3(value):
+ return value.generic_method_binding[bool](3)
+ ");
+
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+ var result = module.InvokeMethod(targetMethod, new[] { obj }).As();
+
+ Assert.AreEqual(expectedReturn, result);
+ }
+ }
+
+ [TestCase("StaticReadonlyActionProperty", "static_readonly_action_property", new object[] { })]
+ [TestCase("StaticReadonlyActionWithParamsProperty", "static_readonly_action_with_params_property", new object[] { 42 })]
+ [TestCase("StaticReadonlyFuncProperty", "static_readonly_func_property", new object[] { })]
+ [TestCase("StaticReadonlyFuncWithParamsProperty", "static_readonly_func_with_params_property", new object[] { 42 })]
+ [TestCase("StaticReadonlyExpressionBodiedActionProperty", "static_readonly_expression_bodied_action_property", new object[] { })]
+ [TestCase("StaticReadonlyExpressionBodiedActionWithParamsProperty", "static_readonly_expression_bodied_action_with_params_property", new object[] { 42 })]
+ [TestCase("StaticReadonlyExpressionBodiedFuncProperty", "static_readonly_expression_bodied_func_property", new object[] { })]
+ [TestCase("StaticReadonlyExpressionBodiedFuncWithParamsProperty", "static_readonly_expression_bodied_func_with_params_property", new object[] { 42 })]
+ [TestCase("StaticReadonlyActionField", "static_readonly_action_field", new object[] { })]
+ [TestCase("StaticReadonlyActionWithParamsField", "static_readonly_action_with_params_field", new object[] { 42 })]
+ [TestCase("StaticReadonlyFuncField", "static_readonly_func_field", new object[] { })]
+ [TestCase("StaticReadonlyFuncWithParamsField", "static_readonly_func_with_params_field", new object[] { 42 })]
+ [TestCase("StaticReadonlyExpressionBodiedActionField", "static_readonly_expression_bodied_action_field", new object[] { })]
+ [TestCase("StaticReadonlyExpressionBodiedActionWithParamsField", "static_readonly_expression_bodied_action_with_params_field", new object[] { 42 })]
+ [TestCase("StaticReadonlyExpressionBodiedFuncField", "static_readonly_expression_bodied_func_field", new object[] { })]
+ [TestCase("StaticReadonlyExpressionBodiedFuncWithParamsField", "static_readonly_expression_bodied_func_with_params_field", new object[] { 42 })]
+ public void StaticReadonlyCallableFieldsAndPropertiesAreBothUpperAndLowerCased(string propertyName, string snakeCasedName, object[] args)
+ {
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+
+ var lowerCasedName = snakeCasedName.ToLowerInvariant();
+ var upperCasedName = snakeCasedName.ToUpperInvariant();
+
+ var memberInfo = typeof(SnakeCaseNamesTesClass).GetMember(propertyName).First();
+ var callableType = memberInfo switch
+ {
+ PropertyInfo propertyInfo => propertyInfo.PropertyType,
+ FieldInfo fieldInfo => fieldInfo.FieldType,
+ _ => throw new InvalidOperationException()
+ };
+
+ var property = obj.GetAttr(propertyName).AsManagedObject(callableType);
+ var lowerCasedProperty = obj.GetAttr(lowerCasedName).AsManagedObject(callableType);
+ var upperCasedProperty = obj.GetAttr(upperCasedName).AsManagedObject(callableType);
+
+ Assert.IsNotNull(property);
+ Assert.IsNotNull(property as MulticastDelegate);
+ Assert.AreSame(property, lowerCasedProperty);
+ Assert.AreSame(property, upperCasedProperty);
+
+ var call = () =>
+ {
+ try
+ {
+ (property as Delegate).DynamicInvoke(args);
+ }
+ catch (TargetInvocationException e)
+ {
+ throw e.InnerException;
+ }
+ };
+
+ var exception = Assert.Throws(() => call());
+ Assert.AreEqual("Pepe", exception.Message);
+ }
+
+ [TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")]
+ [TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "public_static_readonly_string_getter_only_property")]
+ public void NonCallableStaticReadonlyFieldsAndPropertiesAreOnlyUpperCased(string propertyName, string snakeCasedName)
+ {
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+ var lowerCasedName = snakeCasedName.ToLowerInvariant();
+ var upperCasedName = snakeCasedName.ToUpperInvariant();
+
+ Assert.IsTrue(obj.HasAttr(propertyName));
+ Assert.IsTrue(obj.HasAttr(upperCasedName));
+ Assert.IsFalse(obj.HasAttr(lowerCasedName));
+ }
+
+ [TestCase("AddNumbersAndGetHalf", "add_numbers_and_get_half")]
+ [TestCase("AddNumbersAndGetHalf_Static", "add_numbers_and_get_half_static")]
+ public void BindsSnakeCaseClassMethods(string originalMethodName, string snakeCaseMethodName)
+ {
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+ using var a = 10.ToPython();
+ using var b = 20.ToPython();
+
+ var originalMethodResult = obj.InvokeMethod(originalMethodName, a, b).As();
+ var snakeCaseMethodResult = obj.InvokeMethod(snakeCaseMethodName, a, b).As();
+
+ Assert.AreEqual(15, originalMethodResult);
+ Assert.AreEqual(originalMethodResult, snakeCaseMethodResult);
+ }
+
+ [TestCase("PublicStringField", "public_string_field")]
+ [TestCase("PublicStaticStringField", "public_static_string_field")]
+ [TestCase("PublicReadonlyStringField", "public_readonly_string_field")]
+ // Constants
+ [TestCase("PublicConstStringField", "PUBLIC_CONST_STRING_FIELD")]
+ [TestCase("PublicStaticReadonlyStringField", "PUBLIC_STATIC_READONLY_STRING_FIELD")]
+ public void BindsSnakeCaseClassFields(string originalFieldName, string snakeCaseFieldName)
+ {
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+
+ var expectedValue = originalFieldName switch
+ {
+ "PublicStringField" => "public_string_field",
+ "PublicConstStringField" => "public_const_string_field",
+ "PublicReadonlyStringField" => "public_readonly_string_field",
+ "PublicStaticStringField" => "public_static_string_field",
+ "PublicStaticReadonlyStringField" => "public_static_readonly_string_field",
+ _ => throw new ArgumentException("Invalid field name")
+ };
+
+ var originalFieldValue = obj.GetAttr(originalFieldName).As();
+ var snakeCaseFieldValue = obj.GetAttr(snakeCaseFieldName).As();
+
+ Assert.AreEqual(expectedValue, originalFieldValue);
+ Assert.AreEqual(expectedValue, snakeCaseFieldValue);
+ }
+
+ [Test]
+ public void CanSetFieldUsingSnakeCaseName()
+ {
+ var obj = new SnakeCaseNamesTesClass();
+ using var pyObj = obj.ToPython();
+
+ // Try with the original field name
+ var newValue1 = "new value 1";
+ using var pyNewValue1 = newValue1.ToPython();
+ pyObj.SetAttr("PublicStringField", pyNewValue1);
+ Assert.AreEqual(newValue1, obj.PublicStringField);
+
+ // Try with the snake case field name
+ var newValue2 = "new value 2";
+ using var pyNewValue2 = newValue2.ToPython();
+ pyObj.SetAttr("public_string_field", pyNewValue2);
+ Assert.AreEqual(newValue2, obj.PublicStringField);
+ }
+
+ [Test]
+ public void CanSetStaticFieldUsingSnakeCaseName()
+ {
+ using (Py.GIL())
+ {
+ var module = PyModule.FromString("module", $@"
+from clr import AddReference
+AddReference(""Python.EmbeddingTest"")
+
+from Python.EmbeddingTest import *
+
+def SetCamelCaseStaticProperty(value):
+ ClassManagerTests.SnakeCaseNamesTesClass.PublicStaticStringField = value
+
+def SetSnakeCaseStaticProperty(value):
+ ClassManagerTests.SnakeCaseNamesTesClass.public_static_string_field = value
+ ");
+
+ // Try with the original field name
+ var newValue1 = "new value 1";
+ using var pyNewValue1 = newValue1.ToPython();
+ module.InvokeMethod("SetCamelCaseStaticProperty", pyNewValue1);
+ Assert.AreEqual(newValue1, SnakeCaseNamesTesClass.PublicStaticStringField);
+
+ // Try with the snake case field name
+ var newValue2 = "new value 2";
+ using var pyNewValue2 = newValue2.ToPython();
+ module.InvokeMethod("SetSnakeCaseStaticProperty", pyNewValue2);
+ Assert.AreEqual(newValue2, SnakeCaseNamesTesClass.PublicStaticStringField);
+ }
+ }
+
+ [TestCase("PublicStringProperty", "public_string_property")]
+ [TestCase("PublicStringGetOnlyProperty", "public_string_get_only_property")]
+ [TestCase("PublicStaticStringProperty", "public_static_string_property")]
+ [TestCase("PublicStaticReadonlyStringPrivateSetterProperty", "public_static_readonly_string_private_setter_property")]
+ [TestCase("PublicStaticReadonlyStringProtectedSetterProperty", "public_static_readonly_string_protected_setter_property")]
+ [TestCase("PublicStaticReadonlyStringInternalSetterProperty", "public_static_readonly_string_internal_setter_property")]
+ [TestCase("PublicStaticReadonlyStringProtectedInternalSetterProperty", "public_static_readonly_string_protected_internal_setter_property")]
+ [TestCase("ProtectedStringGetOnlyProperty", "protected_string_get_only_property")]
+ [TestCase("ProtectedStaticStringProperty", "protected_static_string_property")]
+ [TestCase("ProtectedStaticReadonlyStringPrivateSetterProperty", "protected_static_readonly_string_private_setter_property")]
+ // Constants
+ [TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "PUBLIC_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
+ [TestCase("PublicStaticReadonlyStringExpressionBodiedProperty", "PUBLIC_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]
+ [TestCase("ProtectedStaticReadonlyStringGetterOnlyProperty", "PROTECTED_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
+ [TestCase("ProtectedStaticReadonlyStringExpressionBodiedProperty", "PROTECTED_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]
+
+ public void BindsSnakeCaseClassProperties(string originalPropertyName, string snakeCasePropertyName)
+ {
+ using var obj = new SnakeCaseNamesTesClass().ToPython();
+ var expectedValue = originalPropertyName switch
+ {
+ "PublicStringProperty" => "public_string_property",
+ "PublicStringGetOnlyProperty" => "public_string_get_only_property",
+ "PublicStaticStringProperty" => "public_static_string_property",
+ "PublicStaticReadonlyStringPrivateSetterProperty" => "public_static_readonly_string_private_setter_property",
+ "PublicStaticReadonlyStringProtectedSetterProperty" => "public_static_readonly_string_protected_setter_property",
+ "PublicStaticReadonlyStringInternalSetterProperty" => "public_static_readonly_string_internal_setter_property",
+ "PublicStaticReadonlyStringProtectedInternalSetterProperty" => "public_static_readonly_string_protected_internal_setter_property",
+ "PublicStaticReadonlyStringGetterOnlyProperty" => "public_static_readonly_string_getter_only_property",
+ "PublicStaticReadonlyStringExpressionBodiedProperty" => "public_static_readonly_string_expression_bodied_property",
+ "ProtectedStringGetOnlyProperty" => "protected_string_get_only_property",
+ "ProtectedStaticStringProperty" => "protected_static_string_property",
+ "ProtectedStaticReadonlyStringGetterOnlyProperty" => "protected_static_readonly_string_getter_only_property",
+ "ProtectedStaticReadonlyStringPrivateSetterProperty" => "protected_static_readonly_string_private_setter_property",
+ "ProtectedStaticReadonlyStringExpressionBodiedProperty" => "protected_static_readonly_string_expression_bodied_property",
+ _ => throw new ArgumentException("Invalid property name")
+ };
+
+ var originalPropertyValue = obj.GetAttr(originalPropertyName).As();
+ var snakeCasePropertyValue = obj.GetAttr(snakeCasePropertyName).As();
+
+ Assert.AreEqual(expectedValue, originalPropertyValue);
+ Assert.AreEqual(expectedValue, snakeCasePropertyValue);
+ }
+
+ [Test]
+ public void CanSetPropertyUsingSnakeCaseName()
+ {
+ var obj = new SnakeCaseNamesTesClass();
+ using var pyObj = obj.ToPython();
+
+ // Try with the original property name
+ var newValue1 = "new value 1";
+ using var pyNewValue1 = newValue1.ToPython();
+ pyObj.SetAttr("PublicStringProperty", pyNewValue1);
+ Assert.AreEqual(newValue1, obj.PublicStringProperty);
+
+ // Try with the snake case property name
+ var newValue2 = "new value 2";
+ using var pyNewValue2 = newValue2.ToPython();
+ pyObj.SetAttr("public_string_property", pyNewValue2);
+ Assert.AreEqual(newValue2, obj.PublicStringProperty);
+ }
+
+ [Test]
+ public void CanSetStaticPropertyUsingSnakeCaseName()
+ {
+ using (Py.GIL())
+ {
+ var module = PyModule.FromString("module", $@"
+from clr import AddReference
+AddReference(""Python.EmbeddingTest"")
+
+from Python.EmbeddingTest import *
+
+def SetCamelCaseStaticProperty(value):
+ ClassManagerTests.SnakeCaseNamesTesClass.PublicStaticStringProperty = value
+
+def SetSnakeCaseStaticProperty(value):
+ ClassManagerTests.SnakeCaseNamesTesClass.public_static_string_property = value
+ ");
+
+ // Try with the original property name
+ var newValue1 = "new value 1";
+ using var pyNewValue1 = newValue1.ToPython();
+ module.InvokeMethod("SetCamelCaseStaticProperty", pyNewValue1);
+ Assert.AreEqual(newValue1, SnakeCaseNamesTesClass.PublicStaticStringProperty);
+
+ // Try with the snake case property name
+ var newValue2 = "new value 2";
+ using var pyNewValue2 = newValue2.ToPython();
+ module.InvokeMethod("SetSnakeCaseStaticProperty", pyNewValue2);
+ Assert.AreEqual(newValue2, SnakeCaseNamesTesClass.PublicStaticStringProperty);
+ }
+ }
+
+ [TestCase("PublicStringEvent")]
+ [TestCase("public_string_event")]
+ public void BindsSnakeCaseEvents(string eventName)
+ {
+ var obj = new SnakeCaseNamesTesClass();
+ using var pyObj = obj.ToPython();
+
+ var value = "";
+ var eventHandler = new EventHandler((sender, arg) => { value = arg; });
+
+ // Try with the original event name
+ using (Py.GIL())
+ {
+ var module = PyModule.FromString("module", $@"
+def AddEventHandler(obj, handler):
+ obj.{eventName} += handler
+
+def RemoveEventHandler(obj, handler):
+ obj.{eventName} -= handler
+ ");
+
+ using var pyEventHandler = eventHandler.ToPython();
+
+ module.InvokeMethod("AddEventHandler", pyObj, pyEventHandler);
+ obj.InvokePublicStringEvent("new value 1");
+ Assert.AreEqual("new value 1", value);
+
+ module.InvokeMethod("RemoveEventHandler", pyObj, pyEventHandler);
+ obj.InvokePublicStringEvent("new value 2");
+ Assert.AreEqual("new value 1", value); // Should not have changed
+ }
+ }
+
+ [TestCase("PublicStaticStringEvent")]
+ [TestCase("public_static_string_event")]
+ public void BindsSnakeCaseStaticEvents(string eventName)
+ {
+ var value = "";
+ var eventHandler = new EventHandler((sender, arg) => { value = arg; });
+
+ // Try with the original event name
+ using (Py.GIL())
+ {
+ var module = PyModule.FromString("module", $@"
+from clr import AddReference
+AddReference(""Python.EmbeddingTest"")
+
+from Python.EmbeddingTest import *
+
+def AddEventHandler(handler):
+ ClassManagerTests.SnakeCaseNamesTesClass.{eventName} += handler
+
+def RemoveEventHandler(handler):
+ ClassManagerTests.SnakeCaseNamesTesClass.{eventName} -= handler
+ ");
+
+ using var pyEventHandler = eventHandler.ToPython();
+
+ module.InvokeMethod("AddEventHandler", pyEventHandler);
+ SnakeCaseNamesTesClass.InvokePublicStaticStringEvent("new value 1");
+ Assert.AreEqual("new value 1", value);
+
+ module.InvokeMethod("RemoveEventHandler", pyEventHandler);
+ SnakeCaseNamesTesClass.InvokePublicStaticStringEvent("new value 2");
+ Assert.AreEqual("new value 1", value); // Should not have changed
+ }
+ }
+
+ private static IEnumerable SnakeCasedNamedArgsTestCases
+ {
+ get
+ {
+ var stringParam = "string";
+ var charParam = 'c';
+ var intParam = 1;
+ var floatParam = 2.0f;
+ var doubleParam = 3.0;
+ var decimalParam = 4.0m;
+ var boolParam = true;
+ var dateTimeParam = new DateTime(2013, 01, 05);
+
+ // 1. All kwargs:
+
+ // 1.1. Original method name:
+ var args = Array.Empty