diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93963c70a..9d6a59854 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,12 @@ on: - master pull_request: + +env: + LIBRARY_NAME: 'ansys-pythonnet' + jobs: + build-test: name: Build and Test runs-on: ${{ matrix.os }}-latest @@ -93,4 +98,14 @@ jobs: pip install --force --no-deps --target src/perf_tests/baseline/ pythonnet==2.5.2 dotnet test --configuration Release --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/perf_tests/ - # TODO: Run mono tests on Windows? + - name: "Build library source and wheel artifacts" + run: | + python -m pip install build twine wheel + python -m build && python -m twine check dist/* + + - name: "Upload distribution artifact" + uses: actions/upload-artifact@v3.1.0 + with: + name: ${{ env.LIBRARY_NAME }}-py${{ matrix.python}}-${{ matrix.os }}-artifacts + path: dist/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index 6159b1b14..cc7e4a651 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,167 @@ cov-int/ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json + + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.cov +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +doc/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# Visual Studio +.vs/ + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/README.rst b/README.rst index f2907f43a..461672e4c 100644 --- a/README.rst +++ b/README.rst @@ -1,158 +1,6 @@ -pythonnet - Python.NET -=========================== +Ansys fork of [pythonnet](https://github.com/pythonnet/pythonnet) -|Join the chat at https://gitter.im/pythonnet/pythonnet| |stackexchange shield| +We will try to keep this up-to-date with pythonnet and upstream changes that might benefit the pythonnet community -|gh shield| - -|license shield| - -|pypi package version| |conda-forge version| |python supported shield| - -|nuget preview shield| |nuget release shield| - -Python.NET is a package that gives Python programmers nearly -seamless integration with the .NET Common Language Runtime (CLR) and -provides a powerful application scripting tool for .NET developers. It -allows Python code to interact with the CLR, and may also be used to -embed Python into a .NET application. - -Calling .NET code from Python ------------------------------ - -Python.NET allows CLR namespaces to be treated essentially as Python packages. - -.. code-block:: python - - import clr - from System import String - from System.Collections import * - -To load an assembly, use the ``AddReference`` function in the ``clr`` -module: - -.. code-block:: python - - import clr - clr.AddReference("System.Windows.Forms") - from System.Windows.Forms import Form - -By default, Mono will be used on Linux and macOS, .NET Framework on Windows. For -details on the loading of different runtimes, please refer to the documentation. - -.NET Core -~~~~~~~~~ - -If .NET Core is installed in a default location or the ``dotnet`` CLI tool is on -the ``PATH``, loading it instead of the default (Mono/.NET Framework) runtime -just requires setting either the environment variable -``PYTHONNET_RUNTIME=coreclr`` or calling ``pythonnet.load`` explicitly: - -.. code-block:: python - - from pythonnet import load - load("coreclr") - - import clr - - -Embedding Python in .NET ------------------------- - -- You must set ``Runtime.PythonDLL`` property or ``PYTHONNET_PYDLL`` environment variable - starting with version 3.0, otherwise you will receive ``BadPythonDllException`` - (internal, derived from ``MissingMethodException``) upon calling ``Initialize``. - Typical values are ``python38.dll`` (Windows), ``libpython3.8.dylib`` (Mac), - ``libpython3.8.so`` (most other Unix-like operating systems). -- Then call ``PythonEngine.Initialize()``. If you plan to use Python objects from - multiple threads, also call ``PythonEngine.BeginAllowThreads()``. -- All calls to python should be inside a - ``using (Py.GIL()) {/* Your code here */}`` block. -- Import python modules using ``dynamic mod = Py.Import("mod")``, then - you can call functions as normal, eg ``mod.func(args)``. -- Use ``mod.func(args, Py.kw("keywordargname", keywordargvalue))`` or - ``mod.func(args, keywordargname: keywordargvalue)`` to apply keyword - arguments. -- All python objects should be declared as ``dynamic`` type. -- Mathematical operations involving python and literal/managed types - must have the python object first, eg. ``np.pi * 2`` works, - ``2 * np.pi`` doesn't. - -Example -~~~~~~~ - -.. code-block:: csharp - - static void Main(string[] args) - { - PythonEngine.Initialize(); - using (Py.GIL()) - { - dynamic np = Py.Import("numpy"); - Console.WriteLine(np.cos(np.pi * 2)); - - dynamic sin = np.sin; - Console.WriteLine(sin(5)); - - double c = (double)(np.cos(5) + sin(5)); - Console.WriteLine(c); - - dynamic a = np.array(new List<float> { 1, 2, 3 }); - Console.WriteLine(a.dtype); - - dynamic b = np.array(new List<float> { 6, 5, 4 }, dtype: np.int32); - Console.WriteLine(b.dtype); - - Console.WriteLine(a * b); - Console.ReadKey(); - } - } - -Output: - -.. code:: csharp - - 1.0 - -0.958924274663 - -0.6752620892 - float64 - int32 - [ 6. 10. 12.] - - - -Resources ---------- - -Information on installation, FAQ, troubleshooting, debugging, and -projects using pythonnet can be found in the Wiki: - -https://github.com/pythonnet/pythonnet/wiki - -Mailing list - https://mail.python.org/mailman/listinfo/pythondotnet -Chat - https://gitter.im/pythonnet/pythonnet - -.NET Foundation ---------------- -This project is supported by the `.NET Foundation <https://dotnetfoundation.org>`_. - -.. |Join the chat at https://gitter.im/pythonnet/pythonnet| image:: https://badges.gitter.im/pythonnet/pythonnet.svg - :target: https://gitter.im/pythonnet/pythonnet?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -.. |license shield| image:: https://img.shields.io/badge/license-MIT-blue.svg?maxAge=3600 - :target: ./LICENSE -.. |pypi package version| image:: https://img.shields.io/pypi/v/pythonnet.svg - :target: https://pypi.python.org/pypi/pythonnet -.. |python supported shield| image:: https://img.shields.io/pypi/pyversions/pythonnet.svg - :target: https://pypi.python.org/pypi/pythonnet -.. |stackexchange shield| image:: https://img.shields.io/badge/StackOverflow-python.net-blue.svg - :target: http://stackoverflow.com/questions/tagged/python.net -.. |conda-forge version| image:: https://img.shields.io/conda/vn/conda-forge/pythonnet.svg - :target: https://anaconda.org/conda-forge/pythonnet -.. |nuget preview shield| image:: https://img.shields.io/nuget/vpre/pythonnet - :target: https://www.nuget.org/packages/pythonnet/ -.. |nuget release shield| image:: https://img.shields.io/nuget/v/pythonnet - :target: https://www.nuget.org/packages/pythonnet/ -.. |gh shield| image:: https://github.com/pythonnet/pythonnet/workflows/GitHub%20Actions/badge.svg - :target: https://github.com/pythonnet/pythonnet/actions?query=branch%3Amaster +Changes relative to pythonnet: +- Revert of [#1240](https://github.com/pythonnet/pythonnet/pull/1240) diff --git a/pyproject.toml b/pyproject.toml index 52f1adb18..bd564139f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "pythonnet" -description = ".NET and Mono integration for Python" +name = "ansys-pythonnet" +description = ".NET and Mono integration for Python (Ansys, Inc. fork)" license = {text = "MIT"} readme = "README.rst" @@ -34,12 +34,12 @@ classifiers = [ dynamic = ["version"] [[project.authors]] -name = "The Contributors of the Python.NET Project" -email = "pythonnet@python.org" +name = "ANSYS, Inc." +email = "pyansys.maintainers@ansys.com" [project.urls] -Homepage = "https://pythonnet.github.io/" -Sources = "https://github.com/pythonnet/pythonnet" +Homepage = "pythonnet.docs.pyansys.com" +Sources = "https://github.com/pyansys/ansys-pythonnet" [tool.setuptools] zip-safe = false diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 73bbd4a3a..d5052682b 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -138,12 +138,6 @@ internal static NewReference ToPython(object? value, Type type) } } - if (type.IsInterface) - { - var ifaceObj = (InterfaceObject)ClassManager.GetClassImpl(type); - return ifaceObj.TryWrapObject(value); - } - if (type.IsArray || type.IsEnum) { return CLRObject.GetReference(value, type); diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 07ed4fe22..205c35f49 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -945,7 +945,7 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a Type pt = pi[i].ParameterType; if (pt.IsByRef) { - using var v = Converter.ToPython(binding.args[i], pt.GetElementType()); + using var v = Converter.ToPython(binding.args[i], pt); Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal()); n++; } diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs index ab0b73368..9817d865e 100644 --- a/src/testing/subclasstest.cs +++ b/src/testing/subclasstest.cs @@ -89,24 +89,13 @@ public static string test_bar(IInterfaceTest x, string s, int i) } // test instances can be constructed in managed code - public static SubClassTest create_instance(Type t) - { - return (SubClassTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { }); - } - - public static IInterfaceTest create_instance_interface(Type t) + public static IInterfaceTest create_instance(Type t) { return (IInterfaceTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { }); } - // test instances pass through managed code unchanged ... - public static SubClassTest pass_through(SubClassTest s) - { - return s; - } - - // ... but the return type is an interface type, objects get wrapped - public static IInterfaceTest pass_through_interface(IInterfaceTest s) + // test instances pass through managed code unchanged + public static IInterfaceTest pass_through(IInterfaceTest s) { return s; } diff --git a/tests/test_array.py b/tests/test_array.py index d207a36fb..80c60383f 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1335,10 +1335,9 @@ def test_special_array_creation(): assert value[1].__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ value = Array[ISayHello1]([inst, inst]) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ assert value.Length == 2 inst = System.Exception("badness") diff --git a/tests/test_generic.py b/tests/test_generic.py index 6d514d638..6364878b8 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -330,6 +330,7 @@ def test_generic_method_type_handling(): assert_generic_method_by_type(ShortEnum, ShortEnum.Zero) assert_generic_method_by_type(System.Object, InterfaceTest()) assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1) + assert_generic_method_by_type(ISayHello1, InterfaceTest(), 1) def test_correct_overload_selection(): @@ -558,11 +559,10 @@ def test_method_overload_selection_with_generic_types(): value = MethodTest.Overloaded.__overloads__[vtype](input_) assert value.value.__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ vtype = GenericWrapper[ISayHello1] input_ = vtype(inst) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value.value.__class__ == iface_class + assert value.value.__class__ == inst.__class__ vtype = System.Array[GenericWrapper[int]] input_ = vtype([GenericWrapper[int](0), GenericWrapper[int](1)]) @@ -737,12 +737,11 @@ def test_overload_selection_with_arrays_of_generic_types(): assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 - iface_class = ISayHello1(inst).__class__ gtype = GenericWrapper[ISayHello1] vtype = System.Array[gtype] input_ = vtype([gtype(inst), gtype(inst)]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].value.__class__ == iface_class + assert value[0].value.__class__ == inst.__class__ assert value.Length == 2 diff --git a/tests/test_interface.py b/tests/test_interface.py index ac620684d..bab59322d 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -72,15 +72,15 @@ def test_explicit_cast_to_interface(): def test_interface_object_returned_through_method(): - """Test interface type is used if method return type is interface""" + """Test concrete type is used if method return type is interface""" from Python.Test import InterfaceTest ob = InterfaceTest() hello1 = ob.GetISayHello1() - assert type(hello1).__name__ == 'ISayHello1' - assert hello1.__implementation__.__class__.__name__ == "InterfaceTest" + assert type(hello1).__name__ == 'InterfaceTest' - assert hello1.SayHello() == 'hello 1' + # This doesn't work yet + # assert hello1.SayHello() == 'hello 1' def test_interface_object_returned_through_out_param(): @@ -89,9 +89,10 @@ def test_interface_object_returned_through_out_param(): ob = InterfaceTest() hello2 = ob.GetISayHello2(None) - assert type(hello2).__name__ == 'ISayHello2' + assert type(hello2).__name__ == 'InterfaceTest' - assert hello2.SayHello() == 'hello 2' + # This doesn't work yet + # assert hello2.SayHello() == 'hello 2' def test_interface_out_param_python_impl(): from Python.Test import IOutArg, OutArgCaller @@ -118,13 +119,12 @@ def test_null_interface_object_returned(): assert hello2 is None def test_interface_array_returned(): - """Test interface type used for methods returning interface arrays""" + """Test concrete type used for methods returning interface arrays""" from Python.Test import InterfaceTest ob = InterfaceTest() hellos = ob.GetISayHello1Array() - assert type(hellos[0]).__name__ == 'ISayHello1' - assert hellos[0].__implementation__.__class__.__name__ == "InterfaceTest" + assert type(hellos[0]).__name__ == 'InterfaceTest' def test_implementation_access(): """Test the __implementation__ and __raw_implementation__ properties""" @@ -137,14 +137,14 @@ def test_implementation_access(): def test_interface_collection_iteration(): - """Test interface type is used when iterating over interface collection""" + """Test concrete type is used when iterating over interface collection""" import System from System.Collections.Generic import List elem = System.IComparable(System.Int32(100)) typed_list = List[System.IComparable]() typed_list.Add(elem) for e in typed_list: - assert type(e).__name__ == "IComparable" + assert type(e).__name__ == "int" untyped_list = System.Collections.ArrayList() untyped_list.Add(elem) diff --git a/tests/test_method.py b/tests/test_method.py index b86bbd6b4..eee359c1b 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -583,10 +583,8 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst) assert value.__class__ == inst.__class__ - iface_class = ISayHello1(InterfaceTest()).__class__ value = MethodTest.Overloaded.__overloads__[ISayHello1](inst) - assert value.__class__ != inst.__class__ - assert value.__class__ == iface_class + assert value.__class__ == inst.__class__ atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( @@ -739,12 +737,11 @@ def test_overload_selection_with_array_types(): assert value[0].__class__ == inst.__class__ assert value[1].__class__ == inst.__class__ - iface_class = ISayHello1(inst).__class__ vtype = Array[ISayHello1] input_ = vtype([inst, inst]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].__class__ == iface_class - assert value[1].__class__ == iface_class + assert value[0].__class__ == inst.__class__ + assert value[1].__class__ == inst.__class__ def test_explicit_overload_selection_failure(): diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 504b82548..015c52613 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -123,10 +123,8 @@ def test_interface(): assert ob.bar("bar", 2) == "bar/bar" assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar" - # pass_through will convert from InterfaceTestClass -> IInterfaceTest, - # causing a new wrapper object to be created. Hence id will differ. - x = FunctionsTest.pass_through_interface(ob) - assert id(x) != id(ob) + x = FunctionsTest.pass_through(ob) + assert id(x) == id(ob) def test_derived_class(): @@ -199,14 +197,14 @@ def test_create_instance(): assert id(x) == id(ob) InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__) - ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass) + ob2 = FunctionsTest.create_instance(InterfaceTestClass) assert ob2.foo() == "InterfaceTestClass" assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass" assert ob2.bar("bar", 2) == "bar/bar" assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar" - y = FunctionsTest.pass_through_interface(ob2) - assert id(y) != id(ob2) + y = FunctionsTest.pass_through(ob2) + assert id(y) == id(ob2) def test_events():