Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IsNone and None static attribute for PyObject #148

Merged
merged 14 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CSnakes.Runtime/CPython/Init.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSnakes.Runtime.Python;
using CSnakes.Runtime.Python.Interns;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -83,6 +84,7 @@ private void InitializeEmbeddedPython()
// Import builtins module
var builtinsMod = Import("builtins");
PyNone = GetAttr(builtinsMod, "None");
PyObject.none = new PyNoneObject();
Py_DecRef(builtinsMod);
}
PyEval_SaveThread();
Expand Down
14 changes: 13 additions & 1 deletion src/CSnakes.Runtime/CPython/None.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace CSnakes.Runtime.CPython;
using CSnakes.Runtime.Python;
using System.ComponentModel;

namespace CSnakes.Runtime.CPython;

internal unsafe partial class CPythonAPI
{
Expand All @@ -10,7 +13,16 @@ internal unsafe partial class CPythonAPI
/// <returns>A new reference to None. In newer versions of Python, None is immortal anyway.</returns>
internal static nint GetNone()
{
if (PyNone == IntPtr.Zero)
{
throw new InvalidOperationException("Python is not initialized. You cannot call this method outside of a Python Environment context.");
}
Py_IncRefRaw(PyNone);
return PyNone;
}

internal static bool IsNone(PyObject o)
{
return PyNone == o.DangerousGetHandle();
}
}
5 changes: 3 additions & 2 deletions src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ internal partial class PyObjectTypeConverter : TypeConverter
ITuple t => ConvertFromTuple(context, culture, t),
IEnumerable e => ConvertFromList(context, culture, e),
BigInteger b => ConvertFromBigInteger(context, culture, b),
null => new PyObject(CPythonAPI.GetNone()),
PyObject pyObject => pyObject,
null => PyObject.None,
_ => base.ConvertFrom(context, culture, value)
};

Expand Down Expand Up @@ -179,7 +180,7 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf
{
if (o is null)
{
return new PyObject(CPythonAPI.GetNone());
return PyObject.None;
}

var result = ConvertFrom(context, culture, o);
Expand Down
13 changes: 13 additions & 0 deletions src/CSnakes.Runtime/Python/Interns/ImmortalPyObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CSnakes.Runtime.CPython;
namespace CSnakes.Runtime.Python.Interns;

internal class ImmortalPyObject(nint handle) : PyObject(handle)
{
protected override bool ReleaseHandle() => true;


protected override void Dispose(bool disposing)
{
// I am immortal!!
}
}
18 changes: 18 additions & 0 deletions src/CSnakes.Runtime/Python/Interns/PyNoneObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CSnakes.Runtime.CPython;

namespace CSnakes.Runtime.Python.Interns;

internal sealed class PyNoneObject : ImmortalPyObject

{
public PyNoneObject() : base(CPythonAPI.GetNone())
{
}

public override bool IsNone() => true;

public override string GetRepr() => ToString();

public override string ToString() => "None";

}
22 changes: 16 additions & 6 deletions src/CSnakes.Runtime/Python/PyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class PyObject : SafeHandle
{
private static readonly TypeConverter td = TypeDescriptor.GetConverter(typeof(PyObject));
internal static PyObject? none;

internal PyObject(IntPtr pyObject, bool ownsHandle = true) : base(pyObject, ownsHandle)
{
Expand Down Expand Up @@ -97,7 +98,7 @@
/// Get the type for the object.
/// </summary>
/// <returns>A new reference to the type field.</returns>
public PyObject GetPythonType()
public virtual PyObject GetPythonType()
{
RaiseOnPythonNotInitialized();
using (GIL.Acquire())
Expand All @@ -111,7 +112,7 @@
/// </summary>
/// <param name="name"></param>
/// <returns>Attribute object (new ref)</returns>
public PyObject GetAttr(string name)
public virtual PyObject GetAttr(string name)
{
RaiseOnPythonNotInitialized();
using (GIL.Acquire())
Expand All @@ -120,7 +121,7 @@
}
}

public bool HasAttr(string name)
public virtual bool HasAttr(string name)
{
RaiseOnPythonNotInitialized();
using (GIL.Acquire())
Expand All @@ -133,7 +134,7 @@
/// Get the iterator for the object. This is equivalent to iter(obj) in Python.
/// </summary>
/// <returns>The iterator object (new ref)</returns>
public PyObject GetIter()
public virtual PyObject GetIter()
{
RaiseOnPythonNotInitialized();
using (GIL.Acquire())
Expand All @@ -146,7 +147,7 @@
/// Get the results of the repr() function on the object.
/// </summary>
/// <returns></returns>
public string GetRepr()
public virtual string GetRepr()
{
RaiseOnPythonNotInitialized();
using (GIL.Acquire())
Expand All @@ -157,6 +158,15 @@
}
}

/// <summary>
/// Is the Python object None?
/// </summary>
/// <returns>true if None, else false</returns>
public virtual bool IsNone() => CPythonAPI.IsNone(this);


public static PyObject None { get; } = new PyNoneObject();

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / publish-github-packages

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / publish-github-packages

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 7.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 7.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.11, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 6.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 6.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.10, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 9.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 9.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.12, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.9, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 168 in src/CSnakes.Runtime/Python/PyObject.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest, 3.13.0-rc.1, 8.0.x)

The type or namespace name 'PyNoneObject' could not be found (are you missing a using directive or an assembly reference?)

/// <summary>
/// Call the object. Equivalent to (__call__)(args)
/// All arguments are treated as positional.
Expand Down Expand Up @@ -292,7 +302,7 @@
using (GIL.Acquire())
{
return value is null ?
new PyObject(CPythonAPI.GetNone()) :
PyObject.None :
(PyObject?)td.ConvertFrom(value);
}
}
Expand Down
27 changes: 17 additions & 10 deletions src/CSnakes/Reflection/MethodReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str
{
continue;
}
bool needsConversion = true; // TODO: Skip .From for PyObject arguments.
ExpressionSyntax rhs = IdentifierName(parameter.cSharpParameter.Identifier);
if (needsConversion)
rhs =
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("PyObject"),
IdentifierName("From")))
.WithArgumentList(
ArgumentList(
SingletonSeparatedList(
Argument(
IdentifierName(parameter.cSharpParameter.Identifier)))));

pythonConversionStatements.Add(
LocalDeclarationStatement(
VariableDeclaration(
Expand All @@ -63,16 +78,8 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str
EqualsValueClause(
PostfixUnaryExpression(
SyntaxKind.SuppressNullableWarningExpression,
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("PyObject"),
IdentifierName("From")))
.WithArgumentList(
ArgumentList(
SingletonSeparatedList(
Argument(
IdentifierName(parameter.cSharpParameter.Identifier)))))))))))
rhs
))))))
.WithUsingKeyword(
Token(SyntaxKind.UsingKeyword)));
}
Expand Down
5 changes: 2 additions & 3 deletions src/CSnakes/Reflection/TypeReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType)
"Optional" => AsPredefinedType(pythonType.Arguments[0]),
"Generator" => CreateGeneratorType(pythonType.Arguments[0], pythonType.Arguments[1], pythonType.Arguments[2]),
// Todo more types... see https://docs.python.org/3/library/stdtypes.html#standard-generic-classes
_ => SyntaxFactory.ParseTypeName("PyObject"),// TODO : Should be nullable?
_ => SyntaxFactory.ParseTypeName("PyObject"),
};
}
return pythonType.Name switch
Expand All @@ -36,8 +36,7 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType)
"float" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)),
"bool" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
"bytes" => SyntaxFactory.ParseTypeName("byte[]"),
// Todo more types...
_ => SyntaxFactory.ParseTypeName("PyObject"),// TODO : Should be nullable?
_ => SyntaxFactory.ParseTypeName("PyObject"),
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/Integration.Tests/Integration.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<None Remove="python\test_false_returns.py" />
<None Remove="python\test_generators.py" />
<None Remove="python\test_keywords.py" />
<None Remove="python\test_none.py" />
<None Remove="python\test_reserved.py" />
<None Remove="python\test_tuples.py" />
</ItemGroup>
Expand Down Expand Up @@ -49,6 +50,9 @@
<AdditionalFiles Include="python\test_keywords.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
<AdditionalFiles Include="python\test_none.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
<AdditionalFiles Include="python\test_reserved.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
Expand Down
23 changes: 23 additions & 0 deletions src/Integration.Tests/NoneTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using CSnakes.Runtime.Python;

namespace Integration.Tests;
public class NoneTests : IntegrationTestBase
{
[Fact]
public void TestReturnsNoneIsNone()
{
var mod = Env.TestNone();
using PyObject result = mod.ReturnsNone();
Assert.True(result.IsNone());
}

[Fact]
public void TestNullArgAsNone()
{
PyObject none = PyObject.None;
Assert.True(none.IsNone());
// Give to function
var mod = Env.TestNone();
Assert.True(mod.TestNoneResult(none));
}
}
5 changes: 5 additions & 0 deletions src/Integration.Tests/python/test_none.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def returns_none():
return None

def test_none_result(arg) -> bool:
return arg is None
Loading