Skip to content

Extensions

These modules attach query, filter, and per-instance helper methods onto the plain Pydantic models (so Bill stays a data model, and Bill.get_actions() / Bills.filter(...) are defined here instead). They're registered when congressgov.services.extensions is imported, which happens automatically as part of import congressgov. See _template.py in the repo if you're adding a new one.

Query builder

Generic query builder shared by every collection model's extension module.

One CollectionQuery implementation backs Members.query(), Bills.query(), etc., so each entity doesn't need its own filter/sort/paginate class. Field mappings let a query value like "CA" also match "California"; field validation catches typos and suggests corrections via fuzzy matching.

FieldMapping dataclass

FieldMapping(expand_value: Callable[[Any], list[Any]] | None = None, enum_class: Type[Enum] | None = None)

Per-field shorthand-query config: either enum_class=StateCode or a custom expand_value=lambda v: [v, v.upper()].

get_expander

get_expander() -> Callable[[Any], list[Any]] | None

Return the configured expander, preferring expand_value over enum_class.

Source code in src/congressgov/services/extensions/_query_builder.py
77
78
79
80
81
82
83
def get_expander(self) -> Callable[[Any], list[Any]] | None:
    """Return the configured expander, preferring `expand_value` over `enum_class`."""
    if self.expand_value:
        return self.expand_value
    if self.enum_class:
        return enum_expander(self.enum_class)
    return None

QueryConfig dataclass

QueryConfig(collection_class: type[CollectionT], items_field: str, item_class: type[ItemT] | None = None, field_mappings: dict[str, FieldMapping] | None = None, validate_fields: bool = True)

Bases: Generic[ItemT, CollectionT]

Collection class, items field name, and optional validation/mapping settings that parameterize a CollectionQuery for one entity type.

CollectionQuery

CollectionQuery(items: list[ItemT], config: QueryConfig[ItemT, CollectionT])

Bases: Generic[ItemT, CollectionT]

Filter/sort/paginate a list of items, eagerly or lazily (chained).

Example: query.filter(state="CA", lazy=True).order_by("name").execute().

Source code in src/congressgov/services/extensions/_query_builder.py
104
105
106
107
108
109
110
111
def __init__(
    self,
    items: list[ItemT],
    config: QueryConfig[ItemT, CollectionT]
):
    self._items = items
    self._config = config
    self._valid_fields: set[str] | None = None

filter

filter(lazy: bool = False, **kwargs) -> CollectionT | CollectionQuery[ItemT, CollectionT]

Filter items by field values (e.g. state="CA" also matches "California" when a field mapping is configured). Pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/_query_builder.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def filter(
    self,
    lazy: bool = False,
    **kwargs
) -> CollectionT | CollectionQuery[ItemT, CollectionT]:
    """Filter items by field values (e.g. state="CA" also matches "California"
    when a field mapping is configured). Pass lazy=True to keep chaining."""
    if not isinstance(lazy, bool):
        raise TypeError(
            f"filter() 'lazy' parameter must be a boolean, got {type(lazy).__name__}. "
            f"If you're trying to pass a lambda or function, use .where() instead: "
            f"collection.query().where(lambda x: ...)"
        )

    # A common mistake: calling filter(lambda x: ...) as if it were Python's builtin filter().
    if kwargs and any(callable(v) for v in list(kwargs.values())[:1]):
        raise TypeError(
            "filter() does not accept callable/lambda values. "
            "Use .where() for predicate-based filtering: "
            "collection.query().where(lambda item: ...)"
        )

    for field in kwargs.keys():
        self._validate_field(field)

    filtered = [
        item for item in self._items
        if all(self._field_matches(item, k, v) for k, v in kwargs.items())
    ]
    new_query = CollectionQuery(filtered, self._config)
    return new_query if lazy else new_query.execute()

where

where(predicate: Callable[[ItemT], bool], lazy: bool = False) -> CollectionT | CollectionQuery[ItemT, CollectionT]

Filter items with a predicate function, e.g. where(lambda x: x.year > 2020).

Source code in src/congressgov/services/extensions/_query_builder.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def where(
    self,
    predicate: Callable[[ItemT], bool],
    lazy: bool = False
) -> CollectionT | CollectionQuery[ItemT, CollectionT]:
    """Filter items with a predicate function, e.g. ``where(lambda x: x.year > 2020)``."""
    if not callable(predicate):
        raise TypeError(
            f"where() expects a callable predicate function, got {type(predicate).__name__}. "
            f"Usage: collection.query().where(lambda item: item.field == value)"
        )

    if not isinstance(lazy, bool):
        raise TypeError(
            f"where() 'lazy' parameter must be a boolean, got {type(lazy).__name__}"
        )

    try:
        filtered = [item for item in self._items if predicate(item)]
    except AttributeError as e:
        raise AttributeError(
            f"Error in where() predicate: {e}\n"
            f"Make sure the field exists on the item. "
            f"Available fields can be checked with validation enabled."
        ) from e
    except Exception as e:
        raise RuntimeError(
            f"Error executing where() predicate: {e}\n"
            f"Predicate: {predicate}"
        ) from e

    new_query = CollectionQuery(filtered, self._config)
    return new_query if lazy else new_query.execute()

order_by

order_by(field: str, reverse: bool = False, lazy: bool = False) -> CollectionT | CollectionQuery[ItemT, CollectionT]

Sort items by a field name, ascending unless reverse=True.

Source code in src/congressgov/services/extensions/_query_builder.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def order_by(
    self,
    field: str,
    reverse: bool = False,
    lazy: bool = False
) -> CollectionT | CollectionQuery[ItemT, CollectionT]:
    """Sort items by a field name, ascending unless reverse=True."""
    self._validate_field(field)

    # None sorts first without discarding legitimate falsy sort keys
    # (0, False, "") the way `getattr(...) or ""` would.
    def _sort_key(item: ItemT) -> tuple[int, Any]:
        value = getattr(item, field, None)
        return (0, "") if value is None else (1, value)

    sorted_items = sorted(
        self._items,
        key=_sort_key,
        reverse=reverse
    )
    new_query = CollectionQuery(sorted_items, self._config)
    return new_query if lazy else new_query.execute()

limit

limit(n: int, lazy: bool = False) -> CollectionT | CollectionQuery[ItemT, CollectionT]

Keep only the first n items.

Source code in src/congressgov/services/extensions/_query_builder.py
266
267
268
269
270
271
272
273
def limit(
    self,
    n: int,
    lazy: bool = False
) -> CollectionT | CollectionQuery[ItemT, CollectionT]:
    """Keep only the first n items."""
    new_query = CollectionQuery(self._items[:n], self._config)
    return new_query if lazy else new_query.execute()

skip

skip(n: int, lazy: bool = False) -> CollectionT | CollectionQuery[ItemT, CollectionT]

Drop the first n items.

Source code in src/congressgov/services/extensions/_query_builder.py
275
276
277
278
279
280
281
282
def skip(
    self,
    n: int,
    lazy: bool = False
) -> CollectionT | CollectionQuery[ItemT, CollectionT]:
    """Drop the first n items."""
    new_query = CollectionQuery(self._items[n:], self._config)
    return new_query if lazy else new_query.execute()

execute

execute() -> CollectionT

Materialize the current items into a collection instance.

Source code in src/congressgov/services/extensions/_query_builder.py
284
285
286
287
def execute(self) -> CollectionT:
    """Materialize the current items into a collection instance."""
    kwargs = {self._config.items_field: self._items}
    return self._config.collection_class(**kwargs)

first

first() -> ItemT | None

Get the first item, or None if empty.

Source code in src/congressgov/services/extensions/_query_builder.py
289
290
291
def first(self) -> ItemT | None:
    """Get the first item, or None if empty."""
    return self._items[0] if self._items else None

last

last() -> ItemT | None

Get the last item, or None if empty.

Source code in src/congressgov/services/extensions/_query_builder.py
293
294
295
def last(self) -> ItemT | None:
    """Get the last item, or None if empty."""
    return self._items[-1] if self._items else None

count

count() -> int

Count the number of items.

Source code in src/congressgov/services/extensions/_query_builder.py
297
298
299
def count(self) -> int:
    """Count the number of items."""
    return len(self._items)

exists

exists() -> bool

Check if any items exist.

Source code in src/congressgov/services/extensions/_query_builder.py
301
302
303
def exists(self) -> bool:
    """Check if any items exist."""
    return len(self._items) > 0

to_list

to_list() -> list[ItemT]

Return the current item list (canonical alternative to iterating the collection).

Source code in src/congressgov/services/extensions/_query_builder.py
305
306
307
def to_list(self) -> list[ItemT]:
    """Return the current item list (canonical alternative to iterating the collection)."""
    return self._items

group_by

group_by(field: str) -> dict[Any, CollectionT]

Group items into a dict keyed by field value, e.g. {"CA": Members(...), ...}.

Source code in src/congressgov/services/extensions/_query_builder.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def group_by(self, field: str) -> dict[Any, CollectionT]:
    """Group items into a dict keyed by field value, e.g. ``{"CA": Members(...), ...}``."""
    self._validate_field(field)

    groups: dict[Any, list[ItemT]] = {}
    for item in self._items:
        key = getattr(item, field, None)
        groups.setdefault(key, []).append(item)

    # Convert each group to a collection instance
    result = {}
    for key, items in groups.items():
        kwargs = {self._config.items_field: items}
        result[key] = self._config.collection_class(**kwargs)

    return result

__iter__

__iter__() -> Iterator[ItemT]

Allow iteration over items.

Source code in src/congressgov/services/extensions/_query_builder.py
327
328
329
def __iter__(self) -> Iterator[ItemT]:
    """Allow iteration over items."""
    return iter(self._items)

__len__

__len__() -> int

Support len() function.

Source code in src/congressgov/services/extensions/_query_builder.py
331
332
333
def __len__(self) -> int:
    """Support len() function."""
    return len(self._items)

__getitem__

__getitem__(key: int | slice) -> ItemT | CollectionT

Support indexing and slicing.

Source code in src/congressgov/services/extensions/_query_builder.py
335
336
337
338
339
340
341
def __getitem__(self, key: int | slice) -> ItemT | CollectionT:
    """Support indexing and slicing."""
    result = self._items[key]
    if isinstance(key, slice):
        kwargs = {self._config.items_field: result}
        return self._config.collection_class(**kwargs)
    return result

__bool__

__bool__() -> bool

Support truthiness checks.

Source code in src/congressgov/services/extensions/_query_builder.py
343
344
345
def __bool__(self) -> bool:
    """Support truthiness checks."""
    return bool(self._items)

__repr__

__repr__() -> str

String representation.

Source code in src/congressgov/services/extensions/_query_builder.py
347
348
349
350
def __repr__(self) -> str:
    """String representation."""
    class_name = self._config.collection_class.__name__
    return f"<{class_name}Query: {len(self._items)} items>"

enum_expander

enum_expander(enum_class: Type[Enum]) -> Callable[[Any], list[Any]]

Build a value expander from an Enum's code/value pairs.

Example: enum_expander(StateCode)("CA") returns ["CA", "California"], and the reverse lookup works too, so either form matches a query.

Source code in src/congressgov/services/extensions/_query_builder.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def enum_expander(enum_class: Type[Enum]) -> Callable[[Any], list[Any]]:
    """Build a value expander from an Enum's code/value pairs.

    Example: ``enum_expander(StateCode)("CA")`` returns ``["CA", "California"]``,
    and the reverse lookup works too, so either form matches a query.
    """
    code_to_value = {member.name: member.value for member in enum_class}
    value_to_code = {member.value.lower(): member.name for member in enum_class}

    def expander(query_value: Any) -> list[Any]:
        if not isinstance(query_value, str):
            return [query_value]

        values = [query_value]
        upper_query = query_value.upper()
        lower_query = query_value.lower()

        if upper_query in code_to_value:  # code -> value, e.g. "CA" -> "California"
            values.append(code_to_value[upper_query])

        if lower_query in value_to_code:  # value -> code, e.g. "California" -> "CA"
            values.append(value_to_code[lower_query])

        return values

    return expander

create_query_builder

create_query_builder(collection_class: type[CollectionT], items_field: str, item_class: type[Any] | None = None, field_mappings: dict[str, FieldMapping] | None = None, validate_fields: bool = True) -> type[CollectionQuery[Any, CollectionT]]

Build a CollectionQuery subclass pre-bound to one collection/item class pair.

Example::

MembersQuery = create_query_builder(
    collection_class=Members,
    items_field="members",
    item_class=Member,
    field_mappings={"state": FieldMapping(enum_class=StateCode)},
)
query.filter(state="CA")   # matches "California" too, via the mapping
query.filter(stte="CA")    # raises ValueError with a "did you mean" suggestion
Source code in src/congressgov/services/extensions/_query_builder.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
def create_query_builder(
    collection_class: type[CollectionT],
    items_field: str,
    item_class: type[Any] | None = None,
    field_mappings: dict[str, FieldMapping] | None = None,
    validate_fields: bool = True
) -> type[CollectionQuery[Any, CollectionT]]:
    """Build a `CollectionQuery` subclass pre-bound to one collection/item class pair.

    Example::

        MembersQuery = create_query_builder(
            collection_class=Members,
            items_field="members",
            item_class=Member,
            field_mappings={"state": FieldMapping(enum_class=StateCode)},
        )
        query.filter(state="CA")   # matches "California" too, via the mapping
        query.filter(stte="CA")    # raises ValueError with a "did you mean" suggestion
    """
    config = QueryConfig(
        collection_class=collection_class,
        items_field=items_field,
        item_class=item_class,
        field_mappings=field_mappings,
        validate_fields=validate_fields
    )

    class ConfiguredQuery(CollectionQuery[Any, CollectionT]):
        def __init__(self, items: list[Any]):
            super().__init__(items, config)

    # Give the dynamically created class a readable name for debugging/repr.
    ConfiguredQuery.__name__ = f"{collection_class.__name__}Query"
    ConfiguredQuery.__qualname__ = f"{collection_class.__name__}Query"

    return ConfiguredQuery

Bill / Bills

Query and convenience methods for the Bill and Bills models.

Registered dynamically via _registry so Bill and Bills stay plain data models.

Field Mappings: - type: Supports both codes ("hr") and full names ("HR") Uses LegislationType enum for automatic expansion - originChamber: Supports chamber variations ("House", "H", "House of Representatives") Uses Chamber enum for automatic expansion

Methods registered on Bills (collection): - Query methods: filter(), query(), group_by() - Convenience methods: by_type(), by_congress(), by_chamber(), etc. - Utility methods: enacted(), house_bills(), senate_bills() - List as plain list: query().to_list() - Python protocols: iter, len, getitem, bool, repr (collections_registry)

Methods registered on Bill (singular instance): - expand(): Expand bill attributes (actions, amendments, committees, etc.) - expand_specific_attributes(): Expand only specific attributes - get_available_attributes(): List expandable attributes - get_actions(), get_amendments(), get_committees(): Fetch related data - get_cosponsors(), get_related_bills(), get_subjects(), get_summaries() - get_text_versions(), get_titles()

query

query(self)

Return a query builder for chained filtering.

Source code in src/congressgov/services/extensions/bill.py
121
122
123
124
@register_method(Bills)
def query(self):
    """Return a query builder for chained filtering."""
    return BillsQuery(self.bills or [])

filter

filter(self, *, lazy: bool = False, **kwargs)

Filter by field values; pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/bill.py
127
128
129
130
131
132
133
134
135
136
137
@register_method(Bills)
def filter(self, *, lazy: bool = False, **kwargs):
    """Filter by field values; pass lazy=True to keep chaining."""
    if not isinstance(lazy, bool):
        raise TypeError(
            f"filter() 'lazy' parameter must be a boolean, got {type(lazy).__name__}. "
            f"If you're trying to pass a lambda or function, use .query().where() instead: "
            f"bills.query().where(lambda bill: ...)"
        )

    return self.query().filter(lazy=lazy, **kwargs)

by_type

by_type(self, bill_type: str) -> Bills

Get bills of a specific type.

Source code in src/congressgov/services/extensions/bill.py
140
141
142
143
@register_method(Bills)
def by_type(self, bill_type: str) -> Bills:
    """Get bills of a specific type."""
    return self.query().filter(type=bill_type)

by_congress

by_congress(self, congress: int) -> Bills

Get bills from a specific Congress.

Source code in src/congressgov/services/extensions/bill.py
146
147
148
149
@register_method(Bills)
def by_congress(self, congress: int) -> Bills:
    """Get bills from a specific Congress."""
    return self.query().filter(congress=congress)

by_chamber

by_chamber(self, chamber: str) -> Bills

Get bills by originating chamber.

Source code in src/congressgov/services/extensions/bill.py
152
153
154
155
@register_method(Bills)
def by_chamber(self, chamber: str) -> Bills:
    """Get bills by originating chamber."""
    return self.query().filter(originChamber=chamber)

house_bills

house_bills(self) -> Bills

Get all House bills (hr, hres, hjres, hconres).

Source code in src/congressgov/services/extensions/bill.py
158
159
160
161
162
163
164
165
166
167
@register_method(Bills)
def house_bills(self) -> Bills:
    """Get all House bills (hr, hres, hjres, hconres)."""
    def is_house_bill(b: Bill) -> bool:
        if not b.type:
            return False
        bill_type = b.type.value if hasattr(b.type, 'value') else str(b.type)
        return bill_type.upper() in ["HR", "HRES", "HJRES", "HCONRES"]

    return self.query().where(is_house_bill)

senate_bills

senate_bills(self) -> Bills

Get all Senate bills (s, sres, sjres, sconres).

Source code in src/congressgov/services/extensions/bill.py
170
171
172
173
174
175
176
177
178
179
@register_method(Bills)
def senate_bills(self) -> Bills:
    """Get all Senate bills (s, sres, sjres, sconres)."""
    def is_senate_bill(b: Bill) -> bool:
        if not b.type:
            return False
        bill_type = b.type.value if hasattr(b.type, 'value') else str(b.type)
        return bill_type.upper() in ["S", "SRES", "SJRES", "SCONRES"]

    return self.query().where(is_senate_bill)

resolutions

resolutions(self) -> Bills

Get all resolutions (hres, sres, hjres, sjres, hconres, sconres).

Source code in src/congressgov/services/extensions/bill.py
182
183
184
185
186
187
188
189
190
191
@register_method(Bills)
def resolutions(self) -> Bills:
    """Get all resolutions (hres, sres, hjres, sjres, hconres, sconres)."""
    def is_resolution(b: Bill) -> bool:
        if not b.type:
            return False
        bill_type = b.type.value if hasattr(b.type, 'value') else str(b.type)
        return "RES" in bill_type.upper()

    return self.query().where(is_resolution)

joint_resolutions

joint_resolutions(self) -> Bills

Get joint resolutions only (hjres, sjres).

Source code in src/congressgov/services/extensions/bill.py
194
195
196
197
198
199
200
201
202
203
@register_method(Bills)
def joint_resolutions(self) -> Bills:
    """Get joint resolutions only (hjres, sjres)."""
    def is_joint_resolution(b: Bill) -> bool:
        if not b.type:
            return False
        bill_type = b.type.value if hasattr(b.type, 'value') else str(b.type)
        return bill_type.upper() in ["HJRES", "SJRES"]

    return self.query().where(is_joint_resolution)

enacted

enacted(self) -> Bills

Get bills that became law (have laws attribute populated).

Source code in src/congressgov/services/extensions/bill.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@register_method(Bills)
def enacted(self) -> Bills:
    """Get bills that became law (have laws attribute populated)."""
    def has_laws(b: Bill) -> bool:
        # Check if the bill has laws associated with it
        if not hasattr(b, 'laws') or b.laws is None:
            return False
        # Handle CountRef case (has count attribute)
        if hasattr(b.laws, 'count'):
            return (b.laws.count or 0) > 0
        # Handle list case
        if isinstance(b.laws, list):
            return len(b.laws) > 0
        return False

    return self.query().where(has_laws)

with_actions

with_actions(self) -> Bills

Get bills that have actions recorded.

Source code in src/congressgov/services/extensions/bill.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@register_method(Bills)
def with_actions(self) -> Bills:
    """Get bills that have actions recorded."""
    def has_actions(b: Bill) -> bool:
        if not hasattr(b, 'actions') or b.actions is None:
            return False
        # Handle CountRef case
        if hasattr(b.actions, 'count'):
            return (b.actions.count or 0) > 0
        # Handle list case
        if isinstance(b.actions, list):
            return len(b.actions) > 0
        return False

    return self.query().where(has_actions)

by_sponsor

by_sponsor(self, sponsor_name: str) -> Bills

Get bills by sponsor name (partial match, case-insensitive).

Source code in src/congressgov/services/extensions/bill.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
@register_method(Bills)
def by_sponsor(self, sponsor_name: str) -> Bills:
    """Get bills by sponsor name (partial match, case-insensitive)."""
    def has_sponsor(b: Bill) -> bool:
        if not hasattr(b, 'sponsors') or not b.sponsors:
            return False
        # Handle list of sponsors
        if isinstance(b.sponsors, list):
            for sponsor in b.sponsors:
                if hasattr(sponsor, 'fullName') and sponsor.fullName:
                    if sponsor_name.lower() in sponsor.fullName.lower():
                        return True
                # Also check firstName and lastName
                if hasattr(sponsor, 'firstName') and sponsor.firstName:
                    if sponsor_name.lower() in sponsor.firstName.lower():
                        return True
                if hasattr(sponsor, 'lastName') and sponsor.lastName:
                    if sponsor_name.lower() in sponsor.lastName.lower():
                        return True
        return False

    return self.query().where(has_sponsor)

group_by

group_by(self, field: str)

Group items into a dict keyed by field value.

Source code in src/congressgov/services/extensions/bill.py
265
266
267
268
@register_method(Bills)
def group_by(self, field: str):
    """Group items into a dict keyed by field value."""
    return self.query().group_by(field)

expand

expand(self: 'Bill', client: Any = None, attributes: Optional[list[str]] = None, **kwargs: Any) -> 'Bill'

Fetch related data (actions, amendments, committees, etc.) and return a deepcopy of this Bill with those attributes populated.

Pass attributes to expand only specific ones; otherwise all mapped attributes are expanded. Uses self.client when client is omitted.

Raises:

Type Description
ValueError

If congress/bill_type/bill_number can't be resolved.

Source code in src/congressgov/services/extensions/bill.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
@register_method(Bill)
def expand(
    self: 'Bill',
    client: Any = None,
    attributes: Optional[list[str]] = None,
    **kwargs: Any
) -> 'Bill':
    """Fetch related data (actions, amendments, committees, etc.) and return a
    deepcopy of this Bill with those attributes populated.

    Pass ``attributes`` to expand only specific ones; otherwise all mapped
    attributes are expanded. Uses ``self.client`` when ``client`` is omitted.

    Raises:
        ValueError: If congress/bill_type/bill_number can't be resolved.
    """
    BILL_MAPPINGS, BILL_PARAMETERS = _get_bill_config()
    return expand_sync_instance(
        self,
        mapping=BILL_MAPPINGS,
        parameters=BILL_PARAMETERS,
        client=client,
        attributes=attributes,
        normalize_params=["bill_type"],
        entity_name="Bill",
        post_expand=_post_expand_bill,
        **kwargs,
    )

expand_specific_attributes

expand_specific_attributes(self: 'Bill', *attributes: str, client: Any = None, **kwargs: Any) -> 'Bill'

Expand only the given attributes (varargs), e.g. bill.expand_specific_attributes('actions', 'cosponsors').

Source code in src/congressgov/services/extensions/bill.py
305
306
307
308
309
310
311
312
313
@register_method(Bill)
def expand_specific_attributes(
    self: 'Bill',
    *attributes: str,
    client: Any = None,
    **kwargs: Any
) -> 'Bill':
    """Expand only the given attributes (varargs), e.g. ``bill.expand_specific_attributes('actions', 'cosponsors')``."""
    return self.expand(client=client, attributes=list(attributes), **kwargs)

get_available_attributes

get_available_attributes(self) -> list[str]

List attribute names that expand() can populate (actions, amendments, committees, ...).

Source code in src/congressgov/services/extensions/bill.py
316
317
318
319
320
@register_method(Bill)
def get_available_attributes(self) -> list[str]:
    """List attribute names that ``expand()`` can populate (actions, amendments, committees, ...)."""
    BILL_MAPPINGS, _ = _get_bill_config()
    return list(BILL_MAPPINGS.keys())

get_actions

get_actions(self: 'Bill', client: Any = None, format_: str | None = None, refresh: bool = False, **kwargs: Any) -> 'ActionsModel'

Load this bill's legislative actions (introductions, referrals, votes, passage) into bill.actions. Reuses cached results unless refresh=True.

Source code in src/congressgov/services/extensions/bill.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
@register_method(Bill)
def get_actions(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'ActionsModel':
    """Load this bill's legislative actions (introductions, referrals, votes,
    passage) into ``bill.actions``. Reuses cached results unless refresh=True."""
    return bind_bill_subresource(
        self,
        attribute_name="actions",
        model_class=ActionsModel,
        api_function=bill_actions_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        **kwargs,
    )

get_amendments

get_amendments(self: 'Bill', client: Any = None, format_: str | None = None, offset: int | None = None, limit: int | None = None, from_date_time: str | None = None, to_date_time: str | None = None, sort: str | None = None, refresh: bool = False, **kwargs: Any) -> list[Any]

Load this bill's amendments (proposed during markup or floor consideration) into bill.amendments as a list[Amendment].

Source code in src/congressgov/services/extensions/bill.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
@register_method(Bill)
def get_amendments(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    offset: int | None = None,
    limit: int | None = None,
    from_date_time: str | None = None,
    to_date_time: str | None = None,
    sort: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> list[Any]:
    """Load this bill's amendments (proposed during markup or floor
    consideration) into ``bill.amendments`` as a ``list[Amendment]``."""
    from congressgov.models.base.types import CountRef

    current = getattr(self, "amendments", None)
    if not refresh and current is not None and not isinstance(current, CountRef):
        if isinstance(current, list):
            return current
        resolved_client = ApiService._resolve_client(self, client)
        return assign_collection_items_to_attribute(
            self,
            attribute_name="amendments",
            wrapper=current,
            items_field="amendments",
            client=resolved_client,
        )

    wrapper = bind_bill_subresource(
        self,
        attribute_name="amendments",
        model_class=AmendmentsModel,
        api_function=bill_amendments_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=limit,
        from_date_time=from_date_time,
        to_date_time=to_date_time,
        sort=sort,
        **kwargs,
    )
    resolved_client = ApiService._resolve_client(self, client)
    return assign_collection_items_to_attribute(
        self,
        attribute_name="amendments",
        wrapper=wrapper,
        items_field="amendments",
        client=resolved_client,
    )

get_committees

get_committees(self: 'Bill', client: Any = None, format_: str | None = None, refresh: bool = False, **kwargs: Any) -> 'CommitteesModel'

Load the committees that reviewed this bill into bill.committees.

Source code in src/congressgov/services/extensions/bill.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
@register_method(Bill)
def get_committees(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'CommitteesModel':
    """Load the committees that reviewed this bill into ``bill.committees``."""
    return bind_bill_subresource(
        self,
        attribute_name="committees",
        model_class=CommitteesModel,
        api_function=bill_committees_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        **kwargs,
    )

get_cosponsors

get_cosponsors(self: 'Bill', client: Any = None, format_: str | None = None, offset: int | None = None, limit: int | None = None, from_date_time: str | None = None, to_date_time: str | None = None, sort: str | None = None, refresh: bool = False, **kwargs: Any) -> 'CosponsorsModel'

Load this bill's cosponsors (members backing it besides the primary sponsor) into bill.cosponsors.

Source code in src/congressgov/services/extensions/bill.py
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
@register_method(Bill)
def get_cosponsors(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    offset: int | None = None,
    limit: int | None = None,
    from_date_time: str | None = None,
    to_date_time: str | None = None,
    sort: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'CosponsorsModel':
    """Load this bill's cosponsors (members backing it besides the primary
    sponsor) into ``bill.cosponsors``."""
    return bind_bill_subresource(
        self,
        attribute_name="cosponsors",
        model_class=CosponsorsModel,
        api_function=bill_cosponsors_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=limit,
        from_date_time=from_date_time,
        to_date_time=to_date_time,
        sort=sort,
        **kwargs,
    )
get_related_bills(self: 'Bill', client: Any = None, format_: str | None = None, offset: int | None = None, limit: int | None = None, from_date_time: str | None = None, to_date_time: str | None = None, sort: str | None = None, refresh: bool = False, **kwargs: Any) -> 'BillsModel'

Load bills related to this one into bill.relatedBills.

Source code in src/congressgov/services/extensions/bill.py
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
@register_method(Bill)
def get_related_bills(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    offset: int | None = None,
    limit: int | None = None,
    from_date_time: str | None = None,
    to_date_time: str | None = None,
    sort: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'BillsModel':
    """Load bills related to this one into ``bill.relatedBills``."""
    return bind_bill_subresource(
        self,
        attribute_name="relatedBills",
        model_class=BillsModel,
        api_function=bill_relatedbills_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=limit,
        from_date_time=from_date_time,
        to_date_time=to_date_time,
        sort=sort,
        **kwargs,
    )

get_subjects

get_subjects(self: 'Bill', client: Any = None, format_: str | None = None, offset: int | None = None, limit: int | None = None, from_date_time: str | None = None, to_date_time: str | None = None, sort: str | None = None, refresh: bool = False, **kwargs: Any) -> 'SubjectModel'

Load this bill's policy subjects into bill.subjects.

Source code in src/congressgov/services/extensions/bill.py
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
@register_method(Bill)
def get_subjects(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    offset: int | None = None,
    limit: int | None = None,
    from_date_time: str | None = None,
    to_date_time: str | None = None,
    sort: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'SubjectModel':
    """Load this bill's policy subjects into ``bill.subjects``."""
    return bind_bill_subresource(
        self,
        attribute_name="subjects",
        model_class=SubjectModel,
        api_function=bill_subjects_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=limit,
        from_date_time=from_date_time,
        to_date_time=to_date_time,
        sort=sort,
        **kwargs,
    )

get_summaries

get_summaries(self: 'Bill', client: Any = None, format_: str | None = None, offset: int | None = None, limit: int | None = None, from_date_time: str | None = None, to_date_time: str | None = None, sort: str | None = None, refresh: bool = False, **kwargs: Any) -> 'SummariesModel'

Load CRS summaries of this bill into bill.summaries.

Source code in src/congressgov/services/extensions/bill.py
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
@register_method(Bill)
def get_summaries(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    offset: int | None = None,
    limit: int | None = None,
    from_date_time: str | None = None,
    to_date_time: str | None = None,
    sort: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'SummariesModel':
    """Load CRS summaries of this bill into ``bill.summaries``."""
    return bind_bill_subresource(
        self,
        attribute_name="summaries",
        model_class=SummariesModel,
        api_function=bill_summaries_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=limit,
        from_date_time=from_date_time,
        to_date_time=to_date_time,
        sort=sort,
        **kwargs,
    )

get_text_versions

get_text_versions(self: 'Bill', client: Any = None, format_: str | None = None, refresh: bool = False, **kwargs: Any) -> 'TextVersionsModel'

Load this bill's available text versions into bill.textVersions.

Source code in src/congressgov/services/extensions/bill.py
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
@register_method(Bill)
def get_text_versions(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'TextVersionsModel':
    """Load this bill's available text versions into ``bill.textVersions``."""
    return bind_bill_subresource(
        self,
        attribute_name="textVersions",
        model_class=TextVersionsModel,
        api_function=bill_text_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        **kwargs,
    )

get_titles

get_titles(self: 'Bill', client: Any = None, format_: str | None = None, refresh: bool = False, **kwargs: Any) -> 'TitlesModel'

Load this bill's alternate/official titles into bill.titles.

Source code in src/congressgov/services/extensions/bill.py
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
@register_method(Bill)
def get_titles(
    self: 'Bill',
    client: Any = None,
    format_: str | None = None,
    refresh: bool = False,
    **kwargs: Any
) -> 'TitlesModel':
    """Load this bill's alternate/official titles into ``bill.titles``."""
    return bind_bill_subresource(
        self,
        attribute_name="titles",
        model_class=TitlesModel,
        api_function=bill_titles_sync,
        client=client,
        refresh=refresh,
        format_=format_,
        **kwargs,
    )

Members

Query and convenience methods for the Members model.

Registered dynamically via _registry so Members stay plain data models.

Field Mappings: - state: Supports both codes ("CA") and full names ("California") Uses StateCode enum for automatic expansion

Methods registered: - Query methods: filter(), query(), group_by() - Convenience methods: by_state(), by_party(), by_chamber(), etc. - Utility methods: current(), democrats(), republicans() - List as plain list: query().to_list() - Python protocols: iter, len, getitem, bool, repr (collections_registry)

query

query(self)

Get query builder for chaining operations.

Example

Start a query chain (lazy by default on query builder)

members.query().filter(state="CA", lazy=True).order_by("lastName").execute()

Source code in src/congressgov/services/extensions/members.py
56
57
58
59
60
61
62
63
64
65
@register_method(Members)
def query(self):
    """
    Get query builder for chaining operations.

    Example:
        # Start a query chain (lazy by default on query builder)
        members.query().filter(state="CA", lazy=True).order_by("lastName").execute()
    """
    return MembersQuery(self.members or [])

filter

filter(self, *, lazy: bool = False, **kwargs)

Filter by field values; pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/members.py
68
69
70
71
@register_method(Members)
def filter(self, *, lazy: bool = False, **kwargs):
    """Filter by field values; pass lazy=True to keep chaining."""
    return self.query().filter(lazy=lazy, **kwargs)

by_state

by_state(self, state: str) -> Members

Get members from a specific state (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
74
75
76
77
@register_method(Members)
def by_state(self, state: str) -> Members:
    """Get members from a specific state (always eager, returns Members)."""
    return self.query().filter(state=state)

by_party

by_party(self, party: str) -> Members

Get members of a specific party (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
80
81
82
83
@register_method(Members)
def by_party(self, party: str) -> Members:
    """Get members of a specific party (always eager, returns Members)."""
    return self.query().filter(partyName=party)

by_chamber

by_chamber(self, chamber: str) -> Members

Get members by chamber ("House of Representatives" or "Senate"). Checks the latest term (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
@register_method(Members)
def by_chamber(self, chamber: str) -> Members:
    """
    Get members by chamber ("House of Representatives" or "Senate").
    Checks the latest term (always eager, returns Members).
    """
    def is_in_chamber(m: Member) -> bool:
        if not m.terms or not m.terms.item:
            return False
        items = m.terms.item if isinstance(m.terms.item, list) else [m.terms.item]
        if not items:
            return False
        latest = max(items, key=lambda t: getattr(t, "startYear", 0) or 0)
        return getattr(latest, "chamber", None) == chamber

    return self.query().where(is_in_chamber)

current

current(self) -> Members

Get only current members (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
104
105
106
107
@register_method(Members)
def current(self) -> Members:
    """Get only current members (always eager, returns Members)."""
    return self.query().filter(currentMember=True)

democrats

democrats(self) -> Members

Get all Democratic members (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
110
111
112
113
@register_method(Members)
def democrats(self) -> Members:
    """Get all Democratic members (always eager, returns Members)."""
    return self.by_party("Democratic")

republicans

republicans(self) -> Members

Get all Republican members (always eager, returns Members).

Source code in src/congressgov/services/extensions/members.py
116
117
118
119
@register_method(Members)
def republicans(self) -> Members:
    """Get all Republican members (always eager, returns Members)."""
    return self.by_party("Republican")

group_by

group_by(self, field: str)

Group items into a dict keyed by field value.

Source code in src/congressgov/services/extensions/members.py
122
123
124
125
@register_method(Members)
def group_by(self, field: str):
    """Group items into a dict keyed by field value."""
    return self.query().group_by(field)

expand

expand(self, client: Any = None, attributes: Optional[list[str]] = None, **kwargs: Any) -> Member

Expand attributes of this Member instance by fetching related data from the API.

Replaces count stubs on the member detail (if present) with full SponsoredLegislation / CosponsoredLegislation payloads.

Parameters:

Name Type Description Default
client Any

API client. If None, uses self.client when available.

None
attributes Optional[list[str]]

Attributes to expand. If None, expands all mapped attributes.

None
**kwargs Any

Passed to sponsorship list API functions (e.g. offset, limit, format_).

{}

Returns:

Type Description
Member

A deepcopy of this member with expanded attributes set.

Raises:

Type Description
ValueError

If bioguideId is missing.

Examples:

>>> expanded = member.expand(client=my_client)
>>> expanded = member.expand(attributes=["sponsoredLegislation"])
Source code in src/congressgov/services/extensions/members.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
@register_method(Member)
def expand(
    self,
    client: Any = None,
    attributes: Optional[list[str]] = None,
    **kwargs: Any,
) -> Member:
    """
    Expand attributes of this Member instance by fetching related data from the API.

    Replaces count stubs on the member detail (if present) with full
    ``SponsoredLegislation`` / ``CosponsoredLegislation`` payloads.

    Args:
        client: API client. If None, uses ``self.client`` when available.
        attributes: Attributes to expand. If None, expands all mapped attributes.
        **kwargs: Passed to sponsorship list API functions (e.g. offset, limit, format_).

    Returns:
        A deepcopy of this member with expanded attributes set.

    Raises:
        ValueError: If ``bioguideId`` is missing.

    Examples:
        >>> expanded = member.expand(client=my_client)
        >>> expanded = member.expand(attributes=["sponsoredLegislation"])
    """
    MEMBER_MAPPINGS, MEMBER_PARAMETERS = _get_member_config()
    return expand_sync_instance(
        self,
        mapping=MEMBER_MAPPINGS,
        parameters=MEMBER_PARAMETERS,
        client=client,
        attributes=attributes,
        entity_name="Member",
        **kwargs,
    )

expand_specific_attributes

expand_specific_attributes(self, *attributes: str, client: Any = None, **kwargs: Any) -> Member

Expand only the given attributes (varargs).

Source code in src/congressgov/services/extensions/members.py
203
204
205
206
207
208
209
210
211
@register_method(Member)
def expand_specific_attributes(
    self,
    *attributes: str,
    client: Any = None,
    **kwargs: Any,
) -> Member:
    """Expand only the given attributes (varargs)."""
    return self.expand(client=client, attributes=list(attributes), **kwargs)

get_available_attributes

get_available_attributes(self) -> list[str]

List attribute names that expand() can populate.

Source code in src/congressgov/services/extensions/members.py
214
215
216
217
218
@register_method(Member)
def get_available_attributes(self) -> list[str]:
    """List attribute names that ``expand()`` can populate."""
    MEMBER_MAPPINGS, _ = _get_member_config()
    return list(MEMBER_MAPPINGS.keys())

get_sponsored_legislation

get_sponsored_legislation(self, client: Any = None, format_: str = None, offset: int = None, limit: int | str = None, *, refresh: bool = False) -> Any

Load sponsored legislation for this member into sponsoredLegislation.

Fetches from the API when the attribute is missing or still a count stub (CountRef). Reuses a previously loaded SponsoredLegislation unless refresh=True.

The client is taken from self.client, or from the parent Members collection when this member came from a list/search.

Parameters:

Name Type Description Default
client Any

API client override.

None
format_ str

Response format (default: json).

None
offset int

Number of records to skip.

None
limit int | str

Maximum number of records (int), or 'max' for 250 (API cap).

None
refresh bool

If True, always refetch even when already loaded.

False

Returns:

Type Description
Any

self.sponsoredLegislation after fetch (SponsoredLegislation model).

Example

member = member_service.get(bioguide_id="A000374") sponsored = member.get_sponsored_legislation() assert member.sponsoredLegislation is sponsored

Source code in src/congressgov/services/extensions/members.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
@register_method(Member)
def get_sponsored_legislation(
    self,
    client: Any = None,
    format_: str = None,
    offset: int = None,
    limit: int | str = None,
    *,
    refresh: bool = False,
) -> Any:
    """
    Load sponsored legislation for this member into ``sponsoredLegislation``.

    Fetches from the API when the attribute is missing or still a count stub
    (``CountRef``). Reuses a previously loaded ``SponsoredLegislation`` unless
    ``refresh=True``.

    The client is taken from ``self.client``, or from the parent ``Members``
    collection when this member came from a list/search.

    Args:
        client: API client override.
        format_: Response format (default: json).
        offset: Number of records to skip.
        limit: Maximum number of records (``int``), or ``'max'`` for 250 (API cap).
        refresh: If True, always refetch even when already loaded.

    Returns:
        ``self.sponsoredLegislation`` after fetch (``SponsoredLegislation`` model).

    Example:
        >>> member = member_service.get(bioguide_id="A000374")
        >>> sponsored = member.get_sponsored_legislation()
        >>> assert member.sponsoredLegislation is sponsored
    """
    return bind_related_attribute(
        self,
        attribute_name="sponsoredLegislation",
        model_class=SponsoredLegislationModel,
        api_function=member_sponsorship_list_sync,
        client=client,
        api_params=_member_sponsorship_params(self),
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=_coerce_sponsorship_limit(limit),
    )

get_cosponsored_legislation

get_cosponsored_legislation(self, client: Any = None, format_: str = None, offset: int = None, limit: int | str = None, *, refresh: bool = False) -> Any

Load cosponsored legislation for this member into cosponsoredLegislation.

Same behavior as :meth:get_sponsored_legislation for the cosponsor endpoint.

Returns:

Type Description
Any

self.cosponsoredLegislation after fetch (CosponsoredLegislation model).

Source code in src/congressgov/services/extensions/members.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
@register_method(Member)
def get_cosponsored_legislation(
    self,
    client: Any = None,
    format_: str = None,
    offset: int = None,
    limit: int | str = None,
    *,
    refresh: bool = False,
) -> Any:
    """
    Load cosponsored legislation for this member into ``cosponsoredLegislation``.

    Same behavior as :meth:`get_sponsored_legislation` for the cosponsor endpoint.

    Returns:
        ``self.cosponsoredLegislation`` after fetch (``CosponsoredLegislation`` model).
    """
    return bind_related_attribute(
        self,
        attribute_name="cosponsoredLegislation",
        model_class=CosponsoredLegislationModel,
        api_function=member_cosponsorship_list_sync,
        client=client,
        api_params=_member_sponsorship_params(self),
        refresh=refresh,
        format_=format_,
        offset=offset,
        limit=_coerce_sponsorship_limit(limit),
    )

Cosponsors

Query and convenience methods for the Cosponsors model.

Registered dynamically via _registry so Cosponsors stay plain data models.

Methods registered: - Query methods: filter(), query(), group_by() - Convenience methods: by_state(), by_party(), withdrawn(), etc. - Python protocols: iter, len, getitem, bool, repr

query

query(self)

Return a query builder for chained filtering.

Source code in src/congressgov/services/extensions/cosponsors.py
79
80
81
82
@register_method(Cosponsors)
def query(self):
    """Return a query builder for chained filtering."""
    return CosponsorsQuery(self.cosponsors or [])

filter

filter(self, *, lazy: bool = False, **kwargs)

Filter by field values; pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/cosponsors.py
85
86
87
88
@register_method(Cosponsors)
def filter(self, *, lazy: bool = False, **kwargs):
    """Filter by field values; pass lazy=True to keep chaining."""
    return self.query().filter(lazy=lazy, **kwargs)

by_state

by_state(self, state: str) -> Cosponsors

Get cosponsors from a specific state.

Source code in src/congressgov/services/extensions/cosponsors.py
91
92
93
94
@register_method(Cosponsors)
def by_state(self, state: str) -> Cosponsors:
    """Get cosponsors from a specific state."""
    return self.query().filter(state=state)

by_party

by_party(self, party: str) -> Cosponsors

Get cosponsors from a specific party.

Source code in src/congressgov/services/extensions/cosponsors.py
 97
 98
 99
100
@register_method(Cosponsors)
def by_party(self, party: str) -> Cosponsors:
    """Get cosponsors from a specific party."""
    return self.query().filter(party=party)

democrats

democrats(self) -> Cosponsors

Get all Democratic cosponsors.

Source code in src/congressgov/services/extensions/cosponsors.py
103
104
105
106
@register_method(Cosponsors)
def democrats(self) -> Cosponsors:
    """Get all Democratic cosponsors."""
    return self.by_party("Democratic")

republicans

republicans(self) -> Cosponsors

Get all Republican cosponsors.

Source code in src/congressgov/services/extensions/cosponsors.py
109
110
111
112
@register_method(Cosponsors)
def republicans(self) -> Cosponsors:
    """Get all Republican cosponsors."""
    return self.by_party("Republican")

withdrawn

withdrawn(self) -> Cosponsors

Get cosponsors who have withdrawn support.

Source code in src/congressgov/services/extensions/cosponsors.py
115
116
117
118
119
120
121
122
123
@register_method(Cosponsors)
def withdrawn(self) -> Cosponsors:
    """Get cosponsors who have withdrawn support."""
    def is_withdrawn(c: Cosponsor) -> bool:
        if hasattr(c, 'sponsorshipWithdrawnDate') and c.sponsorshipWithdrawnDate:
            return True
        return False

    return self.query().where(is_withdrawn)

active

active(self) -> Cosponsors

Get cosponsors who have not withdrawn support.

Source code in src/congressgov/services/extensions/cosponsors.py
126
127
128
129
130
131
132
133
134
@register_method(Cosponsors)
def active(self) -> Cosponsors:
    """Get cosponsors who have not withdrawn support."""
    def is_active(c: Cosponsor) -> bool:
        if hasattr(c, 'sponsorshipWithdrawnDate') and c.sponsorshipWithdrawnDate:
            return False
        return True

    return self.query().where(is_active)

group_by

group_by(self, field: str)

Group items into a dict keyed by field value.

Source code in src/congressgov/services/extensions/cosponsors.py
137
138
139
140
@register_method(Cosponsors)
def group_by(self, field: str):
    """Group items into a dict keyed by field value."""
    return self.query().group_by(field)

Actions

Query and convenience methods for the Actions model.

Registered dynamically via _registry so Actions stay plain data models.

Methods registered: - Query methods: filter(), query(), group_by() - Convenience methods: by_type(), by_date(), recent(), etc. - Python protocols: iter, len, getitem, bool, repr

query

query(self)

Return a query builder for chained filtering.

Source code in src/congressgov/services/extensions/actions.py
46
47
48
49
@register_method(Actions)
def query(self):
    """Return a query builder for chained filtering."""
    return ActionsQuery(self.actions or [])

filter

filter(self, *, lazy: bool = False, **kwargs)

Filter by field values; pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/actions.py
52
53
54
55
@register_method(Actions)
def filter(self, *, lazy: bool = False, **kwargs):
    """Filter by field values; pass lazy=True to keep chaining."""
    return self.query().filter(lazy=lazy, **kwargs)

by_type

by_type(self, action_type: str) -> Actions

Get actions by type.

Source code in src/congressgov/services/extensions/actions.py
58
59
60
61
@register_method(Actions)
def by_type(self, action_type: str) -> Actions:
    """Get actions by type."""
    return self.query().filter(type=action_type)

recent

recent(self, days: int = 30) -> Actions

Get actions from the last N days.

Source code in src/congressgov/services/extensions/actions.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@register_method(Actions)
def recent(self, days: int = 30) -> Actions:
    """Get actions from the last N days."""
    def is_recent(a: Action) -> bool:
        if not hasattr(a, 'actionDate') or not a.actionDate:
            return False

        action_date = a.actionDate
        # Handle string dates
        if isinstance(action_date, str):
            try:
                action_date = datetime.fromisoformat(action_date.replace('Z', '+00:00')).date()
            except (ValueError, AttributeError):
                return False
        # Handle datetime objects
        elif isinstance(action_date, datetime):
            action_date = action_date.date()
        elif not isinstance(action_date, date):
            return False

        # Calculate days ago
        today = date.today()
        days_ago = (today - action_date).days
        return days_ago <= days

    return self.query().where(is_recent)

group_by

group_by(self, field: str)

Group items into a dict keyed by field value.

Source code in src/congressgov/services/extensions/actions.py
92
93
94
95
@register_method(Actions)
def group_by(self, field: str):
    """Group items into a dict keyed by field value."""
    return self.query().group_by(field)

Committees

Query and convenience methods for the Committees model.

Registered dynamically via _registry so Committees stay plain data models.

Field Mappings: - chamber: Supports chamber variations ("House", "Senate", "H", "S") Uses Chamber enum for automatic expansion (if available)

Methods registered: - Query methods: filter(), query(), group_by() - Convenience methods: by_chamber(), house_committees(), senate_committees(), etc. - Python protocols: iter, len, getitem, bool, repr

query

query(self)

Return a query builder for chained filtering.

Source code in src/congressgov/services/extensions/committees.py
59
60
61
62
@register_method(Committees)
def query(self):
    """Return a query builder for chained filtering."""
    return CommitteesQuery(self.committees or [])

filter

filter(self, *, lazy: bool = False, **kwargs)

Filter by field values; pass lazy=True to keep chaining.

Source code in src/congressgov/services/extensions/committees.py
65
66
67
68
@register_method(Committees)
def filter(self, *, lazy: bool = False, **kwargs):
    """Filter by field values; pass lazy=True to keep chaining."""
    return self.query().filter(lazy=lazy, **kwargs)

by_chamber

by_chamber(self, chamber: str) -> Committees

Get committees from a specific chamber.

Source code in src/congressgov/services/extensions/committees.py
71
72
73
74
@register_method(Committees)
def by_chamber(self, chamber: str) -> Committees:
    """Get committees from a specific chamber."""
    return self.query().filter(chamber=chamber)

house_committees

house_committees(self) -> Committees

Get all House committees.

Source code in src/congressgov/services/extensions/committees.py
77
78
79
80
81
82
83
84
85
86
@register_method(Committees)
def house_committees(self) -> Committees:
    """Get all House committees."""
    def is_house_committee(c: Committee) -> bool:
        if not hasattr(c, 'chamber') or not c.chamber:
            return False
        chamber_str = c.chamber.value if hasattr(c.chamber, 'value') else str(c.chamber)
        return chamber_str.lower() in ["house", "h", "house of representatives"]

    return self.query().where(is_house_committee)

senate_committees

senate_committees(self) -> Committees

Get all Senate committees.

Source code in src/congressgov/services/extensions/committees.py
89
90
91
92
93
94
95
96
97
98
@register_method(Committees)
def senate_committees(self) -> Committees:
    """Get all Senate committees."""
    def is_senate_committee(c: Committee) -> bool:
        if not hasattr(c, 'chamber') or not c.chamber:
            return False
        chamber_str = c.chamber.value if hasattr(c.chamber, 'value') else str(c.chamber)
        return chamber_str.lower() in ["senate", "s"]

    return self.query().where(is_senate_committee)

by_name

by_name(self, name: str) -> Committees

Get committees by name (partial match, case-insensitive).

Source code in src/congressgov/services/extensions/committees.py
101
102
103
104
105
106
107
108
109
@register_method(Committees)
def by_name(self, name: str) -> Committees:
    """Get committees by name (partial match, case-insensitive)."""
    def name_matches(c: Committee) -> bool:
        if not hasattr(c, 'name') or not c.name:
            return False
        return name.lower() in c.name.lower()

    return self.query().where(name_matches)

subcommittees

subcommittees(self) -> Committees

Get only subcommittees.

Source code in src/congressgov/services/extensions/committees.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@register_method(Committees)
def subcommittees(self) -> Committees:
    """Get only subcommittees."""
    def is_subcommittee(c: Committee) -> bool:
        # Check if isCurrent indicates it's a subcommittee
        if hasattr(c, 'committeeTypeCode') and c.committeeTypeCode:
            type_code = c.committeeTypeCode.value if hasattr(c.committeeTypeCode, 'value') else str(c.committeeTypeCode)
            return type_code.lower() == 'subcommittee'
        # Fall back to checking parent committee
        if hasattr(c, 'parent') and c.parent:
            return True
        return False

    return self.query().where(is_subcommittee)

parent_committees

parent_committees(self) -> Committees

Get only parent/standing committees (no subcommittees).

Source code in src/congressgov/services/extensions/committees.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@register_method(Committees)
def parent_committees(self) -> Committees:
    """Get only parent/standing committees (no subcommittees)."""
    def is_parent_committee(c: Committee) -> bool:
        # Check if it's NOT a subcommittee
        if hasattr(c, 'committeeTypeCode') and c.committeeTypeCode:
            type_code = c.committeeTypeCode.value if hasattr(c.committeeTypeCode, 'value') else str(c.committeeTypeCode)
            return type_code.lower() != 'subcommittee'
        # Fall back to checking parent committee
        if hasattr(c, 'parent') and c.parent:
            return False
        return True

    return self.query().where(is_parent_committee)

group_by

group_by(self, field: str)

Group items into a dict keyed by field value.

Source code in src/congressgov/services/extensions/committees.py
144
145
146
147
@register_method(Committees)
def group_by(self, field: str):
    """Group items into a dict keyed by field value."""
    return self.query().group_by(field)

expand

expand(self, client: Any = None, attributes: Optional[list[str]] = None, **kwargs: Any) -> Committee

Expand attributes of this Committee instance.

Source code in src/congressgov/services/extensions/committees.py
178
179
180
181
182
183
184
185
186
187
188
189
190
@register_method(Committee)
def expand(self, client: Any = None, attributes: Optional[list[str]] = None, **kwargs: Any) -> Committee:
    """Expand attributes of this Committee instance."""
    COMMITTEE_MAPPINGS, COMMITTEE_PARAMETERS = _get_committee_config()
    return expand_sync_instance(
        self,
        mapping=COMMITTEE_MAPPINGS,
        parameters=COMMITTEE_PARAMETERS,
        client=client,
        attributes=attributes,
        entity_name="Committee",
        **kwargs,
    )

get_bills

get_bills(self, client: Any = None, **kwargs: Any) -> Any

Get bills associated with this committee.

Source code in src/congressgov/services/extensions/committees.py
193
194
195
196
197
198
199
200
201
@register_method(Committee)
def get_bills(self, client: Any = None, **kwargs: Any) -> Any:
    """Get bills associated with this committee."""
    resolved_client = ApiService._resolve_client(self, client)
    chamber = getattr(self, 'chamber', None)
    committee_code = getattr(self, 'committee_code', getattr(self, 'committeeCode', None))
    resp = committee_bills_list_sync(client=resolved_client, chamber=chamber, committee_code=committee_code, **kwargs)
    api_env = ApiEnvelope.model_validate(json.loads(resp.content))
    return BillsModelSingular.model_validate(api_env.data)

get_reports

get_reports(self, client: Any = None, **kwargs: Any) -> Any

Get reports associated with this committee.

Source code in src/congressgov/services/extensions/committees.py
204
205
206
207
208
209
210
211
212
@register_method(Committee)
def get_reports(self, client: Any = None, **kwargs: Any) -> Any:
    """Get reports associated with this committee."""
    resolved_client = ApiService._resolve_client(self, client)
    chamber = getattr(self, 'chamber', None)
    committee_code = getattr(self, 'committee_code', getattr(self, 'committeeCode', None))
    resp = committee_reports_by_committee_sync(client=resolved_client, chamber=chamber, committee_code=committee_code, **kwargs)
    api_env = ApiEnvelope.model_validate(json.loads(resp.content))
    return CommitteeReportsModelSingular.model_validate(api_env.data)

get_house_communications

get_house_communications(self, client: Any = None, **kwargs: Any) -> Any

Get House communications for this committee.

Source code in src/congressgov/services/extensions/committees.py
215
216
217
218
219
220
221
222
223
@register_method(Committee)
def get_house_communications(self, client: Any = None, **kwargs: Any) -> Any:
    """Get House communications for this committee."""
    resolved_client = ApiService._resolve_client(self, client)
    chamber = getattr(self, 'chamber', None)
    committee_code = getattr(self, 'committee_code', getattr(self, 'committeeCode', None))
    resp = house_communications_by_committee_sync(client=resolved_client, chamber=chamber, committee_code=committee_code, **kwargs)
    api_env = ApiEnvelope.model_validate(json.loads(resp.content))
    return HouseCommunicationsModelSingular.model_validate(api_env.data)

get_senate_communications

get_senate_communications(self, client: Any = None, **kwargs: Any) -> Any

Get Senate communications for this committee.

Source code in src/congressgov/services/extensions/committees.py
226
227
228
229
230
231
232
233
234
@register_method(Committee)
def get_senate_communications(self, client: Any = None, **kwargs: Any) -> Any:
    """Get Senate communications for this committee."""
    resolved_client = ApiService._resolve_client(self, client)
    chamber = getattr(self, 'chamber', None)
    committee_code = getattr(self, 'committee_code', getattr(self, 'committeeCode', None))
    resp = senate_communications_by_committee_sync(client=resolved_client, chamber=chamber, committee_code=committee_code, **kwargs)
    api_env = ApiEnvelope.model_validate(json.loads(resp.content))
    return SenateCommunicationsModelSingular.model_validate(api_env.data)

get_nominations

get_nominations(self, client: Any = None, **kwargs: Any) -> Any

Get nominations associated with this committee.

Source code in src/congressgov/services/extensions/committees.py
237
238
239
240
241
242
243
244
245
@register_method(Committee)
def get_nominations(self, client: Any = None, **kwargs: Any) -> Any:
    """Get nominations associated with this committee."""
    resolved_client = ApiService._resolve_client(self, client)
    chamber = getattr(self, 'chamber', None)
    committee_code = getattr(self, 'committee_code', getattr(self, 'committeeCode', None))
    resp = nomination_by_committee_sync(client=resolved_client, chamber=chamber, committee_code=committee_code, **kwargs)
    api_env = ApiEnvelope.model_validate(json.loads(resp.content))
    return NominationsModelSingular.model_validate(api_env.data)

get_available_attributes

get_available_attributes(self) -> list[str]

Returns list of expandable attributes.

Source code in src/congressgov/services/extensions/committees.py
248
249
250
251
252
@register_method(Committee)
def get_available_attributes(self) -> list[str]:
    """Returns list of expandable attributes."""
    COMMITTEE_MAPPINGS, _ = _get_committee_config()
    return list(COMMITTEE_MAPPINGS.keys())

URL follow

Extension methods to fetch entities from API URLs on models and refs.

fetch_member

fetch_member(self: Cosponsor, client: Any = None, parent: Any = None, **kwargs: Any) -> Any

Fetch Member from cosponsor url or bioguideId.

Source code in src/congressgov/services/extensions/url_follow.py
276
277
278
279
280
281
282
283
284
285
286
@register_method(Cosponsor)
def fetch_member(
    self: Cosponsor,
    client: Any = None,
    parent: Any = None,
    **kwargs: Any,
) -> Any:
    """Fetch ``Member`` from cosponsor ``url`` or ``bioguideId``."""
    return _fetch_member_from_sponsor_like(
        self, client=client, parent=parent, label="Cosponsor", **kwargs
    )

fetch

fetch(self: CosponsoredLegislationItem, client: Any = None, parent: Any = None, **kwargs: Any) -> Any

Fetch full Bill or Amendment from item url or id fields.

Source code in src/congressgov/services/extensions/url_follow.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
@register_method(CosponsoredLegislationItem)
def fetch(
    self: CosponsoredLegislationItem,
    client: Any = None,
    parent: Any = None,
    **kwargs: Any,
) -> Any:
    """Fetch full ``Bill`` or ``Amendment`` from item ``url`` or id fields."""
    return _fetch_legislation_from_item(
        self,
        client=client,
        parent=parent,
        label="CosponsoredLegislationItem",
        **kwargs,
    )

fetch_legislation

fetch_legislation(self: CosponsoredLegislationItem, client: Any = None, parent: Any = None, **kwargs: Any) -> Any

Deprecated alias for :meth:fetch.

Source code in src/congressgov/services/extensions/url_follow.py
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
@register_method(CosponsoredLegislationItem)
def fetch_legislation(
    self: CosponsoredLegislationItem,
    client: Any = None,
    parent: Any = None,
    **kwargs: Any,
) -> Any:
    """Deprecated alias for :meth:`fetch`."""
    warnings.warn(_DEPRECATED_FETCH_LEGISLATION, DeprecationWarning, stacklevel=2)
    return _fetch_legislation_from_item(
        self,
        client=client,
        parent=parent,
        label="CosponsoredLegislationItem",
        **kwargs,
    )