Skip to content

Commit

Permalink
add doctests about loading data from schemas_yaml
Browse files Browse the repository at this point in the history
closes #100
  • Loading branch information
mscarey committed May 23, 2021
1 parent 785d6b6 commit 2417f10
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 11 deletions.
7 changes: 3 additions & 4 deletions authorityspoke/evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ def _implies_if_concrete(

def __str__(self):
"""Represent object as string without line breaks."""
string = (
f'{("attributed to " + self.statement_attribution.short_string) if self.statement_attribution else ""}'
+ f'{(", asserting " + self.statement.short_string + ",") if self.statement else ""}'
)
string = f'{("attributed to " + self.statement_attribution.short_string) if self.statement_attribution else ""}'
if self.statement:
string += ", asserting " + self.statement.short_string + ","
string = super().__str__().format(string)
return string.replace("exhibit", self.form or "exhibit").strip()

Expand Down
24 changes: 23 additions & 1 deletion authorityspoke/io/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from authorityspoke.rules import Rule

from authorityspoke.io import filepaths, readers
from authorityspoke.io.fake_enactments import FakeClient
from authorityspoke.io.name_index import Mentioned
from authorityspoke.io.schemas_yaml import (
RawEnactment,
Expand Down Expand Up @@ -165,7 +166,28 @@ def read_anchored_holdings_from_file(
filepath: Optional[pathlib.Path] = None,
client: Optional[Client] = None,
) -> AnchoredHoldings:
"""Read holdings from file, with Opinion text anchors for holdings and factors."""
"""
Read holdings from file, with Opinion text anchors for holdings and factors.
This function can accept a file containing Holding summaries in the YAML format
that may contain abbreviations and expandable named entities.
In the example below, a FakeClient is used to add fields to Enactments in the Holding
objects, to avoid using the network or making API calls. The real LegisClient class
would also have worked (with an appropriate API key).
>>> filepath = filepaths.make_filepath(filename="holding_mazza_alaluf.yaml")
>>> with open(filepath, "r") as f:
... yaml.safe_load(f)
{'holdings': [{'inputs': [{'type': 'fact', 'content': "{Mazza-Alaluf} used Mazza-Alaluf's business {Turismo Costa Brava} to commit the New York offense of engaging in the business of receiving money for transmission or transmitting the same, without a license therefor"}], 'outputs': [{'type': 'fact', 'content': 'Mazza-Alaluf operated Turismo Costa Brava without an appropriate money transmitting license in a State where such operation was punishable as a misdemeanor or a felony under State law', 'anchors': "we conclude that sufficient evidence supports Mazza-Alaluf's convictions under 18 U.S.C. § 1960(b)(1)(A) for conspiring to operate and operating a money transmitting business without appropriate state licenses.", 'name': 'operated without license'}], 'enactments': [{'node': '/us/usc/t18/s1960/b/1', 'anchors': 'state money transmitting licenses, see |18 U.S.C. § 1960(b)(1)(A)|', 'exact': 'is operated without an appropriate money transmitting license in a State where such operation is punishable as a misdemeanor or a felony under State law, whether or not the defendant knew that the operation was required to be licensed or that the operation was so punishable', 'name': 'state money transmitting license provision'}], 'universal': True}, {'inputs': ['operated without license', {'type': 'fact', 'content': 'Mazza-Alaluf operated Turismo Costa Brava as a business', 'anchors': 'Mazza-Alaluf does not contest that he owned and managed Turismo'}, {'type': 'fact', 'content': 'Turismo Costa Brava was a money transmitting business', 'anchors': 'record evidence that Turismo conducted substantial money transmitting business in the three states'}], 'despite': [{'type': 'fact', 'content': 'Turismo Costa Brava was a domestic financial institution', 'truth': False, 'anchors': 'without respect to whether or not Turismo was a "domestic financial institution"'}], 'outputs': [{'type': 'fact', 'content': 'Mazza-Alaluf committed the offense of conducting a money transmitting business without a license required by state law', 'anchors': 'a crime to operate a money transmitting business without appropriate state licenses,'}], 'enactments': ['state money transmitting license provision'], 'enactments_despite': [{'node': '/us/usc/t31/s5312/b/1', 'anchors': ['§ 5312(b)(1) (defining "domestic financial institution")']}], 'anchors': [{'prefix': 'Accordingly, we conclude that the', 'suffix': 'In any event'}], 'universal': True, 'mandatory': True}]}
>>> fake_client = FakeClient.from_file("usc.json")
>>> result = read_anchored_holdings_from_file(filepath=filepath, client=fake_client)
>>> selector = result.named_anchors["the fact it was false that <Turismo Costa Brava> was a domestic financial institution"][0]
>>> selector.exact
'without respect to whether or not Turismo was a "domestic financial institution"'
>>> print(result.holdings[0].outputs[0])
the fact that <Mazza-Alaluf> operated <Turismo Costa Brava> without an appropriate money transmitting license in a State where such operation was punishable as a misdemeanor or a felony under State law
"""
raw_holdings = load_anchored_holdings(
filename=filename, directory=directory, filepath=filepath
)
Expand Down
92 changes: 86 additions & 6 deletions authorityspoke/io/schemas_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from authorityspoke.io.nesting import nest_fields
from authorityspoke.io import text_expansion

from authorityspoke.opinions import Opinion, AnchoredHoldings
from authorityspoke.opinions import AnchoredHoldings
from authorityspoke.pleadings import Pleading, Allegation

from authorityspoke.procedures import Procedure
Expand Down Expand Up @@ -101,6 +101,27 @@ def make_object(self, data, **kwargs):


class EnactmentSchema(LegisliceSchema):
"""
Schema to load Enactments from JSON created from user-created YAML.
>>> enactment_data = {
... "name": "original works",
... "node": "/us/usc/t17/s102",
... "start_date": "1990-12-01",
... "content": (
... "Copyright protection subsists, in accordance with this title, "
... "in original works of authorship fixed in any tangible medium of expression, "
... "now known or later developed."),
... "selection": [{"suffix": ", in accordance"}, "title,|in original works of authorship|fixed"]}
>>> schema = EnactmentSchema()
>>> enactment_index = EnactmentIndex()
>>> enactment_index.insert_by_name(enactment_data)
>>> schema.context["enactment_index"] = enactment_index
>>> enactment = schema.load("original works")
>>> enactment.selected_text()
'Copyright protection subsists…in original works of authorship…'
"""

def get_indexed_enactment(self, data, **kwargs):
"""Replace data to load with any object with same name in "enactment_index"."""
if not isinstance(data, str):
Expand All @@ -117,7 +138,17 @@ def format_data_to_load(self, data, **kwargs):


class PredicateSchema(ExpandableSchema):
"""Schema for statements, separate from any claim about their truth or who asserts them."""
"""
Schema for statements, separate from any claim about their truth or who asserts them.
>>> data = {
... "content": "the distance that $officer pursued $suspect was >= 5 miles"
... }
>>> schema = PredicateSchema()
>>> predicate = schema.load(data)
>>> predicate.quantity
<Quantity(5, 'mile')>
"""

__model__ = Predicate
content = fields.Method(
Expand Down Expand Up @@ -174,7 +205,19 @@ def format_data_to_load(self, data: RawPredicate, **kwargs) -> RawPredicate:


class EntitySchema(ExpandableSchema):
"""Schema for Entities, which shouldn't be at the top level of a FactorGroup."""
"""
Schema for Entities, which shouldn't be at the top level of a FactorGroup.
>>> entity = {"name": "Lotus Development Corporation", "generic": False}
>>> schema = EntitySchema()
>>> factor_index = Mentioned()
>>> factor_index.insert_by_name(entity)
>>> schema.context["mentioned"] = factor_index
>>> loaded = schema.load("Lotus Development Corporation")
>>> loaded.generic
False
"""

__model__: Type = Entity
name = fields.Str(missing=None)
Expand All @@ -183,7 +226,16 @@ class EntitySchema(ExpandableSchema):


class FactSchema(ExpandableSchema):
"""Schema for Facts, which may contain arbitrary levels of nesting."""
"""
Schema for Facts, which may contain arbitrary levels of nesting.
>>> data = {"content": "the distance that {Officer Lin} pursued {Al} was >= 5 miles"}
>>> schema = FactSchema()
>>> fact = schema.load(data)
>>> print(fact)
the fact that the distance that {Officer Lin} pursued {Al} was at least 5 mile
"""

__model__: Type = Fact
predicate = fields.Nested(PredicateSchema)
Expand Down Expand Up @@ -269,7 +321,27 @@ def make_object(self, data: RawFactor, **kwargs) -> Fact:


class ExhibitSchema(ExpandableSchema):
"""Schema for an object that may embody a statement."""
"""
Schema for an object that may embody a statement.
>>> fact_data = {
... "content": "the distance that $officer pursued $suspect was >= 5 miles",
... "terms": [
... {"type": "Entity", "name": "Officer Lin"},
... {"type": "Entity", "name": "Al"},
... ],
... }
>>> exhibit_data = {
... "form": "testimony",
... "statement": fact_data,
... "statement_attribution": {"name": "Officer Lin"},
... }
>>> schema = ExhibitSchema()
>>> exhibit = schema.load(exhibit_data)
>>> print(exhibit)
the testimony attributed to <Officer Lin>, asserting the fact that the distance that <Officer Lin> pursued <Al> was at least 5 mile,
"""

__model__: Type = Exhibit
form = fields.Str(missing=None)
Expand Down Expand Up @@ -318,7 +390,15 @@ class EvidenceSchema(ExpandableSchema):


class FactorSchema(OneOfSchema, ExpandableSchema):
"""Schema that directs data to "one of" the other schemas."""
"""
Schema that directs data to "one of" the other schemas.
>>> factor_data = [{"type": "Entity", "name": "Al"}, {"type": "Entity", "name": "Bob"}]
>>> schema = FactorSchema(many=True)
>>> factors = schema.load(factor_data)
>>> factors[1].name
'Bob'
"""

__model__: Type = Factor
type_schemas = {
Expand Down
77 changes: 77 additions & 0 deletions example_data/responses/usc.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,82 @@
"url": "https://authorityspoke.com/api/v1/us/usc/t17/s410/c/",
"parent": "https://authorityspoke.com/api/v1/us/usc/t17/s410/"
}
},
"/us/usc/t18/s1960/b/1": {
"2013-07-18": {
"heading": "",
"start_date": "2013-07-18",
"node": "/us/usc/t18/s1960/b/1",
"text_version": {
"id": 943740,
"url": "https://authorityspoke.com/api/v1/textversions/943740/",
"content": "the term “unlicensed money transmitting business” means a money transmitting business which affects interstate or foreign commerce in any manner or degree and—"
},
"url": "https://authorityspoke.com/api/v1/us/usc/t18/s1960/b/1/",
"end_date": null,
"children": [
{
"heading": "",
"start_date": "2013-07-18",
"node": "/us/usc/t18/s1960/b/1/A",
"text_version": {
"id": 943737,
"url": "https://authorityspoke.com/api/v1/textversions/943737/",
"content": "is operated without an appropriate money transmitting license in a State where such operation is punishable as a misdemeanor or a felony under State law, whether or not the defendant knew that the operation was required to be licensed or that the operation was so punishable;"
},
"url": "https://authorityspoke.com/api/v1/us/usc/t18/s1960/b/1/A/",
"end_date": null,
"children": [],
"citations": []
},
{
"heading": "",
"start_date": "2013-07-18",
"node": "/us/usc/t18/s1960/b/1/B",
"text_version": {
"id": 943738,
"url": "https://authorityspoke.com/api/v1/textversions/943738/",
"content": "fails to comply with the money transmitting business registration requirements under section 5330 of title 31, United States Code, or regulations prescribed under such section; or"
},
"url": "https://authorityspoke.com/api/v1/us/usc/t18/s1960/b/1/B/",
"end_date": null,
"children": [],
"citations": []
},
{
"heading": "",
"start_date": "2013-07-18",
"node": "/us/usc/t18/s1960/b/1/C",
"text_version": {
"id": 943739,
"url": "https://authorityspoke.com/api/v1/textversions/943739/",
"content": "otherwise involves the transportation or transmission of funds that are known to the defendant to have been derived from a criminal offense or are intended to be used to promote or support unlawful activity;"
},
"url": "https://authorityspoke.com/api/v1/us/usc/t18/s1960/b/1/C/",
"end_date": null,
"children": [],
"citations": []
}
],
"citations": [],
"parent": "https://authorityspoke.com/api/v1/us/usc/t18/s1960/b/"
}
},
"/us/usc/t31/s5312/b/1": {
"2013-07-18": {
"heading": "",
"start_date": "2013-07-18",
"node": "/us/usc/t31/s5312/b/1",
"text_version": {
"id": 1267044,
"url": "https://authorityspoke.com/api/v1/textversions/1267044/",
"content": "“domestic financial agency” and “domestic financial institution” apply to an action in the United States of a financial agency or institution."
},
"url": "https://authorityspoke.com/api/v1/us/usc/t31/s5312/b/1/",
"end_date": null,
"children": [],
"citations": [],
"parent": "https://authorityspoke.com/api/v1/us/usc/t31/s5312/b/"
}
}
}
23 changes: 23 additions & 0 deletions tests/io/test_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,29 @@ def test_dump_complex_fact(self, make_complex_fact):
assert shooting_dict["terms"][0]["name"] == "Alice"


class TestExhibitLoad:
def test_load_exhibit_with_bracketed_names(self):
fact_data = {
"content": "the distance that $officer pursued $suspect was >= 5 miles",
"terms": [
{"type": "Entity", "name": "Officer Lin"},
{"type": "Entity", "name": "Al"},
],
}
exhibit_data = {
"form": "testimony",
"statement": fact_data,
"statement_attribution": {"name": "Officer Lin"},
}
schema = schemas_yaml.ExhibitSchema()
exhibit = schema.load(exhibit_data)
assert str(exhibit) == (
"the testimony attributed to <Officer Lin>, "
"asserting the fact that the distance that <Officer Lin> "
"pursued <Al> was at least 5 mile,"
)


class TestExhibitDump:
def test_dump_exhibit(self, make_exhibit):
exhibit = make_exhibit["shooting_affidavit"]
Expand Down
13 changes: 13 additions & 0 deletions tests/io/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ def test_load_and_read_yaml(self):
)
assert len(both_holdings_with_anchors.holdings) == 2

def test_load_from_fake_client(self):
fake_client = FakeClient.from_file("usc.json")
filepath = filepaths.make_filepath(filename="holding_mazza_alaluf.yaml")
result = read_anchored_holdings_from_file(filepath=filepath, client=fake_client)
selector = result.named_anchors[
"the fact it was false that <Turismo Costa Brava> was a domestic financial institution"
][0]
assert (
selector.exact
== 'without respect to whether or not Turismo was a "domestic financial institution"'
)
assert len(result.holdings) == 2


class TestLoadAndReadFake:
client = FakeClient.from_file("usc.json")
Expand Down

0 comments on commit 2417f10

Please sign in to comment.