Skip to content

Commit

Permalink
Merge pull request #35 from openstates/action-related-entities
Browse files Browse the repository at this point in the history
Bills: support bill action related entities in output
  • Loading branch information
jessemortenson authored Jan 29, 2024
2 parents 02cde76 + 094c2f9 commit 90f9c2e
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 5 deletions.
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ See [infrastructure repo](https://github.com/openstates/infrastructure#api-resta

## Running locally

To run locally, you first need to have a running local database [following these instructions](https://docs.openstates.org/contributing/local-database/)
To run locally, you first need to have a running local
database [following these instructions](https://docs.openstates.org/contributing/local-database/)

You also need to have a redis instance running. That can be run via the docker-compose config included in this package
by running `docker compose up redis`
Expand All @@ -44,4 +45,38 @@ DATABASE_URL=postgresql://openstates:openstates@localhost:5405/openstatesorg poe

To test out hitting the API, there will need to be a user account + profile entry with an API key in the local DB. The
scripts involved in the above-mentioned instructions (openstates.org repo init DB) should result in the creation of an
API key called `testkey` that can be used for local testing.
API key called `testkey` that can be used for local testing.

### Run tests

* In addition to having the normal DB container running (as described above), you need to also start the `db-test`
service available in this project's `docker-compose.yml`: in this repo directory, run `docker compose up -d db-test`
* Run all tests: `poetry run pytest` in the CLI; or in PyCharm configure a python test run targeting the
`pytest` module
* Run an individual test: add an argument to either of the above specifying the test file and function name, eg
`api/tests/test_bills.py::test_bills_filter_by_jurisdiction_abbr`

## Architecture

Components of this FastAPI application:

* Routes, found in the `api/` folder, such as `api/bills.py`. These expose HTTP API routes and contain the business
logic executed when a request hits that route.
* SQL Alchemy models, found in the `api/db/models` folder, such as `api/db/models/bills.py` that define the data models
used by business logic to query data.
* Pydantic schemas, found in the `api/schemas.py` folder, which define how data from the database is transformed into
output that is returned by business logic to the client.
* Pagination logic, found in `api/pagination.py` and in route files, provides a semi-magic way for business logic to
use SQL Alchemy models to manage things like includes, pagination of results, etc..

If you want to make a change, such as adding a related entity to output on an endpoint, you likely need to make changes
to:

* The Model file: add the new entity as a model, and a relationship property on the entity that is related to it. This
informs which columns are selected via which join logic.
* The Pydantic schema: add the entity and fields to the output schema. Even if the Model changes are correct, the data
will not show up in API output unless it is in the schema.
* The relevant Pagination object in the route file: you may need to add to `include_map_overrides` to tell the
pagination system that sub-entities should be fetched when an include is requested. If you add a sub-sub entity here,
such as "actions.related_entities" to the `BillPagination`, make sure to explicitly add the sub-entity as well:
"actions". Otherwise, additional queries will be generated to lazy-load the sub-entity.
1 change: 1 addition & 0 deletions api/bills.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class BillPagination(Pagination):
"votes.sources",
"votes.votes.voter",
],
BillInclude.actions: ["actions", "actions.related_entities"],
}
max_per_page = 20

Expand Down
9 changes: 7 additions & 2 deletions api/db/models/bills.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .. import Base
from .common import PrimaryUUID, LinkBase, RelatedEntityBase
from .jurisdiction import LegislativeSession
from .people_orgs import Organization
from .people_orgs import Organization, Person


@lru_cache(100)
Expand Down Expand Up @@ -107,13 +107,18 @@ class BillAction(BillRelatedBase, Base):
date = Column(String)
classification = Column(ARRAY(Text), default=list)
order = Column(Integer)
related_entities = relationship("BillActionRelatedEntity", back_populates="action")


class BillActionRelatedEntity(RelatedEntityBase, Base):
__tablename__ = "opencivicdata_billactionrelatedentity"

action_id = Column(UUID(as_uuid=True), ForeignKey(BillAction.id))
action = relationship(BillAction)
action = relationship(BillAction, back_populates="related_entities")
name = Column(String)
entity_type = Column(String)
organization_id = Column(String, ForeignKey(Organization.id))
person_id = Column(String, ForeignKey(Person.id))


class RelatedBill(PrimaryUUID, Base):
Expand Down
11 changes: 11 additions & 0 deletions api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,24 @@ class Config:
orm_mode = True


class BillActionRelatedEntity(BaseModel):
name: str = Field(..., example="Senate Committee of the Whole")
entity_type: str = Field(..., example="organization")
organization: Optional[Organization] = Field(None, example=None)
person: Optional[CompactPerson]

class Config:
orm_mode = True


class BillAction(BaseModel):
organization: Organization
description: str = Field(..., example="Passed 1st Reading")
date: str = Field(..., example="2020-03-14")
# TODO: enumerate billaction classifiers
classification: List[str] = Field(..., example=["passed"])
order: int
related_entities: Optional[List[BillActionRelatedEntity]]

class Config:
orm_mode = True
Expand Down
2 changes: 1 addition & 1 deletion api/tests/test_bills.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def test_bills_include_basics(client):
"/bills?jurisdiction=ne&session=2020&include=sponsorships&include=abstracts"
"&include=other_titles&include=other_identifiers&include=actions&include=sources"
)
assert query_logger.count == 8
assert query_logger.count == 9
assert response.status_code == 200
for b in response.json()["results"]:
assert len(b["sponsorships"]) == 2
Expand Down

0 comments on commit 90f9c2e

Please sign in to comment.