All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project follows Semantic Versioning. While the library was in early development (0.x), breaking changes appeared between minor versions when needed to fix correctness issues or align with the MIDAS API. The 1.0.0 release tracks the California Energy Commission's MIDAS v2.0 API.
1.0.1 - 2026-06-24
Patch release. Regenerating the spec examples against the live API surfaced two wire-shape details the lenient client silently tolerated; both are now handled and guarded by tests.
- Integer day-types are no longer dropped. Live v2.0 SGIP GHG (MOER) and Flex Alert (ALRT) responses encode
DayStart/DayEndas integers (1=Monday through 7=Sunday, 8=Holiday: the upload-format code), where v1.0 returned weekday strings._parse_day_typepreviously triedDayType(value)and returnedNoneon the resultingValueError, so every MOER/ALRT interval lost itsday_start/day_end.DayType.from_wirenow accepts the integer code, a digit string, or a weekday string; the electricity-rate wire form (unconfirmed pending utility-data migration) is covered by accepting both.
RateInfo.signal_typeandRateInfo.description. v2.0 rate-values responses carry the per-RINSignalTypelabel andDescriptionat the top level (as in the RIN list); these are now surfaced on the entity instead of being silently ignored.- Strict wire-contract validation in the integration suite. New
TestWireContractvalidates raw live responses against themidas-api-specsJSON Schemas (viajsonschema), and the coerced tests now assert day-type values, so future wire drift fails the suite rather than being absorbed by the lenient runtime models. PointMIDAS_SPECS_DIRat amidas-api-specscheckout to enable the schema checks (they skip cleanly if absent). Addsjsonschemaas a dev dependency.
1.0.0 — 2026-06-22
The California Energy Commission released MIDAS v2.0 on 2026-06-22, a breaking change to the live API. v1.0 was removed from the live service that day, so python-midas 1.0.0 is a v2-only release — v1.0 compatibility is intentionally dropped. See doc/v2-migration.md for the v1.0→v2.0 upgrade guide, and the upstream midas-api-specs v2 branch for the spec-level delta. Every change below was verified by a live smoke-test against the production v2.0 API on release day. python-midas is a read-only consumer client: v2.0 GET endpoints are unauthenticated, so create_anonymous_client needs no credentials; the authenticated constructors remain for the utility upload path only.
- Breaking: the per-interval value field is read from
Value(capital V) instead of v1.0's lowercasevalue. The CEC standardised the casing in v2.0;ValueData.from_rawreadsValue. - Breaking:
ValueDatainterval boundaries are a zone-awareperiodtuple. The four zone-naive fieldsdate_start,date_end,time_start, andtime_end(datetime.date/datetime.time) are removed; each interval is exposed asperiod: tuple[pendulum.DateTime, pendulum.DateTime] | None— a(start, end)pair of zone-aware moments. A bare wall-clock time with no zone is ambiguous, and in v2.0 the wire deliversDateStart/TimeStart/DateEnd/TimeEndin UTC for every signal type (v1.0 delivered SGIP GHG and Flex Alert in Pacific Time — an upstream-provider passthrough bug the CEC fixed in v2.0). The pair is composed from the UTC wire date+time and preserved in UTC — the library never normalizes to a display zone. Need a wall-clock date or time? Convert an endpoint yourself:period[0].in_tz("America/Los_Angeles").date(). The original wire strings remain on_raw. (Mirrorspython-oa3'sIntervalPeriod.period.) - Breaking: datetime coercion is zone-aware
pendulum.DateTime, preserving the wire zone. Every coerced timestamp carries an honest timezone —Z-suffixed fields (SystemTime_UTC,SignupCloseDate) stay UTC, as doesLastUpdated, which in v2.0 carries an explicit basic-format offset (+0000);parse_instanthonours it (no Pacific-local shift). The library does not normalize to a single zone; consumers convert with.in_tz(...)as needed. This replaces the prior_parse_datetimethat silently treated every naive field as UTC. - Breaking:
rin_list/coerce_rin_listpeel the v2.0 keyed-object response. v2.0 wraps the entry array in a single-keyed object — on the live API the key is alwaysRates, regardless of the requestedSignalType(theGHGEmissions/FlexAlerts/Allkeys implied by early design notes do not appear on the wire).coerce_rin_list(raw, signal_type)peels the single value without switching on the key name and returns a uniformlist[RinListEntry]; the newRinListResponsemodel validates the shape. - Breaking: lookup tables return a keyed object. v2.0 wraps lookup rows in
{table_name, data: [...]}instead of v1.0's bare array.lookup_table/coerce_lookup_tablepeeldata; the newLookupTableResponsemodel validates the shape.LookupEntrygains optionalpayload_descriptorandunit_type(extra columns theUnittable carries). - Breaking:
SignalTypeenum carries the v2.0 long-form wire labels.SignalType.RATESis now"Electricity Rates"(was"Rates"); new membersGHG_EMISSIONS = "Greenhouse Gas Emissions"andFLEX_ALERT = "California Independent System Operator Flex Alert". v2.0 always populates the per-entrySignalTypefield (v1.0 returnednullfor GHG/Flex Alert entries); the old labels no longer map. - Breaking:
RateTypeenum tracks the v2.0 wire values, which differ by signal type. Electricity rates return the shortRatetypeUploadCode, so the enum now uses short codes (TOU,CPP,RTP,VPP,DSR,V-D,C-D,R-D,T-D) — was the long descriptions ("Time of use", …). GHG returns the long Description"Greenhouse Gas emissions"(RateType.GHG) and Flex Alert returns"Flex Alert"(RateType.FLEX_ALERT); both are retained. Unknown values still pass through as plain strings. - Breaking:
Unitreports v2.0 GHG emissions in grams. NewUnit.G_CO2_PER_KWH = "g/kWh CO2"— values are 1000× larger than v1.0'skg/kWh CO2for the same physical reading.Unit.KG_CO2_PER_KWHis retained for pre-migration historical-archive reads;MIDASClient.ghg()recognises both. - Breaking:
get_historical_data/historical_datause the path-param endpoint/HistoricalData/{rate_id}(was the?id=query param), and reject a range longer than the v2.0 6-month max per call withValueError. The Python signatures are unchanged.
create_anonymous_client— an unauthenticated client for the v2.0 GET endpoints (no token acquired, noAuthorizationheader sent). This is the default, supported access mode for this read-only consumer;create_client/create_auto_clientremain for the utility upload (POST) path only.src/midas/time.py— pendulum-based time module shared in spirit withpython-oa3: thePendulumDateTimeannotated Pydantic type (parses on input, serialises to ISO 8601 preserving the wire offset) plusparse_instant(zone-tagged fields, incl.LastUpdated),parse_local(attach a documented zone to a bare wall-clock string), andparse_value_moment(compose a UTCValueInformationboundary).MIDAS_ZONE = "America/Los_Angeles"documents MIDAS's native administrative zone.RinListResponseandLookupTableResponseentity models for the v2.0 keyed RIN-list and lookup-table responses, exported frommidasandmidas.entities.RateType.MOERfor the v2.0 unified SGIP GHG signal, plus the short electricityRatetypecodes (VPP,DSR,V-D,C-D,R-D,T-D).
get_holidays/holidays/coerce_holidaysand theHolidaymodel — v2.0 retired the standalone/Holidayendpoint (absent from the CEC's published OpenAPI). Remove any holiday calls; theHolidayday-type value in rate schedules (DayType.HOLIDAY) is unaffected.get_historical_list/historical_list/coerce_historical_list— v2.0 retires the/HistoricalListendpoint. Userin_list(signal_type=0)for the full active RIN list.- The
HolidayandTimeZonelookup tables are retired in v2.0 (?LookupTable=Holiday/TimeZoneno longer return data).
0.1.1 — 2026-03-20
RateInfo.idacceptsnullfor empty realtime responses (the live API returns an all-nullRateInfowhen a RIN has no current realtime datapoint).
0.1.0 — 2026-03-20
Initial implementation. Two-layer raw/coerced data model: raw httpx.Response accessors plus coerced Pydantic entities (RateInfo, ValueData, RinListEntry, Holiday, LookupEntry) with Decimal prices and pendulum datetimes, each carrying its original wire dict on _raw. httpx-based MIDASClient with HTTP Basic → bearer-token auth and transparent auto-refresh (AutoTokenAuth); RIN list, rate values, lookup tables, holidays, and historical endpoints; signal-type helpers (ghg, flex_alert, flex_alert_active); domain enums (SignalType, RateType, Unit, DayType).