From ee0497f5d24743175e275fdffe7e85689c29b79d Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sun, 1 Jun 2025 19:29:12 -0400 Subject: [PATCH 01/11] Fix expression precedence --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index eacbc4b..aa0323f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -122,7 +122,7 @@ def test(session, sqlalchemy): pytest_cmd = [ "pytest", "--pyargs", "sqlalchemy_mptt", "--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered" - ] + ["--cov-report", "xml"] if with_coverage else [] + session.posargs + ] + (["--cov-report", "xml"] if with_coverage else []) + session.posargs session.run(*pytest_cmd) From 0964da77ec3d9dc25514e0a9ca9d895ed415caf4 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Wed, 4 Jun 2025 23:12:12 -0400 Subject: [PATCH 02/11] Update formatting --- noxfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index aa0323f..956b23c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -121,7 +121,8 @@ def test(session, sqlalchemy): with_coverage = True pytest_cmd = [ "pytest", "--pyargs", "sqlalchemy_mptt", - "--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered" + "--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered", + "-W", "error:::sqlalchemy_mptt" ] + (["--cov-report", "xml"] if with_coverage else []) + session.posargs session.run(*pytest_cmd) From 053dcad3c73609ef9de96aacb03e9e623c74fd27 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Wed, 4 Jun 2025 23:18:53 -0400 Subject: [PATCH 03/11] Consume all descendants on deletion --- sqlalchemy_mptt/tests/test_stateful.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index b2696a4..58e006d 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -3,7 +3,7 @@ from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule from sqlalchemy import Column, Integer, Boolean, create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, was_deleted from sqlalchemy_mptt import BaseNestedSets, mptt_sessionmaker @@ -44,6 +44,14 @@ def add_root_node(self, visible): @rule(node=consumes(node)) def delete_node(self, node): assume(node in self.session) + # Consume all descendants of the node + for name, value in self.names_to_values.copy().items(): + if node.is_ancestor_of(value): + for var_reference in self.bundles["node"][:]: + if var_reference.name == name: + self.bundles["node"].remove(var_reference) + # Remove the object as well for garbage collection + del self.names_to_values[name] self.session.delete(node) self.session.flush() From 9fa06d05208a7e465b7b8a5a88e48a1e2e4e5e5a Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Wed, 4 Jun 2025 23:41:16 -0400 Subject: [PATCH 04/11] Use posargs as a substitute --- noxfile.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/noxfile.py b/noxfile.py index 956b23c..ce2bc04 100644 --- a/noxfile.py +++ b/noxfile.py @@ -119,11 +119,13 @@ def test(session, sqlalchemy): with_coverage = False else: with_coverage = True - pytest_cmd = [ - "pytest", "--pyargs", "sqlalchemy_mptt", - "--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered", - "-W", "error:::sqlalchemy_mptt" - ] + (["--cov-report", "xml"] if with_coverage else []) + session.posargs + pytest_cmd = ["pytest"] + ( + session.posargs or [ + "--pyargs", "sqlalchemy_mptt", + "--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered", + "-W", "error:::sqlalchemy_mptt" + ] + ) + (["--cov-report", "xml"] if with_coverage else []) session.run(*pytest_cmd) From 6206bb3a0847e7545e1df447a6a53383c1d9e650 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Thu, 5 Jun 2025 00:20:13 -0400 Subject: [PATCH 05/11] Remove unused import --- sqlalchemy_mptt/tests/test_stateful.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index 58e006d..8486932 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -3,7 +3,7 @@ from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule from sqlalchemy import Column, Integer, Boolean, create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, was_deleted +from sqlalchemy.orm import sessionmaker from sqlalchemy_mptt import BaseNestedSets, mptt_sessionmaker From 971e968145171b9bd108609fd29737b3ff473eec Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sat, 7 Jun 2025 19:45:13 -0400 Subject: [PATCH 06/11] Suppress health check --- sqlalchemy_mptt/tests/test_stateful.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index 8486932..bccbb48 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -109,5 +109,5 @@ def validate_get_tree_node_for_custom_query(node_response): # Export the stateful test case TestTreeStates = TreeStateMachine.TestCase TestTreeStates.settings = settings( - max_examples=75, stateful_step_count=25 + max_examples=75, stateful_step_count=25, suppress_health_check=[HealthCheck.too_slow] ) From f700d06d574f45026b48bf1d52e5c0a355c1ac2f Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sat, 7 Jun 2025 19:46:36 -0400 Subject: [PATCH 07/11] Remove given from invariant --- sqlalchemy_mptt/tests/test_stateful.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index bccbb48..570dbd9 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -74,14 +74,16 @@ def check_get_tree_integrity(self): validate_get_tree_node(node) @invariant() - @given(st.none() | st.booleans()) - def check_get_tree_with_custom_query(self, visible): + def check_get_tree_with_custom_query(self): """Check that get_tree response is valid with custom queries.""" - response = Tree.get_tree(self.session, query=lambda x: x.filter_by(visible=visible)) - assert isinstance(response, list) - for node in response: - self.session.refresh(node['node']) - validate_get_tree_node_for_custom_query(node) + for visible in [None, True, False]: + response = Tree.get_tree( + self.session, + query=lambda x: x.filter_by(visible=visible) + .execution_options(populate_existing=True).options(joinedload(Tree.children))) + assert isinstance(response, list) + for node in response: + validate_get_tree_node_for_custom_query(node) def validate_get_tree_node(node_response, level=1): From 21028e969c7d74629f2011dfa5f07078a2a13547 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sat, 7 Jun 2025 19:46:54 -0400 Subject: [PATCH 08/11] Optimize test rules --- sqlalchemy_mptt/tests/test_stateful.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index 570dbd9..7045793 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -1,9 +1,9 @@ """Test cases written using Hypothesis stateful testing framework.""" -from hypothesis import assume, given, settings, strategies as st +from hypothesis import HealthCheck, settings, strategies as st from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule from sqlalchemy import Column, Integer, Boolean, create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import joinedload, sessionmaker from sqlalchemy_mptt import BaseNestedSets, mptt_sessionmaker @@ -37,40 +37,39 @@ def __init__(self): def add_root_node(self, visible): node = Tree(visible=visible) self.session.add(node) - self.session.flush() + self.session.commit() assert node.left < node.right return node @rule(node=consumes(node)) def delete_node(self, node): - assume(node in self.session) # Consume all descendants of the node - for name, value in self.names_to_values.copy().items(): - if node.is_ancestor_of(value): + for name, value in list(self.names_to_values.items()): + if value not in self.session or node.is_ancestor_of(value): for var_reference in self.bundles["node"][:]: if var_reference.name == name: self.bundles["node"].remove(var_reference) # Remove the object as well for garbage collection del self.names_to_values[name] self.session.delete(node) - self.session.flush() + self.session.commit() @rule(target=node, node=node, visible=st.none() | st.booleans()) def add_child(self, node, visible): - assume(node in self.session) child = Tree(parent=node, visible=visible) self.session.add(child) - self.session.flush() + self.session.commit() assert node.left < child.left < child.right < node.right return child @invariant() def check_get_tree_integrity(self): """Check that get_tree response is valid after each operation.""" - response = Tree.get_tree(self.session) + response = Tree.get_tree( + self.session, + query=lambda x: x.execution_options(populate_existing=True).options(joinedload(Tree.children))) assert isinstance(response, list) for node in response: - self.session.refresh(node['node']) validate_get_tree_node(node) @invariant() From 16f8733be3b7d2994d10c1c217dcf070b44e2ce1 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sun, 8 Jun 2025 12:09:06 -0400 Subject: [PATCH 09/11] Add copyright notice --- sqlalchemy_mptt/tests/test_stateful.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index 7045793..1fc8e9a 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2025 fayazkhan +# +# Distributed under terms of the MIT license. """Test cases written using Hypothesis stateful testing framework.""" from hypothesis import HealthCheck, settings, strategies as st from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule @@ -110,5 +116,5 @@ def validate_get_tree_node_for_custom_query(node_response): # Export the stateful test case TestTreeStates = TreeStateMachine.TestCase TestTreeStates.settings = settings( - max_examples=75, stateful_step_count=25, suppress_health_check=[HealthCheck.too_slow] + max_examples=75, stateful_step_count=25#, suppress_health_check=[HealthCheck.too_slow] ) From 00385d5bd291fa927d48c905e7f75389fe3f8302 Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sun, 8 Jun 2025 12:09:57 -0400 Subject: [PATCH 10/11] Update copyright notice --- sqlalchemy_mptt/tests/test_stateful.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index 1fc8e9a..c1a43ba 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # vim:fenc=utf-8 # -# Copyright © 2025 fayazkhan +# Copyright (c) 2025 Fayaz Yusuf Khan # # Distributed under terms of the MIT license. """Test cases written using Hypothesis stateful testing framework.""" From 41705730aeb2339065dc313d194c612840163c6c Mon Sep 17 00:00:00 2001 From: Fayaz Yusuf Khan Date: Sun, 8 Jun 2025 12:10:54 -0400 Subject: [PATCH 11/11] Revert suppress --- sqlalchemy_mptt/tests/test_stateful.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_mptt/tests/test_stateful.py b/sqlalchemy_mptt/tests/test_stateful.py index c1a43ba..4003e74 100644 --- a/sqlalchemy_mptt/tests/test_stateful.py +++ b/sqlalchemy_mptt/tests/test_stateful.py @@ -116,5 +116,5 @@ def validate_get_tree_node_for_custom_query(node_response): # Export the stateful test case TestTreeStates = TreeStateMachine.TestCase TestTreeStates.settings = settings( - max_examples=75, stateful_step_count=25#, suppress_health_check=[HealthCheck.too_slow] + max_examples=75, stateful_step_count=25, suppress_health_check=[HealthCheck.too_slow] )