Legislice Documentation¶
Release v. 0.7.0.
Legislice is a utility for downloading the text of statutes and constitutional provisions, and then creating computable objects representing passages from those provisions.
Guides¶
To access interactive versions of these guides in Jupyter Notebooks, you can download the Legislice repository on GitHub.
Downloading Legislation¶
Legislice is a utility for downloading the text of statutes and constitutional provisions, and then creating computable objects representing passages from those provisions. This guide will show how to get started.
Legislice depends on the AuthoritySpoke API as its source of legislative text. Currently the API serves the text of the United States Constitution, plus versions of the United States Code in effect since 2013. Provisions of the United States Code that were repealed prior to 2013 aren’t yet available through the API, and neither are any regulations or any state law.
Using an API token¶
To get started, make an account on authorityspoke.com. Then go to the User Profile page, where you can find your API token. The token is a 40-character string of random letters and numbers. You’ll be sending this token to AuthoritySpoke to validate each API request, and you should keep it secret as you would a password.
There are several ways for Python to access your API token. One way would be to simply define it as a Python string, like this:
>>> TOKEN = "YOU_COULD_PUT_YOUR_API_TOKEN_HERE"
However, a better option is to make your API token an environment
variable, and then use Python to access that variable. Using a Python
library called dotenv, you
can define an environment variable in a file called .env
in the root
of your project directory. For instance, the contents of the file
.env
could look like this:
LEGISLICE_API_TOKEN=YOUR_API_TOKEN_GOES_HERE
By doing this, you can avoid having a copy of your API token in your Python working file or notebook. That makes it easier to avoid accidentally publishing the API token or sharing it with unauthorized people.
Here’s an example of loading an API token from a .env
file using
dotenv
.
>>> import os
>>> from dotenv import load_dotenv
>>> load_dotenv()
True
>>> TOKEN = os.getenv("LEGISLICE_API_TOKEN")
Now you can use the API token to create a Legislice Client
object.
This object holds your API token, so you can reuse the Client
without re-entering your API token repeatedly.
>>> from legislice.download import Client
>>> client = Client(api_token=TOKEN)
Fetching a provision from the API¶
To download legislation using the Client
, we must specify a
path
to the provision we want, and optionally we can specify the
date
of the version of the provision we want. If we don’t specify
a date, we’ll be given the most recent version of the provision.
The path
citation format is based on the section identifiers in the
United States Legislative Markup
standard,
which is a United States government standard used by the Office of the
Law Revision Counsel for publishing the United States Code. Similar to a
URL path in a web address, the path
format is a series of labels
connected with forward slashes. The first part identifies the
jurisdiction, the second part (if any) identifies the legislative code
within that jurisdiction, the third part identifies the next-level division
of the code such as a numbered title, and so on.
If we don’t know the right citation for the provision we want, we can
sign in to an AuthoritySpoke account and
browse the directory of available
provisions, where the links
to each provision show the correct path
for that provision. Or we can browse an HTML
version of the API itself. If
the error message “Authentication credentials were not
provided” appears, that means we aren’t signed in, and we might want to go
back to the login page.
The fetch()
method makes an API call to AuthoritySpoke, and
returns JSON that is been converted to a Python dict
. There are
fields representing the content
of the provision, the start_date
when the provision went into effect, and more.
Here’s an example of how to fetch the text of the Fourth Amendment using
the Client
.
>>> data = client.fetch(query="/us/const/amendment/IV")
>>> data
{'heading': 'AMENDMENT IV.',
'start_date': '1791-12-15',
'node': '/us/const/amendment/IV',
'text_version': {
'id': 735706,
'url': 'https://authorityspoke.com/api/v1/textversions/735706/',
'content': 'The right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures, shall not be violated, and no Warrants shall issue, but upon probable cause, supported by Oath or affirmation, and particularly describing the place to be searched, and the persons or things to be seized.'},
'url': 'https://authorityspoke.com/api/v1/us/const/amendment/IV/',
'end_date': None,
'children': [],
'citations': [],
'parent': 'https://authorityspoke.com/api/v1/us/const/amendment/'}
Loading an Enactment object¶
If all we needed was to get a JSON response from the API, we could
have used a more general Python library like requests
. Legislice
also lets us load the JSON response as a legislice.enactments.Enactment
object, which
has methods for selecting some but not all of the provision’s
text. One way to load an Enactment
is with the
Client
’s read_from_json()
method.
>>> fourth_a = client.read_from_json(data)
>>> fourth_a.node
'/us/const/amendment/IV'
Instead of always using fetch()
followed by
read_from_json()
, we can combine the two functions together
with read()
. In this example, we’ll use
read()
to load a
constitutional amendment that contains subsections, to show that the
structure of the amendment is preserved in the resulting
Enactment
object.
>>> thirteenth_a = client.read(query="/us/const/amendment/XIII")
The string representation of this provision includes both the selected text (which is the full text of the amendment) as well as a citation to the provision with its effective date.
Currently the only supported citation format is the path-style citation used in United States Legislative Markup. Future versions of Legislice may support the ability to convert to traditional statute citation styles.
>>> str(thirteenth_a)
'/us/const/amendment/XIII (1865-12-18)'
The text of the Thirteenth Amendment is all within Section 1 and Section
2 of the amendment. You can use the Enactment.children
property to
get a list of provisions contained within an Enactment
.
>>> len(thirteenth_a.children)
2
Then we can access each child provision as its own Enactment
object
from the children
list. Remember that lists in Python start at index
0, so if we want Section 2, we’ll find it at index 1 of the
children
list.
>>> str(thirteenth_a.children[1].text)
'Congress shall have power to enforce this article by appropriate legislation.'
Downloading prior versions of an Enactment¶
The API can be used to access specific provisions deeply nested within
the United States Code, and also to access multiple date versions of the
same provision. Here’s a subsection of an appropriations statute as of
2015. We can use the end_date
attribute to find when this version of
the statute was displaced by a new version.
>>> old_grant_objective = client.read(query="/us/usc/t42/s300hh-31/a/1", date="2015-01-01")
>>> old_grant_objective.content
'strengthening epidemiologic capacity to identify and monitor the occurrence of infectious diseases and other conditions of public health importance;'
>>> old_grant_objective.end_date
datetime.date(2019, 7, 5)
And here’s the same provision as of 2020. Its content has changed.
>>> new_grant_objective = client.read(query="/us/usc/t42/s300hh-31/a/1", date="2020-01-01")
>>> new_grant_objective.content
'strengthening epidemiologic capacity to identify and monitor the occurrence of infectious diseases, including mosquito and other vector-borne diseases, and other conditions of public health importance;'
The 2020 version of the statute has None
in its end_date
field
because it’s still in effect.
>>> str(new_grant_objective.end_date)
'None'
Exploring the structure of a legislative code¶
When we query the API for a provision at a path with less than four parts (e.g., when we query for an entire Title of the United States Code), the response doesn’t include the full text of the provision’s children. Instead, it only contains URLs that link to the child nodes. These URL links might help to automate the process of navigating the API and discovering the provisions we want. Here’s an example that discovers the URLs for the articles of the US Constitution.
>>> articles = client.read(query="/us/const/article")
>>> articles.children
['https://authorityspoke.com/api/v1/us/const/article/I/', 'https://authorityspoke.com/api/v1/us/const/article/II/', 'https://authorityspoke.com/api/v1/us/const/article/III/', 'https://authorityspoke.com/api/v1/us/const/article/IV/', 'https://authorityspoke.com/api/v1/us/const/article/V/', 'https://authorityspoke.com/api/v1/us/const/article/VI/', 'https://authorityspoke.com/api/v1/us/const/article/VII/']
Downloading Enactments from cross-references¶
If an Enactment
loaded from the API references other provisions, it may
provide a list of CrossReference
objects when we call its
cross_references()
method. You can pass one of these
CrossReference
objects to the
fetch()
or
read()
method of
the download client to get the referenced Enactment
.
>>> infringement_provision = client.read("/us/usc/t17/s109/b/4")
>>> str(infringement_provision.text)
'Any person who distributes a phonorecord or a copy of a computer program (including any tape, disk, or other medium embodying such program) in violation of paragraph (1) is an infringer of copyright under section 501 of this title and is subject to the remedies set forth in sections 502, 503, 504, and 505. Such violation shall not be a criminal offense under section 506 or cause such person to be subject to the criminal penalties set forth in section 2319 of title 18.'
>>> len(infringement_provision.cross_references())
2
>>> str(infringement_provision.cross_references()[0])
'CrossReference(target_uri="/us/usc/t17/s501", reference_text="section 501 of this title")'
>>> reference_to_title_18 = infringement_provision.cross_references()[1]
>>> referenced_enactment = client.read(reference_to_title_18)
>>> referenced_enactment.text[:239]
'Any person who violates section 506(a) (relating to criminal offenses) of title 17 shall be punished as provided in subsections (b), (c), and (d) and such penalties shall be in addition to any other provisions of title 17 or any other law.'
An important caveat for this feature is that the return value of the
cross_references()
method will only be populated with internal links
that have been marked up in the United States Legislative Markup XML
published by the legislature. Unfortunately, some parts of the United
States Code don’t include any link markup when making references to
other legislation.
Downloading Enactments from inbound citations¶
The method in the previous section finds and downloads Enactments
cited by a known Enactment
. But sometimes we want to discover
provisions that cite to a particular provision. These “inbound”
citations are not stored on the Python Enactment object. Instead, we
have to go back to the download client and make an API request to get
them, using the citations_to()
method.
In this example, we’ll get all the citations to the provision of the
United States Code cited /us/usc/t17/s501
(in other
words, Title 17, Section 501).
This gives us all known provisions that cite to that node
in the document tree, regardless of whether different text has been
enacted at that node at different times.
>>> inbound_refs = client.citations_to("/us/usc/t17/s501")
>>> str(inbound_refs[0])
'InboundReference to /us/usc/t17/s501, from (/us/usc/t17/s109/b/4 2013-07-18)'
We can examine one of these InboundReference
objects to
see the text creating the citation.
>>> inbound_refs[0].content
'Any person who distributes a phonorecord or a copy of a computer program (including any tape, disk, or other medium embodying such program) in violation of paragraph (1) is an infringer of copyright under section 501 of this title and is subject to the remedies set forth in sections 502, 503, 504, and 505. Such violation shall not be a criminal offense under section 506 or cause such person to be subject to the criminal penalties set forth in section 2319 of title 18.'
But an InboundReference
doesn’t have all the same information as an
Enactment
object. Importantly, it doesn’t have the text of any
subsections nested inside the cited provision. We can use the download
Client
again to convert the InboundReference into an Enactment.
>>> citing_enactment = client.read(inbound_refs[0])
>>> citing_enactment.node
'/us/usc/t17/s109/b/4'
>>> citing_enactment.text
'Any person who distributes a phonorecord or a copy of a computer program (including any tape, disk, or other medium embodying such program) in violation of paragraph (1) is an infringer of copyright under section 501 of this title and is subject to the remedies set forth in sections 502, 503, 504, and 505. Such violation shall not be a criminal offense under section 506 or cause such person to be subject to the criminal penalties set forth in section 2319 of title 18.'
This Enactment happens not to have any child nodes nested within it, so its full text is the same as what we saw when we looked at the InboundReference’s content attribute.
>>> citing_enactment.children
[]
Sometimes, an InboundReference
has more than one citation and start
date. That means that the citing text has been enacted in different
places at different times. This can happen because the provisions of a
legislative code have been reorganized and renumbered. Here’s an
example. We’ll look for citations
to Section 1301 of USC Title 2, which
is a section containing definitions of terms that will be used
throughout the rest of Title 2.
>>> refs_to_definitions = client.citations_to("/us/usc/t2/s1301")
>>> str(refs_to_definitions[0])
'InboundReference to /us/usc/t2/s1301, from (/us/usc/t2/s4579/a/4/A 2018-05-09) and 2 other locations'
The citations_to()
method returns a list,
and two of the InboundReferences in this list have been enacted in three different
locations.
>>> str(refs_to_definitions[0].locations[0])
'(/us/usc/t2/s60c-5/a/2/A 2013-07-18)'
When we pass an InboundReference to read()
, the download client
makes an Enactment
from the most recent location where the citing
provision has been enacted.
>>> str(client.read(refs_to_definitions[0]))
'/us/usc/t2/s4579/a/4/A (2018-05-09)'
If we need the Enactment
representing the statutory text before it was
moved and renumbered, we can pass one of the CitingProvisionLocation
objects to the Client
instead. Note that the Enactment we get
this way has the same content text, but a different citation node, an
earlier start date, and an earlier end date.
>>> citing_enactment_before_renumbering = client.read(refs_to_definitions[0].locations[0])
>>> str(citing_enactment_before_renumbering)
'/us/usc/t2/s60c-5/a/2/A (2013-07-18)'
>>> citing_enactment_before_renumbering.end_date
datetime.date(2014, 1, 16)
Comparing Enactments¶
This notebook will show how legislice
provides convenient
functions for comparing passages of legislative text.
As explained in the Downloading Legislation guide,
begin by creating a Client
to download
and create Enactment
objects.
>>> import os
>>> from dotenv import load_dotenv
>>> from legislice.download import Client
>>> load_dotenv()
True
>>> TOKEN = os.getenv("LEGISLICE_API_TOKEN")
>>> client = Client(api_token=TOKEN)
Features of an Enactment¶
Now let’s load the Fourteenth Amendment. We can get its effective date
as a Python datetime.date
object.
>>> fourteenth_amendment = client.read(query="/us/const/amendment/XIV")
>>> fourteenth_amendment.start_date
datetime.date(1868, 7, 28)
We can also view its level
, which is “constitution”, as opposed to
“statute” or “regulation”.
>>> fourteenth_amendment.level
<CodeLevel.CONSTITUTION: 1>
And we can find out the node
, or place in the document tree, for
the Fourteenth Amendment itself as well at its subsections.
>>> fourteenth_amendment.node
'/us/const/amendment/XIV'
>>> fourteenth_amendment.children[0].node
'/us/const/amendment/XIV/1'
We can also isolate some parts of the node
path, such as the
jurisdiction
and code
the amendment comes from.
>>> fourteenth_amendment.jurisdiction
'us'
>>> fourteenth_amendment.code
'const'
Selecting text¶
When we use the text()
method,
we get all the enacted text
in the Fourteenth Amendment,
including all its subsections.
>>> fourteenth_amendment.text
'All persons born or naturalized in the United States, and subject to the jurisdiction thereof, are citizens of the United States and of the State wherein they reside. No State shall make or enforce any law which shall abridge the privileges or immunities of citizens of the United States; nor shall any State deprive any person of life, liberty, or property, without due process of law; nor deny to any person within its jurisdiction the equal protection of the laws. Representatives shall be apportioned among the several States according to their respective numbers, counting the whole number of persons in each State, excluding Indians not taxed. But when the right to vote at any election for the choice of electors for President and Vice President of the United States, Representatives in Congress, the Executive and Judicial officers of a State, or the members of the Legislature thereof, is denied to any of the male inhabitants of such State, being twenty-one years of age, and citizens of the United States, or in any way abridged, except for participation in rebellion, or other crime, the basis of representation therein shall be reduced in the proportion which the number of such male citizens shall bear to the whole number of male citizens twenty-one years of age in such State. No person shall be a Senator or Representative in Congress, or elector of President and Vice President, or hold any office, civil or military, under the United States, or under any State, who, having previously taken an oath, as a member of Congress, or as an officer of the United States, or as a member of any State legislature, or as an executive or judicial officer of any State, to support the Constitution of the United States, shall have engaged in insurrection or rebellion against the same, or given aid or comfort to the enemies thereof. But Congress may by a vote of two-thirds of each House, remove such disability. The validity of the public debt of the United States, authorized by law, including debts incurred for payment of pensions and bounties for services in suppressing insurrection or rebellion, shall not be questioned. But neither the United States nor any State shall assume or pay any debt or obligation incurred in aid of insurrection or rebellion against the United States, or any claim for the loss or emancipation of any slave; but all such debts, obligations and claims shall be held illegal and void. The Congress shall have power to enforce, by appropriate legislation, the provisions of this article.'
However, we might want an Enactment
object that only represents a
part of the Fourteenth Amendment that’s relevant to a particular case.
We can use the select()
method to limit the text of the provision
that’s considered “selected”. One way to do this is with a series of
strings that exactly match the text we want to select. Because we’re selecting only some of
the text, the output of the selected_text()
method
will be different.
>>> passages = fourteenth_amendment.select(["No State shall", "deprive any person of", "liberty", "without due process of law"])
>>> passages.selected_text()
'…No State shall…deprive any person of…liberty…without due process of law…'
The select()
method returns a new
EnactmentPassage
object, which holds
both the Enactment
and a
TextPositionSet
indicating which text is
selected. But if we want to select additional text
without clearing the existing selection, we can
use the EnactmentPassage’s select_more()
method. It’s okay if the selection we pass in
to select_more()
overlaps
with text we’ve already selected.
>>> passages.select_more("life, liberty, or property,")
>>> passages.selected_text()
'…No State shall…deprive any person of life, liberty, or property, without due process of law…'
If we need to select a passage that occurs more than once in the
Enactment, we can import the TextQuoteSelector
class instead of
using strings. With a TextQuoteSelector
, we specify not just the
exact
phrase we want to select, but also a prefix
or suffix
that makes the phrase uniquely identifiable. In this example, the text
being selected is the second instance of the phrase “twenty-one years of
age” in the Fourteenth Amendment.
>>> from legislice import TextQuoteSelector
>>> age_passage = fourteenth_amendment.select(TextQuoteSelector(prefix="male citizens ", exact="twenty-one years of age"))
>>> age_passage.selected_text()
'…twenty-one years of age…'
If we happen to know the start and end indices of the passage we want,
then we can use a TextPositionSelector
or
TextPositionSet
to
select it, instead of specifying the exact text.
>>> from legislice.enactments import TextPositionSelector, TextPositionSet
>>> amendment_passage = fourteenth_amendment.select(TextPositionSet(positions=[TextPositionSelector(start=1921, end=1973), TextPositionSelector(start=2111, end=2135)]))
>>> amendment_passage.selected_text()
'…The validity of the public debt of the United States…shall not be questioned.…'
We can also use the method child_passages()
to get a new EnactmentPassage
with only the subsection
of the Fourteenth Amendment that interests us. The citation stored in the node
attribute
is now different, but the text selector still remains in
place, so we can still get the same selected text.
>>> public_debt_provision = amendment_passage.child_passages[3]
>>> public_debt_provision.node
'/us/const/amendment/XIV/4'
>>> public_debt_provision.selected_text()
'The validity of the public debt of the United States…shall not be questioned.…'
Comparing Selected Text¶
Next, we’ll create a new EnactmentPassage
to compare by
changing the selected text of the original Enactment
to
include all the text that was selected before, plus more.
>>> debt_passage = fourteenth_amendment.select(TextPositionSelector(start=1921, end=2135))
>>> debt_passage.selected_text()
'…The validity of the public debt of the United States, authorized by law, including debts incurred for payment of pensions and bounties for services in suppressing insurrection or rebellion, shall not be questioned.…'
Now we can compare the text selections in these
two EnactmentPassage
s. The
implies()
method checks whether the Enactment
on the left has all the text of the Enactment on the right.
The means()
method checks whether
they both have the same text.
>>> fourteenth_amendment.implies(public_debt_provision)
True
We can also use Python’s built-in “greater than or equal” operator as
an alias for the implies()
method.
>>> fourteenth_amendment >= public_debt_provision
True
Notice that Legislice is able to compare these two passages even though
amendment
is a text selection from the entire Fourteenth Amendment,
while public_debt_provision
is a text selection from only section 4
of the Fourteenth Amendment. We can verify this by checking the “node”
attribute on each Enactment.
>>> fourteenth_amendment.node
'/us/const/amendment/XIV'
>>> public_debt_provision.node
'/us/const/amendment/XIV/4'
To determine whether two Enactments have the same text (and
neither has any more than the other), use
the means()
method. Here’s
how we can check that the Fifth Amendment doesn’t have identical text
to the first section of the Fourteenth Amendment.
>>> fifth_amendment = client.read(query="/us/const/amendment/V")
>>> fifth_amendment.text
'No person shall be held to answer for a capital, or otherwise infamous crime, unless on a presentment or indictment of a Grand Jury, except in cases arising in the land or naval forces, or in the Militia, when in actual service in time of War or public danger; nor shall any person be subject for the same offence to be twice put in jeopardy of life or limb; nor shall be compelled in any Criminal Case to be a witness against himself; nor be deprived of life, liberty, or property, without due process of law; nor shall private property be taken for public use, without just compensation.'
>>> fourteenth_amendment_section_1 = client.read(query="/us/const/amendment/XIV/1")
>>> fifth_amendment.means(fourteenth_amendment_section_1)
False
However, the Fifth Amendment and the first section of the Fourteenth
Amendment both happen to contain the phrase “life, liberty, or property,
without due process of law”. If we select that same passage from both
provisions, then we can use the means()
method to verify that both text selections are identical.
>>> phrase = "life, liberty, or property, without due process of law"
>>> due_process_14 = fourteenth_amendment_section_1.select(phrase)
>>> due_process_5 = fifth_amendment.select(phrase)
>>> due_process_14.means(due_process_5)
True
There are many situations in real legal analysis where it’s helpful to
know if identical text has been enacted at different citations. It could
mean that the identical section has been renumbered, or it could mean
that a judicial interpretation of one Enactment is also relevant to the
other Enactment. Legislice’s implies()
and means()
methods can help
to automate that analysis.
Since >=
is an alias
for implies()
, we might expect
to be able to use ==
as an alias
for means()
. Currently we can’t
do that, because overriding the equals function could interfere with
Python’s ability to determine what objects are identical, and could
cause bugs that would be difficult to diagnose. However, we can use
>
as an alias that returns True
only
if implies()
would return
True
while means()
would return False
.
Combining Enactment Passages¶
When we have two EnactmentPassage
s and
either they are at the same node or one
is a descendant of the other, we can combine them into a
new EnactmentPassage
s
using the addition sign. Here’s an example from a copyright statute in
the United States Code. The example shows that we can load section
/us/usc/t17/s103
, select some text from subsection b
of that
provision, and then add it to a separate Enactment representing the
entirety of subsection /us/usc/t17/s103/a
. Legislice combines the
selected text from subsection a
and subsection b
in the correct order.
>>> s103 = client.read(query="/us/usc/t17/s103", date="2020-01-01")
>>> selections = ["The copyright in such work is independent of", "any copyright protection in the preexisting material."]
>>> s103_passage = s103.select(selections)
>>> s103_passage.selected_text()
'…The copyright in such work is independent of…any copyright protection in the preexisting material.'
>>> s103a = client.read(query="/us/usc/t17/s103/a", date="2020-01-01")
>>> s103a.text
'The subject matter of copyright as specified by section 102 includes compilations and derivative works, but protection for a work employing preexisting material in which copyright subsists does not extend to any part of the work in which such material has been used unlawfully.'
>>> combined_passage = s103_passage + s103a
>>> combined_passage.selected_text()
'The subject matter of copyright as specified by section 102 includes compilations and derivative works, but protection for a work employing preexisting material in which copyright subsists does not extend to any part of the work in which such material has been used unlawfully.…The copyright in such work is independent of…any copyright protection in the preexisting material.'
Enactment Groups¶
When we want to work with groups of Enactments that may or may not be
nested inside one another, we can put them together in
an EnactmentGroup
.
When we create a new EnactmentGroup
or __add__()
two
EnactmentGroups together, any overlapping
Enactment
s inside
will be combined into a single Enactment.
In this example, we create two EnactmentGroups called left
and
right
, each containing two Enactments, and add them together.
Because one of the Enactments
in left
overlaps with one of the Enactments in right
, when we
add left
and right
together those two Enactments will be
combined into one. Thus the resulting EnactmentGroup will contain three
Enactments instead of four.
>>> from legislice import EnactmentGroup
>>> first = client.read(query="/us/const/amendment/I")
>>> establishment_clause=first.select("Congress shall make no law respecting an establishment of religion")
>>> speech_clause = first.select(["Congress shall make no law", "abridging the freedom of speech"])
>>> second = client.read(query="/us/const/amendment/II")
>>> arms_clause = second.select("the right of the people to keep and bear arms, shall not be infringed.")
>>> third = client.read(query="/us/const/amendment/III")
>>> left = EnactmentGroup(passages=[establishment_clause, arms_clause])
>>> right = EnactmentGroup(passages=[third, speech_clause])
>>> combined = left + right
>>> print(combined)
the group of Enactments:
"Congress shall make no law respecting an establishment of religion…abridging the freedom of speech…" (/us/const/amendment/I 1791-12-15)
"…the right of the people to keep and bear arms, shall not be infringed." (/us/const/amendment/II 1791-12-15)
"No soldier shall, in time of peace be quartered in any house, without the consent of the Owner, nor in time of war, but in a manner to be prescribed by law." (/us/const/amendment/III 1791-12-15)
>>> len(combined)
3
Converting Enactments to JSON¶
When we want a representation of a legislative passage that’s precise,
machine-readable, and easy to share over the internet, we can use
Legislice’s JSON schema. Here’s how to convert the Enactment object
called combined_enactment
, which was created in the example above,
to JSON.
As explained in the section above, this JSON represents a selection of three
nonconsecutive passages from the most recent version of
section 103 of Title 17 of the United States Code.
The schema’s json()
method returns a JSON string,
while the dict()
method returns a
Python dictionary.
>>> combined.passages[0].json()
'{"enactment": {"node": "/us/const/amendment/I", "start_date": "1791-12-15", "heading": "AMENDMENT I.", "text_version": {"content": "Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances.", "url": "https://authorityspoke.com/api/v1/textversions/735703/", "id": 735703}, "end_date": null, "first_published": "1788-06-21", "earliest_in_db": "1788-06-21", "anchors": [], "citations": [], "name": "", "children": []}, "selection": {"positions": [{"start": 0, "end": 66}, {"start": 113, "end": 144}], "quotes": []}}'
Formatting Citations (Experimental)¶
Legislice has preliminary support for serializing citations for Enactment objects based on Citation Style Language JSON. The goal of this feature is to support compatibility with Jurism. Please open an issue in the Legislice repo if you have suggestions for how this feature should develop to support your use case.
>>> cares_act_benefits = client.read("/us/usc/t15/s9021/")
>>> cares_act_benefits.heading
'Pandemic unemployment assistance'
>>> citation = cares_act_benefits.as_citation()
>>> str(citation)
'15 U.S. Code § 9021 (2020)'
>>> cares_act_benefits.csl_json()
'{"jurisdiction": "us", "code_level_name": "CodeLevel.STATUTE", "volume": "15", "section": "sec. 9021", "type": "legislation", "container-title": "U.S. Code", "event-date": {"date-parts": [["2020", 4, 10]]}}'
This CSL-JSON format currently only identifies the cited
provision down to the section level. Calling
the as_citation()
method
on a subsection or deeper nested provision will produce
the same citation data as its parent section.
Development Updates¶
Reporting Bugs and Issues¶
Your feedback is very valuable. If you discover a bug, if Legislice isn’t behaving as expected, or if you want to suggest a new feature, please comment or open an issue on Legislice’s GitHub issue tracker. For other comments please use the Twitter contact information below.
If you want to submit a pull request for either Legislice or AuthoritySpoke, please also submit the AuthoritySpoke contributor license agreement.
Current Updates¶
GitHub¶
You can find open issues and current changes to Legislice through its GitHub repo.
Twitter¶
On Twitter, you can follow @authorityspoke or @mcareyaus for project updates.
Changelog¶
0.7.0 (2021-10-21)¶
replace Marshmallow schemas with Pydantic models
add types module for type annotations of TypedDict API responses
add MANIFEST.in (to include readme.md on Python Package Index)
0.6.0 (2021-09-20)¶
add EnactmentPassage class
select_from_text_positions_without_nesting doesn’t accept RangeSet
Enactment.limit_selection.start must be an int
no separate LinkedEnactment class for Enactments with URL links as children
remove BaseEnactment parent class
0.5.2 (2021-05-20)¶
sort EnactmentGroups by level
add California to KNOWN_CODES
0.5.1 (2021-05-08)¶
separate schemas for YAML and JSON input
flag determines if read_from_json uses text expansion
change InboundReference to dataclass
0.5.0 (2021-03-26)¶
add EnactmentGroup class
drop Python 3.7 support
import Citation and Client at top level of library
Client.fetch_cross_reference no longer will ignore “date” param
EnactmentGroup init method can accept None as “enactments” param
remove “text expansion” module and functions
remove ExpandableSchema class
0.4.1 (2020-12-31)¶
fix bug: Client made API request requiring 301 redirect
0.4.0 (2020-12-29)¶
add Citation class
add Citation Style Language JSON serializer methods
remove mock Clients by migrating tests to pytest-vcr
0.3.1 (2020-12-12)¶
order fields in serialized Enactment JSON output format for readability
remove include_start and include_end from serialized Enactment JSON output
fix bug: Enactment.select_all created zero length selectors
0.3.0 (2020-11-17)¶
add CrossReference class as memo of cited Enactment to download
add CitingProvisionLocation as memo of citing Enactment to download
add cross_references attr to Enactment model
add citations_to method to Client class
EnactmentSchema’s content field is moved to a new nested model called TextVersionSchema
add ability to pass CitingProvisionLocation to Client.read
add ability to pass InboundReference to Client.read
0.2.0 (2020-08-30)¶
don’t add ellipsis to selected_text for node with no text
accept list of strings to generate anchorpoint TextPositionSet
combine selected text passages within 3 characters of each other
0.1.1 (2020-08-23)¶
initial release
API Documentation¶
Download Client¶
- class legislice.download.Client(api_token='', api_root='https://authorityspoke.com/api/v1', update_coverage_from_api=True)¶
Downloader for legislative text.
- citations_to(target)¶
Load an InboundReference object for each provision citing the target USLM provision URI.
- Parameters
target (
Union
[str
,Enactment
,CrossReference
]) – a string URI for the cited node, an Enactment at the cited node, or a CrossReference to the cited node- Return type
- Returns
a list of InboundReferences to the cited node
- fetch(query, date='')¶
Download legislative provision from string identifier or cross-reference.
- Parameters
query (
Union
[str
,CitingProvisionLocation
,CrossReference
,InboundReference
]) – A cross-reference to the desired legislative provision, or a path to the desired legislation section using the United States Legislation Markup tree-like citation format.date (
Union
[date
,str
]) – The date of the desired version of the provision to be downloaded. This is not needed if aCrossReference
passed to thequery
param specifies a date. If no date is provided, the API will use the most recent date.
- Return type
- fetch_citations_to(target)¶
Query API for citations to a given target node, withoout loading them as InboundCitations.
- Parameters
target (
Union
[str
,Enactment
,CrossReference
]) – a string URI for the cited node, an Enactment at the cited node, or a CrossReference to the cited node- Return type
- Returns
a list of dicts representing citations to the cited node
- fetch_citing_provision(query)¶
Download legislative provision as Enactment from CitingProvisionLocation.
CitingProvisionLocations are found in the locations attribute of the InboundReference objects obtained when using the citations_to method.
- Return type
- fetch_cross_reference(query, date='')¶
Download legislative provision from cross-reference.
- Parameters
query (
CrossReference
) – A cross-reference to the desired legislative provision. Found by calling thecross_references()
method on anEnactment
that contains one or more citations to other provisions.date (
Union
[date
,str
]) – The date of the desired version of the provision to be downloaded. This is not needed if theCrossReference
passed to thequery
param specifies a date. If no date is provided, the API will use the most recent date.
- Return type
- fetch_db_coverage(code_uri)¶
Document date range of provisions of a code of laws available in API database.
- Return type
PublicationCoverage
- fetch_inbound_reference(query)¶
Download legislative provision from InboundReference.
- Parameters
query (
InboundReference
) – An InboundReference identifying a provision containing a citation.- Return type
- Returns
An Enactment representing the provision containing the citation (not the cited provision).
If the InboundReference has been enacted more than once, the latest one will be chosen.
- fetch_uri(query, date='')¶
Fetch data about legislation at specified path and date from Client’s assigned API root.
- Parameters
query (
str
) – A path to the desired legislation section using the United States Legislation Markup tree-like citation format. Examples: “/us/const/amendment/IV”, “/us/usc/t17/s103”date (
Union
[date
,str
]) – A date when the desired version of the legislation was in effect. This does not need to be the “effective date” or the first date when the version was in effect. However, if you select a date when two versions of the provision were in effect at the same time, you will be given the version that became effective later.
- Return type
- get_db_coverage(uri)¶
Add data about the API’s coverage date range to the Enactment to be loaded.
As a side effect, changes the Client’s “coverage” attribute.
- read(query, date='')¶
Fetch data from Client’s assigned API root and builds Enactment or LinkedEnactment.
All text is selected by default.
- Parameters
path – A path to the desired legislation section using the United States Legislation Markup tree-like citation format. Examples: “/us/const/amendment/IV”, “/us/usc/t17/s103”
date (
Union
[date
,str
]) – A date when the desired version of the legislation was in effect. This does not need to be the “effective date” or the first date when the version was in effect. However, if you select a date when two versions of the provision were in effect at the same time, you will be given the version that became effective later.
- Return type
- read_from_json(data, use_text_expansion=True)¶
Create a new
Enactment
object using imported JSON data.If fields are missing from the JSON, they will be fetched using the API key.
- Return type
- read_passage_from_json(data)¶
Create a new
EnactmentPassage
object using imported JSON data.If fields are missing from the JSON, they will be fetched using the API key.
- Return type
- update_data_from_api_if_needed(data)¶
Update data from API with data from API coverage.
- Parameters
data (
RawEnactment
) – a dict representing the data from the API- Return type
- Returns
a dict representing the data from the API with updated data from API coverage
- update_enactment_from_api(data)¶
Use API to fill in missing fields in a dict representing an
Enactment
.Useful when the dict has missing data because it was created by a user.
- Return type
- update_entries_in_enactment_index(enactment_index)¶
Fill in missing fields in every entry in an
EnactmentIndex
.- Return type
Enactments¶
- class legislice.enactments.Enactment(**data)¶
Base class for Enactments.
Whether connected to subnodes by linking, or nesting.
- Parameters
node – identifier for the site where the provision is codified
heading – full heading of the provision
text_version – full text content at this node, even if not all of it is cited
start_date – date when the text was enacted at the cited location
known_revision_date – whether the “start_date” field is known to be a date when the provision was revised in the code where it was publised. If False, then the Enactment refers to a time range when the text was codified, without asserting that the Enactment was not codified at earlier dates. This field is useful when working from incomplete legislative records.
end_date – date when the text was removed from the cited location
first_published – date when this Enactment’s code was first published.
earliest_in_db – date of the earliest version of this Enactment in the database. Used to determine whether the start_date of the Enactment is a date when the Enactment was amended or first published.
anchors – a list of selectors representing the part of some other document (e.g. an Opinion) that references the Enactment. Unlike the selection field, it doesn’t reference part of the Enactment itself. For use as a temporary place to store the anchors until they can be moved over to the citing document.
name – a user-assigned label for the object
- property code¶
Get “code” part of node identifier.
- convert_quotes_to_position(quotes)¶
Convert quote selector to the corresponding position selector for this Enactment.
- Return type
- convert_selection_to_set(selection)¶
Create a TextPositionSet from a different selection method.
- Return type
- cross_references()¶
Return all cross-references from this node and subnodes.
- Return type
- csl_json()¶
Serialize a citation to this provision in Citation Style Language JSON.
Experimental feature. This CSL-JSON format currently only identifies the cited provision down to the section level. A citation to a subsection or deeper nested provision will be the same as a citation to its parent section.
See https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html for a guide to this CSL-JSON format.
- Return type
- get_identifier_part(index)¶
Get a part of the split node identifier, by number.
- get_string(selection)¶
Use text selector to get corresponding string from Enactment.
- Return type
- property is_federal¶
Check if self is from a federal jurisdiction.
- property jurisdiction¶
Get “sovereign” part of node identifier.
- property known_revision_date: bool¶
Determine if Enactment’s start_date reflects its last revision date.
If not, then the start_date merely reflects the earliest date that versions of the
Enactment
's code exist in the database.- Return type
- property level: legislice.citations.CodeLevel¶
Get level of code for this Enactment, e.g. “statute” or “regulation”.
- Return type
- limit_selection(selection, start=0, end=None)¶
Limit selection to the range defined by start and end points.
- Return type
- limit_selection_to_current_node(selection)¶
Limit selection to the current node.
- Return type
- make_selection(selection=True, start=0, end=None)¶
Make a TextPositionSet for specified text in this Enactment.
- Return type
- make_selection_of_all_text()¶
Return a TextPositionSet of all text in this Enactment.
- Return type
- make_selection_of_this_node()¶
Return a TextPositionSet of the text at this node, not child nodes.
- Return type
- classmethod make_text_version_from_str(value)¶
Allow content to be used to populate text_version.
- Return type
Optional
[TextVersion
]
- property nested_children¶
Get nested children attribute.
- property padded_length¶
Get length of self’s content plus one character for space before next section.
- raise_error_for_extra_selector(selection)¶
Raise an error if any passed selectors begin after the end of the text passage.
- Return type
- rangedict()¶
Return a RangeDict matching text spans to Enactment attributes.
- Return type
RangeDict
- property section¶
Get “section” part of node identifier.
- select(selection=True, start=0, end=None)¶
Select text from Enactment.
- Return type
- select_all()¶
Return a passage for this Enactment, including all subnodes.
- Return type
- property sovereign¶
Get “sovereign” part of node identifier.
- property text¶
Get all text including subnodes, regardless of which text is “selected”.
- text_sequence(include_nones=True)¶
Get a sequence of text passages for this provision and its subnodes.
- Return type
- property title¶
Get “title” part of node identifier.
- tree_selection()¶
Return set of selectors for selected text in this provision and its children.
- Return type
- class legislice.enactments.EnactmentPassage(**data)¶
An Enactment with selectors indicating which text is being referenced.
- as_quotes()¶
Return quote selectors for the selected text.
- Return type
- property child_passages: List[legislice.enactments.EnactmentPassage]¶
Return a list of EnactmentPassages for this Enactment’s children.
- Return type
- property code¶
Get “code” part of node identifier.
- property end_date¶
Get the earliest end date of any provision version included in the passage.
- property is_federal¶
Check if self is from a federal jurisdiction.
- property jurisdiction¶
Get “sovereign” part of node identifier.
- property level: legislice.citations.CodeLevel¶
Get level of code for this Enactment, e.g. “statute” or “regulation”.
- Return type
- limit_selection(start=0, end=None)¶
Limit selection to the range defined by start and end points.
- Return type
- means(other)¶
Find whether meaning of
self
is equivalent to that ofother
.Self
must be neither broader nor narrower thanother
to return True.
- property node¶
Get the node that this Enactment is from.
- property section¶
Get “section” part of node identifier.
- select_more_text_at_current_node(added_selection)¶
Select more text at this Enactment’s node, not in child nodes.
- Return type
- select_more_text_from_changed_version(other)¶
Select more text from a different text version at the same citation path.
- Parameters
other (
EnactmentPassage
) – AnEnactment
representing different text enacted at a different time, at the same node (or USLM path citation) as self. This Element’snode
attribute must be the same string as self’s node attribute. It’s not sufficient for other to have an Enactment listed in its_children
attribute with the same node attribute, or for other to have the same node attribute as an ancestor of self.- Return type
- select_more_text_in_current_branch(added_selection)¶
Select more text within this Enactment’s tree_selection, including child nodes.
- Return type
- selected_text()¶
Return this provision’s text that is within the ranges described by self.selection.
Based on creating an
anchorpoint.textsequences.TextSequence
from this Enactment’s text content and the ranges in its selection attribute.- Return type
- property sovereign¶
Get “sovereign” part of node identifier.
- property start_date¶
Get the latest start date of any provision version included in the passage.
- property text¶
Get all text including subnodes, regardless of which text is “selected”.
- text_sequence(include_nones=True)¶
List the phrases in the Enactment selected by TextPositionSelectors.
- Parameters
include_nones (
bool
) – Whether the list of phrases should include None to indicate a block of unselected text- Return type
- property title¶
Get “title” part of node identifier.
- class legislice.enactments.CrossReference(**data)¶
A legislative provision’s citation to another provision.
- Parameters
target_uri – the path to the target provision from the document root.
target_url – the URL to fetch the target provision from an API.
reference_text – The text in the citing provision that represents the cross-reference. Generally, this text identifies the target provision.
target_node – an identifier for the target URI in the API.
- class legislice.enactments.InboundReference(**data)¶
A memo that a TextVersion has a citation to a specified target provision.
- latest_location()¶
Get most recent location where the citing text has been enacted.
- Return type
- classmethod search_citations_for_reference_text(values)¶
Get reference_text field from nested “citations” model.
- Return type
- class legislice.enactments.CitingProvisionLocation(**data)¶
Memo indicating where an Enactment can be downloaded.
- Parameters
node – location of the citing provision in a legislative code
start_date – start date of the citing version of the provision
heading – heading text for the citing provision
- legislice.enactments.consolidate_enactments(enactments)¶
Citations¶
- class legislice.citations.CodeLevel(value)¶
Identifier for the type of legislative code.
- class legislice.citations.Citation(**data)¶
A citation style for referring to an
Enactment
in written text.Intended for use with Citation Style Language (CSL).
- static csl_date_format(revision_date)¶
Convert event date to Citation Style Language format.
- csl_dict()¶
Return citation as a Citation Style Language object.
- classmethod validate_code(obj)¶
Standardize the code name for the styled citation.
- classmethod validate_section(value)¶
Make sure the “section” part of the styled citation starts with the right abbreviation.
- classmethod validate_volume(value)¶
Make sure the “title” identifier for the styled citation doesn’t start with “t”.
Enactment Groups¶
- class legislice.groups.EnactmentGroup(**data)¶
Group of Enactments with comparison methods.
- __add__(other)¶
Combine two EnactmentGroups, consolidating any duplicate Enactments.
- Return type
- __hash__ = None¶
- __iter__()¶
so dict(model) works
- __str__()¶
Return str(self).
- classmethod consolidate_passages(obj)¶
Consolidate overlapping EnactmentPassages into fewer objects.
- Return type
- implies(other)¶
Determine whether self includes all the text of another Enactment or EnactmentGroup.
- Return type
- classmethod sort_passages(obj)¶
Sort federal to state, constitutional to statute to regulation, and then alphabetically.
- Return type
Types¶
TypedDict data structures used for communicating between an API and Legislice.
- class legislice.types.CitingProvisionLocationDict(*args, **kwargs)¶
Location of provision citing to the provision that is the subject of the API call.
Can occur as part of an FetchedCitationDict created by a call to the citations_to API endpoint.
- class legislice.types.CrossReferenceDict(*args, **kwargs)¶
A citation from the provision that is the subject of the API call, leading to another provision.
- class legislice.types.FetchedCitationDict(*args, **kwargs)¶
Information about a statute that cites the subject of the API call.
Created by a call to the citations_to API endpoint.
- class legislice.types.InboundReferenceDict(*args, **kwargs)¶
Information about statute that cites the subject of the API call, with text of reference.
Created by finding the relevant citation in a FetchedCitationDict, and then pulling the reference field from within the citation.
- class legislice.types.PublicationCoverage(*args, **kwargs)¶
Available dates for a publication covered in an API’s database.
- class legislice.types.RawEnactment(*args, **kwargs)¶
Data for creating an Enactment.
- class legislice.types.RawEnactmentPassage(*args, **kwargs)¶
Data for creating an EnactmentPassage.
- class legislice.types.RawPositionSelector(*args, **kwargs)¶
Data for a TextPositionSelector.
- class legislice.types.RawQuoteSelector(*args, **kwargs)¶
Data for a TextQuoteSelector.
- class legislice.types.RawSelectorSet(*args, **kwargs)¶
Data for a set of Anchorpoint selectors.
Used in Legislice to specify the cited part of an Enactment.