Skip to content

Commit

Permalink
Holding.init checks exclusive param
Browse files Browse the repository at this point in the history
but the checks still happen again when getting implied Rules
  • Loading branch information
mscarey committed Apr 5, 2021
1 parent 13d4ce6 commit b6ab60f
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 21 deletions.
1 change: 1 addition & 0 deletions authorityspoke/holdings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def __post_init__(self):
+ "not implemented. Try expressing this in another way "
+ "without the 'exclusive' keyword."
)
self.rule.procedure.valid_for_exclusive_tag()

@classmethod
def from_factors(
Expand Down
24 changes: 24 additions & 0 deletions authorityspoke/procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,27 @@ def union(
except StopIteration:
return None
return self._union_from_explanation(other, explanation)

def valid_for_exclusive_tag(self) -> None:
if len(self.outputs) != 1:
raise ValueError(
"The 'exclusive' attribute is not allowed for Holdings "
+ "with more than one 'output' Factor. If the set of Factors "
+ "in 'inputs' is really the only way to reach any of the "
+ "'outputs', consider making a separate 'exclusive' Rule "
+ "for each output."
)
if self.outputs[0].absent:
raise ValueError(
"The 'exclusive' attribute is not allowed for Holdings "
+ "with an 'absent' 'output' Factor. This would indicate "
+ "that the output can or must be present in every litigation "
+ "unless specified inputs are present, which is unlikely."
)
if not self.inputs:
raise ValueError(
"The 'exclusive' attribute is not allowed for Holdings "
+ "with no 'input' Factors, since the 'exclusive' attribute "
+ "asserts that the inputs are the only way to reach the output."
)
return None
22 changes: 1 addition & 21 deletions authorityspoke/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,27 +202,7 @@ def get_contrapositives(self) -> Iterator[Rule]:
:returns:
iterator yielding :class:`Rule`\s.
"""

if len(self.outputs) != 1:
raise ValueError(
"The 'exclusive' attribute is not allowed for Rules "
+ "with more than one 'output' Factor. If the set of Factors "
+ "in 'inputs' is really the only way to reach any of the "
+ "'outputs', consider making a separate 'exclusive' Rule "
+ "for each output."
)
if self.outputs[0].absent:
raise ValueError(
"The 'exclusive' attribute is not allowed for Rules "
+ "with an 'absent' 'output' Factor. This would indicate "
+ "that the output can or must be present in every litigation "
+ "unless specified inputs are present, which is unlikely."
)
if not self.inputs:
raise ValueError(
"The 'exclusive' attribute is not allowed for Rules "
+ "with no 'input' Factors."
)
self.procedure.valid_for_exclusive_tag()

for input_factor in self.inputs:
result = deepcopy(self)
Expand Down
24 changes: 24 additions & 0 deletions tests/test_holdings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dotenv import load_dotenv
from legislice.download import Client

from authorityspoke.procedures import Procedure
from authorityspoke.rules import Rule
from authorityspoke.holdings import Holding

from nettlesome.terms import ContextRegister, TermSequence
Expand Down Expand Up @@ -46,6 +48,28 @@ def test_case_class_name_for_fact_within_holding(self, make_opinion_with_holding
assert "the Fact that <Lotus" not in str(lotus.holdings[2])
assert "the fact that <Lotus" in str(lotus.holdings[2])

def test_holding_without_inputs_not_exclusive(self, make_factor):
with pytest.raises(ValueError):
Holding(Rule(Procedure(outputs=make_factor["f_no_crime"])), exclusive=True)

def test_holding_with_absent_output_not_exclusive(self, make_exhibit):
with pytest.raises(ValueError):
Holding(
Rule(Procedure(outputs=make_exhibit["reciprocal_testimony_absent"])),
exclusive=True,
)

def test_holding_with_two_outputs_not_exclusive(self, make_factor):
with pytest.raises(ValueError):
Holding(
Rule(
Procedure(
outputs=[make_factor["f_no_crime"], make_factor["f_shooting"]]
)
),
exclusive=True,
)

def test_infer_from_exclusive(self, make_opinion_with_holding):
"""
Test that the correct inference is made from a Holding being marked
Expand Down
14 changes: 14 additions & 0 deletions tests/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def test_new_concrete_context(self, make_holding):
)
assert "<He-Man> operated" in str(different)

def test_terms_of_rule_are_its_factors(self, make_rule):
assert len(make_rule["h1"].terms) == 3

def test_new_context_non_generic(self, make_holding, watt_factor):
changes = ContextRegister()
changes.insert_pair(watt_factor["f1"], watt_factor["f7"])
Expand All @@ -64,6 +67,13 @@ def test_new_context_non_generic_from_list_error(self, make_holding, watt_factor
[watt_factor["f1"], watt_factor["f7"], watt_factor["f2"]]
)

def test_generic_term_by_str(self, make_rule):
holding = make_rule["h2"]
assert len(holding.generic_terms_by_str()) == 2
holding.generic = True
result = holding.generic_terms_by_str()
assert len(result) == 1

def test_cannot_change_context_with_predicates(self, make_holding, make_predicate):
with pytest.raises(TypeError):
make_holding["h1"].new_context({make_predicate["p1"]: make_predicate["p7"]})
Expand Down Expand Up @@ -604,6 +614,10 @@ def test_add_factor_to_rule(self, make_complex_rule, make_factor):
)
assert two_input_rule.means(c["accept_murder_fact_from_relevance_and_shooting"])

def test_cannot_add_holding_to_rule(self, make_rule, make_holding):
with pytest.raises(TypeError):
make_rule["h1"] + make_holding["h1"]

def test_add_enactment_to_rule_reverse(self, make_complex_rule, e_due_process_5):
"""
Test that you can make a new version of a :class:`.Rule`,
Expand Down

0 comments on commit b6ab60f

Please sign in to comment.