Skip to content

Commit

Permalink
[Stdlib] Add PythonObject.__contains__
Browse files Browse the repository at this point in the history
Signed-off-by: rd4com <[email protected]>
  • Loading branch information
rd4com committed Jul 22, 2024
1 parent 491c145 commit 50f4cfd
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 28 deletions.
2 changes: 1 addition & 1 deletion stdlib/src/python/_cpython.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ struct CPython:
name: StringRef,
) -> Int:
var r = self.lib.get_function[
fn (PyObjectPtr, DTypePointer[DType.uint8]) -> Int
fn (PyObjectPtr, UnsafePointer[UInt8]) -> Int
]("PyObject_HasAttrString")(obj, name.data)
return r

Expand Down
2 changes: 1 addition & 1 deletion stdlib/src/python/object.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ struct PythonObject(
if cpython.PyObject_HasAttrString(self.py_object, "__contains__"):
return self._call_single_arg_method("__contains__", rhs).__bool__()
for v in self:
if v[] == rhs:
if v == rhs:
return True
return False

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Class_no_iterable_no_contains:
x = 1

class Class_no_iterable_but_contains:
x = 123
def __contains__(self, rhs):
return rhs == self.x

class Class_iterable_no_contains:
def __init__(self):
self.data = [123]

def __iter__(self):
self.i = 0
return self

def __next__(self):
if self.i >= len(self.data):
raise StopIteration
else:
tmp = self.data[self.i]
self.i += 1
return tmp
26 changes: 0 additions & 26 deletions stdlib/test/python/test_python_object.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -408,31 +408,6 @@ fn test_none() raises:
assert_true(n is None)


def test_contains_dunder():
with assert_raises(contains="'int' object is not iterable"):
var z = PythonObject(0)
_ = 5 in z

var x = PythonObject([1.1, 2.2])
assert_true(1.1 in x)
assert_false(3.3 in x)

x = PythonObject(["Hello", "World"])
assert_true("World" in x)

x = PythonObject((1.5, 2))
assert_true(1.5 in x)
assert_false(3.5 in x)

var y = Dict[PythonObject, PythonObject]()
y["A"] = "A"
y["B"] = 5
x = PythonObject(y)
assert_true("A" in x)
assert_false("C" in x)
assert_true("B" in x)


def main():
# initializing Python instance calls init_python
var python = Python()
Expand All @@ -447,4 +422,3 @@ def main():
test_dict()
test_none()
test_nested_object()
test_contains_dunder()
67 changes: 67 additions & 0 deletions stdlib/test/python/test_python_object_dunder_contains.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2024, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #
# XFAIL: asan && !system-darwin
# RUN: %mojo %s

from python import Python, PythonObject
from testing import assert_equal, assert_false, assert_raises, assert_true

def test_contains_dunder(inout python: Python):
with assert_raises(contains="'int' object is not iterable"):
var z = PythonObject(0)
_ = 5 in z

var x = PythonObject([1.1, 2.2])
assert_true(1.1 in x)
assert_false(3.3 in x)

x = PythonObject(["Hello", "World"])
assert_true("World" in x)

x = PythonObject((1.5, 2))
assert_true(1.5 in x)
assert_false(3.5 in x)

var y = Dict[PythonObject, PythonObject]()
y["A"] = "A"
y["B"] = 5
x = PythonObject(y)
assert_true("A" in x)
assert_false("C" in x)
assert_true("B" in x)

#tests with python modules:
module = python.import_module(
"module_for_test_python_object_dunder_contains"
)

x = module.Class_no_iterable_but_contains()
assert_true(123 in x)

x = module.Class_no_iterable_no_contains()
with assert_raises(
contains = "'Class_no_iterable_no_contains' object is not iterable"
):
_ = 123 in x

x = module.Class_iterable_no_contains()
assert_true(123 in x)
assert_false(234 in x)
x.data.append(234)
assert_true(234 in x)


def main():
# initializing Python instance calls init_python
var python = Python()
test_contains_dunder(python)
39 changes: 39 additions & 0 deletions stdlib/test/python/test_testing_evironement_is_clean_A.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2024, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #
# XFAIL: asan && !system-darwin
# RUN: %mojo %s

from python import Python, PythonObject
from testing import assert_equal, assert_false, assert_raises, assert_true

# Goal: assert that there are no pollutions between tests.
# Invariant: test A is performed before test B
# The tests are in pairs:
# - test_testing_environement_is_clean_A
# (modify the environement)
# - test_testing_environement_is_clean_B
# (check that modifications are not reflected there)

def test_populate_environement_a(inout python: Python):
python.eval("x = 123")
assert_equal(int(python.evaluate("x")), 123)

_ = python.import_module("my_module")
modules = python.import_module("sys").modules.keys()
assert_true(
modules.__getattr__("__contains__")("my_module").__bool__()
)

def main():
var python = Python()
test_populate_environement_a(python)
33 changes: 33 additions & 0 deletions stdlib/test/python/test_testing_evironement_is_clean_B.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2024, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #
# XFAIL: asan && !system-darwin
# RUN: %mojo %s

from python import Python, PythonObject
from testing import assert_equal, assert_false, assert_raises, assert_true

# Goal: assert that there are no pollutions between tests.
# see test_testing_environement_is_clean_A for description.

def test_populate_environement_b(inout python: Python):
with assert_raises(contains = "name 'x' is not defined"):
_ = python.evaluate("x")

modules = python.import_module("sys").modules.keys()
assert_false(
modules.__getattr__("__contains__")("my_module").__bool__()
)

def main():
var python = Python()
test_populate_environement_b(python)

0 comments on commit 50f4cfd

Please sign in to comment.