va-lis-client 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. va_lis_client-0.1.0/LICENSE +21 -0
  2. va_lis_client-0.1.0/PKG-INFO +538 -0
  3. va_lis_client-0.1.0/README.md +507 -0
  4. va_lis_client-0.1.0/pyproject.toml +54 -0
  5. va_lis_client-0.1.0/setup.cfg +4 -0
  6. va_lis_client-0.1.0/src/va_lis_client/__init__.py +4 -0
  7. va_lis_client-0.1.0/src/va_lis_client/client.py +598 -0
  8. va_lis_client-0.1.0/src/va_lis_client/exceptions.py +2 -0
  9. va_lis_client-0.1.0/src/va_lis_client/http.py +34 -0
  10. va_lis_client-0.1.0/src/va_lis_client/models/__init__.py +121 -0
  11. va_lis_client-0.1.0/src/va_lis_client/models/calendar.py +256 -0
  12. va_lis_client-0.1.0/src/va_lis_client/models/committee.py +111 -0
  13. va_lis_client-0.1.0/src/va_lis_client/models/common.py +53 -0
  14. va_lis_client-0.1.0/src/va_lis_client/models/docket.py +167 -0
  15. va_lis_client-0.1.0/src/va_lis_client/models/event.py +171 -0
  16. va_lis_client-0.1.0/src/va_lis_client/models/legislation.py +201 -0
  17. va_lis_client-0.1.0/src/va_lis_client/models/schedule.py +88 -0
  18. va_lis_client-0.1.0/src/va_lis_client/models/session.py +51 -0
  19. va_lis_client-0.1.0/src/va_lis_client/models/text.py +168 -0
  20. va_lis_client-0.1.0/src/va_lis_client/rate_limiter.py +67 -0
  21. va_lis_client-0.1.0/src/va_lis_client.egg-info/PKG-INFO +538 -0
  22. va_lis_client-0.1.0/src/va_lis_client.egg-info/SOURCES.txt +24 -0
  23. va_lis_client-0.1.0/src/va_lis_client.egg-info/dependency_links.txt +1 -0
  24. va_lis_client-0.1.0/src/va_lis_client.egg-info/requires.txt +10 -0
  25. va_lis_client-0.1.0/src/va_lis_client.egg-info/top_level.txt +1 -0
  26. va_lis_client-0.1.0/tests/test_client.py +166 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonny Fuller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,538 @@
1
+ Metadata-Version: 2.4
2
+ Name: va-lis-client
3
+ Version: 0.1.0
4
+ Summary: Python client for the Virginia Legislative Information System (LIS) API
5
+ Author-email: Jonny Fuller <mr.waffles@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/JonnyWaffles/va-lis-client
8
+ Project-URL: Repository, https://github.com/JonnyWaffles/va-lis-client
9
+ Project-URL: Bug Tracker, https://github.com/JonnyWaffles/va-lis-client/issues
10
+ Keywords: virginia,legislature,LIS,legislation,API,client
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pydantic>=2.0
23
+ Requires-Dist: requests
24
+ Requires-Dist: urllib3
25
+ Provides-Extra: rate-limiting
26
+ Requires-Dist: redis>=4.0; extra == "rate-limiting"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # va-lis-client
33
+
34
+ Python client for the **Virginia Legislative Information System (LIS) REST API**.
35
+
36
+ Built from live API responses — the official OpenAPI specs are unreliable
37
+ (response envelope keys, field casing, and parameter behavior all differ from
38
+ the docs).
39
+
40
+ - Developer portal: https://lis.virginia.gov/developers
41
+ - API key registration: https://lis.virginia.gov/apiregistration
42
+ - Help desk: lis@dlas.virginia.gov
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install va-lis-client
48
+ ```
49
+
50
+ With Redis rate limiting support:
51
+
52
+ ```bash
53
+ pip install "va-lis-client[rate-limiting]"
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ ```python
59
+ import os
60
+ from va_lis_client import LISClient
61
+
62
+ # Pass the key directly, or set the LIS_API_KEY environment variable
63
+ client = LISClient(api_key="your-api-key-here")
64
+ # or: os.environ["LIS_API_KEY"] = "your-key"; client = LISClient()
65
+
66
+ # The GA's current working session. During the interim this is the
67
+ # *upcoming* session, not the one that just ended — see
68
+ # "Bill identity & carry-over" below.
69
+ session = client.get_default_session()
70
+
71
+ # All bills in that session
72
+ bills = client.get_session_bills(session_code=int(session.SessionCode))
73
+
74
+ # Full detail for a bill
75
+ bill = client.get_bill(legislation_id=98525)
76
+
77
+ # Bill text (HTML) and summaries
78
+ texts = client.get_bill_text_detail(legislation_id=98525, session_code=20261)
79
+ summaries = client.get_bill_summaries(legislation_number="HB1", session_code=20261)
80
+ ```
81
+
82
+ ## Authentication
83
+
84
+ Every request sends a `WebAPIKey` header with a partner GUID obtained at
85
+ https://lis.virginia.gov/apiregistration.
86
+
87
+ Set the key via environment variable:
88
+
89
+ ```bash
90
+ export LIS_API_KEY=your-guid-here
91
+ ```
92
+
93
+ Or pass it directly to the constructor:
94
+
95
+ ```python
96
+ client = LISClient(api_key="your-guid-here")
97
+ ```
98
+
99
+ **Important:** Heartbeat endpoints accept *any* key — even garbage. Use
100
+ `client.check_api_key()` to actually validate a key via the
101
+ PartnerAuthentication service.
102
+
103
+ ## Data availability
104
+
105
+ - The session reference list reaches back to **1994**.
106
+ - Bill data has been observed working for the **2024–2027 sessions** (as of
107
+ Aug 2026). Earlier guidance said only 2025–2026 was authorized via the API.
108
+ - Older session data is available as `legacylis.virginia.gov` CSV downloads.
109
+ - No rate limits are documented anywhere.
110
+
111
+ ## Identifier cheat-sheet
112
+
113
+ Bills are uniquely identified by **session + bill number**, but the API uses
114
+ several overlapping identifiers:
115
+
116
+ | Identifier | Example | Notes |
117
+ |---|---|---|
118
+ | `SessionCode` | `20261` | Year + sequence. `20261` = 2026 Regular, `20262` = Special I |
119
+ | `SessionID` | `59` | Surrogate PK. Either code or ID works as query param |
120
+ | `LegislationNumber` | `"HB1"` | **Must be unpadded** — `HB0001` returns 204 |
121
+ | `LegislationID` | `98525` | Surrogate PK for the *logical* bill — reused across carry-over (see below) |
122
+ | `DocumentCode` | `"HB1ER"` | Bill number + version suffix |
123
+ | `LegislationTextID` | `257719` | PK for a specific text version |
124
+ | `CommitteeID` | `14` | Surrogate PK for a committee |
125
+ | `CommitteeNumber` | `"H14"` | Chamber prefix + number, e.g. `H14`, `S02` |
126
+ | `CalendarID` | `21146` | PK for a House floor calendar |
127
+ | `DocketID` | `21114` | PK for a Senate committee docket |
128
+ | `ScheduleID` | `3626` | PK for a scheduled meeting |
129
+ | `LegislationEventID` | `1561089` | PK for a bill history event |
130
+
131
+ ## Bill identity & carry-over
132
+
133
+ Virginia's even-year sessions may carry unfinished bills over into the
134
+ following odd-year session — never the reverse, never twice, and never across
135
+ a two-year General Assembly term. The API models this by **reusing the
136
+ `LegislationID`** (confirmed against the live API, 2026-08-12):
137
+
138
+ - A carried-over bill appears in **both** sessions' `get_session_bills` lists
139
+ with the same `LegislationID` (e.g. HB9, ID `98631`, in `20261` and `20271`).
140
+ - `get_bill(legislation_id)` returns top-level `SessionCode`/`SessionID` as
141
+ `None`. The `Sessions` list carries one cross-ref per session the bill
142
+ appears in — both the even and odd session for a carried-over bill — and is
143
+ the explicit lineage record.
144
+ - So `LegislationID` identifies the *logical bill within a GA term*: at most
145
+ two session appearances (even, then odd if continued). Session-scoped
146
+ identity is `(SessionCode, LegislationNumber)` or
147
+ `(SessionCode, LegislationID)`.
148
+ - An odd-year bill list starts as pure carry-over — as of Aug 2026, `20271`
149
+ returned 443 bills, every one sharing its ID with `20261`. Continued bills
150
+ must be acted on by mid-November of the even year or they die, so expect
151
+ that set to shrink before the odd session convenes.
152
+
153
+ **Sync warning:** if you mirror bills into a database, don't put a global
154
+ unique constraint on `LegislationID` — the first odd-year sync will violate it
155
+ on the first carried-over bill. Scope uniqueness to
156
+ `(session, LegislationID)`.
157
+
158
+ **Default session:** `get_default_session()` tracks the GA's *working*
159
+ session, not the last one convened. Once a session wraps up (sine die, veto
160
+ session, enactments effective July 1), the default advances to the upcoming
161
+ session during the interim — by Aug 2026 it was already `20271`, which is
162
+ where continued bills and (from mid-November) new prefiles accumulate. For
163
+ retrospective work on a just-ended session, pass its explicit session code.
164
+
165
+ ## Response envelope gotchas
166
+
167
+ The docs say `ListItems` everywhere. Reality:
168
+
169
+ | Endpoint | Docs say | Actual key |
170
+ |---|---|---|
171
+ | Sessions | `ListItems` | `Sessions` |
172
+ | Legislation list | `ListItems` | `Legislations` |
173
+ | Legislation detail | `ListItems` | `Legislations` |
174
+ | Legislation statuses | `ListItems` | `References` |
175
+ | Text list | `ListItems` | `LegislationTextList` |
176
+ | Text detail | `ListItems` | `TextsList` |
177
+ | Summaries | `ListItems` | `LegislationSummaries` |
178
+ | Version refs | `ListItems` | `LegislationVersionList` |
179
+ | Partner check | flat | `PartnerList` |
180
+ | Calendars | `ListItems` | `Calendars` |
181
+ | Dockets | `ListItems` | `Dockets` |
182
+ | Schedules | `ListItems` | `Schedules` |
183
+ | Committees | `ListItems` | `Committees` |
184
+ | Legislation events | `ListItems` | `LegislationEvents` |
185
+ | Schedule types | `ListItems` | `ScheduleTypes` |
186
+ | Meeting rooms | `ListItems` | `MeetingRooms` |
187
+
188
+ ## API services
189
+
190
+ ### Currently implemented
191
+
192
+ #### Session (`/Session/api/`)
193
+ - `get_sessions(year)` → list of sessions for a year
194
+ - `get_default_session()` → the current/active session
195
+
196
+ #### Legislation (`/Legislation/api/`)
197
+ - `get_session_bills(session_code)` → lightweight bill list for a session
198
+ - `get_bill(legislation_id)` → full bill detail with all patrons
199
+ - `get_legislation_statuses()` → all 52 status reference entries
200
+
201
+ #### LegislationText (`/LegislationText/api/`)
202
+ - `get_legislation_versions()` → 13 version type references (Introduced, Enrolled, etc.)
203
+ - `get_bill_texts(legislation_number, session_code)` → text version list
204
+ - `get_bill_text_detail(legislation_id, session_code)` → full HTML bill text
205
+
206
+ #### LegislationSummary (`/LegislationSummary/api/`)
207
+ - `get_bill_summaries(legislation_number, session_code)` → bill summaries (HTML)
208
+
209
+ #### LegislationEvent (`/LegislationEvent/api/`)
210
+ - `get_bill_events(legislation_id)` → chronological action history for a bill
211
+ - `get_event_types()` → ~3,900 event type references
212
+ - `get_actor_types()` → 5 actor types (House, Senate, Committee, etc.)
213
+
214
+ ### Modeled but not yet wired to client methods
215
+
216
+ #### Schedule (`/Schedule/api/`)
217
+ The master meeting calendar. All committee hearings, caucuses, floor sessions.
218
+
219
+ | Endpoint | Description |
220
+ |---|---|
221
+ | `getschedulelistasync` | List meetings by date range, committee, type, room |
222
+ | `previewvcalfileasync` | Generate vCal file for calendar integration |
223
+ | `getmeetingroomsreferenceasync` | Room reference (by chamber) |
224
+ | `getscheduletypesreferenceasync` | Schedule type reference |
225
+
226
+ Key fields: `ScheduleDate`, `ScheduleTime` (often free-text like "15 minutes
227
+ after the Senate adjourns"), `RoomDescription`, `OwnerName` (committee),
228
+ `IsCancelled`, `ScheduleType` (Committee/Chamber/Conference/Caucus/Docket/Other).
229
+
230
+ **Data quality warning:** `ScheduleTime` is frequently free-text, not a
231
+ parseable time. The API faithfully reflects whatever the LIS clerks entered.
232
+
233
+ #### Calendar (`/Calendar/api/`)
234
+ Floor calendars (both chambers) and committee dockets (Senate only).
235
+
236
+ | Endpoint | Description |
237
+ |---|---|
238
+ | `getcalendarlistasync` | List calendars by session + chamber |
239
+ | `getcalendarsbyidasync` | Full calendar with categories, agendas, votes |
240
+ | `getdocketlistasync` | List dockets by committee (**Senate only**) |
241
+ | `getdocketlistbycommitteenumberasync` | Dockets by committee number (**Senate only**) |
242
+ | `getdocketsbyidasync` | Full docket with agenda items, bills, patrons |
243
+ | `getcalendaractionsreferenceasync` | Calendar action reference |
244
+ | `getcalendarcategorytypesreferenceasync` | Category type reference |
245
+ | `getcalendartypesreferenceasync` | Calendar type reference |
246
+
247
+ **Important:** Dockets are **Senate-only**. The House uses Calendars. Requesting
248
+ a docket with `chamberCode=H` returns HTTP 400 with the message
249
+ "Dockets can only be for ChamberCode = S".
250
+
251
+ Docket detail includes:
252
+ - `DocketCategories` → `DocketItems` → bills with summaries and patrons
253
+ - `CommitteeMember` roster with roles (Chair, etc.)
254
+ - `Staff` assignments
255
+ - `Schedules` (linked back to the Schedule service for when/where)
256
+ - PDF/JSON file downloads
257
+
258
+ Calendar detail includes:
259
+ - `CalendarCategories` → `Agendas` → bills with vote tallies
260
+ - `AgendaItems` → `VoteMember` with per-member vote responses (Y/N)
261
+ - Order of business (Call to Order, Invocation, etc.)
262
+
263
+ #### Committee (`/Committee/api/`)
264
+
265
+ | Endpoint | Description |
266
+ |---|---|
267
+ | `getcommitteesasync` | Full committee detail (by number or date) |
268
+ | `getcommitteelistasync` | Shallow list by session + chamber |
269
+ | `getcommitteebyidasync` | Single committee by ID + session |
270
+
271
+ Key fields: `CommitteeID`, `CommitteeNumber` (e.g. `H14`, `S02`), `Name`,
272
+ `ChamberCode`, `Abbreviation`, `MeetingNote`, `ParentCommitteeID` (for
273
+ subcommittees).
274
+
275
+ ### Not yet explored
276
+
277
+ These services exist in the portal but haven't been investigated:
278
+
279
+ - AdvancedLegislationSearch
280
+ - CommunicationFileGeneration
281
+ - Contact
282
+ - LegislationByMember
283
+ - LegislationCollections
284
+ - LegislationCommunications
285
+ - LegislationFileGeneration
286
+ - LegislationPatron
287
+ - LegislationSubject
288
+ - Member
289
+ - MemberVoteSearch
290
+ - MembersByCommittee
291
+ - MinutesBook
292
+ - Organization
293
+ - Person
294
+ - Personnel
295
+
296
+ ## How meetings/dockets work
297
+
298
+ ```
299
+ Committee ──→ Docket (Senate) or Calendar (House) ──→ Schedule ──→ Room
300
+ │ │
301
+ │ has DocketItems/Agendas │ has ScheduleDate,
302
+ │ (which bills are up) │ ScheduleTime (free-text!),
303
+ │ │ RoomDescription,
304
+ └────────────────────────────────────────│ IsCancelled
305
+ └──→ VoteRoom reference
306
+ ```
307
+
308
+ - **Schedule** is the "when and where" — but `ScheduleTime` is often free-text
309
+ like "15 minutes after adjournment"
310
+ - **Docket/Calendar** is the "what" — which bills are on the agenda
311
+ - **Committee** is the "who" — membership, chair, staff
312
+
313
+ ## How a bill becomes law (Virginia)
314
+
315
+ A bill's lifecycle through the General Assembly, mapped to LIS status IDs.
316
+ Virginia has a bicameral legislature (House of Delegates + Senate). A bill
317
+ must pass both chambers in identical form before going to the Governor.
318
+
319
+ ### The happy path
320
+
321
+ ```
322
+ ORIGINATING CHAMBER
323
+ ───────────────────
324
+ ┌─ Prefiled (Nov–Jan) ──→ Introduced (1)
325
+ │ │
326
+ │ ▼
327
+ │ In Committee (2)
328
+ │ ┌────┴────┐
329
+ │ │ │
330
+ │ In Subcommittee Left In Committee (20)
331
+ │ (19) = dead, session ends
332
+ │ │
333
+ │ ▼
334
+ │ Reported Out (3 or 52)
335
+ │ Committee votes to send
336
+ │ bill to the full chamber
337
+ │ │
338
+ │ ▼
339
+ │ Floor vote in originating chamber
340
+ │ Passed House (4) or Passed Senate (5)
341
+
342
+ │ CROSSING OVER
343
+ │ ─────────────
344
+ │ Bill goes to the other chamber
345
+ │ and repeats: Committee → Floor
346
+ │ │
347
+ │ ▼
348
+ │ Passed Both Chambers (6, 39, 40)
349
+ │ = "Enrolled" version created (ER)
350
+ │ │
351
+ │ ▼
352
+
353
+ │ GOVERNOR
354
+ │ ────────
355
+ │ Communicated (13) → With Governor (7)
356
+ │ │
357
+ │ ▼
358
+ │ Awaiting Governor's Action (38)
359
+ │ ┌────┼────────────┐
360
+ │ │ │ │
361
+ │ ▼ ▼ ▼
362
+ │ Approved Gov's Gov's Veto
363
+ │ (8) Recommend. (24, 26)
364
+ │ │ (23, 27) │
365
+ │ ▼ │ ▼
366
+ │ Enacted ▼ Override vote
367
+ │ (36) Chambers or bill dies
368
+ │ │ adopt? (33)
369
+ │ ▼ │
370
+ │ Acts of ▼
371
+ │ Assembly Back to
372
+ │ Chapter Enrolled
373
+ │ (25)
374
+ └────────────────────────────────────────────────
375
+ ```
376
+
377
+ ### Key status groups
378
+
379
+ **Filing & introduction:**
380
+
381
+ | ID | DisplayName | Internal Name | What happened |
382
+ |---|---|---|---|
383
+ | 11 | Preview | Preview | Prefiled but session hasn't started yet |
384
+ | 1 | Introduced | Introduced | Formally introduced on the chamber floor |
385
+
386
+ **Committee phase** — where most bills die:
387
+
388
+ | ID | DisplayName | Internal Name | What happened |
389
+ |---|---|---|---|
390
+ | 2 | In Committee | In Committee | Referred to a standing committee |
391
+ | 37 | Committee Referral Pending | Committee Referral Pending | Awaiting committee assignment |
392
+ | 19 | In Subcommittee | In Subcommittee | Referred to a subcommittee |
393
+ | 20 | Left In Committee | Left In Committee | Bill was never voted on — effectively killed |
394
+ | 41 | In Committee | Continued From | Carried over from a previous session into committee |
395
+ | 3 | In House | Reported Out-House | Committee voted to advance (House side) |
396
+ | 52 | In Senate | Reported Out-Senate | Committee voted to advance (Senate side) |
397
+ | 30 | Incorporated | Incorporated | Merged into another bill |
398
+
399
+ **Floor votes:**
400
+
401
+ | ID | DisplayName | Internal Name | What happened |
402
+ |---|---|---|---|
403
+ | 4 | Passed House | Passed House | Full House approved |
404
+ | 5 | Passed Senate | Passed Senate | Full Senate approved |
405
+ | 12 | Engrossed | Engrossed | Passed one chamber, snapshot before crossover |
406
+ | 28 | Engrossed | Engrossed with Amendment | Same but with amendments applied |
407
+ | 29 | Engrossed | Reengrossed with Amendment | Re-amended after engrossment |
408
+
409
+ **Conference** — when the two chambers passed different versions:
410
+
411
+ | ID | DisplayName | Internal Name | What happened |
412
+ |---|---|---|---|
413
+ | 34 | Conference Requested | Conference Requested | One chamber asks for a conference committee |
414
+ | 16 | In Conference | In Conference | Conference committee is negotiating |
415
+ | 31 | In Conference | Conference Report Agreed | Conferees reached agreement |
416
+ | 32 | In Conference | Conference Report Rejected | Conferees' report was rejected |
417
+ | 39 | Passed | Conference Report Adopted | Both chambers accepted the conference version |
418
+ | 45 | Failed | Failed in Conference | Conferees couldn't agree — bill dies |
419
+
420
+ **Passed both chambers:**
421
+
422
+ | ID | DisplayName | Internal Name | What happened |
423
+ |---|---|---|---|
424
+ | 6 | Passed | Passed Both | Both chambers approved identical text (→ version 3, Enrolled) |
425
+ | 40 | Passed | Passed | Generic "passed" status |
426
+ | 35 | Enrolled | Enrolled-House | Final enrolled text prepared (House origin) |
427
+ | 44 | Enrolled | Enrolled-Senate | Final enrolled text prepared (Senate origin) |
428
+
429
+ **Governor pipeline:**
430
+
431
+ | ID | DisplayName | Internal Name | What happened |
432
+ |---|---|---|---|
433
+ | 13 | Communicated | Communicated | Bill formally transmitted to the Governor |
434
+ | 7 | With Governor | With Governor | Governor has received the bill |
435
+ | 14 | Awaiting Signature | Awaiting Signature | Waiting for physical signature |
436
+ | 38 | Awaiting Governor's Action | Awaiting Governor's Action | Governor is reviewing |
437
+ | 8 | Approved | Approved | **Governor signed it** (→ version 4, Chaptered) |
438
+ | 36 | Enacted | Enacted | Becomes law (→ version 4, Chaptered) |
439
+ | 25 | Acts of Assembly Chapter | Acts of Assembly Chapter | Assigned a chapter number |
440
+ | 23 | Governor's Recommendation | Governor's Recommendation | Sent back with proposed amendments (→ version 10) |
441
+ | 27 | Governor's Recommendation | Governor's Recommendation | Same (no version link) |
442
+ | 33 | Governor's Recommendation Adopted | Gov Recommendation Adopted | Chambers accepted the amendments |
443
+ | 24 | Governor's Veto | Governor's Veto | **Governor vetoed** (→ version 9, Veto Explanation) |
444
+ | 26 | Governor's Veto | Governor's Veto | Same (no version link) |
445
+
446
+ **Terminal / carry-over:**
447
+
448
+ | ID | DisplayName | Internal Name | What happened |
449
+ |---|---|---|---|
450
+ | 9 | Failed | Failed | Bill defeated on a floor vote |
451
+ | 10 | Continued | Continued To | Carried over to next session |
452
+ | 46 | Continued | Continued to House | Carried over, sitting in House |
453
+ | 47 | Continued | Continued to Senate | Carried over, sitting in Senate |
454
+ | 48 | Continued | Continued to Conference | Carried over, in conference |
455
+
456
+ ### Why there are 52 statuses for ~15 states
457
+
458
+ LIS uses separate status IDs to track *how* a bill entered a logical state.
459
+ For example, "In Conference" has four IDs distinguishing whether the conference
460
+ just started, the report was agreed, rejected, or continued. The `Name`
461
+ (internal) captures the sub-reason; the `DisplayName` (user-facing) collapses
462
+ them.
463
+
464
+ ### Version-linked statuses
465
+
466
+ Some statuses have a `LegislationVersionID` indicating which bill text version
467
+ corresponds to that state:
468
+
469
+ | Status | Version ID | Version Name |
470
+ |---|---|---|
471
+ | Passed Both (6) | 3 | Enrolled |
472
+ | Approved (8) | 4 | Chaptered |
473
+ | Enacted (36) | 4 | Chaptered |
474
+ | Engrossed with Amendment (28) | 2 | Engrossed |
475
+ | Reengrossed with Amendment (29) | 6 | Reengrossed |
476
+ | Governor's Recommendation (23) | 10 | Gov Recommendation |
477
+ | Governor's Veto (24) | 9 | Veto Explanation |
478
+ | Reenrolled-House (42) | 7 | Reenrolled |
479
+ | Reenrolled-Senate (43) | 7 | Reenrolled |
480
+
481
+ ## Rate limiting (optional)
482
+
483
+ If you're running many workers and want to share a global rate limit across
484
+ them, install the `rate-limiting` extra and use `try_acquire_request_permit`:
485
+
486
+ ```bash
487
+ pip install "va-lis-client[rate-limiting]"
488
+ ```
489
+
490
+ ```python
491
+ import redis
492
+ from va_lis_client.rate_limiter import try_acquire_request_permit
493
+
494
+ r = redis.from_url("redis://localhost:6379/0")
495
+
496
+ if try_acquire_request_permit(redis_client=r, rate_limit=100):
497
+ client.get_session_bills(session_code=20261)
498
+ else:
499
+ # back off
500
+ ...
501
+ ```
502
+
503
+ Or set `REDIS_URL` and `LIS_RATE_LIMIT` environment variables and call with
504
+ no arguments — the module picks them up automatically.
505
+
506
+ ## Windows / government CA roots
507
+
508
+ On Windows, Python's OpenSSL doesn't automatically load system certificates,
509
+ which can cause SSL errors when connecting to hosts with enterprise or
510
+ government CA roots. `va-lis-client` includes a `SystemCertSSLAdapter` that
511
+ loads the system certificate store automatically, so no manual cert bundling
512
+ is needed.
513
+
514
+ ## Package structure
515
+
516
+ ```
517
+ src/va_lis_client/
518
+ ├── __init__.py # re-exports LISClient, LISClientError
519
+ ├── client.py # HTTP client — returns Pydantic models
520
+ ├── exceptions.py # LISClientError
521
+ ├── http.py # SystemCertSSLAdapter + shared requests session
522
+ ├── rate_limiter.py # optional Redis-based rate limiter
523
+ └── models/
524
+ ├── __init__.py # re-exports all models
525
+ ├── common.py # Heartbeat, Partner
526
+ ├── session.py # Session, SessionEvent
527
+ ├── legislation.py # Patron, Legislation, LegislationStatus, ...
528
+ ├── text.py # LegislationTextItem, LegislationTextDetail, ...
529
+ ├── event.py # LegislationEvent, LegislationEventType, ActorType
530
+ ├── committee.py # Committee, CommitteeMember, CommitteeAction
531
+ ├── schedule.py # Schedule, ScheduleType, MeetingRoom
532
+ ├── calendar.py # CalendarDetail, Agenda, VoteMember, ...
533
+ └── docket.py # DocketDetail, DocketItem, DocketCategory, ...
534
+ ```
535
+
536
+ ## License
537
+
538
+ MIT