metergraph-core 0.2.42__tar.gz → 0.2.53__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.
- {metergraph_core-0.2.42/src/metergraph_core.egg-info → metergraph_core-0.2.53}/PKG-INFO +1 -1
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/pyproject.toml +1 -1
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/catalog.py +112 -32
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/data/prices.yaml +1047 -81
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/loader.py +69 -12
- {metergraph_core-0.2.42 → metergraph_core-0.2.53/src/metergraph_core.egg-info}/PKG-INFO +1 -1
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/MANIFEST.in +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/README.md +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/setup.cfg +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/__init__.py +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/billing.py +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/data/models.yaml +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/models.py +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core/retrieval.py +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core.egg-info/SOURCES.txt +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core.egg-info/requires.txt +0 -0
- {metergraph_core-0.2.42 → metergraph_core-0.2.53}/src/metergraph_core.egg-info/top_level.txt +0 -0
|
@@ -81,6 +81,7 @@ _BEDROCK_CHANNEL = "aws-bedrock"
|
|
|
81
81
|
_BEDROCK_CHANNELS = frozenset({_BEDROCK_CHANNEL, "aws-bedrock-geo"})
|
|
82
82
|
_BEDROCK_VERSION_SUFFIX = re.compile(r"-v\d+(?::\d+)?$")
|
|
83
83
|
_BEDROCK_GLOBAL_PREFIX = "global."
|
|
84
|
+
_GLOBAL_REGION = "global"
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
def _bedrock_fallback_ids(model_key: str) -> tuple[tuple[str, bool], ...]:
|
|
@@ -163,6 +164,28 @@ class Alias:
|
|
|
163
164
|
pricing_channel: str
|
|
164
165
|
rules: Mapping[str, Any]
|
|
165
166
|
publisher: str | None = None
|
|
167
|
+
effective_from: datetime | None = None
|
|
168
|
+
effective_to: datetime | None = None
|
|
169
|
+
source_url: str | None = None
|
|
170
|
+
# Limits an identifier to matching regions; global matches every snapshot.
|
|
171
|
+
price_region: tuple[str, ...] = ()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _crosses_price_region(alias: Alias) -> bool:
|
|
175
|
+
"""Whether global-prefix fallback would cross a price-region boundary."""
|
|
176
|
+
return bool(alias.price_region) and alias.price_region != (_GLOBAL_REGION,)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _effective_windows_overlap(left: Alias, right: Alias) -> bool:
|
|
180
|
+
return (
|
|
181
|
+
left.effective_to is None
|
|
182
|
+
or right.effective_from is None
|
|
183
|
+
or right.effective_from < left.effective_to
|
|
184
|
+
) and (
|
|
185
|
+
right.effective_to is None
|
|
186
|
+
or left.effective_from is None
|
|
187
|
+
or left.effective_from < right.effective_to
|
|
188
|
+
)
|
|
166
189
|
|
|
167
190
|
|
|
168
191
|
@dataclass(frozen=True, slots=True)
|
|
@@ -421,28 +444,40 @@ def _price_tokens(
|
|
|
421
444
|
class CatalogSnapshot:
|
|
422
445
|
def __init__(
|
|
423
446
|
self,
|
|
424
|
-
aliases: Mapping[tuple[str, str], Alias],
|
|
447
|
+
aliases: Mapping[tuple[str, str], Alias | tuple[Alias, ...] | list[Alias]],
|
|
425
448
|
prices: list[Price],
|
|
426
449
|
*,
|
|
427
450
|
region: str,
|
|
428
451
|
) -> None:
|
|
429
|
-
self._aliases =
|
|
452
|
+
self._aliases = {
|
|
453
|
+
key: (value,) if isinstance(value, Alias) else tuple(value)
|
|
454
|
+
for key, value in aliases.items()
|
|
455
|
+
}
|
|
430
456
|
self._prices: dict[tuple[str, str], list[Price]] = {}
|
|
431
|
-
|
|
457
|
+
deployment_aliases: dict[tuple[str, str], list[Alias]] = {}
|
|
432
458
|
self._region = region.strip().lower()
|
|
433
|
-
for (_, observed_model),
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
459
|
+
for (_, observed_model), candidates in self._aliases.items():
|
|
460
|
+
for alias in candidates:
|
|
461
|
+
channel = alias.pricing_channel.strip().lower()
|
|
462
|
+
for model in (observed_model, alias.canonical_id):
|
|
463
|
+
key = (model.strip().lower(), channel)
|
|
464
|
+
deployment_aliases.setdefault(key, []).append(alias)
|
|
465
|
+
self._deployment_aliases = {
|
|
466
|
+
key: tuple(candidates) for key, candidates in deployment_aliases.items()
|
|
467
|
+
}
|
|
468
|
+
for (model, channel), candidates in self._deployment_aliases.items():
|
|
469
|
+
for index, alias in enumerate(candidates):
|
|
470
|
+
for other in candidates[index + 1:]:
|
|
471
|
+
if not _effective_windows_overlap(alias, other):
|
|
472
|
+
continue
|
|
473
|
+
if (
|
|
474
|
+
alias.canonical_id != other.canonical_id
|
|
475
|
+
or dict(alias.rules) != dict(other.rules)
|
|
476
|
+
):
|
|
477
|
+
raise ValueError(
|
|
478
|
+
f"ambiguous deployment alias {model!r} "
|
|
479
|
+
f"for channel {channel!r}"
|
|
480
|
+
)
|
|
446
481
|
for price in prices:
|
|
447
482
|
self._prices.setdefault((price.model_id, price.pricing_channel), []).append(
|
|
448
483
|
price
|
|
@@ -450,10 +485,36 @@ class CatalogSnapshot:
|
|
|
450
485
|
for candidates in self._prices.values():
|
|
451
486
|
candidates.sort(key=lambda price: price.effective_from, reverse=True)
|
|
452
487
|
|
|
488
|
+
def _alias_for(self, candidates: tuple[Alias, ...], at: datetime) -> Alias | None:
|
|
489
|
+
when = _coerce_datetime(at)
|
|
490
|
+
effective = [
|
|
491
|
+
alias
|
|
492
|
+
for alias in candidates
|
|
493
|
+
if (alias.effective_from is None or alias.effective_from <= when)
|
|
494
|
+
and (alias.effective_to is None or when < alias.effective_to)
|
|
495
|
+
]
|
|
496
|
+
# Regional pins constrain availability; global pins match everywhere.
|
|
497
|
+
for region in dict.fromkeys((self._region, _GLOBAL_REGION)):
|
|
498
|
+
for alias in effective:
|
|
499
|
+
if region in alias.price_region:
|
|
500
|
+
return alias
|
|
501
|
+
for alias in effective:
|
|
502
|
+
if not alias.price_region:
|
|
503
|
+
return alias
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
def _price_regions(self, alias: Alias) -> tuple[str, ...]:
|
|
507
|
+
if not alias.price_region:
|
|
508
|
+
return tuple(dict.fromkeys((self._region, "*", _GLOBAL_REGION)))
|
|
509
|
+
for region in (self._region, _GLOBAL_REGION):
|
|
510
|
+
if region in alias.price_region:
|
|
511
|
+
return (region,)
|
|
512
|
+
return ()
|
|
513
|
+
|
|
453
514
|
def _price_for(self, alias: Alias, at: datetime) -> Price | None:
|
|
454
515
|
at = at if at.tzinfo else at.replace(tzinfo=timezone.utc)
|
|
455
516
|
candidates = self._prices.get((alias.model_id, alias.pricing_channel), [])
|
|
456
|
-
for region in
|
|
517
|
+
for region in self._price_regions(alias):
|
|
457
518
|
for price in candidates:
|
|
458
519
|
if price.region.lower() != region:
|
|
459
520
|
continue
|
|
@@ -476,19 +537,21 @@ class CatalogSnapshot:
|
|
|
476
537
|
model_key = model.strip().lower()
|
|
477
538
|
canonical_ids = {
|
|
478
539
|
alias.canonical_id
|
|
479
|
-
for (known_model, _channel),
|
|
540
|
+
for (known_model, _channel), candidates in self._deployment_aliases.items()
|
|
480
541
|
if known_model == model_key
|
|
542
|
+
for alias in candidates
|
|
481
543
|
}
|
|
482
544
|
if len(canonical_ids) != 1:
|
|
483
545
|
return None
|
|
484
546
|
channels: set[str] = set()
|
|
485
547
|
for canonical_id in canonical_ids:
|
|
486
|
-
for
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
548
|
+
for candidates in self._deployment_aliases.values():
|
|
549
|
+
for alias in candidates:
|
|
550
|
+
if alias.canonical_id != canonical_id:
|
|
551
|
+
continue
|
|
552
|
+
channel = direct_channel_for_provider(alias.publisher)
|
|
553
|
+
if channel is not None and (canonical_id, channel) in self._prices:
|
|
554
|
+
channels.add(channel)
|
|
492
555
|
return next(iter(channels)) if len(channels) == 1 else None
|
|
493
556
|
|
|
494
557
|
def resolve_price(
|
|
@@ -503,17 +566,22 @@ class CatalogSnapshot:
|
|
|
503
566
|
|
|
504
567
|
model_key = str(model or "").strip().lower()
|
|
505
568
|
channel_key = str(channel or "").strip().lower()
|
|
506
|
-
|
|
569
|
+
when = _coerce_datetime(at)
|
|
570
|
+
candidates = self._deployment_aliases.get((model_key, channel_key), ())
|
|
571
|
+
alias = self._alias_for(candidates, when)
|
|
507
572
|
if alias is None and channel_key in _BEDROCK_CHANNELS:
|
|
508
573
|
for candidate, dropped_global in _bedrock_fallback_ids(model_key):
|
|
509
574
|
if dropped_global and channel_key != _BEDROCK_CHANNEL:
|
|
510
575
|
continue
|
|
511
|
-
|
|
576
|
+
candidates = self._deployment_aliases.get((candidate, channel_key), ())
|
|
577
|
+
alias = self._alias_for(candidates, when)
|
|
578
|
+
if alias is not None and dropped_global and _crosses_price_region(alias):
|
|
579
|
+
alias = None
|
|
512
580
|
if alias is not None:
|
|
513
581
|
break
|
|
514
582
|
if alias is None:
|
|
515
583
|
return None
|
|
516
|
-
price = self._price_for(alias,
|
|
584
|
+
price = self._price_for(alias, when)
|
|
517
585
|
if price is None:
|
|
518
586
|
return None
|
|
519
587
|
return ResolvedPrice(
|
|
@@ -539,19 +607,31 @@ class CatalogSnapshot:
|
|
|
539
607
|
provider_key = str(provider or "").strip().lower()
|
|
540
608
|
provider_key = _PROVIDER_ALIASES.get(provider_key, provider_key)
|
|
541
609
|
model_key = str(model or "").strip().lower()
|
|
542
|
-
|
|
610
|
+
when = _coerce_datetime(at)
|
|
611
|
+
candidates = self._aliases.get((provider_key, model_key), ())
|
|
612
|
+
alias_known = bool(candidates)
|
|
613
|
+
alias = self._alias_for(candidates, when)
|
|
543
614
|
if alias is None and provider_key == "bedrock":
|
|
544
615
|
for candidate, dropped_global in _bedrock_fallback_ids(model_key):
|
|
545
|
-
|
|
616
|
+
fallback = self._aliases.get((provider_key, candidate), ())
|
|
617
|
+
alias_known = alias_known or bool(fallback)
|
|
618
|
+
found = self._alias_for(fallback, when)
|
|
546
619
|
if found is None or found.pricing_channel not in _BEDROCK_CHANNELS:
|
|
547
620
|
continue
|
|
548
|
-
if dropped_global and
|
|
621
|
+
if dropped_global and (
|
|
622
|
+
found.pricing_channel != _BEDROCK_CHANNEL
|
|
623
|
+
or _crosses_price_region(found)
|
|
624
|
+
):
|
|
549
625
|
continue
|
|
550
626
|
alias = found
|
|
551
627
|
break
|
|
552
628
|
if alias is None:
|
|
629
|
+
if alias_known:
|
|
630
|
+
return CostResult(
|
|
631
|
+
None, None, None, "unpriced", ("no_effective_alias",)
|
|
632
|
+
)
|
|
553
633
|
return CostResult(None, None, None, "unpriced", ("unknown_model",))
|
|
554
|
-
price = self._price_for(alias,
|
|
634
|
+
price = self._price_for(alias, when)
|
|
555
635
|
if price is None:
|
|
556
636
|
return CostResult(
|
|
557
637
|
alias.canonical_id,
|
|
@@ -564,7 +644,7 @@ class CatalogSnapshot:
|
|
|
564
644
|
cost, reasons = _price_tokens(
|
|
565
645
|
price,
|
|
566
646
|
{**price.rules, **alias.rules},
|
|
567
|
-
at=
|
|
647
|
+
at=when,
|
|
568
648
|
input_tokens=input_tokens,
|
|
569
649
|
output_tokens=output_tokens,
|
|
570
650
|
cache_read_tokens=cache_read_tokens,
|