Compare commits

..

2 Commits

Author SHA1 Message Date
4b838383ed Attrs benchmark 2023-10-19 22:04:13 +02:00
b4b0a7f72d Tweaks 2023-10-19 21:30:04 +02:00
12 changed files with 111 additions and 65 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
/vscode /vscode
__pycache__ __pycache__
/test_data.json

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
run:
@python main.py

View File

View File

@ -0,0 +1,10 @@
import json
from benchmark.base import BenchmarkBase
from benchmark.attrs_benchmark.models import PlanogramsBulkInputPayload
class AttrsBenchmark(BenchmarkBase):
def _benchmark(self) -> None:
test_data = self._read_test_file()
json_data = json.loads(test_data)
data = PlanogramsBulkInputPayload(**json_data)

View File

@ -0,0 +1,58 @@
from enum import Enum
from typing import Optional
from uuid import uuid4
from attr import define, field
import attrs
class ColumnItemType(str, Enum):
PRODUCT = "PRODUCT"
COMPONENT = "COMPONENT"
@define
class CorrelationId:
correlation_id: str = field(factory=lambda: uuid4().hex)
@define
class ColumnsInput:
column_number: int = field(
default=None, validator=[attrs.validators.ge(0), attrs.validators.lt(32767)]
)
external_product_id: Optional[str] = field(
default=None,
validator=[attrs.validators.min_len(1), attrs.validators.max_len(32)],
)
old_qty: Optional[int] = field(
default=None,
validator=[attrs.validators.ge(0), attrs.validators.lt(2147483647)],
)
new_qty: Optional[int] = field(
default=None,
validator=[attrs.validators.ge(0), attrs.validators.lt(2147483647)],
)
old_price: Optional[float] = field(
default=None,
validator=[attrs.validators.ge(0), attrs.validators.lt(99999999.99)],
)
new_price: Optional[float] = field(
default=None,
validator=[attrs.validators.ge(0), attrs.validators.lt(99999999.99)],
)
select_map: Optional[list[int]] = field(default=None)
item_type: Optional[ColumnItemType] = field(factory=lambda: ColumnItemType.PRODUCT)
@define
class PlanogramInput(CorrelationId):
machine_external_id: Optional[str] = field(
default=None,
validator=[attrs.validators.min_len(1), attrs.validators.max_len(32)],
)
columns: list[ColumnsInput] = field(factory=list)
@define
class PlanogramsBulkInputPayload:
planograms: list[PlanogramInput] = field(factory=list)

View File

@ -21,12 +21,12 @@ class CorrelationId(Struct, rename="camel"):
class ColumnsInput(Struct, rename="camel"): class ColumnsInput(Struct, rename="camel"):
column_number: StrictSmallInt column_number: StrictSmallInt
external_product_id: Optional[ExternalId] = field(default=None) external_product_id: Optional[ExternalId] = None
old_qty: Optional[QuantityInt] = field(default_factory=lambda: None) old_qty: Optional[QuantityInt] = None
new_qty: Optional[QuantityInt] = field(default_factory=lambda: None) new_qty: Optional[QuantityInt] = None
old_price: Optional[PriceFloat] = field(default_factory=lambda: None) old_price: Optional[PriceFloat] = None
new_price: Optional[PriceFloat] = field(default_factory=lambda: None) new_price: Optional[PriceFloat] = None
select_map: Optional[list[StrictSmallInt]] = field(default_factory=lambda: None) select_map: Optional[list[StrictSmallInt]] = None
item_type: Optional[ColumnItemType] = field( item_type: Optional[ColumnItemType] = field(
default_factory=lambda: ColumnItemType.PRODUCT default_factory=lambda: ColumnItemType.PRODUCT
) )
@ -36,16 +36,6 @@ class PlanogramInput(CorrelationId, Struct, rename="camel"):
machine_external_id: ExternalId = field(default="") machine_external_id: ExternalId = field(default="")
columns: list[ColumnsInput] = field(default_factory=list) columns: list[ColumnsInput] = field(default_factory=list)
# class Config:
# title = "Planogram"
# alias_generator = to_camel_case
# populate_by_name = True
# str_strip_whitespace = True
class PlanogramsBulkInputPayload(Struct, rename="camel"): class PlanogramsBulkInputPayload(Struct, rename="camel"):
planograms: list[PlanogramInput] = field(default_factory=list) planograms: list[PlanogramInput] = field(default_factory=list)
# class Config:
# populate_by_name = True
# alias_generator = to_camel_case

View File

@ -25,22 +25,19 @@ class CorrelationId(BaseModel):
class ColumnsInput(BaseModel): class ColumnsInput(BaseModel):
column_number: StrictSmallInt = Field( column_number: StrictSmallInt = Field(
alias="columnNumber", description="View index in Televend" description="View index in Televend",
) )
external_product_id: Optional[str] = Field( external_product_id: Optional[str] = Field(
default=None, default=None,
alias="externalProductId",
description="Product or Component external ID used for product/component identification in external partner's ERP system", description="Product or Component external ID used for product/component identification in external partner's ERP system",
min_length=1, min_length=1,
max_length=32, max_length=32,
) )
old_qty: Optional[QuantityInt] = Field( old_qty: Optional[QuantityInt] = Field(
default_factory=lambda: None, default_factory=lambda: None,
alias="oldQty",
) )
new_qty: Optional[QuantityInt] = Field( new_qty: Optional[QuantityInt] = Field(
default_factory=lambda: None, default_factory=lambda: None,
alias="newQty",
) )
old_price: Optional[float] = Field( old_price: Optional[float] = Field(
@ -56,13 +53,10 @@ class ColumnsInput(BaseModel):
le=99999999.99, le=99999999.99,
) )
select_map: Optional[List[StrictSmallInt]] = Field( select_map: Optional[List[StrictSmallInt]] = Field(default_factory=lambda: None)
default_factory=lambda: None, alias="selectMap"
)
item_type: Optional[ColumnItemType] = Field( item_type: Optional[ColumnItemType] = Field(
default_factory=lambda: ColumnItemType.PRODUCT, default_factory=lambda: ColumnItemType.PRODUCT,
alias="itemType",
description="MUST be set if item is COMPONENT", description="MUST be set if item is COMPONENT",
) )
@ -71,27 +65,6 @@ class ColumnsInput(BaseModel):
alias_generator = to_camel_case alias_generator = to_camel_case
str_strip_whitespace = True str_strip_whitespace = True
# @root_validator
# def check_required_fields(cls, values):
# if values.get("external_product_id"):
# if values.get("old_qty") is None:
# raise ValueError(
# f"provide oldQty for product {values['external_product_id']}"
# )
# if values.get("new_qty") is None:
# raise ValueError(
# f"provide newQty for product {values['external_product_id']}"
# )
# if values.get("old_price") is None:
# raise ValueError(
# f"provide oldPrc for product {values['external_product_id']}"
# )
# if values.get("new_price") is None:
# raise ValueError(
# f"provide newPrc for product {values['external_product_id']}"
# )
# return values
@validator("item_type") @validator("item_type")
def set_item_type(cls, value): def set_item_type(cls, value):
return value or ColumnItemType.PRODUCT return value or ColumnItemType.PRODUCT
@ -99,7 +72,6 @@ class ColumnsInput(BaseModel):
class PlanogramInput(CorrelationId, BaseModel): class PlanogramInput(CorrelationId, BaseModel):
machine_external_id: str = Field( machine_external_id: str = Field(
alias="machineExternalId",
description="Machine external ID", description="Machine external ID",
min_length=1, min_length=1,
max_length=32, max_length=32,
@ -124,15 +96,3 @@ class PlanogramsBulkInputPayload(BaseModel):
class Config: class Config:
populate_by_name = True populate_by_name = True
alias_generator = to_camel_case alias_generator = to_camel_case
# @root_validator
# def check_machine_unique(cls, values):
# planograms = values.get("planograms", [])
# external_ids = set()
# for planogram in planograms:
# if planogram.machine_external_id in external_ids:
# raise ValueError(
# f"Machine externalId must be unique! Duplicate {planogram.machine_external_id}"
# )
# external_ids.add(planogram.machine_external_id)
# return values

16
main.py
View File

@ -1,20 +1,26 @@
from pathlib import Path from pathlib import Path
from benchmark.attrs_benchmark.benchmark import AttrsBenchmark
from benchmark.factories import create_test_file from benchmark.factories import create_test_file
from benchmark.msgspec_benchmark.benchmark import MsgSpecBenchmark from benchmark.msgspec_benchmark.benchmark import MsgSpecBenchmark
from benchmark.pydantic_benchmark.benchmark import PydanticBenchmark from benchmark.pydantic_benchmark.benchmark import PydanticBenchmark
TEST_DATA_FILE = Path("test_data.json") TEST_DATA_FILE = Path("test_data.json")
BIG_TEST_DATA_FILE = Path("test_data-big.json")
def main() -> None: def main(test_file: Path) -> None:
pydantic_benchmark = PydanticBenchmark(TEST_DATA_FILE) pydantic_benchmark = PydanticBenchmark(test_file)
pydantic_benchmark.execute() pydantic_benchmark.execute()
msgspec_benchmark = MsgSpecBenchmark(TEST_DATA_FILE) attrs_benchmark = AttrsBenchmark(test_file)
attrs_benchmark.execute()
msgspec_benchmark = MsgSpecBenchmark(test_file)
msgspec_benchmark.execute() msgspec_benchmark.execute()
if __name__ == "__main__": if __name__ == "__main__":
# create_test_file(TEST_DATA_FILE) # create_test_file(BIG_TEST_DATA_FILE)
main() main(BIG_TEST_DATA_FILE)

20
poetry.lock generated
View File

@ -11,6 +11,24 @@ files = [
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
] ]
[[package]]
name = "attrs"
version = "23.1.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.7"
files = [
{file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
{file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
]
[package.extras]
cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
dev = ["attrs[docs,tests]", "pre-commit"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
[[package]] [[package]]
name = "faker" name = "faker"
version = "19.9.0" version = "19.9.0"
@ -277,4 +295,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "f67f7e3c078da6291615ead8743e8eb520d31e62bab449b5cd5f3e9b3b3f540e" content-hash = "43640240ca1c3ae251a946f70979654201b8adac0d9a770c92d64deb83d13610"

View File

@ -11,6 +11,7 @@ python = "^3.11"
pydantic = "^2.4.2" pydantic = "^2.4.2"
msgspec = "^0.18.4" msgspec = "^0.18.4"
polyfactory = "^2.9.0" polyfactory = "^2.9.0"
attrs = "^23.1.0"
[build-system] [build-system]

1
test_data-big.json Normal file

File diff suppressed because one or more lines are too long

1
test_data.json Normal file

File diff suppressed because one or more lines are too long