granicus_archiver.legistar.rss_parser¶
- granicus_archiver.legistar.rss_parser.FUTURE_TIMEDELTA = datetime.timedelta(days=1)¶
Amount of time after a
meeting_datethat must pass before it is no longer considered a future item
- granicus_archiver.legistar.rss_parser.is_guid(item: str) TypeIs[GUID][source]¶
Check whether the given value is a valid
GUID
- granicus_archiver.legistar.rss_parser.is_real_guid(item: str) TypeIs[REAL_GUID][source]¶
Check whether the given value is a valid
REAL_GUID
- class granicus_archiver.legistar.rss_parser.GuidCompare(guid: GUID)[source]¶
Bases:
objectHelper to compare
GUID'sSince the “GUID’s” (loose term because they aren’t really GUID’s) contain date/time information, it can actually be useful to determine whether an update is needed from a feed item or not.
Instances can be compared using the
==,!=,>,>=,<and<=operators.Using the following GUID:
>>> real_guid_a = 'F239FB22-A00A-6FF1-3E97-0F36043B96F6' >>> a = f'{real_guid_a}-2023-01-01-12-30-00' >>> b = f'{real_guid_a}-2024-01-01-12-30-00'
Both
aandbuse the same GUID, butais one year behindb>>> GuidCompare(a) == a True >>> GuidCompare(a) != b True >>> GuidCompare(a) == b False >>> GuidCompare(a) > b False >>> GuidCompare(a) < b True >>> GuidCompare(b) > a True
If the GUID portion does not match, equality checks will reflect that in equality checks:
>>> real_guid_b = '8F6DD61F-3498-3FF1-12B9-38DBE1CA9B06' >>> c = f'{real_guid_b}-2024-01-01-12-30-00' >>> GuidCompare(a) == c False >>> GuidCompare(b) == c False >>> GuidCompare(c) == a False >>> GuidCompare(c) == b False
<,>checks however are not supported in this case:>>> GuidCompare(c) > a Traceback (most recent call last): ... TypeError: '>' not supported ...
- Parameters:
guid (GUID)
- class granicus_archiver.legistar.rss_parser.FeedItem(title: str, link: URL, guid: GUID, category: Category, meeting_date: datetime, pub_date: datetime)[source]¶
Bases:
SerializableAn RSS feed item representing a meeting in the Legistar calendar
A typical item representation would be
<item> <title>City Council - 9/9/2024 - 2:00 PM</title> <link>https://mansfield.legistar.com/Gateway.aspx?.......</link> <guid isPermaLink="false">...</guid> <description/> <category>City Council</category> <pubDate>Tue, 10 Sep 2024 16:52:26 GMT</pubDate> </item>
Note the value for the
<title>element. It contains the title (or what should be the title) followed by a date and a time. Note also that the<pubDate>field would appear as9/10/2024 - 11:52 AMafter time zone conversion (instead of9/9/2024 - 2:00 PM). This is likely the last time the item was altered in Legistar (explaining the discrepancy).This makes the
pubDateuseless for determining the scheduled date/time for the event and we are forced instead to extract it from thetitleand hope for the best.Since there is no timezone information available for it however, we’re also forced to assume that the timezone is fixed as the municipality’s local time (and we all know what assuming does).
- Parameters:
- title: str¶
The meeting title. This is confusingly a combination of the meeting name, date and time in the RSS feed (see notes above). When parsed, the date and time are stripped, leaving only the title string
- category: Category¶
The meeting “category” (sometimes also referred to as “Department”) Note that this may or may not match the value of
Clip.location, but that is the intent.
- meeting_date: datetime¶
The scheduled date and time of the meeting, parsed from the original
titleand converted to the localtimezone
- ITEM_IN_PAST_DELTA: ClassVar[timedelta] = datetime.timedelta(days=365)¶
Amount of time to consider an item as “in the past” (default is one year)
- TZ: ClassVar[ZoneInfo | None] = None¶
Local timezone used to parse
meeting_date
- property is_in_past: bool¶
Whether the item is older than
ITEM_IN_PAST_DELTA
- classmethod to_csv(*items: FeedItem) str[source]¶
Get a comma-separated representation for the given feed items
The result will include a header followed by the results of
to_csv_line()for each item given.
- to_csv_line() str[source]¶
Get the comma-separated values of this item
The attributes returned will be
meeting_date(thedate()portion only)
- Return type:
- class granicus_archiver.legistar.rss_parser.Feed(items: Iterable[FeedItem] | None = None, category_maps: dict[Location, Category] | None = None)[source]¶
Bases:
SerializableAn representation of Legistar’s calendar RSS feed
The URL for this should have the options configured to show “All Years” and “All Departments” on the main
/Calendar.aspxpage. That is, unless there are more than 100 meetings in your agenda history (which is very likely to be the case).The RSS feed that legistar generates, with all of their years of wisdom, limits the number of results to 100 items making it almost completely useless for archival purposes.
The only known method to get around this is to parse separate feeds by choosing the “Departments” and sometimes each year individually. This seems (and is!) a horribly laborious process, but it’s definitely easier than manually downloading and naming over 4000 files for around 2000 meetings!
- category_maps: dict[Location, Category]¶
A
dictof any custom mappings to match theClip.locationfields to their appropriateFeedItem.categoryThe keys for this should be the
locationwith the values set to thecategory.
- classmethod from_feed(doc_str: str | bytes, category_maps: dict[Location, Category] | None = None, overflow_allowed: bool = False) Self[source]¶
Create an instance by parsing the supplied RSS data
- Parameters:
category_maps (dict[Location, Category] | None) – Value for the feed’s
category_mapsoverflow_allowed (bool) – If
Truedisables raisingLegistarThinksRSSCanPaginateErrorif the feed’s item count is 100. The default (False) allows exception to be raised.
- Raises:
LegistarThinksRSSCanPaginateError – If the feed’s item count is 100 and overflow_allowed is
False- Return type:
- find_clip_match(clip: Clip, search_delta: timedelta = datetime.timedelta(seconds=14400)) FeedItem[source]¶
Attempt to match the given clip to a
FeedItemThe
Clip.locationis first used to filter items bycategory(using any custom overrides incategory_maps).A match between the
Clip.datetimeandFeedItem.meeting_dateis then searched and the closest match is returned if within +/- four hours.- Raises:
CategoryError – If no category match was found
DatetimeError – If a match could not be found for clip’s the datetime
- Parameters:
- Return type: