service-capacity-modeling 0.3.88__tar.gz → 0.3.89__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.
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/PKG-INFO +1 -1
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/enum_utils.py +42 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/interface.py +9 -12
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/counter.py +6 -6
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/evcache.py +2 -2
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/kafka.py +2 -2
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/PKG-INFO +1 -1
- service_capacity_modeling-0.3.89/tests/test_enum_utils.py +316 -0
- service_capacity_modeling-0.3.88/tests/test_enum_utils.py +0 -140
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/LICENSE +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/README.md +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/capacity_planner.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/pricing/aws/3yr-reserved_ec2.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/pricing/aws/3yr-reserved_zz-overrides.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/profiles.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5d.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5n.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6id.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c7a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c7i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c8i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i3en.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i4i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i7i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m4.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m5.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m5n.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6id.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6idn.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6in.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m7a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m7i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m8i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r4.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r5.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r5n.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6id.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6idn.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6in.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r7a.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r7i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r8i.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_drives.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_instances.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_services.json +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/common.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/headroom_strategy.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/aurora.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/cassandra.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/control.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/crdb.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/ddb.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/elasticsearch.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/entity.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/graphkv.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/iso_date_math.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/key_value.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/postgres.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/rds.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/stateless_java.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/time_series.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/time_series_config.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/wal.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/org/netflix/zookeeper.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/models/utils.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/stats.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/tools/__init__.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/tools/auto_shape.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/tools/fetch_pricing.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/tools/generate_missing.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling/tools/instance_families.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/SOURCES.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/dependency_links.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/entry_points.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/requires.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/service_capacity_modeling.egg-info/top_level.txt +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/setup.cfg +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/setup.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_arguments.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_buffers.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_common.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_desire_merge.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_generate_scenarios.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_hardware.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_hardware_shapes.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_headroom_strategy.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_io2.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_model_dump.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_reproducible.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_simulation.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_utils.py +0 -0
- {service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_working_set.py +0 -0
|
@@ -5,6 +5,7 @@ runtime-accessible docstrings.
|
|
|
5
5
|
|
|
6
6
|
import ast
|
|
7
7
|
import inspect
|
|
8
|
+
import sys
|
|
8
9
|
from enum import Enum
|
|
9
10
|
from functools import partial
|
|
10
11
|
from operator import is_
|
|
@@ -16,6 +17,47 @@ from pydantic.json_schema import JsonSchemaValue
|
|
|
16
17
|
from pydantic_core import CoreSchema
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
__all__ = ["StrEnum", "enum_docstrings"]
|
|
21
|
+
|
|
22
|
+
# StrEnum backport for Python 3.10 compatibility
|
|
23
|
+
# On Python 3.11+, use the stdlib version
|
|
24
|
+
if sys.version_info >= (3, 11):
|
|
25
|
+
from enum import StrEnum as StrEnum # pylint: disable=useless-import-alias
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
class StrEnum(str, Enum):
|
|
29
|
+
"""Backport of Python 3.11 StrEnum.
|
|
30
|
+
|
|
31
|
+
Provides consistent string behavior across all Python versions:
|
|
32
|
+
- f"{x}" returns the value (not "Foo.BAR")
|
|
33
|
+
- str(x) returns the value (not "Foo.BAR")
|
|
34
|
+
- x == "value" returns True (string comparison works)
|
|
35
|
+
|
|
36
|
+
This addresses PEP 663 which changed str(Enum) behavior in Python 3.11,
|
|
37
|
+
making (str, Enum) return "Foo.BAR" in f-strings instead of the value.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __new__(cls, value: str, *args: Any, **kwargs: Any) -> "StrEnum":
|
|
41
|
+
if not isinstance(value, str):
|
|
42
|
+
raise TypeError(f"{value!r} is not a string")
|
|
43
|
+
member = str.__new__(cls, value)
|
|
44
|
+
member._value_ = value
|
|
45
|
+
return member
|
|
46
|
+
|
|
47
|
+
def __str__(self) -> str:
|
|
48
|
+
return str(self.value)
|
|
49
|
+
|
|
50
|
+
def __format__(self, format_spec: str) -> str:
|
|
51
|
+
# Ensures f-strings return value, not "Foo.BAR"
|
|
52
|
+
return str(self.value).__format__(format_spec)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _generate_next_value_(
|
|
56
|
+
name: str, start: int, count: int, last_values: list[str]
|
|
57
|
+
) -> str:
|
|
58
|
+
return name.lower()
|
|
59
|
+
|
|
60
|
+
|
|
19
61
|
E = TypeVar("E", bound=Enum)
|
|
20
62
|
|
|
21
63
|
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
import re
|
|
5
5
|
import sys
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from enum import Enum
|
|
8
7
|
from fractions import Fraction
|
|
9
8
|
from functools import lru_cache
|
|
10
9
|
from typing import Any
|
|
@@ -22,6 +21,7 @@ from pydantic import ConfigDict
|
|
|
22
21
|
from pydantic import Field
|
|
23
22
|
|
|
24
23
|
from service_capacity_modeling.enum_utils import enum_docstrings
|
|
24
|
+
from service_capacity_modeling.enum_utils import StrEnum
|
|
25
25
|
|
|
26
26
|
GIB_IN_BYTES = 1024 * 1024 * 1024
|
|
27
27
|
MIB_IN_BYTES = 1024 * 1024
|
|
@@ -46,16 +46,13 @@ class ExcludeUnsetModel(BaseModel):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
@enum_docstrings
|
|
49
|
-
class IntervalModel(
|
|
49
|
+
class IntervalModel(StrEnum):
|
|
50
50
|
"""Statistical distribution models for approximating intervals
|
|
51
51
|
|
|
52
52
|
When we have uncertainty intervals (low, mid, high), we need to choose
|
|
53
53
|
a probability distribution to model that uncertainty for simulation purposes.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
-
def __str__(self) -> str:
|
|
57
|
-
return str(self.value)
|
|
58
|
-
|
|
59
56
|
def __repr__(self) -> str:
|
|
60
57
|
return f"D({self.value})"
|
|
61
58
|
|
|
@@ -204,7 +201,7 @@ def normalized_aws_size(name: str) -> Fraction:
|
|
|
204
201
|
###############################################################################
|
|
205
202
|
|
|
206
203
|
|
|
207
|
-
class Lifecycle(
|
|
204
|
+
class Lifecycle(StrEnum):
|
|
208
205
|
"""Represents the lifecycle of hardware from initial preview
|
|
209
206
|
to end-of-life.
|
|
210
207
|
|
|
@@ -223,7 +220,7 @@ class Lifecycle(str, Enum):
|
|
|
223
220
|
|
|
224
221
|
|
|
225
222
|
@enum_docstrings
|
|
226
|
-
class DriveType(
|
|
223
|
+
class DriveType(StrEnum):
|
|
227
224
|
"""Represents the type and attachment model of storage drives
|
|
228
225
|
|
|
229
226
|
Drives can be either local (ephemeral, instance-attached) or network-attached
|
|
@@ -366,7 +363,7 @@ class Drive(ExcludeUnsetModel):
|
|
|
366
363
|
|
|
367
364
|
|
|
368
365
|
@enum_docstrings
|
|
369
|
-
class Platform(
|
|
366
|
+
class Platform(StrEnum):
|
|
370
367
|
"""Represents the CPU architecture or managed service platform
|
|
371
368
|
|
|
372
369
|
Hardware can run on different CPU architectures (x86_64, ARM) or be a fully
|
|
@@ -617,7 +614,7 @@ class Pricing(ExcludeUnsetModel):
|
|
|
617
614
|
|
|
618
615
|
|
|
619
616
|
@enum_docstrings
|
|
620
|
-
class AccessPattern(
|
|
617
|
+
class AccessPattern(StrEnum):
|
|
621
618
|
"""The access pattern determines capacity planning priorities: latency-sensitive
|
|
622
619
|
services target low P99 latency, while throughput-oriented services optimize
|
|
623
620
|
for maximum requests per second.
|
|
@@ -633,7 +630,7 @@ class AccessPattern(str, Enum):
|
|
|
633
630
|
|
|
634
631
|
|
|
635
632
|
@enum_docstrings
|
|
636
|
-
class AccessConsistency(
|
|
633
|
+
class AccessConsistency(StrEnum):
|
|
637
634
|
"""
|
|
638
635
|
Generally speaking consistency is expensive, so models need to know what
|
|
639
636
|
kind of consistency will be required in order to estimate CPU usage
|
|
@@ -851,7 +848,7 @@ class CurrentClusters(ExcludeUnsetModel):
|
|
|
851
848
|
|
|
852
849
|
|
|
853
850
|
@enum_docstrings
|
|
854
|
-
class BufferComponent(
|
|
851
|
+
class BufferComponent(StrEnum):
|
|
855
852
|
"""Represents well known buffer components such as compute and storage
|
|
856
853
|
|
|
857
854
|
Note that while these are common and defined here for models to share,
|
|
@@ -887,7 +884,7 @@ class BufferComponent(str, Enum):
|
|
|
887
884
|
|
|
888
885
|
|
|
889
886
|
@enum_docstrings
|
|
890
|
-
class BufferIntent(
|
|
887
|
+
class BufferIntent(StrEnum):
|
|
891
888
|
"""Defines the intent of buffer directives for capacity planning"""
|
|
892
889
|
|
|
893
890
|
desired = "desired"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
|
-
from enum import Enum
|
|
3
2
|
from typing import Any
|
|
4
3
|
from typing import Callable
|
|
5
4
|
from typing import Dict
|
|
@@ -10,6 +9,7 @@ from pydantic import Field
|
|
|
10
9
|
|
|
11
10
|
from .stateless_java import nflx_java_app_capacity_model
|
|
12
11
|
from .stateless_java import NflxJavaAppArguments
|
|
12
|
+
from service_capacity_modeling.enum_utils import StrEnum
|
|
13
13
|
from service_capacity_modeling.interface import AccessConsistency
|
|
14
14
|
from service_capacity_modeling.interface import AccessPattern
|
|
15
15
|
from service_capacity_modeling.interface import CapacityDesires
|
|
@@ -26,13 +26,13 @@ from service_capacity_modeling.interface import RegionContext
|
|
|
26
26
|
from service_capacity_modeling.models import CapacityModel
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class NflxCounterCardinality(
|
|
29
|
+
class NflxCounterCardinality(StrEnum):
|
|
30
30
|
low = "low"
|
|
31
31
|
medium = "medium"
|
|
32
32
|
high = "high"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
class NflxCounterMode(
|
|
35
|
+
class NflxCounterMode(StrEnum):
|
|
36
36
|
best_effort = "best-effort"
|
|
37
37
|
eventual = "eventual"
|
|
38
38
|
exact = "exact"
|
|
@@ -95,7 +95,7 @@ class NflxCounterCapacityModel(CapacityModel):
|
|
|
95
95
|
) -> Tuple[Tuple[str, Callable[[CapacityDesires], CapacityDesires]], ...]:
|
|
96
96
|
stores = []
|
|
97
97
|
|
|
98
|
-
if extra_model_arguments["counter.mode"] == NflxCounterMode.best_effort
|
|
98
|
+
if extra_model_arguments["counter.mode"] == NflxCounterMode.best_effort:
|
|
99
99
|
stores.append(("org.netflix.evcache", lambda x: x))
|
|
100
100
|
else:
|
|
101
101
|
# Shared evcache cluster is used for eventual and exact counters
|
|
@@ -114,9 +114,9 @@ class NflxCounterCapacityModel(CapacityModel):
|
|
|
114
114
|
# high cardinality : rollups happen once every 10 seconds
|
|
115
115
|
# TODO: Account for read amplification from time slice configs
|
|
116
116
|
# for better model accuracy
|
|
117
|
-
if counter_cardinality == NflxCounterCardinality.low
|
|
117
|
+
if counter_cardinality == NflxCounterCardinality.low:
|
|
118
118
|
rollups_per_second = counter_deltas_per_second.scale(0.0167)
|
|
119
|
-
elif counter_cardinality == NflxCounterCardinality.medium
|
|
119
|
+
elif counter_cardinality == NflxCounterCardinality.medium:
|
|
120
120
|
rollups_per_second = counter_deltas_per_second.scale(0.0333)
|
|
121
121
|
else:
|
|
122
122
|
rollups_per_second = counter_deltas_per_second.scale(0.1)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import math
|
|
3
|
-
from enum import Enum
|
|
4
3
|
from typing import Any
|
|
5
4
|
from typing import Dict
|
|
6
5
|
from typing import Optional
|
|
@@ -9,6 +8,7 @@ from typing import Tuple
|
|
|
9
8
|
from pydantic import BaseModel
|
|
10
9
|
from pydantic import Field
|
|
11
10
|
|
|
11
|
+
from service_capacity_modeling.enum_utils import StrEnum
|
|
12
12
|
from service_capacity_modeling.interface import AccessConsistency
|
|
13
13
|
from service_capacity_modeling.interface import AccessPattern
|
|
14
14
|
from service_capacity_modeling.interface import Buffer
|
|
@@ -45,7 +45,7 @@ from service_capacity_modeling.stats import dist_for_interval
|
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class Replication(
|
|
48
|
+
class Replication(StrEnum):
|
|
49
49
|
none = "none"
|
|
50
50
|
sets = "sets"
|
|
51
51
|
evicts = "evicts"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import math
|
|
3
|
-
from enum import Enum
|
|
4
3
|
from typing import Any
|
|
5
4
|
from typing import Dict
|
|
6
5
|
from typing import Optional
|
|
@@ -9,6 +8,7 @@ from typing import Tuple
|
|
|
9
8
|
from pydantic import BaseModel
|
|
10
9
|
from pydantic import Field
|
|
11
10
|
|
|
11
|
+
from service_capacity_modeling.enum_utils import StrEnum
|
|
12
12
|
from service_capacity_modeling.interface import AccessConsistency
|
|
13
13
|
from service_capacity_modeling.interface import AccessPattern
|
|
14
14
|
from service_capacity_modeling.interface import Buffer
|
|
@@ -45,7 +45,7 @@ from service_capacity_modeling.models.org.netflix.iso_date_math import iso_to_se
|
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class ClusterType(
|
|
48
|
+
class ClusterType(StrEnum):
|
|
49
49
|
strong = "strong"
|
|
50
50
|
ha = "high-availability"
|
|
51
51
|
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from pydantic import ValidationError
|
|
4
|
+
|
|
5
|
+
from service_capacity_modeling.interface import AccessConsistency
|
|
6
|
+
from service_capacity_modeling.interface import AccessPattern
|
|
7
|
+
from service_capacity_modeling.interface import BufferComponent
|
|
8
|
+
from service_capacity_modeling.interface import BufferIntent
|
|
9
|
+
from service_capacity_modeling.interface import DriveType
|
|
10
|
+
from service_capacity_modeling.interface import IntervalModel
|
|
11
|
+
from service_capacity_modeling.interface import Lifecycle
|
|
12
|
+
from service_capacity_modeling.interface import Platform
|
|
13
|
+
from service_capacity_modeling.models.org.netflix.counter import NflxCounterCardinality
|
|
14
|
+
from service_capacity_modeling.models.org.netflix.counter import NflxCounterMode
|
|
15
|
+
from service_capacity_modeling.models.org.netflix.evcache import Replication
|
|
16
|
+
from service_capacity_modeling.models.org.netflix.kafka import ClusterType
|
|
17
|
+
|
|
18
|
+
# List of all enums that should have per-member docstrings
|
|
19
|
+
DOCUMENTED_ENUMS = [
|
|
20
|
+
IntervalModel,
|
|
21
|
+
DriveType,
|
|
22
|
+
Platform,
|
|
23
|
+
AccessPattern,
|
|
24
|
+
AccessConsistency,
|
|
25
|
+
BufferComponent,
|
|
26
|
+
BufferIntent,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize("enum_class", DOCUMENTED_ENUMS)
|
|
31
|
+
def test_enums_have_docstrings(enum_class):
|
|
32
|
+
"""Test that all interface.py enums have comprehensive per-member
|
|
33
|
+
docstrings
|
|
34
|
+
|
|
35
|
+
This test ensures that all enum members have their own
|
|
36
|
+
runtime-accessible docstrings, which makes them discoverable via
|
|
37
|
+
help(), IDE tooltips, and the __doc__ attribute.
|
|
38
|
+
|
|
39
|
+
The enums use the @enum_docstrings decorator which parses source code
|
|
40
|
+
to attach docstrings that appear below each member (following PEP 257
|
|
41
|
+
attribute docstring conventions).
|
|
42
|
+
|
|
43
|
+
See: https://stackoverflow.com/questions/19330460/
|
|
44
|
+
how-do-i-put-docstrings-on-enums
|
|
45
|
+
"""
|
|
46
|
+
enum_name = enum_class.__name__
|
|
47
|
+
|
|
48
|
+
# Check class has a docstring
|
|
49
|
+
assert enum_class.__doc__ is not None, f"{enum_name} must have a class docstring"
|
|
50
|
+
assert len(enum_class.__doc__) > 0, f"{enum_name} class docstring must not be empty"
|
|
51
|
+
|
|
52
|
+
# Check each member has its own unique docstring
|
|
53
|
+
for member in enum_class:
|
|
54
|
+
assert member.__doc__ is not None, (
|
|
55
|
+
f"{enum_name}.{member.name} must have a docstring. "
|
|
56
|
+
f"Add a docstring after the member definition:\n"
|
|
57
|
+
f' {member.name} = "{member.value}"\n'
|
|
58
|
+
f' """Your documentation here"""'
|
|
59
|
+
)
|
|
60
|
+
assert len(member.__doc__.strip()) > 0, (
|
|
61
|
+
f"{enum_name}.{member.name} docstring must not be empty"
|
|
62
|
+
)
|
|
63
|
+
# Verify it's not just the class docstring (should be member-specific)
|
|
64
|
+
assert member.__doc__ != enum_class.__doc__, (
|
|
65
|
+
f"{enum_name}.{member.name} should have its own docstring, "
|
|
66
|
+
f"not inherit the class docstring. "
|
|
67
|
+
f"Did the @enum_docstrings decorator work?"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Verify different members have different docstrings
|
|
71
|
+
members = list(enum_class)
|
|
72
|
+
if len(members) >= 2:
|
|
73
|
+
assert members[0].__doc__ != members[1].__doc__, (
|
|
74
|
+
f"{enum_name}: Different enum members should have different docstrings"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize("enum_class", DOCUMENTED_ENUMS)
|
|
79
|
+
def test_enums_json_schema_includes_member_docstrings(enum_class):
|
|
80
|
+
"""Test that enum member docstrings appear in Pydantic JSON schemas
|
|
81
|
+
|
|
82
|
+
The @enum_docstrings decorator adds __get_pydantic_json_schema__ to
|
|
83
|
+
generate oneOf schemas with per-member descriptions. This ensures
|
|
84
|
+
enum documentation is available in API schemas, OpenAPI specs, etc.
|
|
85
|
+
"""
|
|
86
|
+
enum_name = enum_class.__name__
|
|
87
|
+
|
|
88
|
+
# Create a test model using this enum
|
|
89
|
+
TestModel = type(
|
|
90
|
+
"TestModel",
|
|
91
|
+
(BaseModel,),
|
|
92
|
+
{"__annotations__": {"field": enum_class}},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Get the JSON schema
|
|
96
|
+
schema = TestModel.model_json_schema()
|
|
97
|
+
|
|
98
|
+
# Check the enum definition exists in $defs
|
|
99
|
+
assert "$defs" in schema, f"{enum_name}: Schema missing $defs"
|
|
100
|
+
assert enum_name in schema["$defs"], f"{enum_name}: Enum not in $defs"
|
|
101
|
+
|
|
102
|
+
enum_schema = schema["$defs"][enum_name]
|
|
103
|
+
|
|
104
|
+
# Check oneOf exists with member descriptions
|
|
105
|
+
assert "oneOf" in enum_schema, (
|
|
106
|
+
f"{enum_name}: JSON schema missing oneOf for member descriptions"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
one_of = enum_schema["oneOf"]
|
|
110
|
+
assert len(one_of) == len(enum_class), (
|
|
111
|
+
f"{enum_name}: oneOf should have {len(enum_class)} entries"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Verify each member has proper schema entry
|
|
115
|
+
for member in enum_class:
|
|
116
|
+
matching_entries = [
|
|
117
|
+
entry for entry in one_of if entry.get("const") == member.value
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
assert len(matching_entries) == 1, (
|
|
121
|
+
f"{enum_name}.{member.name}: Should have exactly one oneOf entry"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
entry = matching_entries[0]
|
|
125
|
+
|
|
126
|
+
# Check required fields
|
|
127
|
+
assert "const" in entry, f"{enum_name}.{member.name}: Missing 'const'"
|
|
128
|
+
assert "title" in entry, f"{enum_name}.{member.name}: Missing 'title'"
|
|
129
|
+
assert "description" in entry, (
|
|
130
|
+
f"{enum_name}.{member.name}: Missing 'description'"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Verify description matches member docstring
|
|
134
|
+
assert entry["description"] == member.__doc__, (
|
|
135
|
+
f"{enum_name}.{member.name}: Schema description doesn't match "
|
|
136
|
+
f"member.__doc__"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Verify description is not empty and not the class docstring
|
|
140
|
+
assert len(entry["description"].strip()) > 0, (
|
|
141
|
+
f"{enum_name}.{member.name}: Description should not be empty"
|
|
142
|
+
)
|
|
143
|
+
assert entry["description"] != enum_class.__doc__, (
|
|
144
|
+
f"{enum_name}.{member.name}: Description should be member-specific, "
|
|
145
|
+
f"not the class docstring"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
###############################################################################
|
|
150
|
+
# StrEnum Behavior Tests (PEP 663) #
|
|
151
|
+
###############################################################################
|
|
152
|
+
#
|
|
153
|
+
# These tests validate that StrEnum provides consistent string behavior across
|
|
154
|
+
# all Python versions (3.10, 3.11, 3.12).
|
|
155
|
+
#
|
|
156
|
+
# Background: PEP 663 changed (str, Enum) behavior in Python 3.11:
|
|
157
|
+
# - Python 3.10: f"{x}" returns value, str(x) returns "Foo.BAR"
|
|
158
|
+
# - Python 3.11: f"{x}" returns "Foo.BAR", str(x) returns "Foo.BAR"
|
|
159
|
+
#
|
|
160
|
+
# StrEnum provides consistent behavior where f"{x}", str(x), and x.value ALL
|
|
161
|
+
# return the value string, making enum usage predictable across Python versions.
|
|
162
|
+
#
|
|
163
|
+
# See: https://peps.python.org/pep-0663/
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# All StrEnum classes in the codebase
|
|
167
|
+
STRENUM_CLASSES = [
|
|
168
|
+
IntervalModel,
|
|
169
|
+
Lifecycle,
|
|
170
|
+
DriveType,
|
|
171
|
+
Platform,
|
|
172
|
+
AccessPattern,
|
|
173
|
+
AccessConsistency,
|
|
174
|
+
BufferComponent,
|
|
175
|
+
BufferIntent,
|
|
176
|
+
NflxCounterCardinality,
|
|
177
|
+
NflxCounterMode,
|
|
178
|
+
Replication,
|
|
179
|
+
ClusterType,
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
184
|
+
def test_strenum_inherits_from_str(enum_class):
|
|
185
|
+
"""Test that all enum classes inherit from str (StrEnum behavior).
|
|
186
|
+
|
|
187
|
+
This ensures the enum can be used directly in string contexts.
|
|
188
|
+
"""
|
|
189
|
+
for member in enum_class:
|
|
190
|
+
assert isinstance(member, str), (
|
|
191
|
+
f"{enum_class.__name__}.{member.name} should be a str instance"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
196
|
+
def test_strenum_fstring_returns_value(enum_class):
|
|
197
|
+
"""Test that f-strings return the enum value, not 'EnumName.member'.
|
|
198
|
+
|
|
199
|
+
This is the key behavior that changed in Python 3.11 (PEP 663).
|
|
200
|
+
Without StrEnum, f"{x}" returns "Foo.BAR" instead of "bar".
|
|
201
|
+
"""
|
|
202
|
+
for member in enum_class:
|
|
203
|
+
fstring_result = f"{member}"
|
|
204
|
+
assert fstring_result == member.value, (
|
|
205
|
+
f'f"{{{{member}}}}" for {enum_class.__name__}.{member.name} returned '
|
|
206
|
+
f'"{fstring_result}", expected "{member.value}"'
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
211
|
+
def test_strenum_str_returns_value(enum_class):
|
|
212
|
+
"""Test that str(x) returns the enum value, not 'EnumName.member'.
|
|
213
|
+
|
|
214
|
+
With StrEnum, str(x) should return the value for consistent behavior.
|
|
215
|
+
"""
|
|
216
|
+
for member in enum_class:
|
|
217
|
+
str_result = str(member)
|
|
218
|
+
assert str_result == member.value, (
|
|
219
|
+
f"str({enum_class.__name__}.{member.name}) returned "
|
|
220
|
+
f'"{str_result}", expected "{member.value}"'
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
225
|
+
def test_strenum_format_returns_value(enum_class):
|
|
226
|
+
"""Test that format()/format spec returns the enum value.
|
|
227
|
+
|
|
228
|
+
This uses "{}".format() which should behave like f-strings.
|
|
229
|
+
"""
|
|
230
|
+
for member in enum_class:
|
|
231
|
+
format_result = "{}".format(member) # pylint: disable=consider-using-f-string
|
|
232
|
+
assert format_result == member.value, (
|
|
233
|
+
f'"{{}}".format({enum_class.__name__}.{member.name}) returned '
|
|
234
|
+
f'"{format_result}", expected "{member.value}"'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
239
|
+
def test_strenum_equals_string(enum_class):
|
|
240
|
+
"""Test that enum members compare equal to their string values.
|
|
241
|
+
|
|
242
|
+
This is critical for usage with Pydantic model_dump() and dict comparisons.
|
|
243
|
+
"""
|
|
244
|
+
for member in enum_class:
|
|
245
|
+
assert member == member.value, (
|
|
246
|
+
f"{enum_class.__name__}.{member.name} == {member.value!r} should be True"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@pytest.mark.parametrize("enum_class", STRENUM_CLASSES)
|
|
251
|
+
def test_strenum_works_as_dict_key_with_string_lookup(enum_class):
|
|
252
|
+
"""Test that enum members work as dict keys with string lookup.
|
|
253
|
+
|
|
254
|
+
When model_dump() returns enum objects, we should be able to use strings
|
|
255
|
+
to look up values in dicts keyed by those enums.
|
|
256
|
+
"""
|
|
257
|
+
for member in enum_class:
|
|
258
|
+
test_dict = {member: "test_value"}
|
|
259
|
+
# String lookup should work because member IS a string
|
|
260
|
+
assert test_dict.get(member.value) == "test_value", (
|
|
261
|
+
f"Dict keyed by {enum_class.__name__}.{member.name} should be "
|
|
262
|
+
f"accessible via string {member.value!r}"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_strenum_pydantic_validation_accepts_valid_strings():
|
|
267
|
+
"""Test that Pydantic accepts valid enum string values."""
|
|
268
|
+
|
|
269
|
+
class TestModel(BaseModel):
|
|
270
|
+
pattern: AccessPattern
|
|
271
|
+
consistency: AccessConsistency
|
|
272
|
+
|
|
273
|
+
# Should accept string values
|
|
274
|
+
model = TestModel(pattern="latency", consistency="eventual")
|
|
275
|
+
assert model.pattern == AccessPattern.latency
|
|
276
|
+
assert model.consistency == AccessConsistency.eventual
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_strenum_pydantic_validation_rejects_invalid_strings():
|
|
280
|
+
"""Test that Pydantic rejects invalid enum string values.
|
|
281
|
+
|
|
282
|
+
This ensures strict validation - arbitrary strings are NOT accepted.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
class TestModel(BaseModel):
|
|
286
|
+
pattern: AccessPattern
|
|
287
|
+
|
|
288
|
+
with pytest.raises(ValidationError):
|
|
289
|
+
TestModel(pattern="invalid_pattern_value")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_strenum_pydantic_model_dump_preserves_enum():
|
|
293
|
+
"""Test that model_dump() returns enum objects that behave as strings.
|
|
294
|
+
|
|
295
|
+
By default, Pydantic returns enum objects (not raw strings) from model_dump().
|
|
296
|
+
With StrEnum, these objects ARE strings, so comparisons work correctly.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
class TestModel(BaseModel):
|
|
300
|
+
pattern: AccessPattern
|
|
301
|
+
drive: DriveType
|
|
302
|
+
|
|
303
|
+
model = TestModel(pattern="latency", drive="local-ssd")
|
|
304
|
+
dumped = model.model_dump()
|
|
305
|
+
|
|
306
|
+
# model_dump() returns enum objects by default
|
|
307
|
+
assert dumped["pattern"] == AccessPattern.latency
|
|
308
|
+
assert dumped["drive"] == DriveType.local_ssd
|
|
309
|
+
|
|
310
|
+
# But they should compare equal to strings because they ARE strings
|
|
311
|
+
assert dumped["pattern"] == "latency"
|
|
312
|
+
assert dumped["drive"] == "local-ssd"
|
|
313
|
+
|
|
314
|
+
# And isinstance should work
|
|
315
|
+
assert isinstance(dumped["pattern"], str)
|
|
316
|
+
assert isinstance(dumped["drive"], str)
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from pydantic import BaseModel
|
|
3
|
-
|
|
4
|
-
from service_capacity_modeling.interface import AccessConsistency
|
|
5
|
-
from service_capacity_modeling.interface import AccessPattern
|
|
6
|
-
from service_capacity_modeling.interface import BufferComponent
|
|
7
|
-
from service_capacity_modeling.interface import BufferIntent
|
|
8
|
-
from service_capacity_modeling.interface import DriveType
|
|
9
|
-
from service_capacity_modeling.interface import IntervalModel
|
|
10
|
-
from service_capacity_modeling.interface import Platform
|
|
11
|
-
|
|
12
|
-
# List of all enums that should have per-member docstrings
|
|
13
|
-
DOCUMENTED_ENUMS = [
|
|
14
|
-
IntervalModel,
|
|
15
|
-
DriveType,
|
|
16
|
-
Platform,
|
|
17
|
-
AccessPattern,
|
|
18
|
-
AccessConsistency,
|
|
19
|
-
BufferComponent,
|
|
20
|
-
BufferIntent,
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@pytest.mark.parametrize("enum_class", DOCUMENTED_ENUMS)
|
|
25
|
-
def test_enums_have_docstrings(enum_class):
|
|
26
|
-
"""Test that all interface.py enums have comprehensive per-member
|
|
27
|
-
docstrings
|
|
28
|
-
|
|
29
|
-
This test ensures that all enum members have their own
|
|
30
|
-
runtime-accessible docstrings, which makes them discoverable via
|
|
31
|
-
help(), IDE tooltips, and the __doc__ attribute.
|
|
32
|
-
|
|
33
|
-
The enums use the @enum_docstrings decorator which parses source code
|
|
34
|
-
to attach docstrings that appear below each member (following PEP 257
|
|
35
|
-
attribute docstring conventions).
|
|
36
|
-
|
|
37
|
-
See: https://stackoverflow.com/questions/19330460/
|
|
38
|
-
how-do-i-put-docstrings-on-enums
|
|
39
|
-
"""
|
|
40
|
-
enum_name = enum_class.__name__
|
|
41
|
-
|
|
42
|
-
# Check class has a docstring
|
|
43
|
-
assert enum_class.__doc__ is not None, f"{enum_name} must have a class docstring"
|
|
44
|
-
assert len(enum_class.__doc__) > 0, f"{enum_name} class docstring must not be empty"
|
|
45
|
-
|
|
46
|
-
# Check each member has its own unique docstring
|
|
47
|
-
for member in enum_class:
|
|
48
|
-
assert member.__doc__ is not None, (
|
|
49
|
-
f"{enum_name}.{member.name} must have a docstring. "
|
|
50
|
-
f"Add a docstring after the member definition:\n"
|
|
51
|
-
f' {member.name} = "{member.value}"\n'
|
|
52
|
-
f' """Your documentation here"""'
|
|
53
|
-
)
|
|
54
|
-
assert len(member.__doc__.strip()) > 0, (
|
|
55
|
-
f"{enum_name}.{member.name} docstring must not be empty"
|
|
56
|
-
)
|
|
57
|
-
# Verify it's not just the class docstring (should be member-specific)
|
|
58
|
-
assert member.__doc__ != enum_class.__doc__, (
|
|
59
|
-
f"{enum_name}.{member.name} should have its own docstring, "
|
|
60
|
-
f"not inherit the class docstring. "
|
|
61
|
-
f"Did the @enum_docstrings decorator work?"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# Verify different members have different docstrings
|
|
65
|
-
members = list(enum_class)
|
|
66
|
-
if len(members) >= 2:
|
|
67
|
-
assert members[0].__doc__ != members[1].__doc__, (
|
|
68
|
-
f"{enum_name}: Different enum members should have different docstrings"
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@pytest.mark.parametrize("enum_class", DOCUMENTED_ENUMS)
|
|
73
|
-
def test_enums_json_schema_includes_member_docstrings(enum_class):
|
|
74
|
-
"""Test that enum member docstrings appear in Pydantic JSON schemas
|
|
75
|
-
|
|
76
|
-
The @enum_docstrings decorator adds __get_pydantic_json_schema__ to
|
|
77
|
-
generate oneOf schemas with per-member descriptions. This ensures
|
|
78
|
-
enum documentation is available in API schemas, OpenAPI specs, etc.
|
|
79
|
-
"""
|
|
80
|
-
enum_name = enum_class.__name__
|
|
81
|
-
|
|
82
|
-
# Create a test model using this enum
|
|
83
|
-
TestModel = type(
|
|
84
|
-
"TestModel",
|
|
85
|
-
(BaseModel,),
|
|
86
|
-
{"__annotations__": {"field": enum_class}},
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# Get the JSON schema
|
|
90
|
-
schema = TestModel.model_json_schema()
|
|
91
|
-
|
|
92
|
-
# Check the enum definition exists in $defs
|
|
93
|
-
assert "$defs" in schema, f"{enum_name}: Schema missing $defs"
|
|
94
|
-
assert enum_name in schema["$defs"], f"{enum_name}: Enum not in $defs"
|
|
95
|
-
|
|
96
|
-
enum_schema = schema["$defs"][enum_name]
|
|
97
|
-
|
|
98
|
-
# Check oneOf exists with member descriptions
|
|
99
|
-
assert "oneOf" in enum_schema, (
|
|
100
|
-
f"{enum_name}: JSON schema missing oneOf for member descriptions"
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
one_of = enum_schema["oneOf"]
|
|
104
|
-
assert len(one_of) == len(enum_class), (
|
|
105
|
-
f"{enum_name}: oneOf should have {len(enum_class)} entries"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# Verify each member has proper schema entry
|
|
109
|
-
for member in enum_class:
|
|
110
|
-
matching_entries = [
|
|
111
|
-
entry for entry in one_of if entry.get("const") == member.value
|
|
112
|
-
]
|
|
113
|
-
|
|
114
|
-
assert len(matching_entries) == 1, (
|
|
115
|
-
f"{enum_name}.{member.name}: Should have exactly one oneOf entry"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
entry = matching_entries[0]
|
|
119
|
-
|
|
120
|
-
# Check required fields
|
|
121
|
-
assert "const" in entry, f"{enum_name}.{member.name}: Missing 'const'"
|
|
122
|
-
assert "title" in entry, f"{enum_name}.{member.name}: Missing 'title'"
|
|
123
|
-
assert "description" in entry, (
|
|
124
|
-
f"{enum_name}.{member.name}: Missing 'description'"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
# Verify description matches member docstring
|
|
128
|
-
assert entry["description"] == member.__doc__, (
|
|
129
|
-
f"{enum_name}.{member.name}: Schema description doesn't match "
|
|
130
|
-
f"member.__doc__"
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
# Verify description is not empty and not the class docstring
|
|
134
|
-
assert len(entry["description"].strip()) > 0, (
|
|
135
|
-
f"{enum_name}.{member.name}: Description should not be empty"
|
|
136
|
-
)
|
|
137
|
-
assert entry["description"] != enum_class.__doc__, (
|
|
138
|
-
f"{enum_name}.{member.name}: Description should be member-specific, "
|
|
139
|
-
f"not the class docstring"
|
|
140
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_arguments.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_desire_merge.py
RENAMED
|
File without changes
|
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_hardware.py
RENAMED
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_hardware_shapes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_model_dump.py
RENAMED
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_reproducible.py
RENAMED
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_simulation.py
RENAMED
|
File without changes
|
|
File without changes
|
{service_capacity_modeling-0.3.88 → service_capacity_modeling-0.3.89}/tests/test_working_set.py
RENAMED
|
File without changes
|