From 889899f3c7f42cdece3d2bc120ec49c6c5e7786a Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 11:24:03 -0800 Subject: [PATCH 1/9] chore: add pydantic dependency Add pydantic to project dependencies and refresh lockfile to support upcoming model migration to Pydantic v2. --- poetry.lock | 181 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 615c660..52de79e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "build" version = "1.4.0" @@ -257,6 +268,160 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "pydantic" +version = "2.12.5" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + [[package]] name = "pygments" version = "2.19.2" @@ -410,6 +575,20 @@ files = [ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "urllib3" version = "2.6.3" @@ -449,4 +628,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "0dea405a869e2eab4fcecf63f0167133bf79afa9385ecfb9b7a8e6b69423c24e" +content-hash = "7413638efb3c23e0e04b32ca1cf4a49cb1838cd5c30a00ac987f79c2e688b77f" diff --git a/pyproject.toml b/pyproject.toml index eca7b6e..254058f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ [tool.poetry.dependencies] python = ">=3.10" requests = ">=2" +pydantic = "^2.0" [tool.poetry.group.dev.dependencies] pytest = "^8.0" From 77fecd6bd618a455f818065d3d8e163e852f36d4 Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 12:14:10 -0800 Subject: [PATCH 2/9] chore: add shared MLBBaseModel and base tests - Introduce MLBBaseModel (Pydantic v2) with extra="ignore" and populate_by_name - Export MLBBaseModel from models package - Add unit tests to verify ignoring unknown fields and alias population --- mlbstatsapi/models/__init__.py | 3 +++ mlbstatsapi/models/base.py | 15 +++++++++++++++ tests/test_base_model.py | 22 ++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 mlbstatsapi/models/base.py create mode 100644 tests/test_base_model.py diff --git a/mlbstatsapi/models/__init__.py b/mlbstatsapi/models/__init__.py index e69de29..649da90 100644 --- a/mlbstatsapi/models/__init__.py +++ b/mlbstatsapi/models/__init__.py @@ -0,0 +1,3 @@ +from .base import MLBBaseModel + +__all__ = ["MLBBaseModel"] diff --git a/mlbstatsapi/models/base.py b/mlbstatsapi/models/base.py new file mode 100644 index 0000000..171109d --- /dev/null +++ b/mlbstatsapi/models/base.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, ConfigDict + + +class MLBBaseModel(BaseModel): + """Common base for all MLB Stats API models. + + - Pydantic v2 + - Ignores unknown fields to remain resilient to API changes + - populate_by_name allows alias-based population when needed + """ + + model_config = ConfigDict( + extra="ignore", + populate_by_name=True, + ) diff --git a/tests/test_base_model.py b/tests/test_base_model.py new file mode 100644 index 0000000..138109d --- /dev/null +++ b/tests/test_base_model.py @@ -0,0 +1,22 @@ +from pydantic import Field + +from mlbstatsapi.models import MLBBaseModel + + +class Sample(MLBBaseModel): + id: int + full_name: str = Field(alias="fullName") + + +def test_ignore_extra_fields(): + obj = Sample(id=1, full_name="Test", extra_field="ignored") + assert obj.id == 1 + assert obj.full_name == "Test" + # Extra fields should be ignored and not set as attributes + assert not hasattr(obj, "extra_field") + + +def test_populate_by_name_alias(): + # populate_by_name allows alias population when present + obj = Sample(id=1, fullName="Alias Name") # type: ignore[arg-type] + assert obj.full_name == "Alias Name" From 67f6ef5e9674adf57305442a382da7774910243a Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 14:12:00 -0800 Subject: [PATCH 3/9] refactor: migrate core models to Pydantic with Pythonic field names BREAKING CHANGES: - All model field names are now snake_case (e.g., spring_league instead of springleague) - Class names standardized to PascalCase (e.g., TeamRecords instead of Teamrecords) - Models now raise ValidationError instead of TypeError for missing required fields Models converted: - Sport, Season - Venue, Location, TimeZone, FieldInfo, VenueDefaultCoordinates - League, LeagueRecord - Division - Team, TeamRecord, Record, OverallLeagueRecord, TypeRecords, DivisionRecords, LeagueRecords, Records - Standings, TeamRecords, Streak - Attendance, AttendanceRecords, AttendanceTotals, AttendanceHighLowGame, AttendanceGameType Key improvements: - All models inherit from MLBBaseModel with extra=ignore (handles new API fields gracefully) - Field aliases maintain API compatibility (e.g., Field(alias=springleague)) - populate_by_name=True allows both old and new names in constructors - Fixed several type mismatches (season: int, active: bool, elevation: int, etc.) - Updated all affected tests to use new field names --- mlbstatsapi/models/attendances/attendance.py | 26 +- mlbstatsapi/models/attendances/attributes.py | 277 +++++++++--------- mlbstatsapi/models/divisions/__init__.py | 6 +- mlbstatsapi/models/divisions/division.py | 68 ++--- mlbstatsapi/models/leagues/league.py | 128 ++++---- mlbstatsapi/models/seasons/season.py | 141 +++++---- mlbstatsapi/models/sports/sport.py | 36 +-- mlbstatsapi/models/standings/__init__.py | 2 +- mlbstatsapi/models/standings/attributes.py | 208 ++++++------- mlbstatsapi/models/standings/standings.py | 51 ++-- mlbstatsapi/models/teams/__init__.py | 10 +- mlbstatsapi/models/teams/attributes.py | 224 +++++++------- mlbstatsapi/models/teams/team.py | 146 ++++----- mlbstatsapi/models/venues/attributes.py | 140 +++++---- mlbstatsapi/models/venues/venue.py | 54 ++-- .../attendance/test_attendance.py | 28 +- .../external_tests/division/test_division.py | 12 +- tests/external_tests/league/test_league.py | 30 +- tests/external_tests/sport/test_sport.py | 8 +- .../standings/test_standings.py | 10 +- tests/external_tests/team/test_team.py | 8 +- tests/external_tests/venue/test_venue.py | 6 +- .../standings/test_standings_mock.py | 6 +- 23 files changed, 763 insertions(+), 862 deletions(-) diff --git a/mlbstatsapi/models/attendances/attendance.py b/mlbstatsapi/models/attendances/attendance.py index 5132b4c..c611c9b 100644 --- a/mlbstatsapi/models/attendances/attendance.py +++ b/mlbstatsapi/models/attendances/attendance.py @@ -1,23 +1,19 @@ -from dataclasses import dataclass, field -from typing import Union, List +from typing import List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from .attributes import AttendanceTotals, AttendanceRecords -@dataclass -class Attendance: + +class Attendance(MLBBaseModel): """ A class to represent attendance. + Attributes ---------- - copyright : str - Copyright message records : List[AttendanceRecords] - List of attendance records - aggregatetotals : AttendanceAggregateTotals - Attendence aggregate total numbers for query + List of attendance records. + aggregate_totals : AttendanceTotals + Attendance aggregate total numbers for query. """ - aggregatetotals: Union[AttendanceTotals, dict] - records: Union[List[AttendanceRecords], List[dict]] = field(default_factory=list) - - def __post_init__(self): - self.records = [AttendanceRecords(**record) for record in self.records if self.records] - self.aggregatetotals = AttendanceTotals(**self.aggregatetotals) \ No newline at end of file + aggregate_totals: AttendanceTotals = Field(alias="aggregatetotals") + records: List[AttendanceRecords] = [] diff --git a/mlbstatsapi/models/attendances/attributes.py b/mlbstatsapi/models/attendances/attributes.py index c96e5b4..bdc3251 100644 --- a/mlbstatsapi/models/attendances/attributes.py +++ b/mlbstatsapi/models/attendances/attributes.py @@ -1,190 +1,175 @@ -from dataclasses import dataclass, field -from typing import Optional, Union +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team -@dataclass -class AttendanceHighLowGameContent: +class AttendanceHighLowGameContent(MLBBaseModel): """ - A class to represent attendance records. + A class to represent attendance game content. + Attributes ---------- link : str - games content endpoint link + Games content endpoint link. """ link: str -@dataclass -class AttendanceHighLowGame: +class AttendanceHighLowGame(MLBBaseModel): """ - A class to represent attendance High and Low games. + A class to represent attendance high and low games. + Attributes ---------- - gamepk : int - Games Id number + game_pk : int + Game's ID number. link : str - games endpoint link + Games endpoint link. content : AttendanceHighLowGameContent - Content for this game - daynight : str - Type of time of day for game + Content for this game. + day_night : str + Time of day for game (day or night). """ - gamepk: int + game_pk: int = Field(alias="gamepk") link: str - content: Union[AttendanceHighLowGameContent, dict] - daynight: str - - def __post_init__(self): - self.content = AttendanceHighLowGameContent(**self.content) + content: AttendanceHighLowGameContent + day_night: str = Field(alias="daynight") -@dataclass -class AttendenceGameType: +class AttendanceGameType(MLBBaseModel): """ - A class to represent Attendance Game Type. + A class to represent attendance game type. + Attributes ---------- id : str - Game type id + Game type ID. description : str - Game type description + Game type description. """ id: str description: str -@dataclass(repr=False) -class AttendanceRecords: +class AttendanceRecords(MLBBaseModel): """ A class to represent attendance records. + Attributes ---------- - openingstotal : int - Total amount of openings - openingstotalaway : int - Total amount of opening away games - openingstotalhome : int - Total amount of opening home games - openingstotallost : int - Total amount of openings lost - gamestotal : int - Total amount of games - gamesawaytotal : int - Total amount of away games - gameshometotal : int - Total amount of home games + openings_total : int + Total number of openings. + openings_total_away : int + Total number of opening away games. + openings_total_home : int + Total number of opening home games. + openings_total_lost : int + Total number of openings lost. + games_total : int + Total number of games. + games_away_total : int + Total number of away games. + games_home_total : int + Total number of home games. year : str - Year as a string - attendanceaverageaway : int - Average attendance for away games - attendanceaveragehome : int - Average attendance for home games - attendanceaverageytd : int - Average attendance year to date - attendancehigh : int - Attendance High number - attendancehighdate : str - Attendance high date - attendancehighgame : AttendanceHighLowGame - Attendance high game - attendancelow : int - Attendance low number - attendancelowdate : str - Attendance low date - attendancelowgame : AttendanceHighLowGame - Attendance low game - attendanceopeningaverage : int - Attendance opening average - attendancetotal : int - Attendance total - attendancetotalaway : int - Attendance total away - attendancetotalhome : int - Attendance total home - gametype : AttendenceGameType - Game type + Year as a string. + attendance_average_away : int + Average attendance for away games. + attendance_average_home : int + Average attendance for home games. + attendance_average_ytd : int + Average attendance year to date. + attendance_high : int + Attendance high number. + attendance_high_date : str + Attendance high date. + attendance_high_game : AttendanceHighLowGame + Attendance high game. + attendance_low : int + Attendance low number. + attendance_low_date : str + Attendance low date. + attendance_low_game : AttendanceHighLowGame + Attendance low game. + attendance_opening_average : int + Attendance opening average. + attendance_total : int + Attendance total. + attendance_total_away : int + Attendance total away. + attendance_total_home : int + Attendance total home. + game_type : AttendanceGameType + Game type. team : Team - Team + Team. """ - openingstotal: int - openingstotalaway: int - openingstotalhome: int - openingstotallost: int - gamestotal: int - gamesawaytotal: int - gameshometotal: int + openings_total: int = Field(alias="openingstotal") + openings_total_away: int = Field(alias="openingstotalaway") + openings_total_home: int = Field(alias="openingstotalhome") + openings_total_lost: int = Field(alias="openingstotallost") + games_total: int = Field(alias="gamestotal") + games_away_total: int = Field(alias="gamesawaytotal") + games_home_total: int = Field(alias="gameshometotal") year: str - attendanceaverageytd: int - gametype: Union[AttendenceGameType, dict] - team: Union[Team, dict] - attendancetotal: Optional[int] = None - attendanceaverageaway: Optional[int] = None - attendanceaveragehome: Optional[int] = None - attendancehigh: Optional[int] = None - attendancehighdate: Optional[str] = None - attendancehighgame: Optional[Union[AttendanceHighLowGame, dict]] = None - attendancelow: Optional[int] = None - attendancelowdate: Optional[str] = None - attendancelowgame: Optional[Union[AttendanceHighLowGame, dict]] = None - attendancetotalaway: Optional[int] = None - attendancetotalhome: Optional[int] = None - attendanceopeningaverage: Optional[int] = None - - def __post_init__(self): - self.attendancehighgame = AttendanceHighLowGame(**self.attendancehighgame) if self.attendancehighgame else self.attendancehighgame - self.attendancelowgame = AttendanceHighLowGame(**self.attendancelowgame) if self.attendancelowgame else self.attendancelowgame - self.gameType = AttendenceGameType(**self.gametype) - self.team = Team(**self.team) + attendance_average_ytd: int = Field(alias="attendanceaverageytd") + game_type: AttendanceGameType = Field(alias="gametype") + team: Team + attendance_total: Optional[int] = Field(default=None, alias="attendancetotal") + attendance_average_away: Optional[int] = Field(default=None, alias="attendanceaverageaway") + attendance_average_home: Optional[int] = Field(default=None, alias="attendanceaveragehome") + attendance_high: Optional[int] = Field(default=None, alias="attendancehigh") + attendance_high_date: Optional[str] = Field(default=None, alias="attendancehighdate") + attendance_high_game: Optional[AttendanceHighLowGame] = Field(default=None, alias="attendancehighgame") + attendance_low: Optional[int] = Field(default=None, alias="attendancelow") + attendance_low_date: Optional[str] = Field(default=None, alias="attendancelowdate") + attendance_low_game: Optional[AttendanceHighLowGame] = Field(default=None, alias="attendancelowgame") + attendance_total_away: Optional[int] = Field(default=None, alias="attendancetotalaway") + attendance_total_home: Optional[int] = Field(default=None, alias="attendancetotalhome") + attendance_opening_average: Optional[int] = Field(default=None, alias="attendanceopeningaverage") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class AttendanceTotals: +class AttendanceTotals(MLBBaseModel): """ - A class to represent attendance aggregate toatls. + A class to represent attendance aggregate totals. + Attributes ---------- - openingstotalaway : int - Total amount of opening game attendance number - openingstotalhome : int - Total amount of opening home game attendance number - openingstotallost : int - Total amount of opening games lost - openingstotalytd : int - Total amount of opening games year to date - attendanceaverageaway : int - Average away game attendance - attendanceaveragehome : int - Average home game attendance - attendanceaverageytd : int - Average attendance year to date - attendancehigh : int - Attendance high - attendancehighdate : str - Attendance high date - attendancetotal : int - Attendance total - attendancetotalaway : int - Attendace total away - attendancetotalhome : int - Attendance total home + openings_total_away : int + Total opening game attendance number for away games. + openings_total_home : int + Total opening home game attendance number. + openings_total_lost : int + Total number of opening games lost. + openings_total_ytd : int + Total number of opening games year to date. + attendance_average_away : int + Average away game attendance. + attendance_average_home : int + Average home game attendance. + attendance_average_ytd : int + Average attendance year to date. + attendance_high : int + Attendance high. + attendance_high_date : str + Attendance high date. + attendance_total : int + Attendance total. + attendance_total_away : int + Attendance total away. + attendance_total_home : int + Attendance total home. """ - openingstotalaway: int - openingstotalhome: int - openingstotallost: int - openingstotalytd: int - attendanceaverageytd: int - attendancehigh: int - attendancehighdate: str - attendancetotal: int - attendancetotalaway: int - attendancetotalhome: int - attendanceaverageaway: Optional[int] = None - attendanceaveragehome: Optional[int] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + openings_total_away: int = Field(alias="openingstotalaway") + openings_total_home: int = Field(alias="openingstotalhome") + openings_total_lost: int = Field(alias="openingstotallost") + openings_total_ytd: int = Field(alias="openingstotalytd") + attendance_average_ytd: int = Field(alias="attendanceaverageytd") + attendance_high: int = Field(alias="attendancehigh") + attendance_high_date: str = Field(alias="attendancehighdate") + attendance_total: int = Field(alias="attendancetotal") + attendance_total_away: int = Field(alias="attendancetotalaway") + attendance_total_home: int = Field(alias="attendancetotalhome") + attendance_average_away: Optional[int] = Field(default=None, alias="attendanceaverageaway") + attendance_average_home: Optional[int] = Field(default=None, alias="attendanceaveragehome") diff --git a/mlbstatsapi/models/divisions/__init__.py b/mlbstatsapi/models/divisions/__init__.py index 2e92696..a8dc125 100644 --- a/mlbstatsapi/models/divisions/__init__.py +++ b/mlbstatsapi/models/divisions/__init__.py @@ -1 +1,5 @@ -from .division import Division \ No newline at end of file +from .division import Division +from mlbstatsapi.models.leagues import League + +# Rebuild to resolve forward reference to League +Division.model_rebuild() \ No newline at end of file diff --git a/mlbstatsapi/models/divisions/division.py b/mlbstatsapi/models/divisions/division.py index 3fb4b05..bedcd88 100644 --- a/mlbstatsapi/models/divisions/division.py +++ b/mlbstatsapi/models/divisions/division.py @@ -1,59 +1,55 @@ -from dataclasses import dataclass -from typing import Optional, Union - -from mlbstatsapi.models.leagues import League +from __future__ import annotations +from typing import Optional, TYPE_CHECKING +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.sports import Sport +if TYPE_CHECKING: + from mlbstatsapi.models.leagues import League + -@dataclass(repr=False) -class Division: +class Division(MLBBaseModel): """ A class to represent a division. Attributes ---------- id : int - id number of the divison - name : str - name of the division + ID number of the division. link : str - link of the division + API link for the division. + name : str + Name of the division. season : str - Current season for the division - nameshort : str - Short name for the division + Current season for the division. + name_short : str + Short name for the division. abbreviation : str - Abbreviation of the divison name + Abbreviation of the division name. league : League - League this division is in + League this division is in. sport : Sport - Sport this divison is in - haswildcard : bool - If this league has a wildcard - sortorder : int - Sort order - numplayoffteams : int - Number of playoff teams in division + Sport this division is in. + has_wildcard : bool + Whether this league has a wildcard. + sort_order : int + Sort order. + num_playoff_teams : int + Number of playoff teams in division. active : bool - Current status of this division + Current status of this division. """ id: int link: str name: Optional[str] = None season: Optional[str] = None - nameshort: Optional[str] = None + name_short: Optional[str] = Field(default=None, alias="nameshort") abbreviation: Optional[str] = None - league: Optional[Union[League, dict]] = None - sport: Optional[Union[Sport, dict]] = None - haswildcard: Optional[bool] = None - sortorder: Optional[int] = None - numplayoffteams: Optional[int] = None + league: Optional[League] = None + sport: Optional[Sport] = None + has_wildcard: Optional[bool] = Field(default=None, alias="haswildcard") + sort_order: Optional[int] = Field(default=None, alias="sortorder") + num_playoff_teams: Optional[int] = Field(default=None, alias="numplayoffteams") active: Optional[bool] = None - def __post_init__(self): - self.league = League(**self.league) if self.league else self.league - self.sport = Sport(**self.sport) if self.sport else self.sport - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + model_config = {"arbitrary_types_allowed": True} diff --git a/mlbstatsapi/models/leagues/league.py b/mlbstatsapi/models/leagues/league.py index 152986a..508883f 100644 --- a/mlbstatsapi/models/leagues/league.py +++ b/mlbstatsapi/models/leagues/league.py @@ -1,25 +1,24 @@ -from dataclasses import dataclass -from typing import Optional, Union - +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.sports import Sport from mlbstatsapi.models.seasons import Season -@dataclass -class LeagueRecord: +class LeagueRecord(MLBBaseModel): """ - A class to represent a leaguerecord. + A class to represent a league record. Attributes ---------- wins : int - number of wins in leaguerecord + Number of wins in league record. losses : int - number of losses in leaguerecord - ties : int - number of ties in leaguerecord + Number of losses in league record. pct : str - winning pct of leaguerecord + Winning percentage of league record. + ties : int + Number of ties in league record. """ wins: int losses: int @@ -27,79 +26,70 @@ class LeagueRecord: ties: Optional[int] = None -@dataclass(repr=False) -class League: +class League(MLBBaseModel): """ A class to represent a league. Attributes ---------- id : int - id number of the league - name : str - name of the league + ID number of the league. link : str - link of the league + API link for the league. + name : str + Name of the league. abbreviation : str - abbreviation the league - nameshort : str - Short name for the league - seasonstate : str - State of the leagues season - haswildcard : bool - Status of the leagues wildcard - hassplitseason : bool - Status of the leagues split season - numgames : int - Total number of league games - hasplayoffpoints : bool - Status of the leagues playoff points - numteams : int - Total number of team in league - numwildcardteams : int - Total number of wildcard teams in league - seasondateinfo : Season - Season obj + Abbreviation of the league. + name_short : str + Short name for the league. + season_state : str + State of the league's season. + has_wildcard : bool + Status of the league's wildcard. + has_split_season : bool + Status of the league's split season. + num_games : int + Total number of league games. + has_playoff_points : bool + Status of the league's playoff points. + num_teams : int + Total number of teams in league. + num_wildcard_teams : int + Total number of wildcard teams in league. + season_date_info : Season + Season object. season : str - League season - orgcode : str - Leagues orginization code - conferencesinuse : bool - Status of the in use conferences of the league - divisionsinuse : bool - Status of leagues divisions in use + League season. + org_code : str + League's organization code. + conferences_in_use : bool + Status of in-use conferences of the league. + divisions_in_use : bool + Status of league's divisions in use. sport : Sport - What 'sport' this league is a part of - sortorder : int - League sort order + What sport this league is a part of. + sort_order : int + League sort order. active : bool - Status on the activity of the league + Status on the activity of the league. """ id: int link: str name: Optional[str] = None abbreviation: Optional[str] = None - nameshort: Optional[str] = None - seasonstate: Optional[str] = None - haswildcard: Optional[bool] = None - hassplitseason: Optional[bool] = None - numgames: Optional[int] = None - hasplayoffpoints: Optional[bool] = None - numteams: Optional[int] = None - numwildcardteams: Optional[int] = None - seasondateinfo: Optional[Union[Season, dict]] = None + name_short: Optional[str] = Field(default=None, alias="nameshort") + season_state: Optional[str] = Field(default=None, alias="seasonstate") + has_wildcard: Optional[bool] = Field(default=None, alias="haswildcard") + has_split_season: Optional[bool] = Field(default=None, alias="hassplitseason") + num_games: Optional[int] = Field(default=None, alias="numgames") + has_playoff_points: Optional[bool] = Field(default=None, alias="hasplayoffpoints") + num_teams: Optional[int] = Field(default=None, alias="numteams") + num_wildcard_teams: Optional[int] = Field(default=None, alias="numwildcardteams") + season_date_info: Optional[Season] = Field(default=None, alias="seasondateinfo") season: Optional[str] = None - orgcode: Optional[str] = None - conferencesinuse: Optional[bool] = None - divisionsinuse: Optional[bool] = None - sport: Optional[Union[Sport, dict]] = None - sortorder: Optional[int] = None + org_code: Optional[str] = Field(default=None, alias="orgcode") + conferences_in_use: Optional[bool] = Field(default=None, alias="conferencesinuse") + divisions_in_use: Optional[bool] = Field(default=None, alias="divisionsinuse") + sport: Optional[Sport] = None + sort_order: Optional[int] = Field(default=None, alias="sortorder") active: Optional[bool] = None - - def __post_init__(self): - self.seasondateinfo = Season(**self.seasondateinfo) if self.seasondateinfo else self.seasondateinfo - self.sport = Sport(**self.sport) if self.sport else self.sport - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/seasons/season.py b/mlbstatsapi/models/seasons/season.py index c709c54..2e90c2d 100644 --- a/mlbstatsapi/models/seasons/season.py +++ b/mlbstatsapi/models/seasons/season.py @@ -1,80 +1,75 @@ -from typing import Optional -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass(repr=False) -class Season: +class Season(MLBBaseModel): """ - This class represents a season object + A class to represent a season. Attributes ---------- - seasonid : str - season id - haswildcard : bool - wild card status - preseasonstartdate : str - pre-season start date - preseasonenddate : str - pre-season end date - seasonstartdate : str - season start date - springstartdate : str - spring start date - springenddate : str - spring end date - regularseasonstartdate : str - regular season start date - lastdate1sthalf : str - last date 1st half - allstardate : str - all star date - firstdate2ndhalf : str - first date 2nd half - regularseasonenddate : str - regular season end date - postseasonstartdate : str - post season start date - postseasonenddate : str - post season end date - seasonenddate : str - season end date - offseasonstartdate : str - off season start date - offseasonenddate : str - off season end date - seasonlevelgamedaytype : str - season level game day type - gamelevelgamedaytype : str - game level game day type - qualifierplateappearances : float - qualifier plate appearances - qualifieroutspitched : int - qualifier outs pitched + season_id : str + Season ID. + has_wildcard : bool + Wild card status. + preseason_start_date : str + Pre-season start date. + preseason_end_date : str + Pre-season end date. + season_start_date : str + Season start date. + spring_start_date : str + Spring start date. + spring_end_date : str + Spring end date. + regular_season_start_date : str + Regular season start date. + last_date_1st_half : str + Last date of 1st half. + all_star_date : str + All-star date. + first_date_2nd_half : str + First date of 2nd half. + regular_season_end_date : str + Regular season end date. + postseason_start_date : str + Post season start date. + postseason_end_date : str + Post season end date. + season_end_date : str + Season end date. + offseason_start_date : str + Off season start date. + offseason_end_date : str + Off season end date. + season_level_gameday_type : str + Season level game day type. + game_level_gameday_type : str + Game level game day type. + qualifier_plate_appearances : float + Qualifier plate appearances. + qualifier_outs_pitched : float + Qualifier outs pitched. """ - - seasonid: str - haswildcard: Optional[bool] = None - preseasonstartdate: Optional[str] = None - preseasonenddate: Optional[str] = None - seasonstartdate: Optional[str] = None - springstartdate: Optional[str] = None - springenddate: Optional[str] = None - regularseasonstartdate: Optional[str] = None - lastdate1sthalf: Optional[str] = None - allstardate: Optional[str] = None - firstdate2ndhalf: Optional[str] = None - regularseasonenddate: Optional[str] = None - postseasonstartdate: Optional[str] = None - postseasonenddate: Optional[str] = None - seasonenddate: Optional[str] = None - offseasonstartdate: Optional[str] = None - offseasonenddate: Optional[str] = None - seasonlevelgamedaytype: Optional[str] = None - gamelevelgamedaytype: Optional[str] = None - qualifierplateappearances: Optional[float] = None - qualifieroutspitched: Optional[int] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + season_id: str = Field(alias="seasonid") + has_wildcard: Optional[bool] = Field(default=None, alias="haswildcard") + preseason_start_date: Optional[str] = Field(default=None, alias="preseasonstartdate") + preseason_end_date: Optional[str] = Field(default=None, alias="preseasonenddate") + season_start_date: Optional[str] = Field(default=None, alias="seasonstartdate") + spring_start_date: Optional[str] = Field(default=None, alias="springstartdate") + spring_end_date: Optional[str] = Field(default=None, alias="springenddate") + regular_season_start_date: Optional[str] = Field(default=None, alias="regularseasonstartdate") + last_date_1st_half: Optional[str] = Field(default=None, alias="lastdate1sthalf") + all_star_date: Optional[str] = Field(default=None, alias="allstardate") + first_date_2nd_half: Optional[str] = Field(default=None, alias="firstdate2ndhalf") + regular_season_end_date: Optional[str] = Field(default=None, alias="regularseasonenddate") + postseason_start_date: Optional[str] = Field(default=None, alias="postseasonstartdate") + postseason_end_date: Optional[str] = Field(default=None, alias="postseasonenddate") + season_end_date: Optional[str] = Field(default=None, alias="seasonenddate") + offseason_start_date: Optional[str] = Field(default=None, alias="offseasonstartdate") + offseason_end_date: Optional[str] = Field(default=None, alias="offseasonenddate") + season_level_gameday_type: Optional[str] = Field(default=None, alias="seasonlevelgamedaytype") + game_level_gameday_type: Optional[str] = Field(default=None, alias="gamelevelgamedaytype") + qualifier_plate_appearances: Optional[float] = Field(default=None, alias="qualifierplateappearances") + qualifier_outs_pitched: Optional[float] = Field(default=None, alias="qualifieroutspitched") diff --git a/mlbstatsapi/models/sports/sport.py b/mlbstatsapi/models/sports/sport.py index 92c200d..4c0826a 100644 --- a/mlbstatsapi/models/sports/sport.py +++ b/mlbstatsapi/models/sports/sport.py @@ -1,37 +1,33 @@ -from typing import Optional -from dataclasses import dataclass, InitVar +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass(repr=False) -class Sport: +class Sport(MLBBaseModel): """ A class to represent a sport. Attributes ---------- id : int - id number of the sport + ID number of the sport. link : str - link of the sport - name : str - name the sport + API link for the sport. + name : str + Name of the sport. code : str - Sport code + Sport code. abbreviation : str - Abbreviation for the sport - sortorder : int - Some sort of sorting order - activestatus : bool - Is the sport active + Abbreviation for the sport. + sort_order : int + Sorting order. + active_status : bool + Whether the sport is active. """ id: int link: str name: Optional[str] = None code: Optional[str] = None abbreviation: Optional[str] = None - sortorder: Optional[int] = None - activestatus: Optional[bool] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + sort_order: Optional[int] = Field(default=None, alias="sortorder") + active_status: Optional[bool] = Field(default=None, alias="activestatus") diff --git a/mlbstatsapi/models/standings/__init__.py b/mlbstatsapi/models/standings/__init__.py index 554de97..65f1579 100644 --- a/mlbstatsapi/models/standings/__init__.py +++ b/mlbstatsapi/models/standings/__init__.py @@ -1,2 +1,2 @@ from .standings import Standings -from .attributes import Teamrecords \ No newline at end of file +from .attributes import TeamRecords, Streak \ No newline at end of file diff --git a/mlbstatsapi/models/standings/attributes.py b/mlbstatsapi/models/standings/attributes.py index 0376481..ecf20be 100644 --- a/mlbstatsapi/models/standings/attributes.py +++ b/mlbstatsapi/models/standings/attributes.py @@ -1,144 +1,104 @@ -from typing import Union, Optional -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.teams.attributes import TeamRecord -from mlbstatsapi.models.teams import Team, TeamRecord -@dataclass -class Streak: +class Streak(MLBBaseModel): """ - + A class to represent a streak. - Attributes: - ___________ - streaktype : str - Steak type - streaknumber : int - Streak number - streakcode : str - Steak code + Attributes + ---------- + streak_type : str + Streak type. + streak_number : int + Streak number. + streak_code : str + Streak code. """ - streaktype: str - streaknumber: int - streakcode: str + streak_type: str = Field(alias="streaktype") + streak_number: int = Field(alias="streaknumber") + streak_code: str = Field(alias="streakcode") -@dataclass(repr=False) -class Teamrecords(TeamRecord): + +class TeamRecords(TeamRecord): """ - Team Records + A class to represent team standings records. - Attributes: - ___________ - team: Team - The team for which the data belongs to. Can be an instance of the Team class or a dictionary with relevant information about the team. - season: int + Attributes + ---------- + team : Team + The team for which the data belongs to. + season : int The season for which the data belongs to. - streak: Streak - The streak of the team. Can be an instance of the Streak class or a dictionary with relevant information about the streak. - divisionrank: str + streak : Streak + The streak of the team. + division_rank : str The rank of the team in their division. - leaguerank: str + league_rank : str The rank of the team in their league. - sportrank: str + sport_rank : str The rank of the team in their sport. - gamesplayed: int - The number of games played by the team. - gamesback: str - The number of games behind the leader in the division. - wildcardgamesback: str - The number of games behind the leader in the wild card race. - leaguegamesback: str - The number of games behind the leader in the league. - springleaguegamesback: str - The number of games behind the leader in the spring league. - sportgamesback: str - The number of games behind the leader in the sport. - divisiongamesback: str + games_back : str The number of games behind the leader in the division. - conferencegamesback: str - The number of games behind the leader in the conference. - leaguerecord: OverallleagueRecord - The overall league record of the team. Can be an instance of the OverallleagueRecord class or a dictionary with relevant information about the record. - lastupdated: str + last_updated : str The date when the data was last updated. - records: Records - The records of the team. Can be an instance of the Records class or a dictionary with relevant information about the records. - runsallowed: int + runs_allowed : int The number of runs allowed by the team. - runsscored: int + runs_scored : int The number of runs scored by the team. - divisionchamp: bool - A flag indicating whether the team is the division champion. - divisionleader: bool - A flag indicating whether the team is the leader in their division. - haswildcard: bool - A flag indicating whether the team has a wild card spot. - clinched: bool - A flag indicating whether the team has clinched a spot in the playoffs. - eliminationnumber: str - The number of games the team needs to win or the number of games their opponents need to lose in order to be eliminated from playoff contention. - wildcardeliminationnumber: str - The number of games the team needs to win or the number of games their opponents need to lose in order to be eliminated from wild card contention. - wins: int - The number of wins of the team. - losses: int - The number of losses of the team. - rundifferential: int - The run differential of the team (runs scored minus runs allowed). - winningpercentage: str - The winning percentage of the team. - wildcardrank: str + division_champ : bool + Whether the team is the division champion. + has_wildcard : bool + Whether the team has a wild card spot. + clinched : bool + Whether the team has clinched a spot in the playoffs. + elimination_number : str + The elimination number for playoffs. + elimination_number_sport : str + The elimination number for sport. + elimination_number_league : str + The elimination number for league. + elimination_number_division : str + The elimination number for division. + elimination_number_conference : str + The elimination number for conference. + wildcard_elimination_number : str + The wildcard elimination number. + run_differential : int + The run differential of the team. + wildcard_rank : str The rank of the team in the wild card race. - wildcardleader: bool - A flag indicating whether the team is the leader in the wild card race. - magicnumber: str - The number of games the team needs to win or the number of games their opponents need to lose in order to clinch a spot in the playoffs. - clinchindicator: Optional - + wildcard_leader : bool + Whether the team is the leader in the wild card race. + magic_number : str + The magic number for clinching. + clinch_indicator : str + Clinch indicator. """ - team: Union[Team, dict] + team: Team season: int - streak: Union[Streak, dict] - divisionrank: str - leaguerank: str - sportrank: str - # gamesplayed: int - gamesback: str - # wildcardgamesback: str - # leaguegamesback: str - # springleaguegamesback: str - # sportgamesback: str - # divisiongamesback: str - # conferencegamesback: str - # leaguerecord: Union[OverallleagueRecord, dict] - lastupdated: str - # records: Union[Records, dict] - runsallowed: int - runsscored: int - divisionchamp: bool - # divisionleader: bool - haswildcard: bool + streak: Streak + division_rank: str = Field(alias="divisionrank") + league_rank: str = Field(alias="leaguerank") + sport_rank: str = Field(alias="sportrank") + games_back: str = Field(alias="gamesback") + last_updated: str = Field(alias="lastupdated") + runs_allowed: int = Field(alias="runsallowed") + runs_scored: int = Field(alias="runsscored") + division_champ: bool = Field(alias="divisionchamp") + has_wildcard: bool = Field(alias="haswildcard") clinched: bool - eliminationnumber: str - eliminationnumbersport: str - eliminationnumberleague: str - eliminationnumberdivision: str - eliminationnumberconference: str - wildcardeliminationnumber: str - # wins: int - # losses: int - rundifferential: int - # winningpercentage: str - wildcardrank: Optional[str] = None - wildcardleader: Optional[bool] = None - magicnumber: Optional[str] = None - clinchindicator: Optional[str] = None - - def __post_init__(self): - self.team = Team(**self.team) - self.streak = Streak(**self.streak) - # self.leaguerecord = OverallleagueRecord(**self.leaguerecord) - # self.records = Records(**self.records) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + elimination_number: str = Field(alias="eliminationnumber") + elimination_number_sport: str = Field(alias="eliminationnumbersport") + elimination_number_league: str = Field(alias="eliminationnumberleague") + elimination_number_division: str = Field(alias="eliminationnumberdivision") + elimination_number_conference: str = Field(alias="eliminationnumberconference") + wildcard_elimination_number: str = Field(alias="wildcardeliminationnumber") + run_differential: int = Field(alias="rundifferential") + wildcard_rank: Optional[str] = Field(default=None, alias="wildcardrank") + wildcard_leader: Optional[bool] = Field(default=None, alias="wildcardleader") + magic_number: Optional[str] = Field(default=None, alias="magicnumber") + clinch_indicator: Optional[str] = Field(default=None, alias="clinchindicator") diff --git a/mlbstatsapi/models/standings/standings.py b/mlbstatsapi/models/standings/standings.py index 1680b71..d64c54c 100644 --- a/mlbstatsapi/models/standings/standings.py +++ b/mlbstatsapi/models/standings/standings.py @@ -1,48 +1,37 @@ -from typing import Union, Optional, List -from dataclasses import dataclass - +from typing import Optional, List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.leagues import League from mlbstatsapi.models.divisions import Division from mlbstatsapi.models.sports import Sport - -from .attributes import Teamrecords +from .attributes import TeamRecords -@dataclass(repr=False) -class Standings: +class Standings(MLBBaseModel): """ - A class representing a standings in a league. - + A class representing standings in a league. Attributes ---------- - standingstype : str + standings_type : str A string indicating the type of standings. - league : league + league : League An object containing information about the league. division : Division - An object containing information about the division + An object containing information about the division. sport : Sport An object containing information about the sport. - lastupdated : str + last_updated : str A string indicating the last time the standing was updated. - teamrecords : List[Teamrecords] - A list of Teamrecord objects containing the data for the teams standings. + team_records : List[TeamRecords] + A list of TeamRecords objects containing the data for the teams standings. + roundrobin : dict + Roundrobin data (if applicable). """ - standingstype: str - league: Union[League, dict] - division: Union[Sport, dict] - lastupdated: str - teamrecords: List[Union[Teamrecords, dict]] - sport: Optional[Union[Sport, dict]] = None + standings_type: str = Field(alias="standingstype") + league: League + division: Division + last_updated: str = Field(alias="lastupdated") + team_records: List[TeamRecords] = Field(alias="teamrecords") + sport: Optional[Sport] = None roundrobin: Optional[dict] = None - - def __post_init__(self): - self.league = League(**self.league) - self.division = Division(**self.division) - self.sport = Sport(**self.sport) if self.sport else None - self.teamrecords = [Teamrecords(**teamrecord) for teamrecord in self.teamrecords] - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/teams/__init__.py b/mlbstatsapi/models/teams/__init__.py index 091851d..a841fca 100644 --- a/mlbstatsapi/models/teams/__init__.py +++ b/mlbstatsapi/models/teams/__init__.py @@ -1,2 +1,10 @@ from .team import Team -from .attributes import TeamRecord \ No newline at end of file +from .attributes import ( + Record, + OverallLeagueRecord, + TypeRecords, + DivisionRecords, + LeagueRecords, + Records, + TeamRecord, +) \ No newline at end of file diff --git a/mlbstatsapi/models/teams/attributes.py b/mlbstatsapi/models/teams/attributes.py index 4bd92ca..8130766 100644 --- a/mlbstatsapi/models/teams/attributes.py +++ b/mlbstatsapi/models/teams/attributes.py @@ -1,185 +1,167 @@ -from typing import Union, Optional, List -from dataclasses import dataclass - +from typing import Optional, List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.divisions import Division from mlbstatsapi.models.leagues import League -@dataclass -class Record: + +class Record(MLBBaseModel): """ - Record + A class to represent a basic record. - Attributes: - ___________ + Attributes + ---------- wins : int - Number of wins + Number of wins. losses : int - Number of losses + Number of losses. pct : str - Percentage + Winning percentage. """ wins: int losses: int pct: str -@dataclass -class OverallleagueRecord(Record): + +class OverallLeagueRecord(Record): """ - Overall League Record - + A class to represent overall league record. - Attributes: - ___________ + Attributes + ---------- wins : int - Overall number of wins in league + Overall number of wins in league. losses : int - Overall number of losses in league + Overall number of losses in league. pct : str - Overall percentage in league + Overall percentage in league. + ties : int + Number of ties. """ ties: int -@dataclass -class Typerecords(Record): + +class TypeRecords(Record): """ - Type records + A class to represent type records. - Attributes: - ___________ + Attributes + ---------- wins : int - Number of wins in type + Number of wins in type. losses : int - Number of losses in type + Number of losses in type. pct : str - Percentage in type + Percentage in type. type : str - Type of record + Type of record. """ type: str -@dataclass -class Divisionrecords(Record): + +class DivisionRecords(Record): """ - Division records + A class to represent division records. - Attributes: - ___________ + Attributes + ---------- wins : int - Number of wins in division + Number of wins in division. losses : int - Number of losses in division + Number of losses in division. pct : str - Percentage in division - division : Divison - Division + Percentage in division. + division : Division + Division. """ - division: Union[Division, dict] + division: Division -@dataclass -class Leaguerecords(Record): + +class LeagueRecords(Record): """ - League records + A class to represent league records. - Attributes: - ___________ + Attributes + ---------- wins : int - Number of wins in league + Number of wins in league. losses : int - Number of losses in league + Number of losses in league. pct : str - Percentage in league + Percentage in league. league : League - League + League. """ - league: Union[League, dict] + league: League + -@dataclass -class Records: - """" +class Records(MLBBaseModel): + """ A class representing the records of a team. - Attributes: - ___________ - splitrecords : Typerecords - A list of split records - divisionrecords : Divisionrecords - A list of division records - overallrecords : Typerecords - A list of overall records - leaguerecords : Leaguerecords - A list of league records - expectedrecords : Typerecords - A list of expected records + Attributes + ---------- + split_records : List[TypeRecords] + A list of split records. + division_records : List[DivisionRecords] + A list of division records. + overall_records : List[TypeRecords] + A list of overall records. + league_records : List[LeagueRecords] + A list of league records. + expected_records : List[TypeRecords] + A list of expected records. """ - splitrecords: Optional[List[Union[Typerecords, dict]]] = None - divisionrecords: Optional[List[Union[Divisionrecords, dict]]] = None - overallrecords: Optional[List[Union[Typerecords, dict]]] = None - leaguerecords: Optional[List[Union[Leaguerecords, dict]]] = None - expectedrecords: Optional[List[Union[Typerecords, dict]]] = None - - def __post_init__(self): - self.splitrecords = [Typerecords(**splitrecord) for splitrecord in self.splitrecords] if self.splitrecords else None - self.divisionrecords = [Divisionrecords(**divisionrecord) for divisionrecord in self.divisionrecords] if self.divisionrecords else None - self.overallrecords = [Typerecords(**overallrecord) for overallrecord in self.overallrecords] if self.overallrecords else None - self.leaguerecords = [Leaguerecords(**leaguerecord) for leaguerecord in self.leaguerecords] if self.leaguerecords else None - self.expectedrecords = [Typerecords(**expectedrecord) for expectedrecord in self.expectedrecords] if self.expectedrecords else None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(repr=False) -class TeamRecord: + split_records: Optional[List[TypeRecords]] = Field(default=None, alias="splitrecords") + division_records: Optional[List[DivisionRecords]] = Field(default=None, alias="divisionrecords") + overall_records: Optional[List[TypeRecords]] = Field(default=None, alias="overallrecords") + league_records: Optional[List[LeagueRecords]] = Field(default=None, alias="leaguerecords") + expected_records: Optional[List[TypeRecords]] = Field(default=None, alias="expectedrecords") + + +class TeamRecord(MLBBaseModel): """ - A class to represent a teams current record. + A class to represent a team's current record. - Attributes + Attributes ---------- - gamesplayed: int + games_played : int The number of games played by the team. - wildcardgamesback: str + wildcard_games_back : str The number of games behind the leader in the wild card race. - leaguegamesback: str + league_games_back : str The number of games behind the leader in the league. - springleaguegamesback: str + spring_league_games_back : str The number of games behind the leader in the spring league. - sportgamesback: str + sport_games_back : str The number of games behind the leader in the sport. - divisiongamesback: str + division_games_back : str The number of games behind the leader in the division. - conferencegamesback: str + conference_games_back : str The number of games behind the leader in the conference. - leaguerecord: OverallleagueRecord - The overall league record of the team. Can be an instance of the OverallleagueRecord class or a dictionary with relevant information about the record. - records: Records - The records of the team. Can be an instance of the Records class or a dictionary with relevant information about the records. - divisionleader: bool - A flag indicating whether the team is the leader in their division. - wins: int + league_record : OverallLeagueRecord + The overall league record of the team. + records : Records + The records of the team. + division_leader : bool + Whether the team is the leader in their division. + wins : int The number of wins of the team. - losses: int + losses : int The number of losses of the team. - winningpercentage: str + winning_percentage : str The winning percentage of the team. """ - gamesplayed: int - wildcardgamesback: str - leaguegamesback: str - springleaguegamesback: str - sportgamesback: str - divisiongamesback: str - conferencegamesback: str - leaguerecord: Union[OverallleagueRecord, dict] - records: Union[Records, dict] - divisionleader: bool + games_played: int = Field(alias="gamesplayed") + wildcard_games_back: str = Field(alias="wildcardgamesback") + league_games_back: str = Field(alias="leaguegamesback") + spring_league_games_back: str = Field(alias="springleaguegamesback") + sport_games_back: str = Field(alias="sportgamesback") + division_games_back: str = Field(alias="divisiongamesback") + conference_games_back: str = Field(alias="conferencegamesback") + league_record: OverallLeagueRecord = Field(alias="leaguerecord") + records: Records + division_leader: bool = Field(alias="divisionleader") wins: int losses: int - winningpercentage: str - - def __post_init__(self): - self.leaguerecord = OverallleagueRecord(**self.leaguerecord) - self.records = Records(**self.records) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + winning_percentage: str = Field(alias="winningpercentage") diff --git a/mlbstatsapi/models/teams/team.py b/mlbstatsapi/models/teams/team.py index 1bb8ff4..c16063f 100644 --- a/mlbstatsapi/models/teams/team.py +++ b/mlbstatsapi/models/teams/team.py @@ -1,105 +1,89 @@ -from typing import List, Dict, Union, Optional -from dataclasses import dataclass, field - +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.leagues import League from mlbstatsapi.models.venues import Venue from mlbstatsapi.models.divisions import Division from mlbstatsapi.models.sports import Sport - from .attributes import TeamRecord -# from mlbstatsapi.models.standings import Teamrecords -@dataclass(repr=False) -class Team: +class Team(MLBBaseModel): """ A class to represent a Team. Attributes ---------- id : int - id number of the team - name : str - name of the team + ID number of the team. link : str - The API link for the team - springleague : League - The spring league of the team - allstarstatus : str - The all status status of the team - season : str - The team's current season + The API link for the team. + name : str + Name of the team. + spring_league : League + The spring league of the team. + all_star_status : str + The all-star status of the team. + season : int + The team's current season. venue : Venue - The team's home venue - springvenue : Venue - The team's spring venue - teamcode : str - team code - filecode : str - filecode name of the team + The team's home venue. + spring_venue : Venue + The team's spring venue. + team_code : str + Team code. + file_code : str + File code name of the team. abbreviation : str - The abbreviation of the team name - teamname : str - The team name - locationname : str - The location of the team - firstyearofplay : str - The first year the team began play + The abbreviation of the team name. + team_name : str + The team name. + location_name : str + The location of the team. + first_year_of_play : str + The first year the team began play. league : League - The league of the team + The league of the team. division : Division - The division the team is in + The division the team is in. sport : Sport - The sport of the team - shortname : str - The shortname of the team + The sport of the team. + short_name : str + The short name of the team. record : TeamRecord - The record of the team - franchisename : str - The franchisename of the team - clubname : str - The clubname of the team - active : str - Active status of the team - parentorgname : str - The name of the parent team or org - parentorgid : str - The id of the partent team or org + The record of the team. + franchise_name : str + The franchise name of the team. + club_name : str + The club name of the team. + active : bool + Active status of the team. + parent_org_name : str + The name of the parent team or org. + parent_org_id : int + The ID of the parent team or org. """ id: int link: str - name: Optional[str] = field(default_factory=dict) - springleague: Union[League, dict] = field(default_factory=dict) - allstarstatus: Optional[str] = None - season: Optional[str] = None - venue: Union[Venue, dict] = field(default_factory=dict) - springvenue: Union[Venue, dict] = field(default_factory=dict) - teamcode: Optional[str] = None - filecode: Optional[str] = None + name: Optional[str] = None + spring_league: Optional[League] = Field(default=None, alias="springleague") + all_star_status: Optional[str] = Field(default=None, alias="allstarstatus") + season: Optional[int] = None + venue: Optional[Venue] = None + spring_venue: Optional[Venue] = Field(default=None, alias="springvenue") + team_code: Optional[str] = Field(default=None, alias="teamcode") + file_code: Optional[str] = Field(default=None, alias="filecode") abbreviation: Optional[str] = None - teamname: Optional[str] = None - locationname: Optional[str] = None - firstyearofplay: Optional[str] = None - league: Union[League, dict] = field(default_factory=dict) - division: Union[Division, dict] = field(default_factory=dict) - sport: Union[Sport, dict] = field(default_factory=dict) - shortname: Optional[str] = None - record: Union[TeamRecord, dict] = None - franchisename: Optional[str] = None - clubname: Optional[str] = None - active: Optional[str] = None - parentorgname: Optional[str] = None - parentorgid: Optional[str] = None - - def __post_init__(self): - self.springleague = League(**self.springleague) if self.springleague else self.springleague - self.venue = Venue(**self.venue) if self.venue else self.venue - self.springvenue = Venue(**self.springvenue) if self.springvenue else self.springvenue - self.league = League(**self.league) if self.league else self.league - self.division = Division(**self.division) if self.division else self.division - self.record = TeamRecord(**self.record) if self.record else self.record - self.sport = Sport(**self.sport) if self.sport else self.sport - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + team_name: Optional[str] = Field(default=None, alias="teamname") + location_name: Optional[str] = Field(default=None, alias="locationname") + first_year_of_play: Optional[str] = Field(default=None, alias="firstyearofplay") + league: Optional[League] = None + division: Optional[Division] = None + sport: Optional[Sport] = None + short_name: Optional[str] = Field(default=None, alias="shortname") + record: Optional[TeamRecord] = None + franchise_name: Optional[str] = Field(default=None, alias="franchisename") + club_name: Optional[str] = Field(default=None, alias="clubname") + active: Optional[bool] = None + parent_org_name: Optional[str] = Field(default=None, alias="parentorgname") + parent_org_id: Optional[int] = Field(default=None, alias="parentorgid") diff --git a/mlbstatsapi/models/venues/attributes.py b/mlbstatsapi/models/venues/attributes.py index 6be91b5..5e8a953 100644 --- a/mlbstatsapi/models/venues/attributes.py +++ b/mlbstatsapi/models/venues/attributes.py @@ -1,125 +1,123 @@ -from typing import Optional, Union -from dataclasses import dataclass, field +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass -class VenueDefaultCoordinates: + +class VenueDefaultCoordinates(MLBBaseModel): """ - A class to represent a venue. + A class to represent venue coordinates. Attributes ---------- latitude : float - The latatude coordinate for this venue + The latitude coordinate for this venue. longitude : float - The longitude coordinate for this venue + The longitude coordinate for this venue. """ latitude: float longitude: float -@dataclass(repr=False) -class Location: + +class Location(MLBBaseModel): """ - A class to represent a Location used by venue. + A class to represent a location used by venue. Attributes ---------- + city : str + City the venue is in. + country : str + Country this venue is in. + state_abbrev : str + The state's abbreviation. address1 : str - Venues first address line + Venue's first address line. address2 : str - Venues second address line - city : str - City the venue is in + Venue's second address line. + address3 : str + Venue's third address line. state : str - The State the venue is in - stateAbbrev : str - The staes abbreviation - postalCode : str - Postal code for this venue - defaultCoordinates : VenueDefaultCoordinates - Long and lat for this venues location - country : str - What country this venue is in + The state the venue is in. + postal_code : str + Postal code for this venue. phone : str - Phone number for this venue + Phone number for this venue. + azimuth_angle : float + Azimuth angle for this venue. + elevation : int + Elevation for this venue. + default_coordinates : VenueDefaultCoordinates + Latitude and longitude for this venue's location. """ city: str country: str - stateabbrev: Optional[str] = None + state_abbrev: Optional[str] = Field(default=None, alias="stateabbrev") address1: Optional[str] = None state: Optional[str] = None - postalcode: Optional[str] = None + postal_code: Optional[str] = Field(default=None, alias="postalcode") phone: Optional[str] = None address2: Optional[str] = None address3: Optional[str] = None - azimuthangle: Optional[str] = None - elevation: Optional[str] = None - defaultcoordinates: Optional[Union[VenueDefaultCoordinates, dict]] = field(default_factory=dict) - - def __post_init__(self): - self.defaultcoordinates = VenueDefaultCoordinates(**self.defaultcoordinates) if self.defaultcoordinates else self.defaultcoordinates + azimuth_angle: Optional[float] = Field(default=None, alias="azimuthangle") + elevation: Optional[int] = None + default_coordinates: Optional[VenueDefaultCoordinates] = Field(default=None, alias="defaultcoordinates") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass -class TimeZone: +class TimeZone(MLBBaseModel): """ - A class to represent a TimeZone Used by venue. + A class to represent a timezone used by venue. Attributes ---------- id : str - id string for a venues timezone + ID string for a venue's timezone. offset : int - The offset for this timezone from + The offset for this timezone. tz : str - Timezone string + Timezone string. + offset_at_game_time : int + Offset at game time. """ id: str offset: int tz: str - offsetatgametime: Optional[int] = None + offset_at_game_time: Optional[int] = Field(default=None, alias="offsetatgametime") -@dataclass(repr=False) -class FieldInfo: + +class FieldInfo(MLBBaseModel): """ - A class to represent a venue Field info. + A class to represent venue field info. Attributes ---------- capacity : int - Capacity for this venue - turfType : str - The type of turf in this venue - roofType : str - What kind of roof for this venue - leftLine : int - Distance down the left line + Capacity for this venue. + turf_type : str + The type of turf in this venue. + roof_type : str + What kind of roof for this venue. + left_line : int + Distance down the left line. left : int - Distance to left - leftCenter : int - Distance to left center + Distance to left. + left_center : int + Distance to left center. center : int - Distance to center - rightCenter : int - Distance to right center + Distance to center. + right_center : int + Distance to right center. right : int - Distance to right - rightLine : int - Distance to right line + Distance to right. + right_line : int + Distance to right line. """ capacity: Optional[int] = None - turftype: Optional[str] = None - rooftype: Optional[str] = None - leftline: Optional[int] = None + turf_type: Optional[str] = Field(default=None, alias="turftype") + roof_type: Optional[str] = Field(default=None, alias="rooftype") + left_line: Optional[int] = Field(default=None, alias="leftline") left: Optional[int] = None - leftcenter: Optional[int] = None + left_center: Optional[int] = Field(default=None, alias="leftcenter") center: Optional[int] = None - rightcenter: Optional[int] = None + right_center: Optional[int] = Field(default=None, alias="rightcenter") right: Optional[int] = None - rightline: Optional[int] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + right_line: Optional[int] = Field(default=None, alias="rightline") diff --git a/mlbstatsapi/models/venues/venue.py b/mlbstatsapi/models/venues/venue.py index aad5075..e8ea792 100644 --- a/mlbstatsapi/models/venues/venue.py +++ b/mlbstatsapi/models/venues/venue.py @@ -1,45 +1,37 @@ -from typing import Optional, Union -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from .attributes import Location, TimeZone, FieldInfo -@dataclass(repr=False) -class Venue: + +class Venue(MLBBaseModel): """ A class to represent a venue. Attributes ---------- id : int - id for this venue - name : str - Name for this venue + ID for this venue. link : str - Link to venues endpoint + Link to venue's endpoint. + name : str + Name for this venue. location : Location - Location for this venue + Location for this venue. timezone : TimeZone - Timezone for this venue - fieldinfo : FieldInfo - Info on this venue's field + Timezone for this venue. + field_info : FieldInfo + Info on this venue's field. active : bool - Is this field currently active + Whether this field is currently active. season : str - This field holds the season + The season. """ - id: int - link: str - name: Optional[str] = None - location: Optional[Union[Location, dict]] = None - timezone: Optional[Union[TimeZone, dict]] = None - fieldinfo: Optional[Union[FieldInfo, dict]] = None - active: Optional[bool] = None - season: Optional[str] = None - - def __post_init__(self): - self.location = Location(**self.location) if self.location else self.location - self.timezone = TimeZone(**self.timezone) if self.timezone else self.timezone - self.fieldinfo = FieldInfo(**self.fieldinfo) if self.fieldinfo else self.fieldinfo - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + id: int + link: str + name: Optional[str] = None + location: Optional[Location] = None + timezone: Optional[TimeZone] = None + field_info: Optional[FieldInfo] = Field(default=None, alias="fieldinfo") + active: Optional[bool] = None + season: Optional[str] = None diff --git a/tests/external_tests/attendance/test_attendance.py b/tests/external_tests/attendance/test_attendance.py index b5ee552..5eea7ad 100644 --- a/tests/external_tests/attendance/test_attendance.py +++ b/tests/external_tests/attendance/test_attendance.py @@ -1,8 +1,10 @@ import unittest from unittest.mock import Mock, patch +from pydantic import ValidationError from mlbstatsapi.models.attendances import Attendance, attendance from mlbstatsapi import Mlb + class TestAttendance(unittest.TestCase): @classmethod def setUpClass(cls) -> None: @@ -12,13 +14,13 @@ def setUpClass(cls) -> None: cls.attendance_team_home = cls.mlb.get_attendance(team_id=134) cls.attendance_season = cls.mlb.get_attendance(team_id=113, params=params) - @classmethod def tearDownClass(cls) -> None: pass - def test_attendance_instance_type_error(self): - with self.assertRaises(TypeError): + def test_attendance_instance_validation_error(self): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): attendance = Attendance() def test_attendance_instance_position_arguments(self): @@ -31,8 +33,22 @@ def test_attendance_has_attributes(self): self.assertIsInstance(self.attendance_team_home, Attendance) self.assertIsInstance(self.attendance_season, Attendance) self.assertTrue(hasattr(self.attendance_team_away, "records")) - self.assertTrue(hasattr(self.attendance_team_away, "aggregatetotals")) + self.assertTrue(hasattr(self.attendance_team_away, "aggregate_totals")) self.assertTrue(hasattr(self.attendance_team_home, "records")) - self.assertTrue(hasattr(self.attendance_team_home, "aggregatetotals")) + self.assertTrue(hasattr(self.attendance_team_home, "aggregate_totals")) self.assertTrue(hasattr(self.attendance_season, "records")) - self.assertTrue(hasattr(self.attendance_season, "aggregatetotals")) + self.assertTrue(hasattr(self.attendance_season, "aggregate_totals")) + + def test_attendance_pythonic_field_names(self): + """Test that Pythonic field names work correctly.""" + record = self.attendance_team_away.records[0] + # Verify snake_case field names are accessible + self.assertIsNotNone(record.openings_total) + self.assertIsNotNone(record.games_total) + self.assertIsNotNone(record.attendance_average_ytd) + self.assertIsNotNone(record.game_type) + + # Verify aggregate totals use snake_case + totals = self.attendance_team_away.aggregate_totals + self.assertIsNotNone(totals.openings_total_away) + self.assertIsNotNone(totals.attendance_total) diff --git a/tests/external_tests/division/test_division.py b/tests/external_tests/division/test_division.py index da1f024..98eeaff 100644 --- a/tests/external_tests/division/test_division.py +++ b/tests/external_tests/division/test_division.py @@ -1,4 +1,5 @@ import unittest +from pydantic import ValidationError from mlbstatsapi.models.divisions import Division from mlbstatsapi import Mlb @@ -14,7 +15,8 @@ def tearDownClass(cls) -> None: pass def test_divisions_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): division = Division() def test_divisions_instance_position_arguments(self): @@ -28,11 +30,11 @@ def test_divisions_has_attributes(self): self.assertTrue(hasattr(self.division, "name")) self.assertTrue(hasattr(self.division, "link")) self.assertTrue(hasattr(self.division, "season")) - self.assertTrue(hasattr(self.division, "nameshort")) + self.assertTrue(hasattr(self.division, "name_short")) self.assertTrue(hasattr(self.division, "abbreviation")) self.assertTrue(hasattr(self.division, "league")) self.assertTrue(hasattr(self.division, "sport")) - self.assertTrue(hasattr(self.division, "haswildcard")) - self.assertTrue(hasattr(self.division, "sortorder")) - self.assertTrue(hasattr(self.division, "numplayoffteams")) + self.assertTrue(hasattr(self.division, "has_wildcard")) + self.assertTrue(hasattr(self.division, "sort_order")) + self.assertTrue(hasattr(self.division, "num_playoff_teams")) self.assertTrue(hasattr(self.division, "active")) diff --git a/tests/external_tests/league/test_league.py b/tests/external_tests/league/test_league.py index ced2d97..45be6ed 100644 --- a/tests/external_tests/league/test_league.py +++ b/tests/external_tests/league/test_league.py @@ -1,4 +1,5 @@ import unittest +from pydantic import ValidationError from mlbstatsapi.models.leagues import League from mlbstatsapi import Mlb @@ -14,7 +15,8 @@ def tearDownClass(cls) -> None: pass def test_league_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): league = League() def test_league_instance_position_arguments(self): @@ -28,19 +30,19 @@ def test_league_has_attributes(self): self.assertTrue(hasattr(self.league, "name")) self.assertTrue(hasattr(self.league, "link")) self.assertTrue(hasattr(self.league, "abbreviation")) - self.assertTrue(hasattr(self.league, "nameshort")) - self.assertTrue(hasattr(self.league, "seasonstate")) - self.assertTrue(hasattr(self.league, "haswildcard")) - self.assertTrue(hasattr(self.league, "hassplitseason")) - self.assertTrue(hasattr(self.league, "numgames")) - self.assertTrue(hasattr(self.league, "hasplayoffpoints")) - self.assertTrue(hasattr(self.league, "numteams")) - self.assertTrue(hasattr(self.league, "numwildcardteams")) - self.assertTrue(hasattr(self.league, "seasondateinfo")) + self.assertTrue(hasattr(self.league, "name_short")) + self.assertTrue(hasattr(self.league, "season_state")) + self.assertTrue(hasattr(self.league, "has_wildcard")) + self.assertTrue(hasattr(self.league, "has_split_season")) + self.assertTrue(hasattr(self.league, "num_games")) + self.assertTrue(hasattr(self.league, "has_playoff_points")) + self.assertTrue(hasattr(self.league, "num_teams")) + self.assertTrue(hasattr(self.league, "num_wildcard_teams")) + self.assertTrue(hasattr(self.league, "season_date_info")) self.assertTrue(hasattr(self.league, "season")) - self.assertTrue(hasattr(self.league, "orgcode")) - self.assertTrue(hasattr(self.league, "conferencesinuse")) - self.assertTrue(hasattr(self.league, "divisionsinuse")) + self.assertTrue(hasattr(self.league, "org_code")) + self.assertTrue(hasattr(self.league, "conferences_in_use")) + self.assertTrue(hasattr(self.league, "divisions_in_use")) self.assertTrue(hasattr(self.league, "sport")) - self.assertTrue(hasattr(self.league, "sortorder")) + self.assertTrue(hasattr(self.league, "sort_order")) self.assertTrue(hasattr(self.league, "active")) diff --git a/tests/external_tests/sport/test_sport.py b/tests/external_tests/sport/test_sport.py index 865f750..18f2de1 100644 --- a/tests/external_tests/sport/test_sport.py +++ b/tests/external_tests/sport/test_sport.py @@ -1,4 +1,5 @@ import unittest +from pydantic import ValidationError from mlbstatsapi.models.sports import Sport from mlbstatsapi import Mlb @@ -14,7 +15,8 @@ def tearDownClass(cls) -> None: pass def test_sport_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): sport = Sport() def test_sport_instance_position_arguments(self): @@ -29,5 +31,5 @@ def test_sport_attributes(self): self.assertTrue(hasattr(self.sport, "name")) self.assertTrue(hasattr(self.sport, "code")) self.assertTrue(hasattr(self.sport, "abbreviation")) - self.assertTrue(hasattr(self.sport, "sortorder")) - self.assertTrue(hasattr(self.sport, "activestatus")) + self.assertTrue(hasattr(self.sport, "sort_order")) + self.assertTrue(hasattr(self.sport, "active_status")) diff --git a/tests/external_tests/standings/test_standings.py b/tests/external_tests/standings/test_standings.py index daae205..8ebb5c6 100644 --- a/tests/external_tests/standings/test_standings.py +++ b/tests/external_tests/standings/test_standings.py @@ -34,12 +34,12 @@ def test_get_standings(self): standing = standings[0] - # sportgamepace should not be none + # standing should not be none self.assertIsNotNone(standing) - # sportgamepace should have attrs set - self.assertTrue(standing.standingstype) - self.assertTrue(standing.lastupdated) + # standing should have attrs set + self.assertTrue(standing.standings_type) + self.assertTrue(standing.last_updated) def test_get_standings_404(self): """This test should return a 200 and """ @@ -72,4 +72,4 @@ def test_get_standings_with_params(self): # standings should not be None self.assertIsNotNone(standings) # list should not be empty - self.assertNotEqual(standings, []) \ No newline at end of file + self.assertNotEqual(standings, []) diff --git a/tests/external_tests/team/test_team.py b/tests/external_tests/team/test_team.py index 4d7f547..7de0a50 100644 --- a/tests/external_tests/team/test_team.py +++ b/tests/external_tests/team/test_team.py @@ -1,4 +1,5 @@ -import unittest +import unittest +from pydantic import ValidationError from mlbstatsapi.models.teams import Team from mlbstatsapi.models.venues import Venue from mlbstatsapi.models.divisions import Division @@ -17,7 +18,8 @@ def tearDownClass(cls) -> None: pass def test_team_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): team = Team() def test_team_instance_id_instance_success(self): @@ -58,4 +60,4 @@ def test_get_teams_for_sport(self): self.assertIsInstance(self.teams, list) # teams should not be empty list - self.assertNotEqual(self.teams, []) \ No newline at end of file + self.assertNotEqual(self.teams, []) diff --git a/tests/external_tests/venue/test_venue.py b/tests/external_tests/venue/test_venue.py index 30ae808..5ed98f1 100644 --- a/tests/external_tests/venue/test_venue.py +++ b/tests/external_tests/venue/test_venue.py @@ -1,4 +1,5 @@ import unittest +from pydantic import ValidationError from mlbstatsapi.models.venues import Venue from mlbstatsapi import Mlb @@ -14,7 +15,8 @@ def tearDownClass(cls) -> None: pass def test_venue_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): venue = Venue() def test_venue_instance_position_arguments(self): @@ -30,5 +32,5 @@ def test_venue_attributes(self): self.assertTrue(hasattr(self.venue, "name")) self.assertTrue(hasattr(self.venue, "location")) self.assertTrue(hasattr(self.venue, "timezone")) - self.assertTrue(hasattr(self.venue, "fieldinfo")) + self.assertTrue(hasattr(self.venue, "field_info")) self.assertTrue(hasattr(self.venue, "active")) diff --git a/tests/mock_tests/standings/test_standings_mock.py b/tests/mock_tests/standings/test_standings_mock.py index bef712f..f546c98 100644 --- a/tests/mock_tests/standings/test_standings_mock.py +++ b/tests/mock_tests/standings/test_standings_mock.py @@ -51,6 +51,6 @@ def test_get_standings(self, m): # sportgamepace should not be none self.assertIsNotNone(standing) - # sportgamepace should have attrs set - self.assertTrue(standing.standingstype) - self.assertTrue(standing.lastupdated) \ No newline at end of file + # standings should have attrs set + self.assertTrue(standing.standings_type) + self.assertTrue(standing.last_updated) \ No newline at end of file From 78d9a66bcf3f91c85d161c93be09b696d88cdaca Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 15:18:33 -0800 Subject: [PATCH 4/9] refactor: migrate people and data models to Pydantic Models converted: - people/attributes: BatSide, PitchHand, Position, Status, Home, School - people/people: Person, Player, Coach, Batter, Pitcher, DraftPick - data/data: PitchBreak, PitchCoordinates, PitchData, HitCoordinates, HitData, CodeDesc, Violation, Count, PlayDetails Key changes: - All fields use snake_case with aliases for API compatibility - Player uses model_validator to handle position -> primary_position mapping - PlayDetails uses field_validator to convert empty dicts to None - Fixed type mismatches: current_team is Team (not str), launch_angle is float - Updated person tests to use new field names --- mlbstatsapi/models/data/data.py | 456 ++++++++++----------- mlbstatsapi/models/people/__init__.py | 2 +- mlbstatsapi/models/people/attributes.py | 78 ++-- mlbstatsapi/models/people/people.py | 401 +++++++++--------- tests/external_tests/person/test_person.py | 13 +- 5 files changed, 451 insertions(+), 499 deletions(-) diff --git a/mlbstatsapi/models/data/data.py b/mlbstatsapi/models/data/data.py index be5fed4..6ca2498 100644 --- a/mlbstatsapi/models/data/data.py +++ b/mlbstatsapi/models/data/data.py @@ -1,96 +1,91 @@ -from dataclasses import dataclass, field, InitVar -from typing import List, Union, Dict, Any, Optional +from typing import Optional, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel - - -@dataclass(repr=False) -class PitchBreak: +class PitchBreak(MLBBaseModel): """ - A class to hold pitch pitch break data - + A class to hold pitch break data. + Attributes ---------- - breakangle : float - Degrees clockwise (batter's view) that the plane of - the pitch deviates from the vertical - breaklength : float + break_angle : float + Degrees clockwise (batter's view) that the plane of + the pitch deviates from the vertical. + break_vertical : float + Vertical break of the pitch. + break_vertical_induced : float + Induced vertical break. + break_horizontal : float + Horizontal break of the pitch. + break_length : float Max distance that the pitch separates from the straight - line between pitch start and pitch end - breaky : int - Distance from home plate where the break is greatest - spinrate : int - Pitch spinRate - spindirection : int - Pitch spinDirection + line between pitch start and pitch end. + break_y : int + Distance from home plate where the break is greatest. + spin_rate : float + Pitch spin rate. + spin_direction : float + Pitch spin direction. """ - breakangle: Optional[float] - breakvertical: Optional[float] - breakverticalinduced: Optional[float] - breakhorizontal: Optional[float] - spinrate: Optional[float] = None - spindirection: Optional[float] = None - breaklength: Optional[float] = None - breaky: Optional[float] = None + break_angle: Optional[float] = Field(default=None, alias="breakangle") + break_vertical: Optional[float] = Field(default=None, alias="breakvertical") + break_vertical_induced: Optional[float] = Field(default=None, alias="breakverticalinduced") + break_horizontal: Optional[float] = Field(default=None, alias="breakhorizontal") + spin_rate: Optional[float] = Field(default=None, alias="spinrate") + spin_direction: Optional[float] = Field(default=None, alias="spindirection") + break_length: Optional[float] = Field(default=None, alias="breaklength") + break_y: Optional[float] = Field(default=None, alias="breaky") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class PitchCoordinates: +class PitchCoordinates(MLBBaseModel): """ - A class to hold pitch coordinates for playLog + A class to hold pitch coordinates for playLog. Attributes ---------- - ay : float, default=None - Ball acceleration on the y axis - az : float, default=None - Ball acceleration on the z axis - pfxx : float, default=None - horizontal movement of the ball in inches - pfxz : float, default=None - Vertical movement of the ball in inches - px : float, default=None - Horizontal position in feet of the ball as it - crosses the front axis of home plate - pz : float, default=None - Vertical position in feet of the ball as it - crosses the front axis of home plate - vx0 : float, default=None - Velocity of the ball from the x-axis - vy0 : float, default=None - Velocity of the ball from the y axis, this - is negative becuase 0,0,0 is behind the batter - and the ball travels from pitcher mound towards 0,0,0 - vz0 : float, default=None - Velocity of the ball from the z axis - x0 : float, default=None - Coordinate location of the ball at the point it was - reeased from pitchers hand on the x axis (time=0) - y0 : float, default=None - Coordinate location of the ball at the point it was - reeased from pitchers hand on the y axis (time=0) - z0 : float, default=None - Coordinate location of the ball at the point it was - reeased from pitchers hand on the z axis (time=0) - ax : float, default=None - Ball acceleration on the x axis - x : float, default=None - X coordinate where pitch crossed front of home plate - y : float, default=None - Y coordinate where pitch crossed front of home plate + ay : float + Ball acceleration on the y axis. + az : float + Ball acceleration on the z axis. + pfx_x : float + Horizontal movement of the ball in inches. + pfx_z : float + Vertical movement of the ball in inches. + p_x : float + Horizontal position in feet of the ball as it + crosses the front axis of home plate. + p_z : float + Vertical position in feet of the ball as it + crosses the front axis of home plate. + v_x0 : float + Velocity of the ball from the x-axis. + v_y0 : float + Velocity of the ball from the y axis. + v_z0 : float + Velocity of the ball from the z axis. + x0 : float + Coordinate location of the ball at release on x axis. + y0 : float + Coordinate location of the ball at release on y axis. + z0 : float + Coordinate location of the ball at release on z axis. + ax : float + Ball acceleration on the x axis. + x : float + X coordinate where pitch crossed front of home plate. + y : float + Y coordinate where pitch crossed front of home plate. """ ay: Optional[float] = None az: Optional[float] = None - pfxx: Optional[float] = None - pfxz: Optional[float] = None - px: Optional[float] = None - pz: Optional[float] = None - vx0: Optional[float] = None - vy0: Optional[float] = None - vz0: Optional[float] = None + pfx_x: Optional[float] = Field(default=None, alias="pfxx") + pfx_z: Optional[float] = Field(default=None, alias="pfxz") + p_x: Optional[float] = Field(default=None, alias="px") + p_z: Optional[float] = Field(default=None, alias="pz") + v_x0: Optional[float] = Field(default=None, alias="vx0") + v_y0: Optional[float] = Field(default=None, alias="vy0") + v_z0: Optional[float] = Field(default=None, alias="vz0") x0: Optional[float] = None y0: Optional[float] = None z0: Optional[float] = None @@ -98,24 +93,20 @@ class PitchCoordinates: x: Optional[float] = None y: Optional[float] = None - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class PitchData: +class PitchData(MLBBaseModel): """ - A class to hold data on a pitch - + A class to hold data on a pitch. + Attributes ---------- - startspeed : float + start_speed : float The starting speed of the pitch. - endspeed : float + end_speed : float The ending speed of the pitch. - strikezonetop : float + strike_zone_top : float The top of the strike zone. - strikezonebottom : float + strike_zone_bottom : float The bottom of the strike zone. coordinates : PitchCoordinates The coordinates of the pitch. @@ -123,240 +114,215 @@ class PitchData: The break data of the pitch. zone : float The zone in which the pitch was thrown. - typeconfidence : float + type_confidence : float The confidence in the type of pitch thrown. - platetime : float + plate_time : float The amount of time the pitch was in the air. extension : float The extension of the pitch. - strikezonewidth : float - The width of the strikezone - strikezonedepth : float - The depth of the strikezone + strike_zone_width : float + The width of the strike zone. + strike_zone_depth : float + The depth of the strike zone. """ - strikezonetop: float - strikezonebottom: float - breaks: Union[PitchBreak, dict] - coordinates: Optional[Union[PitchCoordinates, dict]] = field(default_factory=dict) + strike_zone_top: float = Field(alias="strikezonetop") + strike_zone_bottom: float = Field(alias="strikezonebottom") + breaks: PitchBreak + coordinates: Optional[PitchCoordinates] = None extension: Optional[float] = None - startspeed: Optional[float] = None - endspeed: Optional[float] = None + start_speed: Optional[float] = Field(default=None, alias="startspeed") + end_speed: Optional[float] = Field(default=None, alias="endspeed") zone: Optional[float] = None - typeconfidence: Optional[float] = None - platetime: Optional[float] = None - strikezonewidth: Optional[float] = None - strikezonedepth: Optional[float] = None + type_confidence: Optional[float] = Field(default=None, alias="typeconfidence") + plate_time: Optional[float] = Field(default=None, alias="platetime") + strike_zone_width: Optional[float] = Field(default=None, alias="strikezonewidth") + strike_zone_depth: Optional[float] = Field(default=None, alias="strikezonedepth") - def __post_init__(self): - self.coordinates = PitchCoordinates(**self.coordinates) if self.coordinates else self.coordinates - self.breaks = PitchBreak(**self.breaks) if self.breaks else self.breaks - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass -class HitCoordinates: +class HitCoordinates(MLBBaseModel): """ - A class to represent a play events hit location coordinates. + A class to represent a play event's hit location coordinates. Attributes ---------- - coordx : int - X coordinate for hit - coordy : int - Y coordinate for hit + coord_x : float + X coordinate for hit. + coord_y : float + Y coordinate for hit. """ - coordx: Optional[float] = None - coordy: Optional[float] = None + coord_x: Optional[float] = Field(default=None, alias="coordx") + coord_y: Optional[float] = Field(default=None, alias="coordy") @property def x(self): - return self.coordx + return self.coord_x @property def y(self): - return self.coordy + return self.coord_y -@dataclass(repr=False) -class HitData: + +class HitData(MLBBaseModel): """ - A class to represent a play events hit data. + A class to represent a play event's hit data. Attributes ---------- - launchspeed : float - Hit launch speed - launchangle : int - Hit launch angle - totaldistance : int - Hits total distance + launch_speed : float + Hit launch speed. + launch_angle : float + Hit launch angle. + total_distance : float + Hit's total distance. trajectory : str - Hit trajectory + Hit trajectory. hardness : str - Hit hardness - location : str - Hit location - coordinates : HitCoordinate - Hit coordinates + Hit hardness. + location : int + Hit location. + coordinates : HitCoordinates + Hit coordinates. """ - - - coordinates: Union[HitCoordinates, dict] + coordinates: HitCoordinates trajectory: Optional[str] = None hardness: Optional[str] = None location: Optional[int] = None - launchspeed: Optional[float] = None - launchangle: Optional[str] = None # this is a negative number and I'm brain farting on those - totaldistance: Optional[float] = None - - def __post_init__(self): - self.coordinates = HitCoordinates(**self.coordinates) if self.coordinates else self.coordinates + launch_speed: Optional[float] = Field(default=None, alias="launchspeed") + launch_angle: Optional[float] = Field(default=None, alias="launchangle") + total_distance: Optional[float] = Field(default=None, alias="totaldistance") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass -class CodeDesc: +class CodeDesc(MLBBaseModel): """ - a class to hold a code and a description + A class to hold a code and a description. Attributes ---------- code : str - the code to reference the attribute using this + The code to reference the attribute. description : str - the description of the attribute using this + The description of the attribute. """ code: str description: Optional[str] = None -@dataclass -class Violation: + +class Violation(MLBBaseModel): """ - + A class to represent a violation during play. + Attributes ---------- type : str - the type of violation during the play + The type of violation during the play. description : str - the description of the violation that occured - player : player - the player that caused the violation + The description of the violation that occurred. + player : dict + The player that caused the violation. """ type: Optional[str] = None description: Optional[str] = None player: Optional[dict] = None -@dataclass(repr=False) -class Count: + +class Count(MLBBaseModel): """ - a class to hold a pitch count and base runners + A class to hold a pitch count and base runners. Attributes ---------- - code : str - code balls : int - number of balls - inning : int - inning number - istopinning : bool - bool to hold status of top inning + Number of balls. outs : int - number of outs - runneron1b : bool - bool to hold 1b runner status - runneron2b : bool - bool to hold 2b runner status - runneron3b : bool - bool to hold 3b runner status + Number of outs. strikes : int - strike count + Strike count. + inning : int + Inning number. + is_top_inning : bool + Status of top inning. + runner_on_1b : bool + 1B runner status. + runner_on_2b : bool + 2B runner status. + runner_on_3b : bool + 3B runner status. """ balls: int outs: int strikes: int inning: Optional[int] = None - runneron1b: Optional[bool] = None - runneron2b: Optional[bool] = None - runneron3b: Optional[bool] = None - istopinning: Optional[bool] = None + runner_on_1b: Optional[bool] = Field(default=None, alias="runneron1b") + runner_on_2b: Optional[bool] = Field(default=None, alias="runneron2b") + runner_on_3b: Optional[bool] = Field(default=None, alias="runneron3b") + is_top_inning: Optional[bool] = Field(default=None, alias="istopinning") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class PlayDetails: +class PlayDetails(MLBBaseModel): """ - A class to represent a gamelog stat for a hitter + A class to represent play details. Attributes ---------- call : CodeDesc - play call code and description + Play call code and description. description : str - description of the play + Description of the play. event : str - type of event - eventtype : str - type of event - isinplay : bool - is the ball in play true or false - isstrike : bool - is the ball a strike true or false - isball : bool - is it a ball true or false - isbasehit : bool - is the event a base hit true or false - isatbat : bool - is the event at bat true or false - isplateappearance : bool - is the event a at play appears true or false + Type of event. + event_type : str + Type of event. + is_in_play : bool + Is the ball in play. + is_strike : bool + Is the ball a strike. + is_ball : bool + Is it a ball. + is_base_hit : bool + Is the event a base hit. + is_at_bat : bool + Is the event at bat. + is_plate_appearance : bool + Is the event a plate appearance. type : CodeDesc - type of pitch code and description - batside : CodeDesc - bat side code and description - pitchhand : CodeDesc - pitch hand code and description - fromcatcher : bool + Type of pitch code and description. + bat_side : CodeDesc + Bat side code and description. + pitch_hand : CodeDesc + Pitch hand code and description. + from_catcher : bool + From catcher flag. """ - call: Optional[Union[CodeDesc, dict]] = None - isinplay: Optional[bool] = None - isstrike: Optional[bool] = None - isscoringplay: Optional[bool] = None - isout: Optional[bool] = None - runnergoing: Optional[bool] = None - isball: Optional[bool] = None - isbasehit: Optional[bool] = None - isatbat: Optional[bool] = None - isplateappearance: Optional[bool] = None - batside: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) - pitchhand: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) - eventtype: Optional[str] = None + call: Optional[CodeDesc] = None + is_in_play: Optional[bool] = Field(default=None, alias="isinplay") + is_strike: Optional[bool] = Field(default=None, alias="isstrike") + is_scoring_play: Optional[bool] = Field(default=None, alias="isscoringplay") + is_out: Optional[bool] = Field(default=None, alias="isout") + runner_going: Optional[bool] = Field(default=None, alias="runnergoing") + is_ball: Optional[bool] = Field(default=None, alias="isball") + is_base_hit: Optional[bool] = Field(default=None, alias="isbasehit") + is_at_bat: Optional[bool] = Field(default=None, alias="isatbat") + is_plate_appearance: Optional[bool] = Field(default=None, alias="isplateappearance") + bat_side: Optional[CodeDesc] = Field(default=None, alias="batside") + pitch_hand: Optional[CodeDesc] = Field(default=None, alias="pitchhand") + event_type: Optional[str] = Field(default=None, alias="eventtype") event: Optional[str] = None description: Optional[str] = None - type: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) - awayscore: Optional[int] = None - homescore: Optional[int] = None - hasreview: Optional[bool] = None + type: Optional[CodeDesc] = None + away_score: Optional[int] = Field(default=None, alias="awayscore") + home_score: Optional[int] = Field(default=None, alias="homescore") + has_review: Optional[bool] = Field(default=None, alias="hasreview") code: Optional[str] = None - ballcolor: Optional[str] = None - trailcolor: Optional[str] = None - fromcatcher: Optional[bool] = None - disengagementnum: Optional[int] = None - violation: Optional[Union[Violation, dict]] = field(default_factory=dict) - - def __post_init__(self): - self.call = CodeDesc(**self.call) if self.call else self.call - self.batside = CodeDesc(**self.batside) if self.batside else self.batside - self.pitchhand = CodeDesc(**self.pitchhand) if self.pitchhand else self.pitchhand - self.type = CodeDesc(**self.type) if self.type else self.type - self.violation = Violation(**self.violation) if self.violation else self.violation + ball_color: Optional[str] = Field(default=None, alias="ballcolor") + trail_color: Optional[str] = Field(default=None, alias="trailcolor") + from_catcher: Optional[bool] = Field(default=None, alias="fromcatcher") + disengagement_num: Optional[int] = Field(default=None, alias="disengagementnum") + violation: Optional[Violation] = None - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + @field_validator('bat_side', 'pitch_hand', 'type', 'call', 'violation', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/people/__init__.py b/mlbstatsapi/models/people/__init__.py index 14b3469..c3eeac0 100644 --- a/mlbstatsapi/models/people/__init__.py +++ b/mlbstatsapi/models/people/__init__.py @@ -1,2 +1,2 @@ -from .attributes import BatSide, Position, PitchHand, Status +from .attributes import BatSide, Position, PitchHand, Status, Home, School from .people import Player, Coach, Person, Batter, Pitcher, DraftPick diff --git a/mlbstatsapi/models/people/attributes.py b/mlbstatsapi/models/people/attributes.py index 778753e..dcc7f2e 100644 --- a/mlbstatsapi/models/people/attributes.py +++ b/mlbstatsapi/models/people/attributes.py @@ -1,53 +1,52 @@ -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass -class BatSide: +class BatSide(MLBBaseModel): """ - A class to represent a BatSide. + A class to represent a bat side. Attributes ---------- code : str - code number of the BatSide - descritpion: str - description of the BatSide + Code of the bat side. + description : str + Description of the bat side. """ code: str description: str -@dataclass -class PitchHand: +class PitchHand(MLBBaseModel): """ - A class to represent a PitchHand. + A class to represent a pitch hand. Attributes ---------- code : str - code number of the PitchHand - descritpion: str - description of the PitchHand + Code of the pitch hand. + description : str + Description of the pitch hand. """ code: str description: str -@dataclass -class Position: +class Position(MLBBaseModel): """ - A class to represent a batside. + A class to represent a position. Attributes ---------- - code: str - code number of the Position - name: str - the name of the Position - type: str - the type of the Position - abbreviation: str - the abbreviation of the Position + code : str + Code of the position. + name : str + Name of the position. + type : str + Type of the position. + abbreviation : str + Abbreviation of the position. """ code: str name: str @@ -55,25 +54,24 @@ class Position: abbreviation: str -@dataclass -class Status: +class Status(MLBBaseModel): """ - A dataclass to hold player status + A class to represent player status. Attributes ---------- - code: str - code of the player - description: str - description of the status + code : str + Code of the player status. + description : str + Description of the status. """ code: str description: str -@dataclass -class Home: + +class Home(MLBBaseModel): """ - A home is a where a draft player is from + A class to represent where a draft player is from. Attributes ---------- @@ -88,16 +86,16 @@ class Home: state: str country: str -@dataclass -class School: + +class School(MLBBaseModel): """ - Represents the school the draft player is from. + A class to represent the school a draft player is from. Attributes ---------- name : str The name of the school. - schoolclass : str + school_class : str The class the student is in. city : str The city where the school is located. @@ -107,7 +105,7 @@ class School: The state where the school is located. """ name: str - schoolclass: str + school_class: str = Field(alias="schoolclass") city: str country: str - state: str \ No newline at end of file + state: str diff --git a/mlbstatsapi/models/people/people.py b/mlbstatsapi/models/people/people.py index 209d741..15c6ef0 100644 --- a/mlbstatsapi/models/people/people.py +++ b/mlbstatsapi/models/people/people.py @@ -1,222 +1,214 @@ -from dataclasses import dataclass, field, InitVar -from typing import Union, Dict, Any, Optional - +from __future__ import annotations +from typing import Optional, Any, TYPE_CHECKING +from pydantic import Field, field_validator, model_validator +from mlbstatsapi.models.base import MLBBaseModel from .attributes import BatSide, Position, PitchHand, Status, Home, School from mlbstatsapi.models.teams import Team from mlbstatsapi.models.data import CodeDesc -# from mlbstatsapi.models.drafts import Home, College -@dataclass(repr=False) -class Person: +class Person(MLBBaseModel): """ A class to represent a Person. Attributes ---------- id : int - id number of the person - full_name : str - full_name of the person + ID number of the person. link : str - Api link to person - primaryposition : Position - PrimaryPosition of the Person - pitchhand : str - PitchHand of the Person - batside : str - BatSide of the Person - fullname : str - full name of the Person - firstname : str - First name of the Person - lastname : str - Last name of the Person - primarynumber : str - Primary number of the Person - birthdate : str - Birth date of the Person - currentteam : str - The current Team of the Person - currentage : str - The current age of the Person - birthcity : str - The birthcity of the Person - birthstateprovince : str - The province of the birth state + API link to person. + primary_position : Position + Primary position of the person. + pitch_hand : PitchHand + Pitch hand of the person. + bat_side : BatSide + Bat side of the person. + full_name : str + Full name of the person. + first_name : str + First name of the person. + last_name : str + Last name of the person. + primary_number : str + Primary number of the person. + birth_date : str + Birth date of the person. + current_team : Team + The current team of the person. + current_age : int + The current age of the person. + birth_city : str + The birth city of the person. + birth_state_province : str + The province of the birth state. height : str - The height of the Person - weight : str - The weight of the Person - active : str - The active status of the Person - usename : str - The use name of the Person - middlename : str - The middle name of the Person - boxscorename : str - The box score name of the Person + The height of the person. + weight : int + The weight of the person. + active : bool + The active status of the person. + use_name : str + The use name of the person. + middle_name : str + The middle name of the person. + boxscore_name : str + The box score name of the person. nickname : str - The nickname of the Person - draftyear : int - The draft year of the Person - mlbdebutdate : str - The MLB debut date of the Person - namefirstlast : str - The first and last name of the Person - nameslug : str - The name slug of the Person - firstlastname : str - The first and last name of the Person - lastfirstname : str - The last and first name of the Person - lastinitname : str - The last init name of the Person - initlastname : str - The init last name of the Person - fullfmlname : str - The full fml name of the Person - fulllfmname : str - The full lfm name of the Person - uselastname : str - The last name of the - birthcountry : str - The birth country of the Person + The nickname of the person. + draft_year : int + The draft year of the person. + mlb_debut_date : str + The MLB debut date of the person. + name_first_last : str + The first and last name of the person. + name_slug : str + The name slug of the person. + first_last_name : str + The first and last name of the person. + last_first_name : str + The last and first name of the person. + last_init_name : str + The last init name of the person. + init_last_name : str + The init last name of the person. + full_fml_name : str + The full FML name of the person. + full_lfm_name : str + The full LFM name of the person. + use_last_name : str + The use last name of the person. + birth_country : str + The birth country of the person. pronunciation : str - The pronuciation of the Person's name - strikezonetop : float - The strike zone top of the Person - strikezonebottom : float - The strike zone bottom of the Person - nametitle : str - The name title of the Person + The pronunciation of the person's name. + strike_zone_top : float + The strike zone top of the person. + strike_zone_bottom : float + The strike zone bottom of the person. + name_title : str + The name title of the person. gender : str - The gender of the Person - isplayer : bool - The player status of the Person - isverified : bool - The verification of the Person - namematrilineal : str - The name matrilineal of the Person - deathdate : str - The death date of the Person - deathcity : str - The death city of the Person - deathcountry : str - The death country of the Person - lastplayeddate : str - The last played date of the Person - namesuffix : str - The namesuffix of the Person + The gender of the person. + is_player : bool + The player status of the person. + is_verified : bool + The verification of the person. + name_matrilineal : str + The name matrilineal of the person. + death_date : str + The death date of the person. + death_city : str + The death city of the person. + death_country : str + The death country of the person. + death_state_province : str + The death state province of the person. + last_played_date : str + The last played date of the person. + name_suffix : str + The name suffix of the person. """ - id: int link: str - primaryposition: Union[Position, Dict[str, Any]] = field(default_factory=dict) - pitchhand: Union[PitchHand, Dict[str, Any]] = field(default_factory=dict) - batside: Union[BatSide, Dict[str, Any]] = field(default_factory=dict) - fullname: Optional[str] = None - firstname: Optional[str] = None - lastname: Optional[str] = None - primarynumber: Optional[str] = None - birthdate: Optional[str] = None - currentteam: Optional[str] = None - currentage: Optional[str] = None - birthcity: Optional[str] = None - birthstateprovince: Optional[str] = None + primary_position: Optional[Position] = Field(default=None, alias="primaryposition") + pitch_hand: Optional[PitchHand] = Field(default=None, alias="pitchhand") + bat_side: Optional[BatSide] = Field(default=None, alias="batside") + full_name: Optional[str] = Field(default=None, alias="fullname") + first_name: Optional[str] = Field(default=None, alias="firstname") + last_name: Optional[str] = Field(default=None, alias="lastname") + primary_number: Optional[str] = Field(default=None, alias="primarynumber") + birth_date: Optional[str] = Field(default=None, alias="birthdate") + current_team: Optional[Team] = Field(default=None, alias="currentteam") + current_age: Optional[int] = Field(default=None, alias="currentage") + birth_city: Optional[str] = Field(default=None, alias="birthcity") + birth_state_province: Optional[str] = Field(default=None, alias="birthstateprovince") height: Optional[str] = None weight: Optional[int] = None active: Optional[bool] = None - usename: Optional[str] = None - middlename: Optional[str] = None - boxscorename: Optional[str] = None + use_name: Optional[str] = Field(default=None, alias="usename") + middle_name: Optional[str] = Field(default=None, alias="middlename") + boxscore_name: Optional[str] = Field(default=None, alias="boxscorename") nickname: Optional[str] = None - draftyear: Optional[int] = None - mlbdebutdate: Optional[str] = None - namefirstlast: Optional[str] = None - nameslug: Optional[str] = None - firstlastname: Optional[str] = None - lastfirstname: Optional[str] = None - lastinitname: Optional[str] = None - initlastname: Optional[str] = None - fullfmlname: Optional[str] = None - fulllfmname: Optional[str] = None - birthcountry: Optional[str] = None + draft_year: Optional[int] = Field(default=None, alias="draftyear") + mlb_debut_date: Optional[str] = Field(default=None, alias="mlbdebutdate") + name_first_last: Optional[str] = Field(default=None, alias="namefirstlast") + name_slug: Optional[str] = Field(default=None, alias="nameslug") + first_last_name: Optional[str] = Field(default=None, alias="firstlastname") + last_first_name: Optional[str] = Field(default=None, alias="lastfirstname") + last_init_name: Optional[str] = Field(default=None, alias="lastinitname") + init_last_name: Optional[str] = Field(default=None, alias="initlastname") + full_fml_name: Optional[str] = Field(default=None, alias="fullfmlname") + full_lfm_name: Optional[str] = Field(default=None, alias="fulllfmname") + birth_country: Optional[str] = Field(default=None, alias="birthcountry") pronunciation: Optional[str] = None - strikezonetop: Optional[float] = None - strikezonebottom: Optional[float] = None - nametitle: Optional[str] = None + strike_zone_top: Optional[float] = Field(default=None, alias="strikezonetop") + strike_zone_bottom: Optional[float] = Field(default=None, alias="strikezonebottom") + name_title: Optional[str] = Field(default=None, alias="nametitle") gender: Optional[str] = None - isplayer: Optional[bool] = None - isverified: Optional[bool] = None - namematrilineal: Optional[str] = None - deathdate: Optional[str] = None - deathcity: Optional[str] = None - deathcountry: Optional[str] = None - deathstateprovince: Optional[str] = None - lastplayeddate: Optional[str] = None - uselastname: Optional[str] = None - namesuffix: Optional[str] = None - - def __post_init__(self): - self.primaryposition = Position(**self.primaryposition) if self.primaryposition else self.primaryposition - self.pitchhand = PitchHand(**self.pitchhand) if self.pitchhand else self.pitchhand - self.batside = BatSide(**self.batside) if self.batside else self.batside + is_player: Optional[bool] = Field(default=None, alias="isplayer") + is_verified: Optional[bool] = Field(default=None, alias="isverified") + name_matrilineal: Optional[str] = Field(default=None, alias="namematrilineal") + death_date: Optional[str] = Field(default=None, alias="deathdate") + death_city: Optional[str] = Field(default=None, alias="deathcity") + death_country: Optional[str] = Field(default=None, alias="deathcountry") + death_state_province: Optional[str] = Field(default=None, alias="deathstateprovince") + last_played_date: Optional[str] = Field(default=None, alias="lastplayeddate") + use_last_name: Optional[str] = Field(default=None, alias="uselastname") + name_suffix: Optional[str] = Field(default=None, alias="namesuffix") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(kw_only=True, repr=False) class Player(Person): """ A class to represent a Player. Attributes ---------- - jerseynumber : str - id number of the person - status : - Status of the player - parentteamid : int - parent team id + jersey_number : str + Jersey number of the player. + status : Status + Status of the player. + parent_team_id : int + Parent team ID. note : str - Optional str that allows a note to be added to a player. Such cases seen - include injury notes. + Optional note about the player (e.g., injury notes). + position : Position + Position of the player (alias for primary_position). """ - parentteamid: Optional[int] = None - jerseynumber: str - position: InitVar[dict] - status: Union[Status, dict] + jersey_number: str = Field(alias="jerseynumber") + status: Status + parent_team_id: Optional[int] = Field(default=None, alias="parentteamid") note: Optional[str] = None - def __post_init__(self, position: dict): - self.primaryposition = Position(**position) if position else None + @model_validator(mode='before') + @classmethod + def handle_position_alias(cls, data): + """Handle 'position' field as alias for 'primary_position'.""" + if isinstance(data, dict) and 'position' in data and 'primary_position' not in data: + data['primary_position'] = data.pop('position') + return data -@dataclass(kw_only=True, repr=False) class Coach(Person): """ - A class to represent a Player. + A class to represent a Coach. Attributes ---------- - jerseynumber : str - id number of the person + jersey_number : str + Jersey number of the coach. job : str - job of the coach - jobid : str - job id of the coach + Job of the coach. + job_id : str + Job ID of the coach. title : str - title of the coach - parentteamid : int + Title of the coach. """ - jerseynumber: str + jersey_number: str = Field(alias="jerseynumber") job: str - jobid: str + job_id: str = Field(alias="jobid") title: str -@dataclass(kw_only=True) + class Batter(Person): """ A class to represent a Batter. @@ -224,75 +216,68 @@ class Batter(Person): pass -@dataclass(kw_only=True) class Pitcher(Person): """ - A class to represent a Pitcher + A class to represent a Pitcher. """ pass -@dataclass(kw_only=True) class DraftPick(Person): """ - Represents a pick made in the MLB draft. + A class to represent a pick made in the MLB draft. Attributes ---------- - bisplayerid : int + bis_player_id : int The unique identifier of the player associated with this draft pick. - pickround : str + pick_round : str The round of the draft in which this pick was made. - picknumber : int + pick_number : int The number of the pick in the round. - roundpicknumber : int + round_pick_number : int The number of the pick overall in the draft. rank : int The rank of the player among all players eligible for the draft. - pickvalue : str + pick_value : str The value of the pick, if known. - signingbonus : str + signing_bonus : str The signing bonus associated with this pick, if known. home : Home Information about the player's home location. - scoutingreport : str - A scouting report on the player's abilities. + scouting_report : str + A scouting report on the player's abilities. school : School Information about the player's school or college. blurb : str - A brief summary of the player's background and accomplishments. - headshotlink : str - A link to a headshot image of the player. - team : Team or dict + A brief summary of the player's background and accomplishments. + headshot_link : str + A link to a headshot image of the player. + team : Team The team that made this draft pick. - drafttype : CodeDesc + draft_type : CodeDesc Information about the type of draft in which this pick was made. - isdrafted : bool + is_drafted : bool Whether or not the player associated with this pick has been drafted. - ispass : bool + is_pass : bool Whether or not the team passed on making a pick in this round. year : str The year in which the draft took place. """ - - bisplayerid: Optional[int] = None - pickround: str - picknumber: int - roundpicknumber: int + pick_round: str = Field(alias="pickround") + pick_number: int = Field(alias="picknumber") + round_pick_number: int = Field(alias="roundpicknumber") + home: Home + school: School + headshot_link: str = Field(alias="headshotlink") + team: Team + draft_type: CodeDesc = Field(alias="drafttype") + is_drafted: bool = Field(alias="isdrafted") + is_pass: bool = Field(alias="ispass") + year: str + bis_player_id: Optional[int] = Field(default=None, alias="bisplayerid") rank: Optional[int] = None - pickvalue: Optional[str] = None - signingbonus: Optional[str] = None - home: Union[Home , dict] - scoutingreport: Optional[str] = None - school: Union[School , dict] + pick_value: Optional[str] = Field(default=None, alias="pickvalue") + signing_bonus: Optional[str] = Field(default=None, alias="signingbonus") + scouting_report: Optional[str] = Field(default=None, alias="scoutingreport") blurb: Optional[str] = None - headshotlink: str - team: Union[Team, dict] - drafttype: Union[CodeDesc, dict] - isdrafted: bool - ispass: bool - year: str - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/tests/external_tests/person/test_person.py b/tests/external_tests/person/test_person.py index d279217..b75f5b3 100644 --- a/tests/external_tests/person/test_person.py +++ b/tests/external_tests/person/test_person.py @@ -1,4 +1,5 @@ -import unittest +import unittest +from pydantic import ValidationError from mlbstatsapi.models.people import Person, Position from mlbstatsapi import Mlb @@ -14,13 +15,14 @@ def tearDownClass(cls) -> None: pass def test_player_instance_type_error(self): - with self.assertRaises(TypeError): + """Pydantic raises ValidationError when required fields are missing.""" + with self.assertRaises(ValidationError): player = Person() def test_player_instance_position_arguments(self): self.assertEqual(self.player.id, 664034) self.assertIsInstance(self.player, Person) - self.assertEqual(self.player.fullname, "Ty France") + self.assertEqual(self.player.full_name, "Ty France") self.assertEqual(self.player.link, "/api/v1/people/664034") def test_get_persons(self): @@ -44,6 +46,7 @@ def test_get_persons(self): self.assertIsInstance(players_l[0], Person) self.assertIsInstance(players_s[0], Person) + class TestPersonPrimaryPosition(unittest.TestCase): @classmethod def setUpClass(cls) -> None: @@ -55,5 +58,5 @@ def tearDownClass(cls) -> None: pass def test_player_position_player_position(self): - self.assertIsInstance(self.position_player.primaryposition, Position) - self.assertTrue(hasattr(self.position_player.primaryposition, "code")) \ No newline at end of file + self.assertIsInstance(self.position_player.primary_position, Position) + self.assertTrue(hasattr(self.position_player.primary_position, "code")) From ca7482102d3aa7e83956473502f829b6ddb11168 Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 21:09:11 -0800 Subject: [PATCH 5/9] refactor: migrate game, gamepace, and homerunderby models to Pydantic Models converted: game/ (41 classes): - Game, MetaData, GameData, GameDataGame, GameDatetime, GameStatus - GameTeams, GameWeather, GameInfo, ReviewInfo, GameReview, GameFlags - GameProbablePitchers, MoundVisits, LiveData, GameDecisions, GameLeaders - Plays, Play, PlayAbout, PlayResult, PlayReviewDetails, PlayMatchup - PlayMatchupSplits, PlayEvent, PlayRunner, RunnerMovement, RunnerDetails - RunnerCredits, PlayByInning, PlayByInningHits, HitsByTeam, HitCoordinates - BoxScore, TopPerformer, BoxScoreTeam, BoxScoreTeams, BoxScoreOfficial - BoxScoreGameStatus, PlayersDictPerson, BoxScoreVL, BoxScoreTeamInfo - Linescore, LinescoreTeamScoring, LinescoreInning, LinescoreTeams - LinescoreOffense, LinescoreDefense gamepace/ (3 classes): - GamePace, GamePaceData, PrPortalCalculatedFields homerunderby/ (11 classes): - HomeRunDerby, Info, EventType, Status, Round, Matchup, Seed - Hits, HitData, Coordinates, TrajectoryData Key changes: - All fields use snake_case with aliases for API compatibility - Class names standardized to PascalCase (Gamepace -> GamePace, etc.) - Field validators handle empty dicts from API as None - Fixed type mismatches: TrajectoryData uses float, Coordinates optional - Updated mlb_api.py imports and return types - Updated all related tests to use new class/field names --- mlbstatsapi/mlb_api.py | 14 +- mlbstatsapi/models/game/__init__.py | 53 ++- mlbstatsapi/models/game/attributes.py | 25 +- mlbstatsapi/models/game/game.py | 50 +-- mlbstatsapi/models/game/gamedata/__init__.py | 14 +- .../models/game/gamedata/attributes.py | 292 ++++++------ mlbstatsapi/models/game/gamedata/gamedata.py | 152 +++---- mlbstatsapi/models/game/livedata/__init__.py | 38 ++ .../models/game/livedata/attributes.py | 56 +-- .../models/game/livedata/boxscore/__init__.py | 11 +- .../game/livedata/boxscore/attributes.py | 184 ++++---- .../models/game/livedata/boxscore/boxscore.py | 80 ++-- .../game/livedata/linescore/__init__.py | 7 + .../game/livedata/linescore/attributes.py | 250 +++++------ .../game/livedata/linescore/linescore.py | 91 ++-- mlbstatsapi/models/game/livedata/livedata.py | 54 +-- .../models/game/livedata/plays/__init__.py | 14 + .../game/livedata/plays/play/__init__.py | 4 + .../game/livedata/plays/play/attributes.py | 147 +++---- .../livedata/plays/play/matchup/__init__.py | 3 +- .../livedata/plays/play/matchup/attributes.py | 19 +- .../livedata/plays/play/matchup/matchup.py | 93 ++-- .../models/game/livedata/plays/play/play.py | 86 ++-- .../livedata/plays/play/playevent/__init__.py | 2 +- .../plays/play/playevent/playevent.py | 124 +++--- .../plays/play/playrunner/__init__.py | 1 + .../plays/play/playrunner/attributes.py | 138 +++--- .../plays/play/playrunner/playrunner.py | 29 +- .../livedata/plays/playbyinning/__init__.py | 1 + .../livedata/plays/playbyinning/attributes.py | 66 ++- .../plays/playbyinning/playbyinning.py | 33 +- .../models/game/livedata/plays/plays.py | 49 +-- mlbstatsapi/models/gamepace/__init__.py | 4 +- mlbstatsapi/models/gamepace/attributes.py | 290 ++++++------ mlbstatsapi/models/gamepace/gamepace.py | 37 +- mlbstatsapi/models/homerunderby/__init__.py | 15 +- mlbstatsapi/models/homerunderby/attributes.py | 416 ++++++++---------- .../models/homerunderby/homerunderby.py | 57 ++- tests/external_tests/game/test_game.py | 26 +- .../external_tests/gamepace/test_gamepace.py | 18 +- .../homerunderby/test_homerunderby.py | 22 +- .../mock_tests/gamepace/test_gamepace_mock.py | 16 +- .../homerunderby/test_homerunderby_mock.py | 14 +- 43 files changed, 1516 insertions(+), 1579 deletions(-) diff --git a/mlbstatsapi/mlb_api.py b/mlbstatsapi/mlb_api.py index 61d7b7b..ce70f54 100644 --- a/mlbstatsapi/mlb_api.py +++ b/mlbstatsapi/mlb_api.py @@ -16,8 +16,8 @@ from mlbstatsapi.models.seasons import Season from mlbstatsapi.models.drafts import Round from mlbstatsapi.models.awards import Award -from mlbstatsapi.models.gamepace import Gamepace -from mlbstatsapi.models.homerunderby import Homerunderby +from mlbstatsapi.models.gamepace import GamePace +from mlbstatsapi.models.homerunderby import HomeRunDerby from mlbstatsapi.models.standings import Standings from .mlb_dataadapter import MlbDataAdapter @@ -1099,7 +1099,7 @@ def get_game_ids(self, date: str = None, return game_ids - def get_gamepace(self, season: str, sport_id=1, **params) -> Union[Gamepace, None]: + def get_gamepace(self, season: str, sport_id=1, **params) -> Union[GamePace, None]: """ Get pace of game metrics for specific sport, league or team. @@ -1164,7 +1164,7 @@ def get_gamepace(self, season: str, sport_id=1, **params) -> Union[Gamepace, Non or 'leagues' in mlb_data.data and mlb_data.data['leagues'] or 'sports' in mlb_data.data and mlb_data.data['sports']): - return Gamepace(**mlb_data.data) + return GamePace(**mlb_data.data) def get_venue(self, venue_id: int, **params) -> Union[Venue, None]: """ @@ -2016,7 +2016,7 @@ def get_awards(self, award_id: str, **params) -> List[Award]: return awards_list - def get_homerun_derby(self, game_id, **params) -> Union[Homerunderby, None]: + def get_homerun_derby(self, game_id, **params) -> Union[HomeRunDerby, None]: """ The homerun derby endpoint on the Stats API allows for users to request information from the MLB database pertaining to the @@ -2036,7 +2036,7 @@ def get_homerun_derby(self, game_id, **params) -> Union[Homerunderby, None]: Returns ------- - Homerunderby object + HomeRunDerby object See Also -------- @@ -2049,7 +2049,7 @@ def get_homerun_derby(self, game_id, **params) -> Union[Homerunderby, None]: None if 'status' in mlb_data.data and mlb_data.data['status']: - return Homerunderby(**mlb_data.data) + return HomeRunDerby(**mlb_data.data) def get_team_stats(self, team_id: int, stats: list, groups: list, **params) -> dict: diff --git a/mlbstatsapi/models/game/__init__.py b/mlbstatsapi/models/game/__init__.py index 53426e3..9785541 100644 --- a/mlbstatsapi/models/game/__init__.py +++ b/mlbstatsapi/models/game/__init__.py @@ -1,5 +1,52 @@ from .game import Game from .attributes import MetaData -from .livedata.plays import Plays -from .livedata.linescore import Linescore -from .livedata.boxscore import BoxScore +from .gamedata import ( + GameData, + GameDataGame, + GameDatetime, + GameStatus, + GameTeams, + GameWeather, + GameInfo, + ReviewInfo, + GameReview, + GameFlags, + GameProbablePitchers, + MoundVisits, +) +from .livedata import ( + LiveData, + GameDecisions, + GameLeaders, + Plays, + Play, + PlayAbout, + PlayResult, + PlayReviewDetails, + PlayMatchup, + PlayMatchupSplits, + PlayEvent, + PlayRunner, + RunnerMovement, + RunnerDetails, + RunnerCredits, + PlayByInning, + PlayByInningHits, + HitsByTeam, + HitCoordinates, + BoxScore, + TopPerformer, + BoxScoreVL, + BoxScoreTeamInfo, + BoxScoreGameStatus, + PlayersDictPerson, + BoxScoreTeam, + BoxScoreTeams, + BoxScoreOfficial, + Linescore, + LinescoreTeamScoring, + LinescoreInning, + LinescoreTeams, + LinescoreOffense, + LinescoreDefense, +) diff --git a/mlbstatsapi/models/game/attributes.py b/mlbstatsapi/models/game/attributes.py index 9977e63..26e45fc 100644 --- a/mlbstatsapi/models/game/attributes.py +++ b/mlbstatsapi/models/game/attributes.py @@ -1,23 +1,24 @@ from typing import List -from dataclasses import dataclass +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass -class MetaData: + +class MetaData(MLBBaseModel): """ - A class to represent a Game's metaData. + A class to represent a Game's metadata. Attributes ---------- wait : int - No idea what this wait signifies + Wait value. timestamp : str - The timeStamp - gameevents : List[str] - Current game events for this game - logicalevents : List[str] - Current logical events for this game + The timestamp. + game_events : List[str] + Current game events for this game. + logical_events : List[str] + Current logical events for this game. """ wait: int timestamp: str - gameevents: List[str] - logicalevents: List[str] \ No newline at end of file + game_events: List[str] = Field(alias="gameevents") + logical_events: List[str] = Field(alias="logicalevents") diff --git a/mlbstatsapi/models/game/game.py b/mlbstatsapi/models/game/game.py index f44f608..08cb937 100644 --- a/mlbstatsapi/models/game/game.py +++ b/mlbstatsapi/models/game/game.py @@ -1,48 +1,34 @@ -from typing import Union, Optional -from dataclasses import dataclass - +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.game.gamedata import GameData from mlbstatsapi.models.game.livedata import LiveData - from .attributes import MetaData -@dataclass -class Game: +class Game(MLBBaseModel): """ A class to represent a Game. Attributes ---------- - gamepk : int - id number of this game + game_pk : int + ID number of this game. link : str - link to the api address for this game - copyright : str - MLB AM copyright information + Link to the API address for this game. metadata : MetaData - metaData of this game - gamedata : GameData - gameData of this game - livedata : LiveData - liveData of this game - - Methods - ------- - id(): - returns this games id + Metadata of this game. + game_data : GameData + Game data of this game. + live_data : LiveData + Live data of this game. """ - gamepk: int + game_pk: int = Field(alias="gamepk") link: str - metadata: Union[MetaData, dict] - gamedata: Union[GameData, dict] - livedata: Union[LiveData, dict] - - def __post_init__(self): - self.metadata = MetaData(**self.metadata) - self.gamedata = GameData(**self.gamedata) - self.livedata = LiveData(**self.livedata) + metadata: MetaData + game_data: GameData = Field(alias="gamedata") + live_data: LiveData = Field(alias="livedata") @property - def id(self): - return self.gamepk + def id(self) -> int: + """Returns this game's ID.""" + return self.game_pk diff --git a/mlbstatsapi/models/game/gamedata/__init__.py b/mlbstatsapi/models/game/gamedata/__init__.py index 34c840a..db2e2b7 100644 --- a/mlbstatsapi/models/game/gamedata/__init__.py +++ b/mlbstatsapi/models/game/gamedata/__init__.py @@ -1,2 +1,14 @@ from .gamedata import GameData -from .attributes import GameStatus, MoundVisits \ No newline at end of file +from .attributes import ( + GameDataGame, + GameDatetime, + GameStatus, + GameTeams, + GameWeather, + GameInfo, + ReviewInfo, + GameReview, + GameFlags, + GameProbablePitchers, + MoundVisits, +) diff --git a/mlbstatsapi/models/game/gamedata/attributes.py b/mlbstatsapi/models/game/gamedata/attributes.py index 9d4b32b..f3f81e1 100644 --- a/mlbstatsapi/models/game/gamedata/attributes.py +++ b/mlbstatsapi/models/game/gamedata/attributes.py @@ -1,273 +1,265 @@ -from typing import Optional, Union -from dataclasses import dataclass, field +from typing import Optional, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person from mlbstatsapi.models.teams import Team -@dataclass -class GameDataGame: +class GameDataGame(MLBBaseModel): """ - A class to represent the this game's game info. + A class to represent the game's game info. Attributes ---------- pk : int - This game's game id + This game's game ID. type : str - This game's game type code - doubleheader : str - Represents if this game is a double header or not + This game's game type code. + double_header : str + Represents if this game is a double header or not. id : str - An unknown Id - gamedaytype : str - This game's gameday type code + An unknown ID. + gameday_type : str + This game's gameday type code. tiebreaker : str - Is this game a tie breaker - gamenumber : int + Is this game a tie breaker. + game_number : int The game number for this game. If double header will be 2. - calendareventid : str - The id for this calendar event + calendar_event_id : str + The ID for this calendar event. season : str - This game's season year - seasondisplay : str - This game's displayed season + This game's season year. + season_display : str + This game's displayed season. """ pk: int type: str - doubleheader: str + double_header: str = Field(alias="doubleheader") id: str - gamedaytype: str + gameday_type: str = Field(alias="gamedaytype") tiebreaker: str - gamenumber: int - calendareventid: str + game_number: int = Field(alias="gamenumber") + calendar_event_id: str = Field(alias="calendareventid") season: str - seasondisplay: str + season_display: str = Field(alias="seasondisplay") -@dataclass(repr=False) -class GameDatetime: +class GameDatetime(MLBBaseModel): """ A class to represent the date time for this game. Attributes ---------- datetime : str - Date time for this game - originaldate : str - The original scheduled date for this game - officialdate : str - The current scheduled date for this game - daynight : str - The current lighting condition game type + Date time for this game. + original_date : str + The original scheduled date for this game. + official_date : str + The current scheduled date for this game. + day_night : str + The current lighting condition game type. time : str - The time + The time. ampm : str - The games am or pm code + The game's am or pm code. + resume_date : str + Resume date if applicable. + resume_datetime : str + Resume datetime if applicable. + resumed_from_date : str + Resumed from date if applicable. + resumed_from_datetime : str + Resumed from datetime if applicable. """ datetime: str - originaldate: str - officialdate: str - daynight: str + original_date: str = Field(alias="originaldate") + official_date: str = Field(alias="officialdate") + day_night: str = Field(alias="daynight") time: str ampm: str - resumedate: Optional[str] = None - resumedatetime: Optional[str] = None - resumedfromdate: Optional[str] = None - resumedfromdatetime: Optional[str] = None + resume_date: Optional[str] = Field(default=None, alias="resumedate") + resume_datetime: Optional[str] = Field(default=None, alias="resumedatetime") + resumed_from_date: Optional[str] = Field(default=None, alias="resumedfromdate") + resumed_from_datetime: Optional[str] = Field(default=None, alias="resumedfromdatetime") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass -class GameStatus: +class GameStatus(MLBBaseModel): """ A class to represent this game's game status. Attributes ---------- - abstractgamestate : str - The abstract game state - codedgamestate : str - The coded game state - detailedstate : str - The detailed game state - statuscode : str - Status code for this game - starttimetbd : bool - If the start time is TBD - abstractgamecode : str - The abstract game code + abstract_game_state : str + The abstract game state. + coded_game_state : str + The coded game state. + detailed_state : str + The detailed game state. + status_code : str + Status code for this game. + start_time_tbd : bool + If the start time is TBD. + abstract_game_code : str + The abstract game code. reason : str - reason for a state. Usually used for delays or cancellations + Reason for a state. Usually used for delays or cancellations. """ - abstractgamestate: str - codedgamestate: str - detailedstate: str - statuscode: str - starttimetbd: bool - abstractgamecode: str + abstract_game_state: str = Field(alias="abstractgamestate") + coded_game_state: str = Field(alias="codedgamestate") + detailed_state: str = Field(alias="detailedstate") + status_code: str = Field(alias="statuscode") + start_time_tbd: bool = Field(alias="starttimetbd") + abstract_game_code: str = Field(alias="abstractgamecode") reason: Optional[str] = None -@dataclass -class GameTeams: +class GameTeams(MLBBaseModel): """ A class to represent the home and away teams. Attributes ---------- away : Team - Away team + Away team. home : Team - Home team + Home team. """ - away: Union[Team, dict] - home: Union[Team, dict] + away: Team + home: Team - def __post_init__(self): - self.away = Team(**self.away) - self.home = Team(**self.home) - -@dataclass -class GameWeather: +class GameWeather(MLBBaseModel): """ A class to represent the weather for this game. Attributes ---------- condition : str - The weather condition + The weather condition. temp : str - The temperature in F + The temperature in F. wind : str - The wind in MPH and the direction + The wind in MPH and the direction. """ condition: str temp: str wind: Optional[str] = None -@dataclass -class GameInfo: +class GameInfo(MLBBaseModel): """ A class to represent the game info for this game. Attributes ---------- + first_pitch : str + The time of the first pitch. attendance : int - The attendance for this game - firstpitch : str - The time of the first pitch - gamedurationminutes : int - The duration of the game in minutes - delaydurationminutes : int - The length of delay for the game in minutes + The attendance for this game. + game_duration_minutes : int + The duration of the game in minutes. + delay_duration_minutes : int + The length of delay for the game in minutes. """ - firstpitch: str + first_pitch: str = Field(alias="firstpitch") attendance: Optional[int] = None - gamedurationminutes: Optional[int] = None - delaydurationminutes: Optional[int] = None + game_duration_minutes: Optional[int] = Field(default=None, alias="gamedurationminutes") + delay_duration_minutes: Optional[int] = Field(default=None, alias="delaydurationminutes") -@dataclass -class ReviewInfo: +class ReviewInfo(MLBBaseModel): """ - A class to represent reviewInfo for each team in this game. + A class to represent review info for each team in this game. Attributes ---------- used : int - How many challenges used + How many challenges used. remaining : int - How many challenges are remaining + How many challenges are remaining. """ used: int remaining: int -@dataclass -class GameReview: +class GameReview(MLBBaseModel): """ A class to represent the Game Reviews for this game. Attributes ---------- - haschallenges : bool - If their are challenges + has_challenges : bool + If there are challenges. away : ReviewInfo - Away team review info + Away team review info. home : ReviewInfo - Home team review info + Home team review info. """ - haschallenges: bool - away: Union[ReviewInfo, dict] - home: Union[ReviewInfo, dict] - - def __post_init__(self): - self.away = ReviewInfo(**self.away) - self.home = ReviewInfo(**self.home) + has_challenges: bool = Field(alias="haschallenges") + away: ReviewInfo + home: ReviewInfo -@dataclass -class GameFlags: +class GameFlags(MLBBaseModel): """ A class to represent the flags for this game. Attributes ---------- - nohitter : bool - If there is a no hitter in this game - perfectgame : bool - If there this game is a perfect game - awayteamnohitter : bool - If the away team has a no hitter - awayteamperfectgame : bool - If the away team has a perfect game - hometeamnohitter : bool - If the home team has a no hitter - hometeamperfectgame : bool - If the home team has a perfect game + no_hitter : bool + If there is a no hitter in this game. + perfect_game : bool + If this game is a perfect game. + away_team_no_hitter : bool + If the away team has a no hitter. + away_team_perfect_game : bool + If the away team has a perfect game. + home_team_no_hitter : bool + If the home team has a no hitter. + home_team_perfect_game : bool + If the home team has a perfect game. """ - nohitter: bool - perfectgame: bool - awayteamnohitter: bool - awayteamperfectgame: bool - hometeamnohitter: bool - hometeamperfectgame: bool + no_hitter: bool = Field(alias="nohitter") + perfect_game: bool = Field(alias="perfectgame") + away_team_no_hitter: bool = Field(alias="awayteamnohitter") + away_team_perfect_game: bool = Field(alias="awayteamperfectgame") + home_team_no_hitter: bool = Field(alias="hometeamnohitter") + home_team_perfect_game: bool = Field(alias="hometeamperfectgame") -@dataclass -class GameProbablePitchers: +class GameProbablePitchers(MLBBaseModel): """ A class to represent the home and away probable pitchers for this game. Attributes ---------- home : Person - Home team probable pitcher + Home team probable pitcher. away : Person - Away team probable pitcher + Away team probable pitcher. """ - away: Union[Person, dict] = field(default_factory=dict) - home: Union[Person, dict] = field(default_factory=dict) + away: Optional[Person] = None + home: Optional[Person] = None - def __post_init__(self): - self.away = Person(**self.away) if self.away else self.away - self.home = Person(**self.home) if self.home else self.home + @field_validator('away', 'home', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass -class MoundVisits: + +class MoundVisits(MLBBaseModel): """ - A class to represent the mound visits for a game + A class to represent the mound visits for a game. + Attributes ---------- - home : Person - Home team probable pitcher - away : Person - Away team probable pitcher + home : dict + Home team mound visits. + away : dict + Away team mound visits. """ - away: dict = field(default_factory=dict) - home: dict = field(default_factory=dict) - + away: dict = {} + home: dict = {} diff --git a/mlbstatsapi/models/game/gamedata/gamedata.py b/mlbstatsapi/models/game/gamedata/gamedata.py index e76e09e..71e3b14 100644 --- a/mlbstatsapi/models/game/gamedata/gamedata.py +++ b/mlbstatsapi/models/game/gamedata/gamedata.py @@ -1,96 +1,98 @@ -from typing import Optional, Union, List -from dataclasses import dataclass, field +from typing import Optional, List, Dict, Any +from pydantic import Field, field_validator, model_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.venues import Venue from mlbstatsapi.models.people import Person +from .attributes import ( + GameDataGame, + GameDatetime, + GameStatus, + GameTeams, + GameWeather, + GameInfo, + GameReview, + GameFlags, + GameProbablePitchers, + MoundVisits, +) -from .attributes import GameDataGame -from .attributes import GameDatetime -from .attributes import GameStatus -from .attributes import GameTeams -from .attributes import GameWeather -from .attributes import GameInfo -from .attributes import GameReview -from .attributes import GameFlags -from .attributes import GameProbablePitchers -from .attributes import MoundVisits -@dataclass(repr=False) -class GameData: +class GameData(MLBBaseModel): """ - A class to represent a games game data. + A class to represent a game's game data. Attributes ---------- game : GameDataGame - game information about this game + Game information about this game. datetime : GameDatetime - Time and dates for this game + Time and dates for this game. status : GameStatus - information on this game's status + Information on this game's status. teams : GameTeams - Our two teams for this game, home and away + Our two teams for this game, home and away. players : List[Person] - A list of all the players for this game + A list of all the players for this game. venue : Venue - Venue information for this game - officialvenue : Venue - The official venue for this game + Venue information for this game. + official_venue : Venue + The official venue for this game. weather : GameWeather The weather for this game. - gameinfo : GameInfo - information on this game + game_info : GameInfo + Information on this game. review : GameReview - Game review info and team challenges + Game review info and team challenges. flags : GameFlags - Flag bools for this game - alerts : List[] - Alerts - probablepitchers : GameProbablePitchers - Home and away probable pitchers for this game - officialscorer : Person - The official scorer for this game - primarydatacaster : Person - The official dataCaster for this game + Flag bools for this game. + alerts : List + Alerts. + probable_pitchers : GameProbablePitchers + Home and away probable pitchers for this game. + official_scorer : Person + The official scorer for this game. + primary_data_caster : Person + The official data caster for this game. + secondary_data_caster : Person + Secondary data caster for this game. + mound_visits : MoundVisits + Mound visits for this game. + abs_challenges : List[dict] + ABS challenges for this game. """ + game: GameDataGame + datetime: GameDatetime + status: GameStatus + teams: GameTeams + players: List[Person] = [] + venue: Venue + official_venue: Venue = Field(alias="officialvenue") + review: GameReview + flags: GameFlags + alerts: List = [] + probable_pitchers: GameProbablePitchers = Field(alias="probablepitchers") + mound_visits: Optional[MoundVisits] = Field(default=None, alias="moundvisits") + game_info: Optional[GameInfo] = Field(default=None, alias="gameinfo") + weather: Optional[GameWeather] = None + official_scorer: Optional[Person] = Field(default=None, alias="officialscorer") + primary_data_caster: Optional[Person] = Field(default=None, alias="primarydatacaster") + secondary_data_caster: Optional[Person] = Field(default=None, alias="secondarydatacaster") + abs_challenges: Optional[List[dict]] = Field(default=None, alias="abschallenges") - game: Union[GameDataGame, dict] - datetime: Union[GameDatetime, dict] - status: Union[GameStatus, dict] - teams: Union[GameTeams, dict] - players: Union[List[Person], dict] - venue: Union[Venue, dict] - officialvenue: Union[Venue, dict] - review: Union[GameReview, dict] - flags: Union[GameFlags, dict] - alerts: List - probablepitchers: Union[GameProbablePitchers, dict] - moundvisits: Optional[Union[MoundVisits, dict]] = None - gameinfo: Union[GameInfo, dict] = field(default_factory=dict) - weather: Union[GameWeather, dict] = field(default_factory=dict) - officialscorer: Optional[Union[Person, dict]] = field(default_factory=dict) - primarydatacaster: Optional[Union[Person, dict]] = field(default_factory=dict) - secondarydatacaster: Optional[Union[Person, dict]] = field(default_factory=dict) - abschallenges: Optional[Union[List[dict], dict]] = field(default_factory=dict) - - def __post_init__(self): - self.game = GameDataGame(**self.game) - self.datetime = GameDatetime(**self.datetime) - self.status = GameStatus(**self.status) - self.teams = GameTeams(**self.teams) - self.players = [Person(**(self.players[key])) for key in self.players] - self.venue = Venue(**self.venue) - self.officialvenue = Venue(**self.officialvenue) - self.weather = GameWeather(**self.weather) if self.weather else self.weather - self.gameinfo = GameInfo(**self.gameinfo) if self.gameinfo else self.gameinfo - self.review = GameReview(**self.review) - self.flags = GameFlags(**self.flags) - self.probablepitchers = GameProbablePitchers(**self.probablepitchers) - self.officialscorer = Person(**self.officialscorer) if self.officialscorer else self.officialscorer - self.primarydatacaster = Person(**self.primarydatacaster) if self.primarydatacaster else self.primarydatacaster - self.secondarydatacaster = Person(**self.secondarydatacaster) if self.secondarydatacaster else self.secondarydatacaster - self.moundvisits = MoundVisits(**self.moundvisits) if self.moundvisits else self.moundvisits - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) + @model_validator(mode='before') + @classmethod + def handle_players_dict(cls, data: Dict[str, Any]) -> Dict[str, Any]: + """Convert players dict to list of Person objects.""" + if isinstance(data, dict) and 'players' in data: + players_data = data['players'] + if isinstance(players_data, dict): + data['players'] = list(players_data.values()) + return data + @field_validator('weather', 'game_info', 'official_scorer', 'primary_data_caster', 'secondary_data_caster', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/game/livedata/__init__.py b/mlbstatsapi/models/game/livedata/__init__.py index b67d751..6c206e6 100644 --- a/mlbstatsapi/models/game/livedata/__init__.py +++ b/mlbstatsapi/models/game/livedata/__init__.py @@ -1 +1,39 @@ from .livedata import LiveData +from .attributes import GameDecisions, GameLeaders +from .plays import ( + Plays, + Play, + PlayAbout, + PlayResult, + PlayReviewDetails, + PlayMatchup, + PlayMatchupSplits, + PlayEvent, + PlayRunner, + RunnerMovement, + RunnerDetails, + RunnerCredits, + PlayByInning, + PlayByInningHits, + HitsByTeam, + HitCoordinates, +) +from .boxscore import ( + BoxScore, + TopPerformer, + BoxScoreVL, + BoxScoreTeamInfo, + BoxScoreGameStatus, + PlayersDictPerson, + BoxScoreTeam, + BoxScoreTeams, + BoxScoreOfficial, +) +from .linescore import ( + Linescore, + LinescoreTeamScoring, + LinescoreInning, + LinescoreTeams, + LinescoreOffense, + LinescoreDefense, +) diff --git a/mlbstatsapi/models/game/livedata/attributes.py b/mlbstatsapi/models/game/livedata/attributes.py index 3bb038b..88c22d3 100644 --- a/mlbstatsapi/models/game/livedata/attributes.py +++ b/mlbstatsapi/models/game/livedata/attributes.py @@ -1,51 +1,41 @@ -from typing import Union, Optional -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person -@dataclass(repr=False) -class GameDecisions: +class GameDecisions(MLBBaseModel): """ - A class to represent the winning and loosing pitcher for this game. + A class to represent the winning and losing pitcher for this game. Only used when a game is over. Attributes ---------- winner : Person - The winning person + The winning pitcher. loser : Person - The loosing person + The losing pitcher. + save : Person + The save pitcher (if applicable). """ - winner: Union[Person, dict] - loser: Union[Person, dict] - save: Optional[Union[Person, dict]] = None + winner: Person + loser: Person + save: Optional[Person] = None - def __post_init__(self): - self.winner = Person(**self.winner) - self.loser = Person(**self.loser) - self.save = Person(**self.save) if self.save else self.save - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass -class GameLeaders: +class GameLeaders(MLBBaseModel): """ - A class to represent this games live data leaders. - Not sure what this data looks like since every game ive seen - has an empty dict for each of these. + A class to represent this game's live data leaders. Attributes ---------- - hitdistance : dict - hit distance - hitspeed : dict - hit speed - pitchspeed : dict - pitch speed + hit_distance : dict + Hit distance leaders. + hit_speed : dict + Hit speed leaders. + pitch_speed : dict + Pitch speed leaders. """ - # Dont know what this populated looks like. Every game ive seen its three empty dicts? - hitdistance: dict - hitspeed: dict - pitchspeed: dict \ No newline at end of file + hit_distance: dict = Field(alias="hitdistance") + hit_speed: dict = Field(alias="hitspeed") + pitch_speed: dict = Field(alias="pitchspeed") diff --git a/mlbstatsapi/models/game/livedata/boxscore/__init__.py b/mlbstatsapi/models/game/livedata/boxscore/__init__.py index cc041c3..9513019 100644 --- a/mlbstatsapi/models/game/livedata/boxscore/__init__.py +++ b/mlbstatsapi/models/game/livedata/boxscore/__init__.py @@ -1 +1,10 @@ -from .boxscore import BoxScore +from .boxscore import BoxScore, TopPerformer +from .attributes import ( + BoxScoreVL, + BoxScoreTeamInfo, + BoxScoreGameStatus, + PlayersDictPerson, + BoxScoreTeam, + BoxScoreTeams, + BoxScoreOfficial, +) diff --git a/mlbstatsapi/models/game/livedata/boxscore/attributes.py b/mlbstatsapi/models/game/livedata/boxscore/attributes.py index 3ef1500..0806d7f 100644 --- a/mlbstatsapi/models/game/livedata/boxscore/attributes.py +++ b/mlbstatsapi/models/game/livedata/boxscore/attributes.py @@ -1,68 +1,63 @@ -from typing import Union, List, Optional -from dataclasses import dataclass, field +from typing import List, Optional, Dict, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person, Position from mlbstatsapi.models.teams import Team from mlbstatsapi.models.data import CodeDesc -@dataclass -class BoxScoreVL: +class BoxScoreVL(MLBBaseModel): """ - A class to represent a boxscore team's infos label and value + A class to represent a boxscore team's info label and value. Attributes ---------- label : str - The label for this peice of info + The label for this piece of info. value : str - The info associated with this label + The info associated with this label. """ label: str - value: str = None + value: Optional[str] = None -@dataclass -class BoxScoreTeamInfo: +class BoxScoreTeamInfo(MLBBaseModel): """ - A class to represent a boxscore team's info + A class to represent a boxscore team's info. Attributes ---------- title : str - Type of information - fieldlist : List[BoxScoreVL] - List holding the info for this info type + Type of information. + field_list : List[BoxScoreVL] + List holding the info for this info type. """ title: str - fieldlist: Union[List[BoxScoreVL], List[dict]] + field_list: List[BoxScoreVL] = Field(alias="fieldlist") - def __post_init__(self): - self.fieldlist = [BoxScoreVL(**fieldlists) for fieldlists in self.fieldlist] - -@dataclass -class GameStatus: +class BoxScoreGameStatus(MLBBaseModel): """ A class representing the game status of a player. Attributes ---------- - iscurrentbatter : bool + is_current_batter : bool Whether the player is the current batter. - iscurrentpitcher : bool + is_current_pitcher : bool Whether the player is the current pitcher. - isonbench : bool + is_on_bench : bool Whether the player is on the bench. - issubstitute : bool + is_substitute : bool Whether the player is a substitute. """ - iscurrentbatter: bool - iscurrentpitcher: bool - isonbench: bool - issubstitute: bool + is_current_batter: bool = Field(alias="iscurrentbatter") + is_current_pitcher: bool = Field(alias="iscurrentpitcher") + is_on_bench: bool = Field(alias="isonbench") + is_substitute: bool = Field(alias="issubstitute") + -@dataclass -class PlayersDictPerson: +class PlayersDictPerson(MLBBaseModel): """ A class representing a person in a dictionary of players. @@ -70,122 +65,101 @@ class PlayersDictPerson: ---------- person : Person The person object. - jerseynumber : str + jersey_number : str The person's jersey number. position : Position The person's position. status : CodeDesc The person's status. - parentteamid : int + parent_team_id : int The ID of the person's parent team. stats : dict A dictionary of the person's stats. - seasonstats : dict + season_stats : dict A dictionary of the person's season stats. - gameStatus : GameStatus + game_status : BoxScoreGameStatus The person's game status. - battingorder : int - The persons place in the batting order if avaliable. - allpositions : Position - All of the person's positions if avaliable. + batting_order : int + The person's place in the batting order if available. + all_positions : List[Position] + All of the person's positions if available. """ - person: Union[Person, dict] - status: Union[CodeDesc, dict] + person: Person + status: CodeDesc stats: dict - seasonstats: dict - gamestatus: Union[GameStatus, dict] - position: Optional[Union[Position, dict]] = None - battingorder: Optional[int] = None - jerseynumber: Optional[str] = None - parentteamid: Optional[int] = None - allpositions: Optional[Union[List[Position], List[dict]]] = None - - def __post_init__(self): - self.person = Person(**self.person) - self.position = Position(**self.position) if self.position else self.position - self.status = CodeDesc(**self.status) - self.gamestatus = GameStatus(**self.gamestatus) - self.allpositions = [Position(**allposition) for allposition in self.allpositions] if self.allpositions else self.allpositions - -@dataclass -class BoxScoreTeam: + season_stats: dict = Field(alias="seasonstats") + game_status: BoxScoreGameStatus = Field(alias="gamestatus") + position: Optional[Position] = None + batting_order: Optional[int] = Field(default=None, alias="battingorder") + jersey_number: Optional[str] = Field(default=None, alias="jerseynumber") + parent_team_id: Optional[int] = Field(default=None, alias="parentteamid") + all_positions: Optional[List[Position]] = Field(default=None, alias="allpositions") + + +class BoxScoreTeam(MLBBaseModel): """ - A class to represent the boxscore team + A class to represent the boxscore team. Attributes ---------- team : Team - This team - teamstats : Dict - Team stats - players : Dict - Players on team + This team. + team_stats : dict + Team stats. + players : dict + Players on team. batters : List[int] - List of batters playerid for this team + List of batter player IDs for this team. pitchers : List[int] - List of pitcher playerid for this team + List of pitcher player IDs for this team. bench : List[int] - List of bench playerid for this team + List of bench player IDs for this team. bullpen : List[int] - Bullpen list of playerid - battingorder : List[int] - Batting order for this team as a list of playerid + Bullpen list of player IDs. + batting_order : List[int] + Batting order for this team as a list of player IDs. info : List[BoxScoreTeamInfo] - Batting and fielding info for team - note : List[str] - Team notes + Batting and fielding info for team. + note : List[BoxScoreVL] + Team notes. """ - team: Union[Team, dict] - teamstats: dict - players: dict + team: Team + team_stats: dict = Field(alias="teamstats") + players: Dict[str, PlayersDictPerson] batters: List[int] pitchers: List[int] bench: List[int] bullpen: List[int] - battingorder: List[int] - info: Union[List[BoxScoreTeamInfo], List[dict]] - note: List[str] - - def __post_init__(self): - self.team = Team(**self.team) - self.info = [BoxScoreTeamInfo(**infos) for infos in self.info] + batting_order: List[int] = Field(alias="battingorder") + info: List[BoxScoreTeamInfo] + note: List[BoxScoreVL] = [] - for player in self.players: - self.players[player] = PlayersDictPerson(**self.players[player]) -@dataclass -class BoxScoreTeams: +class BoxScoreTeams(MLBBaseModel): """ - A class to represent the boxscore home and away teams + A class to represent the boxscore home and away teams. Attributes ---------- home : BoxScoreTeam - Home team boxscore information + Home team boxscore information. away : BoxScoreTeam - Away team boxscore information + Away team boxscore information. """ - home: Union[BoxScoreTeam, dict] - away: Union[BoxScoreTeam, dict] + home: BoxScoreTeam + away: BoxScoreTeam - def __post_init__(self): - self.home = BoxScoreTeam(**self.home) - self.away = BoxScoreTeam(**self.away) -@dataclass -class BoxScoreOffical: +class BoxScoreOfficial(MLBBaseModel): """ - A class to represent an official for this game + A class to represent an official for this game. Attributes ---------- official : Person - The official person - officialtype : str - What type of official this person is + The official person. + official_type : str + What type of official this person is. """ - official: Union[Person, dict] - officialtype: str - - def __post_init__(self): - self.official = Person(**self.official) + official: Person + official_type: str = Field(alias="officialtype") diff --git a/mlbstatsapi/models/game/livedata/boxscore/boxscore.py b/mlbstatsapi/models/game/livedata/boxscore/boxscore.py index 3838c5c..c906852 100644 --- a/mlbstatsapi/models/game/livedata/boxscore/boxscore.py +++ b/mlbstatsapi/models/game/livedata/boxscore/boxscore.py @@ -1,62 +1,52 @@ -from typing import Union, List, Any, Optional -from dataclasses import dataclass, field +from typing import List, Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel +from .attributes import BoxScoreTeams, BoxScoreOfficial, BoxScoreVL, PlayersDictPerson -from .attributes import BoxScoreTeams, BoxScoreOffical, BoxScoreVL, PlayersDictPerson - - -@dataclass -class TopPerformer: +class TopPerformer(MLBBaseModel): """ - A class to represent this games topperformer + A class to represent this game's top performer. Attributes ---------- - player : Player - Player + player : PlayersDictPerson + Player. type : str - The officials for this game - gamescore : int - gamescore - hittinggamescore : int - hitting game score + The type of top performer. + game_score : int + Game score. + hitting_game_score : int + Hitting game score. + pitching_game_score : int + Pitching game score. """ - player: Union[PlayersDictPerson, dict] + player: PlayersDictPerson type: str - gamescore: int - hittinggamescore: Optional[int] = None - pitchinggamescore: Optional[int] = None - - def __post_init__(self): - self.player = PlayersDictPerson(**self.player) + game_score: int = Field(alias="gamescore") + hitting_game_score: Optional[int] = Field(default=None, alias="hittinggamescore") + pitching_game_score: Optional[int] = Field(default=None, alias="pitchinggamescore") -@dataclass -class BoxScore: + +class BoxScore(MLBBaseModel): """ - A class to represent this games boxscore + A class to represent this game's boxscore. Attributes ---------- teams : BoxScoreTeams - Box score data for each team - officials : List[BoxScoreOffical] - The officials for this game + Box score data for each team. + officials : List[BoxScoreOfficial] + The officials for this game. info : List[BoxScoreVL] - Box score information - pitchingnotes : List[str] - Pitching notes for this game + Box score information. + pitching_notes : List[str] + Pitching notes for this game. + top_performers : List[TopPerformer] + Top performers for this game. """ - - teams: Union[BoxScoreTeams, dict] - officials: Union[List[BoxScoreOffical], List[dict]] - info: Union[List[BoxScoreVL], List[dict]] - pitchingnotes: List[str] - topperformers: Optional[List[Union[TopPerformer, dict]]] = field(default_factory=list) - - def __post_init__(self): - self.teams = BoxScoreTeams(**self.teams) - self.officials = [BoxScoreOffical(**official) for official in self.officials] - self.info = [BoxScoreVL(**infos) for infos in self.info] - self.topperformers = [TopPerformer(**topperformer) for topperformer in self.topperformers] - - + teams: BoxScoreTeams + officials: List[BoxScoreOfficial] = [] + info: List[BoxScoreVL] = [] + pitching_notes: List[str] = Field(default=[], alias="pitchingnotes") + top_performers: List[TopPerformer] = Field(default=[], alias="topperformers") diff --git a/mlbstatsapi/models/game/livedata/linescore/__init__.py b/mlbstatsapi/models/game/livedata/linescore/__init__.py index 09b4b43..b13122c 100644 --- a/mlbstatsapi/models/game/livedata/linescore/__init__.py +++ b/mlbstatsapi/models/game/livedata/linescore/__init__.py @@ -1 +1,8 @@ from .linescore import Linescore +from .attributes import ( + LinescoreTeamScoring, + LinescoreInning, + LinescoreTeams, + LinescoreOffense, + LinescoreDefense, +) diff --git a/mlbstatsapi/models/game/livedata/linescore/attributes.py b/mlbstatsapi/models/game/livedata/linescore/attributes.py index 71bf2f2..429a3b9 100644 --- a/mlbstatsapi/models/game/livedata/linescore/attributes.py +++ b/mlbstatsapi/models/game/livedata/linescore/attributes.py @@ -1,184 +1,188 @@ -from typing import Union, Optional -from dataclasses import dataclass, field +from typing import Optional, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person from mlbstatsapi.models.teams import Team -@dataclass -class LinescoreTeamScoreing: + +class LinescoreTeamScoring(MLBBaseModel): """ - A class to represent a games Linescore + A class to represent a games linescore team scoring. Attributes ---------- hits : int - Team hits for this inning + Team hits for this inning. errors : int - Team errors for this inning - leftonbase : int - Player left on base for this inning + Team errors for this inning. + left_on_base : int + Players left on base for this inning. runs : int - Team runs for this inning - iswinner : bool - If team is winner + Team runs for this inning. + is_winner : bool + If team is winner. """ hits: int errors: int - leftonbase: int + left_on_base: int = Field(alias="leftonbase") runs: Optional[int] = None - iswinner: Optional[bool] = None + is_winner: Optional[bool] = Field(default=None, alias="iswinner") + -@dataclass -class LinescoreInning: +class LinescoreInning(MLBBaseModel): """ - A class to represent a inning for a games Linescore + A class to represent an inning for a game's linescore. Attributes ---------- num : int - Inning number - ordinalnum : str - Inning ordinal - home : LinescoreTeamScoreing - Home team inning info - away : LinescoreTeamScoreing - Away team inning info + Inning number. + ordinal_num : str + Inning ordinal. + home : LinescoreTeamScoring + Home team inning info. + away : LinescoreTeamScoring + Away team inning info. """ num: int - ordinalnum: str - home: Union[LinescoreTeamScoreing, dict] - away: Union[LinescoreTeamScoreing, dict] + ordinal_num: str = Field(alias="ordinalnum") + home: Optional[LinescoreTeamScoring] = None + away: Optional[LinescoreTeamScoring] = None - def __post_init__(self): - self.home = LinescoreTeamScoreing(**self.home) if self.home else self.home - self.away = LinescoreTeamScoreing(**self.away) if self.away else self.away + @field_validator('home', 'away', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass -class LinescoreTeams: + +class LinescoreTeams(MLBBaseModel): """ - A class to represent home and away teams in the linescore + A class to represent home and away teams in the linescore. Attributes ---------- - home : LinescoreTeamScoreing - Home team current inning info - away : LinescoreTeamScoreing - Away team current inning info + home : LinescoreTeamScoring + Home team current inning info. + away : LinescoreTeamScoring + Away team current inning info. """ - home: Union[LinescoreTeamScoreing, dict] = field(default_factory=dict) - away: Union[LinescoreTeamScoreing, dict] = field(default_factory=dict) + home: Optional[LinescoreTeamScoring] = None + away: Optional[LinescoreTeamScoring] = None + + @field_validator('home', 'away', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - def __post_init__(self): - self.home = LinescoreTeamScoreing(**self.home) if self.home else self.home - self.away = LinescoreTeamScoreing(**self.away) if self.away else self.away -@dataclass(repr=False) -class LinescoreOffense: +class LinescoreOffense(MLBBaseModel): """ - A class to represent a games current offense + A class to represent a game's current offense. Attributes ---------- batter : Person - Current batter - ondeck : Person - Current on deck batter - inhole : Person - Current in the hole batter + Current batter. + on_deck : Person + Current on deck batter. + in_hole : Person + Current in the hole batter. pitcher : Person - Who is this teams pitcher - battingorder : int - Number in the batting order + Who is this team's pitcher. + batting_order : int + Number in the batting order. team : Team - The team currently on offense + The team currently on offense. + first : str + Runner on first (if any). + second : str + Runner on second (if any). + third : str + Runner on third (if any). """ - team: Union[Team, dict] - batter: Optional[Union[Person, dict]] = field(default_factory=dict) - ondeck: Optional[Union[Person, dict]] = field(default_factory=dict) - inhole: Optional[Union[Person, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Person, dict]] = field(default_factory=dict) - battingorder: Optional[int] = None + team: Team + batter: Optional[Person] = None + on_deck: Optional[Person] = Field(default=None, alias="ondeck") + in_hole: Optional[Person] = Field(default=None, alias="inhole") + pitcher: Optional[Person] = None + batting_order: Optional[int] = Field(default=None, alias="battingorder") first: Optional[str] = None second: Optional[str] = None third: Optional[str] = None - def __post_init__(self): - self.batter = Person(**self.batter) if self.batter else self.batter - self.ondeck = Person(**self.ondeck) if self.ondeck else self.ondeck - self.inhole = Person(**self.inhole) if self.inhole else self.inhole - self.pitcher = Person(**self.pitcher) if self.pitcher else self.pitcher - self.team = Team(**self.team) + @field_validator('batter', 'on_deck', 'in_hole', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class LinescoreDefense: +class LinescoreDefense(MLBBaseModel): """ - A class to represent a games current defense + A class to represent a game's current defense. Attributes ---------- pitcher : Person - Current pitcher + Current pitcher. catcher : Person - Current catcher + Current catcher. first : Person - Current first + Current first baseman. second : Person - Current second + Current second baseman. third : Person - Current third + Current third baseman. shortstop : Person - Current shortstop + Current shortstop. left : Person - Current left + Current left fielder. center : Person - Current center + Current center fielder. right : Person - Current right + Current right fielder. batter : Person - The next batter when this team switches to offense - ondeck : Person - The next ondeck batter when this team switches to offense - inhole : Person - The next inHole batter when this team switches to offense - battingorder : int - Number this team is in the batting order + The next batter when this team switches to offense. + on_deck : Person + The next on deck batter when this team switches to offense. + in_hole : Person + The next in hole batter when this team switches to offense. + batting_order : int + Number this team is in the batting order. team : Team - The team that is playing defense currently + The team that is playing defense currently. """ - team: Union[Team, dict] - pitcher: Optional[Union[Person, dict]] = field(default_factory=dict) - catcher: Optional[Union[Person, dict]] = field(default_factory=dict) - first: Optional[Union[Person, dict]] = field(default_factory=dict) - second: Optional[Union[Person, dict]] = field(default_factory=dict) - third: Optional[Union[Person, dict]] = field(default_factory=dict) - shortstop: Optional[Union[Person, dict]] = field(default_factory=dict) - left: Optional[Union[Person, dict]] = field(default_factory=dict) - center: Optional[Union[Person, dict]] = field(default_factory=dict) - right: Optional[Union[Person, dict]] = field(default_factory=dict) - batter: Optional[Union[Person, dict]] = field(default_factory=dict) - ondeck: Optional[Union[Person, dict]] = field(default_factory=dict) - inhole: Optional[Union[Person, dict]] = field(default_factory=dict) - battingorder: int = None - - - def __post_init__(self): - self.pitcher = Person(**self.pitcher) if self.pitcher else self.pitcher - self.catcher = Person(**self.catcher) if self.catcher else self.catcher - self.first = Person(**self.first) if self.first else self.first - self.second = Person(**self.second) if self.second else self.second - self.third = Person(**self.third) if self.third else self.third - self.shortstop = Person(**self.shortstop) if self.shortstop else self.shortstop - self.left = Person(**self.left) if self.left else self.left - self.center = Person(**self.center) if self.center else self.center - self.right = Person(**self.right) if self.right else self.right - self.batter = Person(**self.batter) if self.batter else self.batter - self.ondeck = Person(**self.ondeck) if self.ondeck else self.ondeck - self.inhole = Person(**self.inhole) if self.inhole else self.inhole - self.team = Team(**self.team) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + team: Team + pitcher: Optional[Person] = None + catcher: Optional[Person] = None + first: Optional[Person] = None + second: Optional[Person] = None + third: Optional[Person] = None + shortstop: Optional[Person] = None + left: Optional[Person] = None + center: Optional[Person] = None + right: Optional[Person] = None + batter: Optional[Person] = None + on_deck: Optional[Person] = Field(default=None, alias="ondeck") + in_hole: Optional[Person] = Field(default=None, alias="inhole") + batting_order: Optional[int] = Field(default=None, alias="battingorder") + + @field_validator( + 'pitcher', 'catcher', 'first', 'second', 'third', 'shortstop', + 'left', 'center', 'right', 'batter', 'on_deck', 'in_hole', + mode='before' + ) + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/game/livedata/linescore/linescore.py b/mlbstatsapi/models/game/livedata/linescore/linescore.py index c89338d..fba9dc3 100644 --- a/mlbstatsapi/models/game/livedata/linescore/linescore.py +++ b/mlbstatsapi/models/game/livedata/linescore/linescore.py @@ -1,67 +1,60 @@ -from typing import Union, List, Optional -from dataclasses import dataclass +from typing import List, Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel +from .attributes import ( + LinescoreInning, + LinescoreTeams, + LinescoreDefense, + LinescoreOffense, +) -from .attributes import LinescoreInning -from .attributes import LinescoreTeams -from .attributes import LinescoreDefense -from .attributes import LinescoreOffense -@dataclass(repr=False) -class Linescore: +class Linescore(MLBBaseModel): """ - A class to represent a games Linescore + A class to represent a game's linescore. Attributes ---------- - currentinning : int - The games current inning - currentinningordinal : str - This innings ordinal - inningstate : str - What state this inning is in - inninghalf : str - WHich half of the inning are we in - istopinning : bool - Is this the top of the inning - scheduledinnings : int - How many innings are scheduled for this game + current_inning : int + The game's current inning. + current_inning_ordinal : str + This inning's ordinal. + inning_state : str + What state this inning is in. + inning_half : str + Which half of the inning we are in. + is_top_inning : bool + Is this the top of the inning. + scheduled_innings : int + How many innings are scheduled for this game. innings : List[LinescoreInning] - Data on each inning + Data on each inning. teams : LinescoreTeams - Line score data on our teams + Line score data on our teams. defense : LinescoreDefense - Current defense + Current defense. offense : LinescoreOffense - Current offense + Current offense. balls : int - current count balls + Current count balls. strikes : int - current count strikes + Current count strikes. outs : int - current count outs + Current count outs. + note : str + Any note for the linescore. """ - - scheduledinnings: int - innings: Union[List[LinescoreInning], List[dict]] - teams: Union[LinescoreTeams, dict] - defense: Union[LinescoreDefense, dict] - offense: Union[LinescoreOffense, dict] + scheduled_innings: int = Field(alias="scheduledinnings") + innings: List[LinescoreInning] = [] + teams: LinescoreTeams + defense: LinescoreDefense + offense: LinescoreOffense balls: Optional[int] = None strikes: Optional[int] = None outs: Optional[int] = None note: Optional[str] = None - currentinning: Optional[int] = None - currentinningordinal: Optional[str] = None - inningstate: Optional[str] = None - inninghalf: Optional[str] = None - istopinning: Optional[bool] = None - - def __post_init__(self): - self.innings = [LinescoreInning(**inning) for inning in self.innings] - self.teams = LinescoreTeams(**self.teams) - self.defense = LinescoreDefense(**self.defense) - self.offense = LinescoreOffense(**self.offense) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + current_inning: Optional[int] = Field(default=None, alias="currentinning") + current_inning_ordinal: Optional[str] = Field(default=None, alias="currentinningordinal") + inning_state: Optional[str] = Field(default=None, alias="inningstate") + inning_half: Optional[str] = Field(default=None, alias="inninghalf") + is_top_inning: Optional[bool] = Field(default=None, alias="istopinning") diff --git a/mlbstatsapi/models/game/livedata/livedata.py b/mlbstatsapi/models/game/livedata/livedata.py index 8f88368..94f2ece 100644 --- a/mlbstatsapi/models/game/livedata/livedata.py +++ b/mlbstatsapi/models/game/livedata/livedata.py @@ -1,45 +1,39 @@ -from typing import Union, Optional -from dataclasses import dataclass, field - +from typing import Optional, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.game.livedata.plays import Plays from mlbstatsapi.models.game.livedata.linescore import Linescore from mlbstatsapi.models.game.livedata.boxscore import BoxScore - from .attributes import GameLeaders, GameDecisions -@dataclass(repr=False) -class LiveData: +class LiveData(MLBBaseModel): """ - A class to represent this games live data. + A class to represent this game's live data. Attributes ---------- plays : Plays - Has the plays for this game + Has the plays for this game. linescore : Linescore - This games linescore + This game's linescore. boxscore : BoxScore - This games boxscore + This game's boxscore. leaders : GameLeaders - The data leaders for this game - decisions : GameDecisions = None - Decisions for this game, Ie a winner or a loser + The data leaders for this game. + decisions : GameDecisions + Decisions for this game (i.e., winner or loser). """ - plays: Union[Plays, dict] - boxscore: Union[BoxScore, dict] - leaders: Union[GameLeaders, dict] - decisions: Optional[Union[GameDecisions, dict]] = field(default_factory=dict) - linescore: Union[Linescore, dict] = field(default_factory=dict) - - - def __post_init__(self): - self.plays = Plays(**self.plays) - self.linescore = Linescore(**self.linescore) if self.linescore else self.linescore - self.boxscore = BoxScore(**self.boxscore) - self.decisions = GameDecisions(**self.decisions) if self.decisions else self.decisions - self.leaders = GameLeaders(**self.leaders) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + plays: Plays + boxscore: BoxScore + leaders: GameLeaders + decisions: Optional[GameDecisions] = None + linescore: Optional[Linescore] = None + + @field_validator('decisions', 'linescore', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/game/livedata/plays/__init__.py b/mlbstatsapi/models/game/livedata/plays/__init__.py index fd58b65..9758654 100644 --- a/mlbstatsapi/models/game/livedata/plays/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/__init__.py @@ -1 +1,15 @@ from .plays import Plays +from .play import ( + Play, + PlayAbout, + PlayResult, + PlayReviewDetails, + PlayMatchup, + PlayMatchupSplits, + PlayEvent, + PlayRunner, + RunnerMovement, + RunnerDetails, + RunnerCredits, +) +from .playbyinning import PlayByInning, PlayByInningHits, HitsByTeam, HitCoordinates diff --git a/mlbstatsapi/models/game/livedata/plays/play/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/__init__.py index be2d3b7..ce09c19 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/play/__init__.py @@ -1 +1,5 @@ from .play import Play +from .attributes import PlayAbout, PlayResult, PlayReviewDetails +from .matchup import PlayMatchup, PlayMatchupSplits +from .playevent import PlayEvent +from .playrunner import PlayRunner, RunnerMovement, RunnerDetails, RunnerCredits diff --git a/mlbstatsapi/models/game/livedata/plays/play/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/attributes.py index a066f43..16cc5ac 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/attributes.py +++ b/mlbstatsapi/models/game/livedata/plays/play/attributes.py @@ -1,111 +1,102 @@ from typing import Optional -from dataclasses import dataclass +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass(repr=False) -class PlayAbout: + +class PlayAbout(MLBBaseModel): """ - A class to represent a information about a play. + A class to represent information about a play. Attributes ---------- - atbatindex : int - Current at bat index - halfinning : str - What side of the inning - istopinning : bool - Is this inning the top of the inning + at_bat_index : int + Current at bat index. + half_inning : str + What side of the inning. + is_top_inning : bool + Is this inning the top of the inning. inning : int - What number of inning we are in - starttime : str - The start time for this play - endtime : str - The end time for this play - iscomplete : bool - Is this play complete - isscoringplay : bool - is this play a scoring play - hasreview : bool - Dose this play have a review - hasout : bool - Does this play have a out - captivatingindex : int - What is the captivating index for this play + What number of inning we are in. + start_time : str + The start time for this play. + end_time : str + The end time for this play. + is_complete : bool + Is this play complete. + is_scoring_play : bool + Is this play a scoring play. + has_review : bool + Does this play have a review. + has_out : bool + Does this play have an out. + captivating_index : int + What is the captivating index for this play. """ - atbatindex: int - halfinning: str - istopinning: bool + at_bat_index: int = Field(alias="atbatindex") + half_inning: str = Field(alias="halfinning") + is_top_inning: bool = Field(alias="istopinning") inning: int - iscomplete: bool - isscoringplay: bool - hasout: bool - captivatingindex: int - endtime: Optional[str] = None - starttime: Optional[str] = None - hasreview: Optional[bool] = None + is_complete: bool = Field(alias="iscomplete") + is_scoring_play: bool = Field(alias="isscoringplay") + has_out: bool = Field(alias="hasout") + captivating_index: int = Field(alias="captivatingindex") + end_time: Optional[str] = Field(default=None, alias="endtime") + start_time: Optional[str] = Field(default=None, alias="starttime") + has_review: Optional[bool] = Field(default=None, alias="hasreview") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class PlayResult: +class PlayResult(MLBBaseModel): """ A class to represent a play result. Attributes ---------- type : str - Play result type + Play result type. event : str - Play event - eventtype : str - Event type + Play event. + event_type : str + Event type. description : str - Event description + Event description. rbi : int - Number of RBI's - awayscore : int - Score for away team - homescore : int - Score for home team - isout : bool - If the play was an out + Number of RBIs. + away_score : int + Score for away team. + home_score : int + Score for home team. + is_out : bool + If the play was an out. """ type: str - awayscore: int - homescore: int + away_score: int = Field(alias="awayscore") + home_score: int = Field(alias="homescore") rbi: Optional[int] = None event: Optional[str] = None - eventtype: Optional[str] = None + event_type: Optional[str] = Field(default=None, alias="eventtype") description: Optional[str] = None - isout: Optional[bool] = None + is_out: Optional[bool] = Field(default=None, alias="isout") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class PlayReviewDetails: +class PlayReviewDetails(MLBBaseModel): """ A class to represent play review details. Attributes ---------- - isoverturned : bool - Was it overturned - inprogress : bool - Is it in progress - reviewtype : str - What type of review - challengeteamid : int - The team issuing the challenge review + is_overturned : bool + Was it overturned. + in_progress : bool + Is it in progress. + review_type : str + What type of review. + challenge_team_id : int + The team issuing the challenge review. + additional_reviews : str + Additional reviews. """ - isoverturned: bool - inprogress: bool - reviewtype: str - challengeteamid: Optional[int] = None - additionalreviews: Optional[str] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) + is_overturned: bool = Field(alias="isoverturned") + in_progress: bool = Field(alias="inprogress") + review_type: str = Field(alias="reviewtype") + challenge_team_id: Optional[int] = Field(default=None, alias="challengeteamid") + additional_reviews: Optional[str] = Field(default=None, alias="additionalreviews") diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py index 5527ce5..2b41dbc 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py @@ -1 +1,2 @@ -from .matchup import PlayMatchupSplits, PlayMatchup \ No newline at end of file +from .matchup import PlayMatchup +from .attributes import PlayMatchupSplits diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py index 12b3c19..e9f27b7 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py @@ -1,19 +1,20 @@ -from dataclasses import dataclass +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass -class PlayMatchupSplits: + +class PlayMatchupSplits(MLBBaseModel): """ - A class to represent a playMatchup Split. + A class to represent a play matchup split. Attributes ---------- batter : str - Batter matchup split + Batter matchup split. pitcher : str - Pitcher matchup split - menonbase : str - Menonbase matchup split + Pitcher matchup split. + men_on_base : str + Men on base matchup split. """ batter: str pitcher: str - menonbase: str \ No newline at end of file + men_on_base: str = Field(alias="menonbase") diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py index 1cfc8d9..0ca20a2 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py @@ -1,64 +1,55 @@ -from typing import Union, Optional, List -from dataclasses import dataclass - +from typing import Optional, List, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person from mlbstatsapi.models.data import CodeDesc - from .attributes import PlayMatchupSplits -@dataclass(repr=False) -class PlayMatchup: + +class PlayMatchup(MLBBaseModel): """ - A class to represent a play Matchup. + A class to represent a play matchup. Attributes ---------- batter : Person - Matchup batter - batside : PlayMatchupSide - batters batside + Matchup batter. + bat_side : CodeDesc + Batter's bat side. pitcher : Person - Matchup pitcher - pitchhand : PlayMatchupSide - Pitchers side - pitcherhotcoldzones : List - Pitcher hot cold zone stats + Matchup pitcher. + pitch_hand : CodeDesc + Pitcher's side. + pitcher_hot_cold_zones : List + Pitcher hot cold zone stats. splits : PlayMatchupSplits - PlayMatchupSplits - batterhotcoldzonestats : List - Batter hot cold zone stats - postonfirst : Person - Runner on first - postonsecond : Person - Runner on second - postonthird : Person - Runner on third + Play matchup splits. + batter_hot_cold_zone_stats : List + Batter hot cold zone stats. + post_on_first : Person + Runner on first. + post_on_second : Person + Runner on second. + post_on_third : Person + Runner on third. """ - batter: Union[Person, dict] - batside: Union[CodeDesc, dict] - pitcher: Union[Person, dict] - pitchhand: Union[CodeDesc, dict] - batterhotcoldzones: List - pitcherhotcoldzones: List - splits: Union[PlayMatchupSplits, dict] - batterhotcoldzonestats: Optional[List] = None - pitcherhotcoldzonestats: Optional[List] = None - postonfirst: Optional[Union[Person, dict]] = None - postonsecond: Optional[Union[Person, dict]] = None - postonthird: Optional[Union[Person, dict]] = None + batter: Person + bat_side: CodeDesc = Field(alias="batside") + pitcher: Person + pitch_hand: CodeDesc = Field(alias="pitchhand") + batter_hot_cold_zones: List = Field(alias="batterhotcoldzones") + pitcher_hot_cold_zones: List = Field(alias="pitcherhotcoldzones") + splits: PlayMatchupSplits + batter_hot_cold_zone_stats: Optional[List] = Field(default=None, alias="batterhotcoldzonestats") + pitcher_hot_cold_zone_stats: Optional[List] = Field(default=None, alias="pitcherhotcoldzonestats") + post_on_first: Optional[Person] = Field(default=None, alias="postonfirst") + post_on_second: Optional[Person] = Field(default=None, alias="postonsecond") + post_on_third: Optional[Person] = Field(default=None, alias="postonthird") - def __post_init__(self): - self.batter = Person(**self.batter) - self.batside = CodeDesc(**self.batside) - self.pitcher = Person(**self.pitcher) - self.pitchhand = CodeDesc(**self.pitchhand) - self.splits = PlayMatchupSplits(**self.splits) - self.batterhotcoldzonestats = self.batterhotcoldzonestats['stats'] if self.batterhotcoldzonestats else self.batterhotcoldzonestats - self.pitcherhotcoldzonestats = self.pitcherhotcoldzonestats['stats'] if self.pitcherhotcoldzonestats else self.pitcherhotcoldzonestats - self.postonfirst = Person(**self.postonfirst) if self.postonfirst else self.postonfirst - self.postonsecond = Person(**self.postonsecond) if self.postonsecond else self.postonsecond - self.postonthird = Person(**self.postonthird) if self.postonthird else self.postonthird - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + @field_validator('batter_hot_cold_zone_stats', 'pitcher_hot_cold_zone_stats', mode='before') + @classmethod + def extract_stats(cls, v: Any) -> Any: + """Extract stats from nested dict if present.""" + if isinstance(v, dict) and 'stats' in v: + return v['stats'] + return v diff --git a/mlbstatsapi/models/game/livedata/plays/play/play.py b/mlbstatsapi/models/game/livedata/plays/play/play.py index 7bde052..999feed 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/play.py +++ b/mlbstatsapi/models/game/livedata/plays/play/play.py @@ -1,6 +1,6 @@ -from typing import Union, Optional, List -from dataclasses import dataclass - +from typing import Optional, List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.game.livedata.plays.play.matchup import PlayMatchup from mlbstatsapi.models.game.livedata.plays.play.playrunner import PlayRunner from mlbstatsapi.models.game.livedata.plays.play.playevent import PlayEvent @@ -8,60 +8,46 @@ from .attributes import PlayAbout, PlayResult, PlayReviewDetails -@dataclass(repr=False) -class Play: +class Play(MLBBaseModel): """ A class to represent a single play in this game. Attributes ---------- result : PlayResult - Play result + Play result. about : PlayAbout - Information about this play - count : PlayCount - This plays count + Information about this play. + count : Count + This play's count. matchup : PlayMatchup - This plays matchup - pitchindex : List[int] - Pitch index for this play, indexing playEvents - actionindex : List[int] - Action index for this play, indexing playEvents - runnerindex : List[int] - Runner index for this play, indexing runners + This play's matchup. + pitch_index : List[int] + Pitch index for this play, indexing play events. + action_index : List[int] + Action index for this play, indexing play events. + runner_index : List[int] + Runner index for this play, indexing runners. runners : List[PlayRunner] - Runners - playevents : List[PlayEvent] - Play events - playendtime : str - Time this play ends - atbatindex : int - The play index number - reviewdetails : PlayReviewDetails - Information on reviews if present + Runners. + play_events : List[PlayEvent] + Play events. + play_end_time : str + Time this play ends. + at_bat_index : int + The play index number. + review_details : PlayReviewDetails + Information on reviews if present. """ - result: Union[PlayResult, dict] - about: Union[PlayAbout, dict] - count: Union[Count, dict] - matchup: Union[PlayMatchup, dict] - pitchindex: List[int] - actionindex: List[int] - runnerindex: List[int] - runners: Union[List[PlayRunner], List[dict]] - playevents: Union[List[PlayEvent], List[dict]] - atbatindex: int - playendtime: Optional[str] = None - reviewdetails: Optional[Union[PlayReviewDetails, dict]] = None - - def __post_init__(self): - self.result = PlayResult(**self.result) - self.about = PlayAbout(**self.about) - self.count = Count(**self.count) - self.matchup = PlayMatchup(**self.matchup) - self.runners = [PlayRunner(**runner) for runner in self.runners] - self.playevents = [PlayEvent(**playevent) for playevent in self.playevents] - self.reviewdetails = PlayReviewDetails(**self.reviewdetails) if self.reviewdetails else self.reviewdetails - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + result: PlayResult + about: PlayAbout + count: Count + matchup: PlayMatchup + pitch_index: List[int] = Field(alias="pitchindex") + action_index: List[int] = Field(alias="actionindex") + runner_index: List[int] = Field(alias="runnerindex") + runners: List[PlayRunner] = [] + play_events: List[PlayEvent] = Field(default=[], alias="playevents") + at_bat_index: int = Field(alias="atbatindex") + play_end_time: Optional[str] = Field(default=None, alias="playendtime") + review_details: Optional[PlayReviewDetails] = Field(default=None, alias="reviewdetails") diff --git a/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py index fa26537..43fc4f5 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py @@ -1 +1 @@ -from .playevent import PlayEvent \ No newline at end of file +from .playevent import PlayEvent diff --git a/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py b/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py index 236fbf7..2caf181 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py +++ b/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py @@ -1,85 +1,73 @@ -from typing import Union, Optional -from dataclasses import dataclass +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person, Position from mlbstatsapi.models.data import Count, HitData, PitchData, PlayDetails -@dataclass(repr=False) -class PlayEvent: + +class PlayEvent(MLBBaseModel): """ - A class to represent a information about a play. + A class to represent a play event. Attributes ---------- details : PlayDetails - Event details + Event details. index : int - Event index - starttime : str - Event start time - endtime : str - Event end time - ispitch : bool - Is this event a pitch + Event index. + start_time : str + Event start time. + end_time : str + Event end time. + is_pitch : bool + Is this event a pitch. type : str - Type - playid : str - Unique play id ? - pitchnumber : int - Pitch number - actionplayid : str - Unique action play id ? - isbaserunningplay : bool - Is there base running this play - issubstitution : bool - Is this a substitution - battingorder : str - A weird batting order string that only has appeared once - count : PlayCount - Count - pitchdata : PitchData - Pitch data - hitdata : HitData - Hit data + Type. + play_id : str + Unique play ID. + pitch_number : int + Pitch number. + action_play_id : str + Unique action play ID. + is_base_running_play : bool + Is there base running this play. + is_substitution : bool + Is this a substitution. + batting_order : str + Batting order string. + count : Count + Count. + pitch_data : PitchData + Pitch data. + hit_data : HitData + Hit data. player : Person - Player + Player. position : Position - Position - replacedplayer : Person - Replaced player + Position. + replaced_player : Person + Replaced player. """ - details: Union[PlayDetails, dict] + details: PlayDetails index: int - ispitch: bool + is_pitch: bool = Field(alias="ispitch") type: str - pfxid: Optional[str] = None - starttime: Optional[str] = None - endtime: Optional[str] = None + pfx_id: Optional[str] = Field(default=None, alias="pfxid") + start_time: Optional[str] = Field(default=None, alias="starttime") + end_time: Optional[str] = Field(default=None, alias="endtime") umpire: Optional[str] = None base: Optional[str] = None - playid: Optional[str] = None - pitchnumber: Optional[int] = None - actionplayid: Optional[str] = None - isbaserunningplay: Optional[bool] = None - issubstitution: Optional[bool] = None - battingorder: Optional[str] = None - count: Optional[Union[Count, dict]] = None - pitchdata: Optional[Union[PitchData, dict]] = None - hitdata: Optional[Union[HitData, dict]] = None - player: Optional[Union[Person, dict]] = None - position: Optional[Union[Position, dict]] = None - replacedplayer: Optional[Union[Person, dict]] = None - reviewdetails: Optional[dict] = None - injurytype: Optional[str] = None - - def __post_init__(self): - self.details = PlayDetails(**self.details) - self.count = Count(**self.count) if self.count else self.count - self.pitchdata = PitchData(**self.pitchdata) if self.pitchdata else self.pitchdata - self.hitdata = HitData(**self.hitdata) if self.hitdata else self.hitdata - self.player = Person(**self.player) if self.player else self.player - self.position = Position(**self.position) if self.position else self.position - self.replacedplayer = Person(**self.replacedplayer) if self.replacedplayer else self.replacedplayer - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + play_id: Optional[str] = Field(default=None, alias="playid") + pitch_number: Optional[int] = Field(default=None, alias="pitchnumber") + action_play_id: Optional[str] = Field(default=None, alias="actionplayid") + is_base_running_play: Optional[bool] = Field(default=None, alias="isbaserunningplay") + is_substitution: Optional[bool] = Field(default=None, alias="issubstitution") + batting_order: Optional[str] = Field(default=None, alias="battingorder") + count: Optional[Count] = None + pitch_data: Optional[PitchData] = Field(default=None, alias="pitchdata") + hit_data: Optional[HitData] = Field(default=None, alias="hitdata") + player: Optional[Person] = None + position: Optional[Position] = None + replaced_player: Optional[Person] = Field(default=None, alias="replacedplayer") + review_details: Optional[dict] = Field(default=None, alias="reviewdetails") + injury_type: Optional[str] = Field(default=None, alias="injurytype") diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py index bfb8f16..1059108 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py @@ -1 +1,2 @@ from .playrunner import PlayRunner +from .attributes import RunnerMovement, RunnerDetails, RunnerCredits diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py index 86d649b..18f21b3 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py @@ -1,106 +1,88 @@ -from typing import Union, Optional -from dataclasses import dataclass - +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person, Position -@dataclass -class RunnerCredits: +class RunnerCredits(MLBBaseModel): """ - A class to represent a runners credit. + A class to represent a runner's credit. Attributes ---------- - player: Person - The player - position: RunnerCreditsPosition - The position - credit: str - The credit + player : Person + The player. + position : Position + The position. + credit : str + The credit. """ - player: Union[Person, dict] - position: Union[Position, dict] + player: Person + position: Position credit: str - def __post_init__(self): - self.player = Person(**self.player) - self.position = Position(**self.position) - -@dataclass(repr=False) -class RunnerMovement: +class RunnerMovement(MLBBaseModel): """ - A class to represent a play runner. + A class to represent a play runner movement. Attributes ---------- - isout: bool - Was the running movement an out - outnumber: int - What is the outnumber - originbase: str - Original base - start: str - What base the runner started from - end: str - What base the runner ended at - outbase: str - Base runner was made out + is_out : bool + Was the running movement an out. + out_number : int + What is the out number (None if not an out). + origin_base : str + Original base. + start : str + What base the runner started from. + end : str + What base the runner ended at. + out_base : str + Base runner was made out. """ - isout: bool - outnumber: int - originbase: Optional[str] = None + is_out: bool = Field(alias="isout") + out_number: Optional[int] = Field(default=None, alias="outnumber") + origin_base: Optional[str] = Field(default=None, alias="originbase") start: Optional[str] = None end: Optional[str] = None - outbase: Optional[str] = None + out_base: Optional[str] = Field(default=None, alias="outbase") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class RunnerDetails: +class RunnerDetails(MLBBaseModel): """ - A class to represent a play runner. + A class to represent a play runner details. Attributes ---------- - event: str - Runner event - eventtype: str - Runner event type - runner: Person - Who the runner is - isscoringevent: bool - Was this a scoring events - rbi: bool - Was this a rbi - earned: bool - Was it earned - teamunearned: bool - Was it unearned - playindex: int - Play index - movementreason: str - Reason for the movement - responsiblepitcher: Person - WHo was the responsible pitcher + event : str + Runner event. + event_type : str + Runner event type. + runner : Person + Who the runner is. + is_scoring_event : bool + Was this a scoring event. + rbi : bool + Was this an RBI. + earned : bool + Was it earned. + team_unearned : bool + Was it unearned. + play_index : int + Play index. + movement_reason : str + Reason for the movement. + responsible_pitcher : Person + Who was the responsible pitcher. """ event: str - eventtype: str - runner: Union[Person, dict] - isscoringevent: bool + event_type: str = Field(alias="eventtype") + runner: Person + is_scoring_event: bool = Field(alias="isscoringevent") rbi: bool earned: bool - teamunearned: bool - playindex: int - movementreason: Optional[str] = None - responsiblepitcher: Optional[Union[Person, dict]] = None - - def __post_init__(self): - self.runner = Person(**self.runner) - self.responsiblepitcher = Person(**self.responsiblepitcher) if self.responsiblepitcher else self.responsiblepitcher - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + team_unearned: bool = Field(alias="teamunearned") + play_index: int = Field(alias="playindex") + movement_reason: Optional[str] = Field(default=None, alias="movementreason") + responsible_pitcher: Optional[Person] = Field(default=None, alias="responsiblepitcher") diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py index d6f09f8..b1ec2f4 100644 --- a/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py @@ -1,28 +1,21 @@ -from typing import Union, List, Optional -from dataclasses import dataclass, field - +from typing import Optional, List +from mlbstatsapi.models.base import MLBBaseModel from .attributes import RunnerMovement, RunnerDetails, RunnerCredits -@dataclass -class PlayRunner: +class PlayRunner(MLBBaseModel): """ A class to represent a play runner. Attributes ---------- + movement : RunnerMovement + Runner movements. + details : RunnerDetails + Runner details. + credits : List[RunnerCredits] + Runner credits. + """ movement: RunnerMovement - Runner movements details: RunnerDetails - Runner details - credits: List[RunnerCredits] - Runner credits - """ - movement: Union[RunnerMovement, dict] - details: Union[RunnerDetails, dict] - credits: Optional[Union[List[RunnerCredits], List[dict]]] = field(default_factory=list) - - def __post_init__(self): - self.movement = RunnerMovement(**self.movement) - self.details = RunnerDetails(**self.details) - self.credits = [RunnerCredits(**credit) for credit in self.credits] + credits: List[RunnerCredits] = [] diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py index 3d02fc8..f62f911 100644 --- a/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py @@ -1 +1,2 @@ from .playbyinning import PlayByInning +from .attributes import PlayByInningHits, HitsByTeam, HitCoordinates diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py index ccbf46f..56136f2 100644 --- a/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py @@ -1,75 +1,65 @@ -from typing import Union, List -from dataclasses import dataclass - +from typing import List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.people import Person -@dataclass -class HitCoordinates: + +class HitCoordinates(MLBBaseModel): """ - A class to represent a Hits coordinates. + A class to represent a hit's coordinates. Attributes ---------- x : float - X coordinate for hit + X coordinate for hit. y : float - Y coordinate for hit + Y coordinate for hit. """ x: float y: float -@dataclass -class HitsByTeam: + +class HitsByTeam(MLBBaseModel): """ - A class to represent a Hit during an inning. + A class to represent a hit during an inning. Attributes ---------- team : Team - This team + This team. inning : int - This inning number + This inning number. pitcher : Person - The pitcher + The pitcher. batter : Person - The batter + The batter. coordinates : HitCoordinates - Hit coordinates + Hit coordinates. type : str - Type + Type. description : str - description + Description. """ - team: Union[Team, dict] + team: Team inning: int - pitcher: Union[Person, dict] - batter: Union[Person, dict] - coordinates: Union[HitCoordinates, dict] + pitcher: Person + batter: Person + coordinates: HitCoordinates type: str description: str - def __post_init__(self): - self.team = Team(**self.team) - self.pitcher = Person(**self.pitcher) - self.batter = Person(**self.batter) - self.coordinates = HitCoordinates(**self.coordinates) -@dataclass -class PlayByInningHits: +class PlayByInningHits(MLBBaseModel): """ - A class to represent a play by inning in this game. + A class to represent a play by inning hits. Attributes ---------- home : List[HitsByTeam] - Home team hits + Home team hits. away : List[HitsByTeam] - Away team hits + Away team hits. """ - home: Union[List[HitsByTeam], List[dict]] - away: Union[List[HitsByTeam], List[dict]] - - def __post_init__(self): - self.home = [HitsByTeam(**home_hit) for home_hit in self.home] - self.away = [HitsByTeam(**away_hit) for away_hit in self.away] + home: List[HitsByTeam] = [] + away: List[HitsByTeam] = [] diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py index d5b8495..883f9b4 100644 --- a/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py @@ -1,31 +1,28 @@ -from typing import Union, List -from dataclasses import dataclass - +from typing import List +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from .attributes import PlayByInningHits -@dataclass -class PlayByInning: + +class PlayByInning(MLBBaseModel): """ A class to represent a play by inning in this game. Attributes ---------- - startindex : int - Starting play index number, indexed with Plays.allPlays - endindex : int - End play index number, indexed with Plays.allPlays + start_index : int + Starting play index number, indexed with Plays.all_plays. + end_index : int + End play index number, indexed with Plays.all_plays. top : List[int] - Play indexes for top of the inning + Play indexes for top of the inning. bottom : List[int] - play indexes for bottom of the inning + Play indexes for bottom of the inning. hits : PlayByInningHits - Hits for the inning by home and away + Hits for the inning by home and away. """ - startindex: int - endindex: int + start_index: int = Field(alias="startindex") + end_index: int = Field(alias="endindex") top: List[int] bottom: List[int] - hits: Union[PlayByInningHits, dict] - - def __post_init__(self): - self.hits = PlayByInningHits(**self.hits) + hits: PlayByInningHits diff --git a/mlbstatsapi/models/game/livedata/plays/plays.py b/mlbstatsapi/models/game/livedata/plays/plays.py index c21c3ea..36e4064 100644 --- a/mlbstatsapi/models/game/livedata/plays/plays.py +++ b/mlbstatsapi/models/game/livedata/plays/plays.py @@ -1,37 +1,34 @@ -from typing import Union, List -from dataclasses import dataclass, field - +from typing import List, Optional, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.game.livedata.plays.play import Play from mlbstatsapi.models.game.livedata.plays.playbyinning import PlayByInning -@dataclass(repr=False) -class Plays: +class Plays(MLBBaseModel): """ A class to represent the plays in this game. Attributes ---------- - allplays : List[Play] - All the plays in this game - currentplay : Play - The current play in this game - scoringplays : List[int] - Which plays are scoring plays, indexed with allPlays - playsbyinning : List[PlayByInning] - Plays by inning + all_plays : List[Play] + All the plays in this game. + current_play : Play + The current play in this game. + scoring_plays : List[int] + Which plays are scoring plays, indexed with all_plays. + plays_by_inning : List[PlayByInning] + Plays by inning. """ - allplays: Union[List[Play], List[dict]] - scoringplays: List[int] - playsbyinning: Union[List[PlayByInning], List[dict]] - currentplay: Union[Play, dict] = field(default_factory=dict) - - - def __post_init__(self): - self.allplays = [Play(**play) for play in self.allplays if play] - self.currentplay = Play(**self.currentplay) if self.currentplay else self.currentplay - self.playsbyinning = [PlayByInning(**inning) for inning in self.playsbyinning if inning] + all_plays: List[Play] = Field(default=[], alias="allplays") + scoring_plays: List[int] = Field(alias="scoringplays") + plays_by_inning: List[PlayByInning] = Field(default=[], alias="playsbyinning") + current_play: Optional[Play] = Field(default=None, alias="currentplay") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + @field_validator('current_play', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/gamepace/__init__.py b/mlbstatsapi/models/gamepace/__init__.py index 4d13e2a..8f57ad2 100644 --- a/mlbstatsapi/models/gamepace/__init__.py +++ b/mlbstatsapi/models/gamepace/__init__.py @@ -1,2 +1,2 @@ -from .gamepace import Gamepace -from .attributes import Gamepacedata \ No newline at end of file +from .gamepace import GamePace +from .attributes import GamePaceData, PrPortalCalculatedFields diff --git a/mlbstatsapi/models/gamepace/attributes.py b/mlbstatsapi/models/gamepace/attributes.py index 9866b1c..ca2097b 100644 --- a/mlbstatsapi/models/gamepace/attributes.py +++ b/mlbstatsapi/models/gamepace/attributes.py @@ -1,186 +1,174 @@ -from typing import Union, Optional -from dataclasses import dataclass - +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.leagues import League from mlbstatsapi.models.sports import Sport -@dataclass(kw_only=True) -class Prportalcalculatedfields: +class PrPortalCalculatedFields(MLBBaseModel): """ - This dataclass represents the calculated fields for a baseball game. + A class representing the calculated fields for a baseball game. - Attributes: + Attributes ---------- - total7inngames : int + total_7_inn_games : int The total number of 7-inning games played. - total9inngames : int + total_9_inn_games : int The total number of 9-inning games played. - totalextrainngames : int + total_extra_inn_games : int The total number of extra-inning games played. - timeper7inngame : str + time_per_7_inn_game : str The average time per 7-inning game. - timeper9inngame : str + time_per_9_inn_game : str The average time per 9-inning game. - timeperextrainngame : str + time_per_extra_inn_game : str The average time per extra-inning game. """ - total7inngames: int - total9inngames: int - totalextrainngames: int - timeper7inngame: str - timeper9inngame: str - timeperextrainngame: str + total_7_inn_games: Optional[int] = Field(default=None, alias="total7inngames") + total_9_inn_games: Optional[int] = Field(default=None, alias="total9inngames") + total_extra_inn_games: Optional[int] = Field(default=None, alias="totalextrainngames") + time_per_7_inn_game: Optional[str] = Field(default=None, alias="timeper7inngame") + time_per_9_inn_game: Optional[str] = Field(default=None, alias="timeper9inngame") + time_per_extra_inn_game: Optional[str] = Field(default=None, alias="timeperextrainngame") -@dataclass(kw_only=True, repr=False) -class Gamepacedata: +class GamePaceData(MLBBaseModel): """ - This dataclass represents a league in a sport, with various statistics and metrics related to its games and performances. + A class representing game pace data for a league, team, or sport. - Attributes: + Attributes ---------- - hitsper9inn : float + hits_per_9_inn : float The number of hits per 9 innings played. - runsper9inn : float + runs_per_9_inn : float The number of runs scored per 9 innings played. - pitchesper9inn : float + pitches_per_9_inn : float The number of pitches thrown per 9 innings played. - plateappearancesper9inn : float + plate_appearances_per_9_inn : float The number of plate appearances per 9 innings played. - hitspergame : float + hits_per_game : float The number of hits per game played. - runspergame : float + runs_per_game : float The number of runs scored per game played. - inningsplayedpergame : float + innings_played_per_game : float The number of innings played per game. - pitchespergame : float + pitches_per_game : float The number of pitches thrown per game played. - pitcherspergame : float + pitchers_per_game : float The number of pitchers used per game played. - plateappearancespergame : float + plate_appearances_per_game : float The number of plate appearances per game played. - totalgametime : str - The total time spent playing games in the league. - totalinningsplayed : float - The total number of innings played in the league. - totalhits : int - The total number of hits in the league. - totalruns : int - The total number of runs scored in the league. - totalplateappearances : int - The total number of plate appearances in the league. - totalpitchers : int - The total number of pitchers used in the league. - totalpitches : int - The total number of pitches thrown in the league. - totalgames : int - The total number of games played in the league. - total7inngames : int - The total number of 7-inning games played in the league. - total9inngames : int - The total number of 9-inning games played in the league. - totalextrainngames : int - The total number of extra inning games played in the league. - timepergame : str - The amount of time spent per game in the league. - timeperpitch : str - The amount of time spent per pitch in the league. - timeperhit : str - The amount of time spent per hit in the league. - timeperrun : str - The amount of time spent per run scored in the league. - timeperplateappearance : str - The amount of time spent per plate appearance in the league. - timeper9inn : str - The amount of time spent per 9 innings played in the league. - timeper77plateappearances : str - The amount of time spent per 7-7 plate appearances in the league. - totalextrainntime : str - The total amount of time spent on extra inning games in the league. - timeper7inngame : str - The amount of time spent per 7-inning game in the league. - total7inngamescompletedearly: int - The total number of 7-inning games completed early in the league. - timeper7inngamewithoutextrainn: str - The amount of time spent per 7-inning game without extra innings in the league. - total7inngamesscheduled : int - The total number of 7-inning games scheduled in the league. - total7inngameswithoutextrainn : int - The total number of 7-inning games played without extra innings in the league. - total9inngamescompletedearly : int - The total number of 9-inning games completed early in the league. - total9inngameswithoutextrainn : int - The total number of 9-inning games - total9inngamesscheduled : int - The total number of 9 inning games scheduled - hitsperrun : float - The number of hits per run - pitchesperpitcher : float - Number of pitches thrown per pitcher + total_game_time : str + The total time spent playing games. + total_innings_played : float + The total number of innings played. + total_hits : int + The total number of hits. + total_runs : int + The total number of runs scored. + total_plate_appearances : int + The total number of plate appearances. + total_pitchers : int + The total number of pitchers used. + total_pitches : int + The total number of pitches thrown. + total_games : int + The total number of games played. + total_7_inn_games : int + The total number of 7-inning games played. + total_9_inn_games : int + The total number of 9-inning games played. + total_extra_inn_games : int + The total number of extra inning games played. + time_per_game : str + The amount of time spent per game. + time_per_pitch : str + The amount of time spent per pitch. + time_per_hit : str + The amount of time spent per hit. + time_per_run : str + The amount of time spent per run scored. + time_per_plate_appearance : str + The amount of time spent per plate appearance. + time_per_9_inn : str + The amount of time spent per 9 innings played. + time_per_77_plate_appearances : str + The amount of time spent per 77 plate appearances. + total_extra_inn_time : str + The total amount of time spent on extra inning games. + time_per_7_inn_game : str + The amount of time spent per 7-inning game. + total_7_inn_games_completed_early : int + The total number of 7-inning games completed early. + time_per_7_inn_game_without_extra_inn : str + The amount of time spent per 7-inning game without extra innings. + total_7_inn_games_scheduled : int + The total number of 7-inning games scheduled. + total_7_inn_games_without_extra_inn : int + The total number of 7-inning games played without extra innings. + total_9_inn_games_completed_early : int + The total number of 9-inning games completed early. + total_9_inn_games_without_extra_inn : int + The total number of 9-inning games without extra innings. + total_9_inn_games_scheduled : int + The total number of 9 inning games scheduled. + hits_per_run : float + The number of hits per run. + pitches_per_pitcher : float + Number of pitches thrown per pitcher. season : str - Season number - team: Team - Team + Season number. + team : Team + Team. league : League - League + League. sport : Sport - Sport` - prportalcalculatedfields : Prportalcalculatedfields - calculated fields for a league + Sport. + pr_portal_calculated_fields : PrPortalCalculatedFields + Calculated fields. """ - hitsper9inn: Optional[float] = None - runsper9inn: Optional[float] = None - pitchesper9inn: Optional[float] = None - plateappearancesper9inn: Optional[float] = None - hitspergame: Optional[float] = None - runspergame: Optional[float] = None - inningsplayedpergame: Optional[float] = None - pitchespergame: Optional[float] = None - pitcherspergame: Optional[float] = None - plateappearancespergame: Optional[float] = None - totalgametime: Optional[str] = None - totalinningsplayed: Optional[float] = None - totalhits: Optional[int] = None - totalruns: Optional[int] = None - totalplateappearances: Optional[int] = None - totalpitchers: Optional[int] = None - totalpitches: Optional[int] = None - totalgames: Optional[int] = None - total7inngames: Optional[int] = None - total9inngames: Optional[int] = None - totalextrainngames: Optional[int] = None - timepergame: Optional[str] = None - timeperpitch: Optional[str] = None - timeperhit: Optional[str] = None - timeperrun: Optional[str] = None - timeperplateappearance: Optional[str] = None - timeper9inn: Optional[str] = None - timeper77plateappearances: Optional[str] = None - totalextrainntime: Optional[str] = None - timeper7inngame: Optional[str] = None - total7inngamescompletedearly: Optional[int] = None - timeper7inngamewithoutextrainn: Optional[str] = None - total7inngamesscheduled: Optional[int] = None - total7inngameswithoutextrainn: Optional[int] = None - total9inngamescompletedearly: Optional[int] = None - total9inngameswithoutextrainn: Optional[int] = None - total9inngamesscheduled: Optional[int] = None - hitsperrun: Optional[float] = None - pitchesperpitcher: Optional[float] = None + hits_per_9_inn: Optional[float] = Field(default=None, alias="hitsper9inn") + runs_per_9_inn: Optional[float] = Field(default=None, alias="runsper9inn") + pitches_per_9_inn: Optional[float] = Field(default=None, alias="pitchesper9inn") + plate_appearances_per_9_inn: Optional[float] = Field(default=None, alias="plateappearancesper9inn") + hits_per_game: Optional[float] = Field(default=None, alias="hitspergame") + runs_per_game: Optional[float] = Field(default=None, alias="runspergame") + innings_played_per_game: Optional[float] = Field(default=None, alias="inningsplayedpergame") + pitches_per_game: Optional[float] = Field(default=None, alias="pitchespergame") + pitchers_per_game: Optional[float] = Field(default=None, alias="pitcherspergame") + plate_appearances_per_game: Optional[float] = Field(default=None, alias="plateappearancespergame") + total_game_time: Optional[str] = Field(default=None, alias="totalgametime") + total_innings_played: Optional[float] = Field(default=None, alias="totalinningsplayed") + total_hits: Optional[int] = Field(default=None, alias="totalhits") + total_runs: Optional[int] = Field(default=None, alias="totalruns") + total_plate_appearances: Optional[int] = Field(default=None, alias="totalplateappearances") + total_pitchers: Optional[int] = Field(default=None, alias="totalpitchers") + total_pitches: Optional[int] = Field(default=None, alias="totalpitches") + total_games: Optional[int] = Field(default=None, alias="totalgames") + total_7_inn_games: Optional[int] = Field(default=None, alias="total7inngames") + total_9_inn_games: Optional[int] = Field(default=None, alias="total9inngames") + total_extra_inn_games: Optional[int] = Field(default=None, alias="totalextrainngames") + time_per_game: Optional[str] = Field(default=None, alias="timepergame") + time_per_pitch: Optional[str] = Field(default=None, alias="timeperpitch") + time_per_hit: Optional[str] = Field(default=None, alias="timeperhit") + time_per_run: Optional[str] = Field(default=None, alias="timeperrun") + time_per_plate_appearance: Optional[str] = Field(default=None, alias="timeperplateappearance") + time_per_9_inn: Optional[str] = Field(default=None, alias="timeper9inn") + time_per_77_plate_appearances: Optional[str] = Field(default=None, alias="timeper77plateappearances") + total_extra_inn_time: Optional[str] = Field(default=None, alias="totalextrainntime") + time_per_7_inn_game: Optional[str] = Field(default=None, alias="timeper7inngame") + total_7_inn_games_completed_early: Optional[int] = Field(default=None, alias="total7inngamescompletedearly") + time_per_7_inn_game_without_extra_inn: Optional[str] = Field(default=None, alias="timeper7inngamewithoutextrainn") + total_7_inn_games_scheduled: Optional[int] = Field(default=None, alias="total7inngamesscheduled") + total_7_inn_games_without_extra_inn: Optional[int] = Field(default=None, alias="total7inngameswithoutextrainn") + total_9_inn_games_completed_early: Optional[int] = Field(default=None, alias="total9inngamescompletedearly") + total_9_inn_games_without_extra_inn: Optional[int] = Field(default=None, alias="total9inngameswithoutextrainn") + total_9_inn_games_scheduled: Optional[int] = Field(default=None, alias="total9inngamesscheduled") + hits_per_run: Optional[float] = Field(default=None, alias="hitsperrun") + pitches_per_pitcher: Optional[float] = Field(default=None, alias="pitchesperpitcher") season: Optional[str] = None - team: Optional[Union[Team, dict]] = None - league: Optional[Union[League, dict]] = None - sport: Optional[Union[Sport, dict]] = None - prportalcalculatedfields: Optional[Union[Prportalcalculatedfields, dict]] = None - - def __post_init__(self): - self.team = Team(**self.team) if self.team else None - self.league = League(**self.league) if self.league else None - self.sport = Sport(**self.sport) if self.sport else None - self.prportalcalculatedfields = Prportalcalculatedfields(**self.prportalcalculatedfields) if self.prportalcalculatedfields else None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + team: Optional[Team] = None + league: Optional[League] = None + sport: Optional[Sport] = None + pr_portal_calculated_fields: Optional[PrPortalCalculatedFields] = Field(default=None, alias="prportalcalculatedfields") diff --git a/mlbstatsapi/models/gamepace/gamepace.py b/mlbstatsapi/models/gamepace/gamepace.py index 764117e..5b341d7 100644 --- a/mlbstatsapi/models/gamepace/gamepace.py +++ b/mlbstatsapi/models/gamepace/gamepace.py @@ -1,28 +1,21 @@ -from dataclasses import dataclass -from typing import Optional, List +from typing import List +from mlbstatsapi.models.base import MLBBaseModel +from .attributes import GamePaceData -from .attributes import Gamepacedata -@dataclass -class Gamepace: +class GamePace(MLBBaseModel): """ - A dataclass representing a gamepace. + A class representing game pace data. - Attributes: + Attributes ---------- - teams : List[Gamepacedata] - A list of teams in the gamepace. - leagues : List[Gamepacedata] - A list of leagues in the gamepace. - sports : List[Gamepacedata] - A list of sports in the gamepace. + teams : List[GamePaceData] + A list of game pace data by team. + leagues : List[GamePaceData] + A list of game pace data by league. + sports : List[GamePaceData] + A list of game pace data by sport. """ - teams: Optional[List[Gamepacedata]] = None - leagues: Optional[List[Gamepacedata]] = None - sports: Optional[List[Gamepacedata]] = None - - - def __post_init__(self): - self.teams = [Gamepacedata(**teams) for teams in self.teams] - self.leagues = [Gamepacedata(**leagues) for leagues in self.leagues] - self.sports = [Gamepacedata(**sports) for sports in self.sports] \ No newline at end of file + teams: List[GamePaceData] = [] + leagues: List[GamePaceData] = [] + sports: List[GamePaceData] = [] diff --git a/mlbstatsapi/models/homerunderby/__init__.py b/mlbstatsapi/models/homerunderby/__init__.py index 8b290c1..25d2862 100644 --- a/mlbstatsapi/models/homerunderby/__init__.py +++ b/mlbstatsapi/models/homerunderby/__init__.py @@ -1,2 +1,13 @@ -from .homerunderby import Homerunderby -from .attributes import Round \ No newline at end of file +from .homerunderby import HomeRunDerby +from .attributes import ( + Round, + Matchup, + Seed, + Hits, + HitData, + TrajectoryData, + Coordinates, + Status, + Info, + EventType, +) diff --git a/mlbstatsapi/models/homerunderby/attributes.py b/mlbstatsapi/models/homerunderby/attributes.py index 9db29dd..1a1c2d4 100644 --- a/mlbstatsapi/models/homerunderby/attributes.py +++ b/mlbstatsapi/models/homerunderby/attributes.py @@ -1,17 +1,17 @@ -from typing import Union, Optional, List -from dataclasses import dataclass - +from typing import Optional, List, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.venues import Venue from mlbstatsapi.models.teams import Team from mlbstatsapi.models.people import Person -@dataclass -class Eventtype: + +class EventType(MLBBaseModel): """ - The Eventtype class holds information about the type of an event. + A class representing the type of an event. - Attributes: - ___________ + Attributes + ---------- code : str The unique code of the event type. name : str @@ -20,313 +20,263 @@ class Eventtype: code: str name: str -@dataclass -class Info: + +class Info(MLBBaseModel): """ - The Info class holds information about an event. + A class representing information about an event. Attributes ---------- id : int The unique identifier of the event. - nongameguid : str - Guid of the event. + non_game_guid : str + GUID of the event. name : str The name of the event. - eventtype : Eventtype - The type of event. Can be an instance of the Eventtype class or a - dictionary containing the attributes for the Eventtype class. - eventdate : str + event_type : EventType + The type of event. + event_date : str The date of the event. venue : Venue - The venue of the event. Can be an instance of the Venue class or - a dictionary containing the attributes for the Venue class. - ismultiday : bool + The venue of the event. + is_multi_day : bool Whether the event spans multiple days. - isprimarycalendar : bool + is_primary_calendar : bool Whether the event is on the primary calendar. - filecode : str + file_code : str The code of the file associated with the event. - eventnumber : int + event_number : int The number of the event. - publicfacing : bool + public_facing : bool Whether the event is public-facing. teams : List[Team] - The teams participating in the event. Can be a list of instances of - the Team class or a list of dictionaries containing the attributes - for the Team class. + The teams participating in the event. """ id: int - nongameguid: str + non_game_guid: str = Field(alias="nongameguid") name: str - eventtype: Union[Eventtype, dict] - eventdate: str - venue: Union[Venue, dict] - ismultiday: bool - isprimarycalendar: bool - filecode: str - eventnumber: int - publicfacing: bool - teams: List[Union[Team, dict]] + event_type: EventType = Field(alias="eventtype") + event_date: str = Field(alias="eventdate") + venue: Venue + is_multi_day: bool = Field(alias="ismultiday") + is_primary_calendar: bool = Field(alias="isprimarycalendar") + file_code: str = Field(alias="filecode") + event_number: int = Field(alias="eventnumber") + public_facing: bool = Field(alias="publicfacing") + teams: List[Team] = [] - def __post_init__(self): - self.eventtype = Eventtype(**self.eventtype) - self.venue = Venue(**self.venue) - self.teams = [Team(**team) for team in self.teams] -@dataclass -class Status: +class Status(MLBBaseModel): """ A class representing the status of a round. Attributes ---------- state : str - The current state of the game or round (e.g. "in progress", "paused", - "ended") - currentround : int - The number of the current round in the game - currentroundtimeleft : str - The amount of time left in the current round, in a human-readable - format (e.g. "4:00") - intiebreaker : bool - Whether the game or round is currently in a tiebreaker - tiebreakernum : int - The number of the current tiebreaker, if applicable - clockstopped : bool - Whether the round clock is currently stopped - bonustime : bool - Whether the round is currently in bonus time + The current state of the game or round. + current_round : int + The number of the current round in the game. + current_round_time_left : str + The amount of time left in the current round. + in_tiebreaker : bool + Whether the game or round is currently in a tiebreaker. + tiebreaker_num : int + The number of the current tiebreaker, if applicable. + clock_stopped : bool + Whether the round clock is currently stopped. + bonus_time : bool + Whether the round is currently in bonus time. """ state: str - currentround: int - currentroundtimeleft: str - intiebreaker: bool - tiebreakernum: int - clockstopped: bool - bonustime: bool + current_round: int = Field(alias="currentround") + current_round_time_left: str = Field(alias="currentroundtimeleft") + in_tiebreaker: bool = Field(alias="intiebreaker") + tiebreaker_num: int = Field(alias="tiebreakernum") + clock_stopped: bool = Field(alias="clockstopped") + bonus_time: bool = Field(alias="bonustime") -@dataclass -class Coordinates: - """" - A class representing the coordinates of a hit + +class Coordinates(MLBBaseModel): + """ + A class representing the coordinates of a hit. Attributes ---------- - coordx : float - The x-coordinate of the hit - coordy : float - The y-coordinate of the hit - landingposx : float - The x-coordinate of the hits's landing position, - if applicable - landingposy : float - The y-coordinate of the hits's landing position, - if applicable + coord_x : float + The x-coordinate of the hit. + coord_y : float + The y-coordinate of the hit. + landing_pos_x : float + The x-coordinate of the hit's landing position. + landing_pos_y : float + The y-coordinate of the hit's landing position. """ - coordx: float - coordy: float - landingposx: float - landingposy: float + coord_x: Optional[float] = Field(default=None, alias="coordx") + coord_y: Optional[float] = Field(default=None, alias="coordy") + landing_pos_x: Optional[float] = Field(default=None, alias="landingposx") + landing_pos_y: Optional[float] = Field(default=None, alias="landingposy") -@dataclass -class Trajectorydata: - """" + +class TrajectoryData(MLBBaseModel): + """ A class representing data on a hit's trajectory in three-dimensional space. Attributes ---------- - trajectorypolynomialx : List[int] - A list of coefficients for the polynomial representing the - x-coordinate of the hits's trajectory - trajectorypolynomialy : List[int] - A list of coefficients for the polynomial representing the - y-coordinate of the hits's trajectory - trajectorypolynomialz : List[int] - A list of coefficients for the polynomial representing the - z-coordinate of the hits's trajectory - validtimeinterval : List[int] - A list of two elements representing the start and end times for which - the polynomial coefficients are valid - measuredtimeinterval : List[int] - A list of two elements representing the start and end times of the - interval during which the hits's trajectory was measured + trajectory_polynomial_x : List[float] + Coefficients for the polynomial representing the x-coordinate trajectory. + trajectory_polynomial_y : List[float] + Coefficients for the polynomial representing the y-coordinate trajectory. + trajectory_polynomial_z : List[float] + Coefficients for the polynomial representing the z-coordinate trajectory. + valid_time_interval : List[float] + Start and end times for which the polynomial coefficients are valid. + measured_time_interval : List[float] + Start and end times of the interval during which trajectory was measured. """ - trajectorypolynomialx: List[int] - trajectorypolynomialy: List[int] - trajectorypolynomialz: List[int] - validtimeinterval: List[int] - measuredtimeinterval: List[int] + trajectory_polynomial_x: List[float] = Field(alias="trajectorypolynomialx") + trajectory_polynomial_y: List[float] = Field(alias="trajectorypolynomialy") + trajectory_polynomial_z: List[float] = Field(alias="trajectorypolynomialz") + valid_time_interval: List[float] = Field(alias="validtimeinterval") + measured_time_interval: List[float] = Field(alias="measuredtimeinterval") + + +class HitData(MLBBaseModel): + """ + A class representing data on a hit. -@dataclass -class Hitdata: - """" - A class representing data on a hit - Attributes ---------- - launchspeed : float - The speed at which the hit was launched - totaldistance : int - The total distance the hit traveled - launchangle : float: None - The angle at which the hit was launched, if applicable - coordinates : Coordinates: None - Coordinates for the hit - trajectorydata : Trajectorydata: None - Trajectory data for the hit + total_distance : int + The total distance the hit traveled. + launch_speed : float + The speed at which the hit was launched. + launch_angle : float + The angle at which the hit was launched. + coordinates : Coordinates + Coordinates for the hit. + trajectory_data : TrajectoryData + Trajectory data for the hit. """ - totaldistance: int - launchspeed: Optional[float] = None - launchangle: Optional[float] = None - coordinates: Optional[Union[Coordinates, dict]] = None - trajectorydata: Optional[Union[Trajectorydata, dict]] = None + total_distance: int = Field(alias="totaldistance") + launch_speed: Optional[float] = Field(default=None, alias="launchspeed") + launch_angle: Optional[float] = Field(default=None, alias="launchangle") + coordinates: Optional[Coordinates] = None + trajectory_data: Optional[TrajectoryData] = Field(default=None, alias="trajectorydata") - def __post_init__(self): - self.coordinates = Coordinates(**self.coordinates) if self.coordinates else None - self.trajectorydata = Trajectorydata(**self.trajectorydata) if self.trajectorydata else None -@dataclass -class Hits: - """" - A class representing a hit in the homerun derby +class Hits(MLBBaseModel): + """ + A class representing a hit in the homerun derby. Attributes ---------- - bonustime : bool - A boolean indicating whether the hit occurred during bonus time. + bonus_time : bool + Whether the hit occurred during bonus time. homerun : bool - A boolean indicating whether the hit was a homerun. + Whether the hit was a homerun. tiebreaker : bool - A boolean indicating whether the hit occurred during a tiebreaker. - hitdata : Hitdata - An object containing the data for the hit. This can either be a - Hitdata object or a dictionary. - ishomerun : bool - A boolean indicating whether the hit was a homerun. This attribute - is a duplicate of the `homerun` attribute. - playid : str - A string containing the ID of the play in which the hit occurred. - timeremaining : str - A string indicating the amount of time remaining in the game when the - hit occurred. - isbonustime : bool - A boolean indicating whether the hit occurred during bonus time. This - attribute is a duplicate of the `bonustime` attribute. - timeremainingseconds : int - A integer indicated the amount of time remaining in seconds - istiebreaker : bool - A boolean indicating whether the hit occurred during a tiebreaker. - This attribute is a duplicate of the `tiebreaker` attribute. + Whether the hit occurred during a tiebreaker. + hit_data : HitData + The data for the hit. + is_homerun : bool + Whether the hit was a homerun. + time_remaining : str + Amount of time remaining when the hit occurred. + is_bonus_time : bool + Whether the hit occurred during bonus time. + time_remaining_seconds : int + Amount of time remaining in seconds. + is_tiebreaker : bool + Whether the hit occurred during a tiebreaker. + play_id : str + The ID of the play in which the hit occurred. """ - bonustime: bool + bonus_time: bool = Field(alias="bonustime") homerun: bool tiebreaker: bool - hitdata: Union[Hitdata, dict] - ishomerun: bool - timeremaining: str - isbonustime: bool - istiebreaker: bool - timeremainingseconds: Optional[int] = None - playid: Optional[str] = None + hit_data: HitData = Field(alias="hitdata") + is_homerun: bool = Field(alias="ishomerun") + time_remaining: str = Field(alias="timeremaining") + is_bonus_time: bool = Field(alias="isbonustime") + is_tiebreaker: bool = Field(alias="istiebreaker") + time_remaining_seconds: Optional[int] = Field(default=None, alias="timeremainingseconds") + play_id: Optional[str] = Field(default=None, alias="playid") - def __post_init__(self): - self.hitdata = Hitdata(**self.hitdata) -@dataclass -class Seed: - """" - A class representing either a high or a low seed in the matchup for - the homerun derby. +class Seed(MLBBaseModel): + """ + A class representing a seed in a homerun derby matchup. Attributes ---------- complete : bool - A boolean indicating whether the seed has been completed. + Whether the seed has been completed. started : bool - A boolean indicating whether the seed has been started. + Whether the seed has been started. winner : bool - A boolean indicating whether the player for this seed is the winner - of the game. + Whether the player for this seed is the winner. player : Person - An object containing the data for the player associated with this - seed. This can either be a Person object or a dictionary. - topderbyhitdata : Hitdata - An object containing the data for the top hit in the seed. This can - either be a Hitdata object or a dictionary. - hits : Hits - An object containing the data for the hits in the seed. This can - either be a Hits object or a dictionary. + The player associated with this seed. + top_derby_hit_data : HitData + The data for the top hit in the seed. + hits : List[Hits] + The hits in the seed. seed : int - An integer representing the seed number. + The seed number. order : int - An integer representing the order in which the seed was played. - iswinner : bool - A boolean indicating whether the player for this seed is the winner - of the game. This attribute is a duplicate of the `winner` attribute. - iscomplete : bool - A boolean indicating whether the seed has been completed. This - attribute is a duplicate of the `complete` attribute. - isstarted : bool - A boolean indicating whether the seed has been started. This - attribute is a duplicate of the `started` attribute. - numhomeruns : int - An integer representing the number of homeruns hit in the seed. + The order in which the seed was played. + is_winner : bool + Whether the player for this seed is the winner. + is_complete : bool + Whether the seed has been completed. + is_started : bool + Whether the seed has been started. + num_homeruns : int + The number of homeruns hit in the seed. """ complete: bool started: bool winner: bool - player: Union[Person, dict] - topderbyhitdata: Union[Hitdata, dict] - hits: Union[Hits, dict] + player: Person + top_derby_hit_data: HitData = Field(alias="topderbyhitdata") + hits: List[Hits] = [] seed: int order: int - iswinner: bool - iscomplete: bool - isstarted: bool - numhomeruns: int + is_winner: bool = Field(alias="iswinner") + is_complete: bool = Field(alias="iscomplete") + is_started: bool = Field(alias="isstarted") + num_homeruns: int = Field(alias="numhomeruns") - def __post_init__(self): - self.player = Person(**self.player) - self.topderbyhitdata = Hitdata(**self.topderbyhitdata) - self.hits = [Hits(**hit) for hit in self.hits] -@dataclass -class Matchup: - """" - A class representing a matchup in the homerun derby - +class Matchup(MLBBaseModel): + """ + A class representing a matchup in the homerun derby. + Attributes ---------- - topseed : Seed - Containing the top seed in the matchup. - bottomseed : Seed - Containing the bottom seed in the matchup. + top_seed : Seed + The top seed in the matchup. + bottom_seed : Seed + The bottom seed in the matchup. """ - topseed: Union[Seed, dict] - bottomseed: Union[Seed, dict] + top_seed: Seed = Field(alias="topseed") + bottom_seed: Seed = Field(alias="bottomseed") - def __post_init__(self): - self.topseed = Seed(**self.topseed) - self.bottomseed = Seed(**self.bottomseed) -@dataclass -class Round: - """" - A class representing a round in the homerun derby +class Round(MLBBaseModel): + """ + A class representing a round in the homerun derby. Attributes ---------- round : int - An integer representing the round number. - numbatters : int - An integer representing the number of batters in the round. + The round number. + num_batters : int + The number of batters in the round. matchups : List[Matchup] - A list of objects containing the data for the matchups in the round. + The matchups in the round. """ round: int - matchups: List[Union[Matchup, dict]] - numbatters: Optional[int] = None - - def __post_init__(self): - self.matchups = [Matchup(**matchup) for matchup in self.matchups] \ No newline at end of file + matchups: List[Matchup] = [] + num_batters: Optional[int] = Field(default=None, alias="numbatters") diff --git a/mlbstatsapi/models/homerunderby/homerunderby.py b/mlbstatsapi/models/homerunderby/homerunderby.py index 360b95e..6670531 100644 --- a/mlbstatsapi/models/homerunderby/homerunderby.py +++ b/mlbstatsapi/models/homerunderby/homerunderby.py @@ -1,42 +1,39 @@ -from typing import Union, List -from dataclasses import dataclass - +from typing import List, Any +from pydantic import field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person - from .attributes import Info, Status, Round -@dataclass -class Homerunderby: +class HomeRunDerby(MLBBaseModel): """ - A class representing a homerun derby + A class representing a homerun derby. Attributes ---------- info : Info - An object containing information about the game. + Information about the event. status : Status - An object containing the status of the game. - rounds : Round - A list of Round objects representing the rounds in the game. + The status of the game. + rounds : List[Round] + The rounds in the game. players : List[Person] - A list of objects containing the data for the players in the game. + The players in the game. """ - info: Union[Info, dict] - status: Union[Status, dict] - rounds: List[Round] - players: List[Union[Person, dict]] - - def __post_init__(self): - self.info = Info(**self.info) - self.status = Status(**self.status) - self.rounds = [Round(**round) for round in self.rounds] - - player_list = [] - - for player in self.players: - if 'stats' in player: - player.pop('stats') - player_list.append(Person(**player)) - - self.players = player_list \ No newline at end of file + info: Info + status: Status + rounds: List[Round] = [] + players: List[Person] = [] + + @field_validator('players', mode='before') + @classmethod + def clean_players(cls, v: Any) -> Any: + """Remove 'stats' key from player dicts before validation.""" + if isinstance(v, list): + cleaned = [] + for player in v: + if isinstance(player, dict) and 'stats' in player: + player = {k: val for k, val in player.items() if k != 'stats'} + cleaned.append(player) + return cleaned + return v diff --git a/tests/external_tests/game/test_game.py b/tests/external_tests/game/test_game.py index b6125a7..5fc05ae 100644 --- a/tests/external_tests/game/test_game.py +++ b/tests/external_tests/game/test_game.py @@ -27,5 +27,27 @@ def test_game_creation(self): def test_game_attrs(self): self.assertTrue(hasattr(self.game, "metadata")) - self.assertTrue(hasattr(self.game, "gamedata")) - self.assertTrue(hasattr(self.game, "livedata")) + self.assertTrue(hasattr(self.game, "game_data")) + self.assertTrue(hasattr(self.game, "live_data")) + + def test_game_pythonic_field_names(self): + """Test that Pythonic field names are accessible.""" + # Test top-level game attributes + self.assertIsNotNone(self.game.game_pk) + self.assertIsNotNone(self.game.metadata) + self.assertIsNotNone(self.game.game_data) + self.assertIsNotNone(self.game.live_data) + + # Test game_data nested attributes + self.assertIsNotNone(self.game.game_data.game) + self.assertIsNotNone(self.game.game_data.datetime) + self.assertIsNotNone(self.game.game_data.status) + self.assertIsNotNone(self.game.game_data.teams) + self.assertIsNotNone(self.game.game_data.venue) + self.assertIsNotNone(self.game.game_data.official_venue) + self.assertIsNotNone(self.game.game_data.probable_pitchers) + + # Test live_data nested attributes + self.assertIsNotNone(self.game.live_data.plays) + self.assertIsNotNone(self.game.live_data.boxscore) + self.assertIsNotNone(self.game.live_data.leaders) diff --git a/tests/external_tests/gamepace/test_gamepace.py b/tests/external_tests/gamepace/test_gamepace.py index 68d44fd..a230502 100644 --- a/tests/external_tests/gamepace/test_gamepace.py +++ b/tests/external_tests/gamepace/test_gamepace.py @@ -1,5 +1,5 @@ import unittest -from mlbstatsapi.models.gamepace import Gamepace, Gamepacedata +from mlbstatsapi.models.gamepace import GamePace, GamePaceData from mlbstatsapi import Mlb @@ -18,19 +18,19 @@ # # set draft id # season_id = 2021 -# # call get_gamepace return Gamepace object +# # call get_gamepace return GamePace object # gamepace = self.mlb.get_gamepace(season_id) -# # Gamepace should not be None +# # GamePace should not be None # self.assertIsNotNone(gamepace) -# self.assertIsInstance(gamepace, Gamepace) +# self.assertIsInstance(gamepace, GamePace) # # list should not be empty # self.assertNotEqual(gamepace.sports, []) # # items in list should be gamepace data -# self.assertIsInstance(gamepace.sports[0], Gamepacedata) +# self.assertIsInstance(gamepace.sports[0], GamePaceData) # sportgamepace = gamepace.sports[0] @@ -38,8 +38,8 @@ # self.assertIsNotNone(sportgamepace) # # sportgamepace should have attrs set -# self.assertTrue(sportgamepace.hitspergame) -# self.assertTrue(sportgamepace.totalgames) +# self.assertTrue(sportgamepace.hits_per_game) +# self.assertTrue(sportgamepace.total_games) # def test_get_gamepace_404(self): # """This test should return a 200 and """ @@ -47,7 +47,7 @@ # # set gamepace season to invalid year # season_id = '2040,21' -# # call get_gamepace return Gamepace object +# # call get_gamepace return GamePace object # gamepace = self.mlb.get_gamepace(season_id) # # gamepace should be None @@ -64,4 +64,4 @@ # # gamepace should not be None # self.assertIsNotNone(gamepace) # # list should not be empty -# self.assertNotEqual(gamepace.leagues, []) \ No newline at end of file +# self.assertNotEqual(gamepace.leagues, []) diff --git a/tests/external_tests/homerunderby/test_homerunderby.py b/tests/external_tests/homerunderby/test_homerunderby.py index a145f7e..abbf2be 100644 --- a/tests/external_tests/homerunderby/test_homerunderby.py +++ b/tests/external_tests/homerunderby/test_homerunderby.py @@ -1,5 +1,5 @@ import unittest -from mlbstatsapi.models.homerunderby import Homerunderby, Round +from mlbstatsapi.models.homerunderby import HomeRunDerby, Round from mlbstatsapi import Mlb @@ -12,34 +12,34 @@ def setUpClass(cls) -> None: def tearDownClass(cls) -> None: pass - def test_get_gamepace(self): + def test_get_homerunderby(self): """This test should return a 200 and Round""" - # set draft id + # set game id game_id = 511101 - # call get_gamepace return Gamepace object + # call get_homerun_derby return HomeRunDerby object derby = self.mlb.get_homerun_derby(game_id) - # Gamepace should not be None + # HomeRunDerby should not be None self.assertIsNotNone(derby) - self.assertIsInstance(derby, Homerunderby) + self.assertIsInstance(derby, HomeRunDerby) # list should not be empty self.assertNotEqual(derby.rounds, []) - # items in list should be gamepace data + # items in list should be Round self.assertIsInstance(derby.rounds[0], Round) def test_get_homerunderby_404(self): - """This test should return a 200 and """ + """This test should return None for invalid game id""" # set gameid to invalid id game_id = '100394810242' - # call get_gamepace return Gamepace object + # call get_homerun_derby return HomeRunDerby object derby = self.mlb.get_homerun_derby(game_id) - # gamepace should be None - self.assertIsNone(derby) \ No newline at end of file + # derby should be None + self.assertIsNone(derby) diff --git a/tests/mock_tests/gamepace/test_gamepace_mock.py b/tests/mock_tests/gamepace/test_gamepace_mock.py index e52500d..99e935b 100644 --- a/tests/mock_tests/gamepace/test_gamepace_mock.py +++ b/tests/mock_tests/gamepace/test_gamepace_mock.py @@ -7,7 +7,7 @@ from mlbstatsapi import Mlb -from mlbstatsapi.models.gamepace import Gamepace, Gamepacedata +from mlbstatsapi.models.gamepace import GamePace, GamePaceData path_to_current_file = os.path.realpath(__file__) @@ -34,25 +34,25 @@ def test_get_gamepace(self, m): # set draft id season_id = 2021 - # call get_gamepace return Gamepace object + # call get_gamepace return GamePace object gamepace = self.mlb.get_gamepace(season_id) - # Gamepace should not be None + # GamePace should not be None self.assertIsNotNone(gamepace) - self.assertIsInstance(gamepace, Gamepace) + self.assertIsInstance(gamepace, GamePace) # list should not be empty self.assertNotEqual(gamepace.sports, []) # items in list should be gamepace data - self.assertIsInstance(gamepace.sports[0], Gamepacedata) + self.assertIsInstance(gamepace.sports[0], GamePaceData) sportgamepace = gamepace.sports[0] # sportgamepace should not be none self.assertIsNotNone(sportgamepace) - # sportgamepace should have attrs set - self.assertTrue(sportgamepace.hitspergame) - self.assertTrue(sportgamepace.totalgames) \ No newline at end of file + # sportgamepace should have attrs set (using Pythonic names) + self.assertTrue(sportgamepace.hits_per_game) + self.assertTrue(sportgamepace.total_games) diff --git a/tests/mock_tests/homerunderby/test_homerunderby_mock.py b/tests/mock_tests/homerunderby/test_homerunderby_mock.py index c3eaeca..a1fcf3d 100644 --- a/tests/mock_tests/homerunderby/test_homerunderby_mock.py +++ b/tests/mock_tests/homerunderby/test_homerunderby_mock.py @@ -7,7 +7,7 @@ from mlbstatsapi import Mlb -from mlbstatsapi.models.homerunderby import Homerunderby, Round +from mlbstatsapi.models.homerunderby import HomeRunDerby, Round path_to_current_file = os.path.realpath(__file__) @@ -31,19 +31,19 @@ def test_get_homerunderby(self, m): m.get('https://statsapi.mlb.com/api/v1/homeRunDerby/511101', json=self.homerunderby_mock, status_code=200) - # set draft id + # set game id game_id = 511101 - # call get_gamepace return Gamepace object + # call get_homerun_derby return HomeRunDerby object derby = self.mlb.get_homerun_derby(game_id) - # Gamepace should not be None + # HomeRunDerby should not be None self.assertIsNotNone(derby) - self.assertIsInstance(derby, Homerunderby) + self.assertIsInstance(derby, HomeRunDerby) # list should not be empty self.assertNotEqual(derby.rounds, []) - # items in list should be gamepace data - self.assertIsInstance(derby.rounds[0], Round) \ No newline at end of file + # items in list should be Round + self.assertIsInstance(derby.rounds[0], Round) From 5945b1f788e08bcdcca3a4da3724f017cfdf7e5c Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 21:21:38 -0800 Subject: [PATCH 6/9] refactor: migrate schedules, drafts, and awards models to Pydantic Models converted: schedules/ (4 classes): - Schedule, ScheduleDates, ScheduleGames, ScheduleHomeAndAway, ScheduleGameTeam drafts/ (4 classes): - Round, DraftPick, DraftHome, DraftSchool awards/ (2 classes): - Awards, Award Key changes: - All fields use snake_case with aliases for API compatibility - Class names: Home -> DraftHome, School -> DraftSchool (avoid conflicts) - Fixed optional fields: DraftSchool.city/state/school_class, DraftHome.state - Field validators handle empty dicts from API as None - Updated all related tests to use new Pythonic field names - Changed TypeError expectations to ValidationError in tests --- mlbstatsapi/models/awards/__init__.py | 2 +- mlbstatsapi/models/awards/attributes.py | 44 +-- mlbstatsapi/models/awards/awards.py | 14 +- mlbstatsapi/models/drafts/__init__.py | 3 +- mlbstatsapi/models/drafts/attributes.py | 25 +- mlbstatsapi/models/drafts/rounds.py | 121 +++---- mlbstatsapi/models/schedules/__init__.py | 2 +- mlbstatsapi/models/schedules/attributes.py | 331 +++++++++--------- mlbstatsapi/models/schedules/schedule.py | 46 +-- tests/external_tests/drafts/test_draft.py | 6 +- .../external_tests/schedule/test_schedule.py | 31 +- tests/mock_tests/drafts/test_draft_mock.py | 6 +- 12 files changed, 302 insertions(+), 329 deletions(-) diff --git a/mlbstatsapi/models/awards/__init__.py b/mlbstatsapi/models/awards/__init__.py index 1a8c530..822f5fd 100644 --- a/mlbstatsapi/models/awards/__init__.py +++ b/mlbstatsapi/models/awards/__init__.py @@ -1,2 +1,2 @@ from .awards import Awards -from .attributes import Award \ No newline at end of file +from .attributes import Award diff --git a/mlbstatsapi/models/awards/attributes.py b/mlbstatsapi/models/awards/attributes.py index 4309645..2e38fda 100644 --- a/mlbstatsapi/models/awards/attributes.py +++ b/mlbstatsapi/models/awards/attributes.py @@ -1,47 +1,37 @@ -from typing import Union, Optional -from dataclasses import dataclass - +from typing import Optional +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.people import Person -@dataclass(kw_only=True, repr=False) -class Award: + +class Award(MLBBaseModel): """ - This class represents an award object + A class representing an award. Attributes ---------- id : str - Award id + Award ID. name : str - Name of the award + Name of the award. date : str - Date of when award was given + Date of when award was given. season : str - Season award is for/from + Season award is for/from. team : Team - Team award was to/ Player is from + Team award was to / Player is from. player : Person - Person award is for - votes : int None - Any votes associated with award - notes : str None - Any notes associated with award + Person award is for. + votes : int + Any votes associated with award. + notes : str + Any notes associated with award. """ - id: str name: str date: str season: str - team: Union[Team, dict] - player: Union[Person, dict] + team: Team + player: Person votes: Optional[int] = None notes: Optional[str] = None - - def __post_init__(self): - self.team = Team(**self.team) - self.player = Person(**self.player) - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/awards/awards.py b/mlbstatsapi/models/awards/awards.py index fb4c4b0..d573009 100644 --- a/mlbstatsapi/models/awards/awards.py +++ b/mlbstatsapi/models/awards/awards.py @@ -1,17 +1,15 @@ from typing import List -from dataclasses import dataclass - +from mlbstatsapi.models.base import MLBBaseModel from .attributes import Award -@dataclass -class Awards: + +class Awards(MLBBaseModel): """ - This class represents an awards object + A class representing an awards collection. Attributes ---------- awards : List[Award] - Awards + List of awards. """ - - awards: List[Award] + awards: List[Award] = [] diff --git a/mlbstatsapi/models/drafts/__init__.py b/mlbstatsapi/models/drafts/__init__.py index c7f7e6a..ad4e43f 100644 --- a/mlbstatsapi/models/drafts/__init__.py +++ b/mlbstatsapi/models/drafts/__init__.py @@ -1 +1,2 @@ -from .rounds import Round \ No newline at end of file +from .rounds import Round, DraftPick +from .attributes import DraftHome, DraftSchool diff --git a/mlbstatsapi/models/drafts/attributes.py b/mlbstatsapi/models/drafts/attributes.py index 1e926a9..460bff0 100644 --- a/mlbstatsapi/models/drafts/attributes.py +++ b/mlbstatsapi/models/drafts/attributes.py @@ -1,10 +1,11 @@ -from dataclasses import dataclass, field +from typing import Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel -@dataclass -class Home: +class DraftHome(MLBBaseModel): """ - A home is a where a draft player is from + A class representing where a draft player is from. Attributes ---------- @@ -16,19 +17,19 @@ class Home: The country where the player is from. """ city: str - state: str country: str + state: Optional[str] = None -@dataclass -class School: + +class DraftSchool(MLBBaseModel): """ - Represents the school the draft player is from. + A class representing the school the draft player is from. Attributes ---------- name : str The name of the school. - schoolclass : str + school_class : str The class the student is in. city : str The city where the school is located. @@ -38,7 +39,7 @@ class School: The state where the school is located. """ name: str - schoolclass: str - city: str country: str - state: str \ No newline at end of file + state: Optional[str] = None + school_class: Optional[str] = Field(default=None, alias="schoolclass") + city: Optional[str] = None diff --git a/mlbstatsapi/models/drafts/rounds.py b/mlbstatsapi/models/drafts/rounds.py index 9f549c3..90b54ca 100644 --- a/mlbstatsapi/models/drafts/rounds.py +++ b/mlbstatsapi/models/drafts/rounds.py @@ -1,97 +1,88 @@ -from dataclasses import dataclass, field -from typing import List, Optional, Union - -from .attributes import School, Home +from typing import List, Optional +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.people import Person from mlbstatsapi.models.data import CodeDesc +from .attributes import DraftSchool, DraftHome + -@dataclass(repr=False) -class DraftPick: +class DraftPick(MLBBaseModel): """ - Represents a pick made in the MLB draft. + A class representing a pick made in the MLB draft. Attributes ---------- - bisplayerid : int - The unique identifier of the player associated with this draft pick. - pickround : str + team : Team + The team that made this draft pick. + draft_type : CodeDesc + Information about the type of draft in which this pick was made. + is_drafted : bool + Whether or not the player associated with this pick has been drafted. + is_pass : bool + Whether or not the team passed on making a pick in this round. + year : str + The year in which the draft took place. + school : DraftSchool + Information about the player's school or college. + home : DraftHome + Information about the player's home location. + pick_round : str The round of the draft in which this pick was made. - picknumber : int + pick_number : int The number of the pick in the round. - displaypicknumber : int + display_pick_number : int The overall pick number displayed. - roundpicknumber : int + round_pick_number : int The number of the pick overall in the draft. + headshot_link : str + A link to a headshot image of the player. + person : Person + The person drafted. + bis_player_id : int + The unique identifier of the player associated with this draft pick. rank : int The rank of the player among all players eligible for the draft. - pickvalue : str + pick_value : str The value of the pick, if known. - signingbonus : str + signing_bonus : str The signing bonus associated with this pick, if known. - home : Home - Information about the player's home location. - scoutingreport : str - A scouting report on the player's abilities. - school : School - Information about the player's school or college. + scouting_report : str + A scouting report on the player's abilities. blurb : str - A brief summary of the player's background and accomplishments. - headshotlink : str - A link to a headshot image of the player. - team : Team or dict - The team that made this draft pick. - drafttype : CodeDesc - Information about the type of draft in which this pick was made. - isdrafted : bool - Whether or not the player associated with this pick has been drafted. - ispass : bool - Whether or not the team passed on making a pick in this round. - year : str - The year in which the draft took place. + A brief summary of the player's background and accomplishments. """ - team: Union[Team, dict] - drafttype: Union[CodeDesc, dict] - isdrafted: bool - ispass: bool + team: Team + draft_type: CodeDesc = Field(alias="drafttype") + is_drafted: bool = Field(alias="isdrafted") + is_pass: bool = Field(alias="ispass") year: str - school: Union[School , dict] - home: Union[Home, dict] - pickround: str - picknumber: int - displaypicknumber: int - roundpicknumber: int - headshotlink: Optional[str] = None - person: Optional[Union[Person, dict]] = None - bisplayerid: Optional[int] = None + school: DraftSchool + home: DraftHome + pick_round: str = Field(alias="pickround") + pick_number: int = Field(alias="picknumber") + display_pick_number: int = Field(alias="displaypicknumber") + round_pick_number: int = Field(alias="roundpicknumber") + headshot_link: Optional[str] = Field(default=None, alias="headshotlink") + person: Optional[Person] = None + bis_player_id: Optional[int] = Field(default=None, alias="bisplayerid") rank: Optional[int] = None - pickvalue: Optional[str] = None - signingbonus: Optional[str] = None - scoutingreport: Optional[str] = None + pick_value: Optional[str] = Field(default=None, alias="pickvalue") + signing_bonus: Optional[str] = Field(default=None, alias="signingbonus") + scouting_report: Optional[str] = Field(default=None, alias="scoutingreport") blurb: Optional[str] = None - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(repr=False) -class Round: +class Round(MLBBaseModel): """ - Represents a round of the MLB draft. + A class representing a round of the MLB draft. Attributes ---------- round : str The round number of the draft, represented as a string. picks : List[DraftPick] - A list of DraftPick objects representing the picks made in this round of the draft. + A list of DraftPick objects representing the picks made in this round. """ round: str - picks: List[DraftPick] - - def __post_init__(self): - self.picks = [DraftPick(**pick) for pick in self.picks] - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + picks: List[DraftPick] = [] diff --git a/mlbstatsapi/models/schedules/__init__.py b/mlbstatsapi/models/schedules/__init__.py index cd8723d..3040f3f 100644 --- a/mlbstatsapi/models/schedules/__init__.py +++ b/mlbstatsapi/models/schedules/__init__.py @@ -1,2 +1,2 @@ from .schedule import Schedule -from .attributes import ScheduleGames \ No newline at end of file +from .attributes import ScheduleGames, ScheduleDates, ScheduleHomeAndAway, ScheduleGameTeam diff --git a/mlbstatsapi/models/schedules/attributes.py b/mlbstatsapi/models/schedules/attributes.py index 2a21ea6..dbac859 100644 --- a/mlbstatsapi/models/schedules/attributes.py +++ b/mlbstatsapi/models/schedules/attributes.py @@ -1,221 +1,210 @@ -from dataclasses import dataclass, field -from typing import Optional, Union, List +from typing import Optional, List, Any +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.venues import Venue from mlbstatsapi.models.game.gamedata import GameStatus from mlbstatsapi.models.teams import Team from mlbstatsapi.models.leagues import LeagueRecord -@dataclass(repr=False) -class ScheduleGameTeam: +class ScheduleGameTeam(MLBBaseModel): """ - A class to represent the sheduled games teams shedule information in a Scheduled Dates game. + A class to represent the scheduled game's team schedule information. Attributes ---------- - leaguerecord : LeagueRecord - League record for this team - score : int - Current score for this team in this game + league_record : LeagueRecord + League record for this team. team : Team - Team info for this game - iswinner : bool - If this team is the winner of this game - splitsquad : bool - Split squad - seriesnumber : int - Series number + Team info for this game. + split_squad : bool + Split squad. + series_number : int + Series number. + score : int + Current score for this team in this game. + is_winner : bool + If this team is the winner of this game. """ - leaguerecord: Union[LeagueRecord, dict] - team: Union[Team, dict] - splitsquad: bool - seriesnumber: Optional[int] = None + league_record: LeagueRecord = Field(alias="leaguerecord") + team: Team + split_squad: bool = Field(alias="splitsquad") + series_number: Optional[int] = Field(default=None, alias="seriesnumber") score: Optional[int] = None - iswinner: Optional[bool] = False - - def __post_init__(self): - self.leaguerecord = LeagueRecord(**self.leaguerecord) - self.team = Team(**self.team) + is_winner: Optional[bool] = Field(default=False, alias="iswinner") - def __repr__(self): - return f'ScheduleGameTeam(gamepk={self.leaguerecord}, team={self.team})' -@dataclass -class ScheduleHomeAndAway: +class ScheduleHomeAndAway(MLBBaseModel): """ - A class to represent both away and home teams in a Schedules Dates game. + A class to represent both away and home teams in a schedule's game. Attributes ---------- home : ScheduleGameTeam - Home team info for this game + Home team info for this game. away : ScheduleGameTeam - Away team info for this game + Away team info for this game. """ - home: Union[ScheduleGameTeam, dict] - away: Union[ScheduleGameTeam, dict] + home: ScheduleGameTeam + away: ScheduleGameTeam - def __post_init__(self): - self.home = ScheduleGameTeam(**self.home) - self.away = ScheduleGameTeam(**self.away) - -@dataclass(repr=False) -class ScheduleGames: +class ScheduleGames(MLBBaseModel): """ - A class to represent a Game in a Schedules Dates. + A class to represent a game in a schedule's dates. Attributes ---------- - gamepk : int - The games id number + game_pk : int + The game's ID number. link : str - The link for this game - gametype : str - This games game type + The link for this game. + game_type : str + This game's game type. season : str - The season this game takes place in - gamedate : str - The date for this game - officialdate : str - The official date for this game + The season this game takes place in. + game_date : str + The date for this game. + official_date : str + The official date for this game. status : GameStatus - The status of this game + The status of this game. teams : ScheduleHomeAndAway - Holds teams and thier info for this game - venue : Venue - The venue this game takes place in + Holds teams and their info for this game. + venue : Venue + The venue this game takes place in. content : dict - Content for this game. Havent found a populated reference yet. Stays as dict - istie : bool - If this game is a tie - gamenumber : int - Game number for this game - publicfacing : bool - Is this game public facing - doubleheader : str - The double header status for this game, "n','y'? - gamedaytype : str - The type of gameday for this game + Content for this game. + is_tie : bool + If this game is a tie. + game_number : int + Game number for this game. + public_facing : bool + Is this game public facing. + double_header : str + The double header status for this game. + gameday_type : str + The type of gameday for this game. tiebreaker : str - Tie breaker for this game, 'n','y'? - calendareventid : str - Calender event Id for this game - seasondisplay : str - Displayed season for this game - daynight : str - Day or night game as a string, 'am','pm'? - scheduledinnings : int - Number of scheduled inning for the game - reversehomeawaystatus : bool - If reverse home and away? - gameguid : str = None - The games guid - inningbreaklength : int - Length of break between innings - gamesinseries : int - Number of games in current series - seriesgamenumber : int - Game number in the current series - seriesdescription : str - Description of this current series - recordsource : str - Record source - ifnecessary : str - If necessary - ifnecessarydescription : str - If necessary description - rescheduledate : str = None - If game is rescheduled, this is the rescheduled date - reschedulegamedate : str = None - rescheduled game date - rescheduledfrom : str = None - rescheduled from - rescheduledfromdate : str = None - rescheduled from date - istie : bool = None - Is tie + Tie breaker for this game. + calendar_event_id : str + Calendar event ID for this game. + season_display : str + Displayed season for this game. + day_night : str + Day or night game as a string. + scheduled_innings : int + Number of scheduled innings for the game. + reverse_home_away_status : bool + If reverse home and away. + series_description : str + Description of this current series. + record_source : str + Record source. + if_necessary : str + If necessary. + if_necessary_description : str + If necessary description. + game_guid : str + The game's GUID. + description : str + Game description. + inning_break_length : int + Length of break between innings. + games_in_series : int + Number of games in current series. + series_game_number : int + Game number in the current series. + reschedule_date : str + Rescheduled date if applicable. + reschedule_game_date : str + Rescheduled game date. + rescheduled_from : str + Rescheduled from. + rescheduled_from_date : str + Rescheduled from date. + resume_date : str + Resume date. + resume_game_date : str + Resume game date. + resumed_from : str + Resumed from. + resumed_from_date : str + Resumed from date. """ - gamepk: int + game_pk: int = Field(alias="gamepk") link: str - gametype: str + game_type: str = Field(alias="gametype") season: str - gamedate: str - officialdate: str + game_date: str = Field(alias="gamedate") + official_date: str = Field(alias="officialdate") venue: Venue content: dict - gamenumber: int - publicfacing: bool - doubleheader: str - gamedaytype: str + game_number: int = Field(alias="gamenumber") + public_facing: bool = Field(alias="publicfacing") + double_header: str = Field(alias="doubleheader") + gameday_type: str = Field(alias="gamedaytype") tiebreaker: str - calendareventid: str - seasondisplay: str - daynight: str - scheduledinnings: int - reversehomeawaystatus: bool - seriesdescription: str - recordsource: str - ifnecessary: str - ifnecessarydescription: str - status: Union[GameStatus, dict] = field(default_factory=dict) - teams: Union[ScheduleHomeAndAway, dict] = field(default_factory=dict) - gameguid: Optional[str] = None + calendar_event_id: str = Field(alias="calendareventid") + season_display: str = Field(alias="seasondisplay") + day_night: str = Field(alias="daynight") + scheduled_innings: int = Field(alias="scheduledinnings") + reverse_home_away_status: bool = Field(alias="reversehomeawaystatus") + series_description: str = Field(alias="seriesdescription") + record_source: str = Field(alias="recordsource") + if_necessary: str = Field(alias="ifnecessary") + if_necessary_description: str = Field(alias="ifnecessarydescription") + status: Optional[GameStatus] = None + teams: Optional[ScheduleHomeAndAway] = None + game_guid: Optional[str] = Field(default=None, alias="gameguid") description: Optional[str] = None - inningbreaklength: Optional[int] = None - rescheduledate: Optional[str] = None - reschedulegamedate: Optional[str] = None - rescheduledfrom: Optional[str] = None - rescheduledfromdate: Optional[str] = None - istie: Optional[bool] = None - resumedate: Optional[str] = None - resumegamedate: Optional[str] = None - resumedfrom: Optional[str] = None - resumedfromdate: Optional[str] = None - seriesgamenumber: Optional[int] = None - gamesinseries: Optional[int] = None - - def __post_init__(self): - self.status = GameStatus(**self.status) if self.status else self.status - self.teams = ScheduleHomeAndAway(**self.teams) if self.teams else self.teams - self.venue = Venue(**self.venue) if self.venue else self.venue - - def __repr__(self): - return f'ScheduleGames(gamepk={self.gamepk}, link={self.link})' - -@dataclass(repr=False) -class ScheduleDates: + inning_break_length: Optional[int] = Field(default=None, alias="inningbreaklength") + reschedule_date: Optional[str] = Field(default=None, alias="rescheduledate") + reschedule_game_date: Optional[str] = Field(default=None, alias="reschedulegamedate") + rescheduled_from: Optional[str] = Field(default=None, alias="rescheduledfrom") + rescheduled_from_date: Optional[str] = Field(default=None, alias="rescheduledfromdate") + is_tie: Optional[bool] = Field(default=None, alias="istie") + resume_date: Optional[str] = Field(default=None, alias="resumedate") + resume_game_date: Optional[str] = Field(default=None, alias="resumegamedate") + resumed_from: Optional[str] = Field(default=None, alias="resumedfrom") + resumed_from_date: Optional[str] = Field(default=None, alias="resumedfromdate") + series_game_number: Optional[int] = Field(default=None, alias="seriesgamenumber") + games_in_series: Optional[int] = Field(default=None, alias="gamesinseries") + + @field_validator('status', 'teams', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + +class ScheduleDates(MLBBaseModel): """ - A class to represent a Schedules Dates. + A class to represent a schedule's dates. Attributes ---------- date : str - Date for the group of games - totalitems : int - Total amount of items for this date - totalevents : int - The number of events for this date - totalgames : int - The number of games for this date - totalgamesinprogress : int - The number of games that are currently in progress for this date + Date for the group of games. + total_items : int + Total amount of items for this date. + total_events : int + The number of events for this date. + total_games : int + The number of games for this date. + total_games_in_progress : int + The number of games that are currently in progress for this date. games : List[ScheduleGames] - A list of games for this date + A list of games for this date. events : list - A list of events for this date. Need to handle this but cant find a populated - reference for this object. It stays as a list for now. + A list of events for this date. """ date: str - totalitems: int - totalevents: int - totalgames: int - totalgamesinprogress: int - events: List - games: List[ScheduleGames] = field(default_factory=list) - - def __post_init__(self): - self.games = [ScheduleGames(**game) for game in self.games ] if self.games else self.games - - def __repr__(self): - return f'ScheduleDates(date={self.date}, totalgames={self.totalgames})' \ No newline at end of file + total_items: int = Field(alias="totalitems") + total_events: int = Field(alias="totalevents") + total_games: int = Field(alias="totalgames") + total_games_in_progress: int = Field(alias="totalgamesinprogress") + events: List = [] + games: List[ScheduleGames] = [] diff --git a/mlbstatsapi/models/schedules/schedule.py b/mlbstatsapi/models/schedules/schedule.py index d1268cd..5f5b2f3 100644 --- a/mlbstatsapi/models/schedules/schedule.py +++ b/mlbstatsapi/models/schedules/schedule.py @@ -1,38 +1,28 @@ from typing import List -from dataclasses import dataclass, field - +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from .attributes import ScheduleDates -@dataclass(repr=False) -class Schedule: +class Schedule(MLBBaseModel): """ A class to represent a Schedule. Attributes ---------- - copyright : str - Copyright - totalitems : int - Total items in schedule - totalevents : int - Total events in schedule - totalgames : int - Total games in schedule - totalgamesinprogress : int - Total games in progress in schedule - dates : ScheduleDates - List of dates with games in schedule + total_items : int + Total items in schedule. + total_events : int + Total events in schedule. + total_games : int + Total games in schedule. + total_games_in_progress : int + Total games in progress in schedule. + dates : List[ScheduleDates] + List of dates with games in schedule. """ - totalitems: int - totalevents: int - totalgames: int - totalgamesinprogress: int - dates: List[ScheduleDates] = field(default_factory=list) - - def __post_init__(self): - self.dates = [ScheduleDates(**date) for date in self.dates if self.dates] - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file + total_items: int = Field(alias="totalitems") + total_events: int = Field(alias="totalevents") + total_games: int = Field(alias="totalgames") + total_games_in_progress: int = Field(alias="totalgamesinprogress") + dates: List[ScheduleDates] = [] diff --git a/tests/external_tests/drafts/test_draft.py b/tests/external_tests/drafts/test_draft.py index 93ac81e..ba431e7 100644 --- a/tests/external_tests/drafts/test_draft.py +++ b/tests/external_tests/drafts/test_draft.py @@ -1,4 +1,4 @@ -import unittest +import unittest from mlbstatsapi import Mlb from mlbstatsapi.models.drafts import Round @@ -40,8 +40,8 @@ def test_get_draft_by_year_id(self): draftpick = draftpicks[0] - # draft pick should have attrs set - self.assertTrue(draftpick.pickround) + # draft pick should have attrs set (using Pythonic field name) + self.assertTrue(draftpick.pick_round) def test_get_draft_by_year_id_404(self): """This test should return a 200 and """ diff --git a/tests/external_tests/schedule/test_schedule.py b/tests/external_tests/schedule/test_schedule.py index d5a8085..6cd0377 100644 --- a/tests/external_tests/schedule/test_schedule.py +++ b/tests/external_tests/schedule/test_schedule.py @@ -1,4 +1,5 @@ import unittest +from pydantic import ValidationError from mlbstatsapi.models.schedules import Schedule from mlbstatsapi import Mlb @@ -13,20 +14,32 @@ def setUpClass(cls) -> None: def tearDownClass(cls) -> None: pass - def test_schedule_instance_type_error(self): - with self.assertRaises(TypeError): + def test_schedule_instance_validation_error(self): + with self.assertRaises(ValidationError): schedule = Schedule() def test_schedule_instance_position_arguments(self): - self.assertEqual(self.schedule.totalitems, 4) - self.assertEqual(self.schedule.totalevents, 0) - self.assertEqual(self.schedule.totalgames, 4) + self.assertEqual(self.schedule.total_items, 4) + self.assertEqual(self.schedule.total_events, 0) + self.assertEqual(self.schedule.total_games, 4) def test_schedule_has_attributes(self): self.assertIsInstance(self.schedule, Schedule) - self.assertTrue(hasattr(self.schedule, "totalitems")) - self.assertTrue(hasattr(self.schedule, "totalevents")) - self.assertTrue(hasattr(self.schedule, "totalgames")) - self.assertTrue(hasattr(self.schedule, "totalgamesinprogress")) + self.assertTrue(hasattr(self.schedule, "total_items")) + self.assertTrue(hasattr(self.schedule, "total_events")) + self.assertTrue(hasattr(self.schedule, "total_games")) + self.assertTrue(hasattr(self.schedule, "total_games_in_progress")) self.assertTrue(hasattr(self.schedule, "dates")) + def test_schedule_pythonic_field_names(self): + """Test that Pythonic field names are accessible.""" + self.assertIsNotNone(self.schedule.total_items) + self.assertIsNotNone(self.schedule.dates) + if self.schedule.dates: + date = self.schedule.dates[0] + self.assertIsNotNone(date.total_games) + if date.games: + game = date.games[0] + self.assertIsNotNone(game.game_pk) + self.assertIsNotNone(game.game_type) + self.assertIsNotNone(game.game_date) diff --git a/tests/mock_tests/drafts/test_draft_mock.py b/tests/mock_tests/drafts/test_draft_mock.py index 6c7a48b..d56065d 100644 --- a/tests/mock_tests/drafts/test_draft_mock.py +++ b/tests/mock_tests/drafts/test_draft_mock.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from typing import Dict, List from unittest.mock import patch import unittest import requests_mock @@ -57,5 +57,5 @@ def test_get_draft_by_year_id(self, m): draftpick = draftpicks[0] - # draft pick should have attrs set - self.assertTrue(draftpick.pickround) \ No newline at end of file + # draft pick should have attrs set (using Pythonic field name) + self.assertTrue(draftpick.pick_round) \ No newline at end of file From b320a8b726972f51084ea00c482d257d573094e0 Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 21:35:50 -0800 Subject: [PATCH 7/9] refactor: migrate stats module to Pydantic Models converted (8 files, 80+ classes): stats.py (base classes): - Stat, Split, PitchArsenalSplit, ExpectedStatistics, Sabermetrics - ZoneCodes, Zones, HotColdZones, Chart, SprayCharts - PitchArsenal, OutsAboveAverage, PlayerGameLogStat hitting.py: - SimpleHittingSplit, AdvancedHittingSplit, HittingPlay - HittingWinLoss, HittingHomeAndAway, HittingCareer, HittingSeason - HittingGameLog, HittingPlayLog, HittingPitchLog, HittingByMonth - HittingVsTeam, HittingVsPlayer, HittingExpectedStatistics, etc. pitching.py: - SimplePitchingSplit, AdvancedPitchingSplit, PitchingPlay - PitchingSeason, PitchingCareer, PitchingGameLog, PitchingLog - PitchingByMonth, PitchingHomeAndAway, PitchingWinLoss - PitchingVsTeam, PitchingVsPlayer, PitchingRankings, etc. fielding.py: - SimpleFieldingSplit, FieldingSeason, FieldingCareer - FieldingHomeAndAway, FieldingGameLog, FieldingByMonth, etc. catching.py: - SimpleCatchingSplit, CatchingSeason, CatchingCareer - CatchingGameLog, CatchingHomeAndAway, CatchingWinLoss, etc. running.py: - RunningOpponentsFaced game.py: - SimpleGameStats, SeasonGame, CareerGame - CareerRegularSeasonGame, CareerPlayoffsGame Key changes: - All fields use snake_case with aliases for API compatibility - Fixed type mismatches: stolenbasepercentage, groundoutstoairouts, atbatsperhomerun, whiffpercentage are strings not ints - Game model fields made optional for stats endpoint compatibility - Field validators handle empty dicts from API as None - Updated all stats tests to use new Pythonic field names (totalsplits -> total_splits) --- mlbstatsapi/models/game/game.py | 7 +- mlbstatsapi/models/stats/catching.py | 308 ++-- mlbstatsapi/models/stats/fielding.py | 548 +++---- mlbstatsapi/models/stats/game.py | 102 +- mlbstatsapi/models/stats/hitting.py | 1170 ++++++++------- mlbstatsapi/models/stats/pitching.py | 1277 ++++++++--------- mlbstatsapi/models/stats/running.py | 27 +- mlbstatsapi/models/stats/stats.py | 354 ++--- mlbstatsapi/models/stats/streak.py | 7 +- tests/external_tests/stats/test_catching.py | 10 +- tests/external_tests/stats/test_fielding.py | 14 +- tests/external_tests/stats/test_hitting.py | 26 +- tests/external_tests/stats/test_pitching.py | 18 +- .../stats/test_game_player_stats_for_game.py | 4 +- .../stats/test_hitting_stats_mock.py | 26 +- .../stats/test_pitching_stats_mock.py | 26 +- 16 files changed, 1946 insertions(+), 1978 deletions(-) diff --git a/mlbstatsapi/models/game/game.py b/mlbstatsapi/models/game/game.py index 08cb937..73ba6cf 100644 --- a/mlbstatsapi/models/game/game.py +++ b/mlbstatsapi/models/game/game.py @@ -1,3 +1,4 @@ +from typing import Optional from pydantic import Field from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.game.gamedata import GameData @@ -24,9 +25,9 @@ class Game(MLBBaseModel): """ game_pk: int = Field(alias="gamepk") link: str - metadata: MetaData - game_data: GameData = Field(alias="gamedata") - live_data: LiveData = Field(alias="livedata") + metadata: Optional[MetaData] = None + game_data: Optional[GameData] = Field(default=None, alias="gamedata") + live_data: Optional[LiveData] = Field(default=None, alias="livedata") @property def id(self) -> int: diff --git a/mlbstatsapi/models/stats/catching.py b/mlbstatsapi/models/stats/catching.py index 4d2edfb..c34a1f5 100644 --- a/mlbstatsapi/models/stats/catching.py +++ b/mlbstatsapi/models/stats/catching.py @@ -1,37 +1,38 @@ -from dataclasses import dataclass -from typing import Optional, Union - +from typing import Optional, List, ClassVar +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.game import Game - from .stats import Split -@dataclass(repr=False) -class SimpleCatchingSplit: +class SimpleCatchingSplit(MLBBaseModel): """ - A class to represent a simple catching split - Age: int - age at beginning of season - gamesplayed : int - The number of games played by the catcher + A class to represent a simple catching split. + + Attributes + ---------- + age : int + Age at beginning of season. + games_played : int + The number of games played by the catcher. runs : int The number of runs given up while catching. - homeruns : int + home_runs : int The number of home runs given up while catching. strikeouts : int The number of strike outs while catching. - baseonballs : int + base_on_balls : int The number of base on balls while catching. - intentionalwalks : int + intentional_walks : int The number of intentional walks while catching. hits : int The number of hits while catching. - hitbypitch : int + hit_by_pitch : int The number of batters hit by a pitch while catching. avg : str The batting average while catching. - atbats : int + at_bats : int The number of at bats while catching. obp : str The on base percentage while catching. @@ -39,274 +40,259 @@ class SimpleCatchingSplit: The slugging percentage while catching. ops : str The on-base slugging while catching. - see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging - caughtstealing : int + caught_stealing : int The number of runners caught stealing by the catcher. - caughtstealingpercentage: str - percentage of runners caught stealing by the catcher. - stolenbases : int + caught_stealing_percentage : str + Percentage of runners caught stealing by the catcher. + stolen_bases : int The number of stolen bases while catching. - stolenbasepercentage : str - The stolen base percentage against the catcher. - earnedruns : int + stolen_base_percentage : str + The stolen base percentage against the catcher. + earned_runs : int The earned run amount against the catcher. - battersfaced : int + batters_faced : int The number of batters faced while catching. - gamespitched : int + games_pitched : int The number of games pitched while catching. - hitbatsmen : int + hit_batsmen : int The number of batters hit by pitches while catching. - wildpitches : int + wild_pitches : int The number of wild pitches while catching. pickoffs : int The number of pick offs while catching. - totalbases : int - The total number of bases - strikeoutwalkratio : str + total_bases : int + The total number of bases. + strikeout_walk_ratio : str The strike out to walk ratio while catching. - catchersinterference : int - The number of times catcher interference commited - sacbunts : int + catchers_interference : int + The number of times catcher interference committed. + sac_bunts : int The number of sac bunts performed while catching. - sacflies : int + sac_flies : int The number of sac flies while catching. - passedball : int + passed_ball : int The number of passed balls while catching. + pickoff_attempts : int + The number of pickoff attempts. """ age: Optional[int] = None - gamesplayed: Optional[int] = None + games_played: Optional[int] = Field(default=None, alias="gamesplayed") runs: Optional[int] = None - homeruns: Optional[int] = None + home_runs: Optional[int] = Field(default=None, alias="homeruns") strikeouts: Optional[int] = None - baseonballs: Optional[int] = None - intentionalwalks: Optional[int] = None + base_on_balls: Optional[int] = Field(default=None, alias="baseonballs") + intentional_walks: Optional[int] = Field(default=None, alias="intentionalwalks") hits: Optional[int] = None - hitbypitch: Optional[int] = None + hit_by_pitch: Optional[int] = Field(default=None, alias="hitbypitch") avg: Optional[str] = None - atbats: Optional[int] = None + at_bats: Optional[int] = Field(default=None, alias="atbats") obp: Optional[str] = None slg: Optional[str] = None ops: Optional[str] = None - caughtstealing: Optional[int] = None - caughtstealingpercentage: Optional[str]=None - stolenbases: Optional[int] = None - stolenbasepercentage: Optional[str] = None - earnedruns: Optional[int] = None - battersfaced: Optional[int] = None - gamespitched: Optional[int] = None - hitbatsmen: Optional[int] = None - wildpitches: Optional[int] = None + caught_stealing: Optional[int] = Field(default=None, alias="caughtstealing") + caught_stealing_percentage: Optional[str] = Field(default=None, alias="caughtstealingpercentage") + stolen_bases: Optional[int] = Field(default=None, alias="stolenbases") + stolen_base_percentage: Optional[str] = Field(default=None, alias="stolenbasepercentage") + earned_runs: Optional[int] = Field(default=None, alias="earnedruns") + batters_faced: Optional[int] = Field(default=None, alias="battersfaced") + games_pitched: Optional[int] = Field(default=None, alias="gamespitched") + hit_batsmen: Optional[int] = Field(default=None, alias="hitbatsmen") + wild_pitches: Optional[int] = Field(default=None, alias="wildpitches") pickoffs: Optional[int] = None - totalbases: Optional[int] = None - strikeoutwalkratio: Optional[str] = None - catchersinterference: Optional[int] = None - sacbunts: Optional[int] = None - sacflies: Optional[int] = None - passedball: Optional[int] = None - pickoffattempts: Optional[int] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(kw_only=True, repr=False) + total_bases: Optional[int] = Field(default=None, alias="totalbases") + strikeout_walk_ratio: Optional[str] = Field(default=None, alias="strikeoutwalkratio") + catchers_interference: Optional[int] = Field(default=None, alias="catchersinterference") + sac_bunts: Optional[int] = Field(default=None, alias="sacbunts") + sac_flies: Optional[int] = Field(default=None, alias="sacflies") + passed_ball: Optional[int] = Field(default=None, alias="passedball") + pickoff_attempts: Optional[int] = Field(default=None, alias="pickoffattempts") + + class CatchingSeason(Split): """ - A class to represent a catching season statistic + A class to represent a catching season statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['season'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['season'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) class CatchingSingleSeason(Split): """ - A class to represent a catching statsSingleSeason statistic + A class to represent a catching statsSingleSeason statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['statsSingleSeason'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['statsSingleSeason'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingYearByYearPlayoffs(Split): """ - A class to represent a catching yearByYearPlayoffs statistic + A class to represent a catching yearByYearPlayoffs statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['yearByYearPlayoffs'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['yearByYearPlayoffs'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingYearByYear(Split): """ - A class to represent a catching yearByYear statistic + A class to represent a catching yearByYear statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['yearByYear'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['yearByYear'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingProjected(Split): """ - A class to represent a catching projectedRos statistic + A class to represent a catching projectedRos statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['projectedRos'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['projectedRos'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingCareer(Split): """ - A class to represent a catching career statistic + A class to represent a catching career statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['career'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['career'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingCareerRegularSeason(Split): """ - A class to represent a catching careerRegularSeason statistic + A class to represent a catching careerRegularSeason statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['careerRegularSeason'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['careerRegularSeason'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingGameLog(Split): """ - A class to represent a catching gameLog statistic + A class to represent a catching gameLog statistic. Attributes ---------- + is_home : bool + Is home bool. + is_win : bool + Is win bool. + date : str + The date. + game : Game + The game. + opponent : Team + The opponent. """ - _stat = ['gameLog'] - ishome: bool - iswin: bool + _stat: ClassVar[List[str]] = ['gameLog'] + is_home: bool = Field(alias="ishome") + is_win: bool = Field(alias="iswin") date: str - game: Union[Game, dict] - opponent: Union[Team, dict] + game: Game + opponent: Team - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingLastXGames(Split): """ - A class to represent a catching lastXGames statistic + A class to represent a catching lastXGames statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['lastXGames'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['lastXGames'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingByDateRange(Split): """ - A class to represent a catching byDateRange statistic + A class to represent a catching byDateRange statistic. Attributes ---------- + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['byDateRange'] - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['byDateRange'] + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingByDayOfWeek(Split): """ - A class to represent a catching byDayOfWeek statistic + A class to represent a catching byDayOfWeek statistic. Attributes ---------- + day_of_week : int + The day of the week. + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['byDayOfWeek'] - dayofweek: int - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['byDayOfWeek'] + day_of_week: int = Field(alias="dayofweek") + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingHomeAndAway(Split): """ - A class to represent a catching homeAndAway statistic + A class to represent a catching homeAndAway statistic. Attributes ---------- + is_home : bool + Is home bool. + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['homeAndAway'] - ishome: bool - stat: Union[SimpleCatchingSplit, dict] + _stat: ClassVar[List[str]] = ['homeAndAway'] + is_home: bool = Field(alias="ishome") + stat: SimpleCatchingSplit - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class CatchingWinLoss(Split): """ - A class to represent a catching winLoss statistic + A class to represent a catching winLoss statistic. Attributes ---------- + is_win : bool + Is win bool. + stat : SimpleCatchingSplit + The catching split stat. """ - _stat = ['winLoss'] - iswin: bool - stat: Union[SimpleCatchingSplit, dict] - - def __post_init__(self): - self.stat = SimpleCatchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLoss'] + is_win: bool = Field(alias="iswin") + stat: SimpleCatchingSplit diff --git a/mlbstatsapi/models/stats/fielding.py b/mlbstatsapi/models/stats/fielding.py index 8a8ac1c..6b392e7 100644 --- a/mlbstatsapi/models/stats/fielding.py +++ b/mlbstatsapi/models/stats/fielding.py @@ -1,460 +1,492 @@ -from dataclasses import dataclass, field -from typing import Optional, Union - +from typing import Optional, Any, List, ClassVar +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Position from mlbstatsapi.models.teams import Team from mlbstatsapi.models.game import Game +from .stats import Split + + +class SimpleFieldingSplit(MLBBaseModel): + """ + A class to represent a simple fielding split. -from .stats import Stat, Split - - -@dataclass(repr=False) -class SimpleFieldingSplit: - """ - A class to represent a simple fielding split - age: int - player age at the start of the season - gamesplayed: int - The number of games played - gamesstarted: int - The number of games started - caughtstealing: int - The number of runners caught stealing - caughtstealingpercentage: str - The percentage of runners caught stealing - stolenbases: int - The number of stolen bases - stolenbasepercentage: str - The stolen base percentage - assists: int - The number of assists - putouts: int - The number of put outs - errors: int - The number of errors commited - chances: int - The number of chances - fielding: str - The number of fielding - rangefactorpergame: str + Attributes + ---------- + age : int + Player age at the start of the season. + games_played : int + The number of games played. + games_started : int + The number of games started. + caught_stealing : int + The number of runners caught stealing. + caught_stealing_percentage : str + The percentage of runners caught stealing. + stolen_bases : int + The number of stolen bases. + stolen_base_percentage : str + The stolen base percentage. + assists : int + The number of assists. + putouts : int + The number of put outs. + errors : int + The number of errors committed. + chances : int + The number of chances. + fielding : str + The fielding percentage. + range_factor_per_game : str Range rating per game. - see also: https://www.mlb.com/glossary/advanced-stats/range-factor - rangefactorper9inn: str + range_factor_per_9_inn : str Range factor per 9 innings. - see also: https://www.mlb.com/glossary/advanced-stats/range-factor - innings: str + innings : str The number of innings played. - games: int + games : int The number of games played. - passedball: int + passed_ball : int The number of passed balls. - doubleplays: int + double_plays : int The number of double plays. - tripleplays: int + triple_plays : int The number of triple plays. - catcherera: str + catcher_era : str The catcher ERA of the fielding stat. - catchersinterference: int - The number of times catchers interfence was commited. - wildpitches: int + catchers_interference : int + The number of times catchers interference was committed. + wild_pitches : int The number of wild pitches. - throwingerrors: int + throwing_errors : int The number of throwing errors. - pickoffs: int + pickoffs : int The number of pick offs. """ - age:Optional[int]=None - position: Optional[Union[Position, dict]] = field(default_factory=dict) - gamesplayed: Optional[int] = None - gamesstarted: Optional[int] = None - caughtstealing: Optional[int] = None - caughtstealingpercentage: Optional[str] = None - stolenbases: Optional[int] = None - stolenbasepercentage: Optional[str] = None + age: Optional[int] = None + position: Optional[Position] = None + games_played: Optional[int] = Field(default=None, alias="gamesplayed") + games_started: Optional[int] = Field(default=None, alias="gamesstarted") + caught_stealing: Optional[int] = Field(default=None, alias="caughtstealing") + caught_stealing_percentage: Optional[str] = Field(default=None, alias="caughtstealingpercentage") + stolen_bases: Optional[int] = Field(default=None, alias="stolenbases") + stolen_base_percentage: Optional[str] = Field(default=None, alias="stolenbasepercentage") assists: Optional[int] = None putouts: Optional[int] = None errors: Optional[int] = None chances: Optional[int] = None fielding: Optional[str] = None - rangefactorpergame: Optional[str] = None - rangefactorper9inn: Optional[str] = None + range_factor_per_game: Optional[str] = Field(default=None, alias="rangefactorpergame") + range_factor_per_9_inn: Optional[str] = Field(default=None, alias="rangefactorper9inn") innings: Optional[str] = None games: Optional[int] = None - passedball: Optional[int] = None - doubleplays: Optional[int] = None - tripleplays: Optional[int] = None - catcherera: Optional[str] = None - catchersinterference: Optional[int] = None - wildpitches: Optional[int] = None - throwingerrors: Optional[int] = None + passed_ball: Optional[int] = Field(default=None, alias="passedball") + double_plays: Optional[int] = Field(default=None, alias="doubleplays") + triple_plays: Optional[int] = Field(default=None, alias="tripleplays") + catcher_era: Optional[str] = Field(default=None, alias="catcherera") + catchers_interference: Optional[int] = Field(default=None, alias="catchersinterference") + wild_pitches: Optional[int] = Field(default=None, alias="wildpitches") + throwing_errors: Optional[int] = Field(default=None, alias="throwingerrors") pickoffs: Optional[int] = None - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + -@dataclass(kw_only=True, repr=False) class FieldingSeasonAdvanced(Split): """ - A class to represent a fielding season Advanced statistic + A class to represent a fielding seasonAdvanced statistic. + Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['seasonAdvanced'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['seasonAdvanced'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingCareerAdvanced(Split): """ - A class to represent a fielding career Advanced statistic + A class to represent a fielding careerAdvanced statistic. + Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['careerAdvanced'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['careerAdvanced'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingSingleSeasonAdvanced(Split): """ - A class to represent a fielding season statistic + A class to represent a fielding statsSingleSeasonAdvanced statistic. Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['statsSingleSeasonAdvanced'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['statsSingleSeasonAdvanced'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingSeason(Split): """ - A class to represent a fielding season statistic + A class to represent a fielding season statistic. Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['season'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['season'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingSingleSeason(Split): """ - A class to represent a fielding statsSingleSeason statistic + A class to represent a fielding statsSingleSeason statistic. Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['statsSingleSeason'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['statsSingleSeason'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingCareer(Split): """ - A class to represent a fielding career statistic + A class to represent a fielding career statistic. Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['career', 'careerRegularSeason'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['career', 'careerRegularSeason'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingCareerPlayoffs(Split): """ - A class to represent a fielding careerPlayoffs statistic + A class to represent a fielding careerPlayoffs statistic. Attributes ---------- position : Position - The position of the player + The position of the player. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['careerPlayoffs'] - position: Optional[Union[Position, dict]] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['careerPlayoffs'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingHomeAndAway(Split): """ - A class to represent a fielding homeAndAway statistic + A class to represent a fielding homeAndAway statistic. Attributes ---------- - ishome : bool - A bool value for is the game at home + is_home : bool + A bool value for is the game at home. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['homeAndAway'] - ishome: bool - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['homeAndAway'] + is_home: bool = Field(alias="ishome") + stat: SimpleFieldingSplit - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) class FieldingHomeAndAwayPlayoffs(Split): """ - A class to represent a fielding homeAndAwayPlayoffs statistic + A class to represent a fielding homeAndAwayPlayoffs statistic. Attributes ---------- - ishome : bool - A bool value for is the game at home + is_home : bool + A bool value for is the game at home. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['homeAndAwayPlayoffs'] - ishome: bool - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['homeAndAwayPlayoffs'] + is_home: bool = Field(alias="ishome") + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingYearByYear(Split): """ - A class to represent a fielding yearByYear statistic + A class to represent a fielding yearByYear statistic. Attributes ---------- + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['yearByYear'] - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYear'] + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingYearByYearAdvanced(Split): """ - A class to represent a fielding yearByYearAdvanced statistic + A class to represent a fielding yearByYearAdvanced statistic. Attributes ---------- + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['yearByYearAdvanced'] - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['yearByYearAdvanced'] + stat: SimpleFieldingSplit - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) class FieldingYearByYearPlayoffs(Split): """ - A class to represent a fielding yearByYearPlayoffs statistic + A class to represent a fielding yearByYearPlayoffs statistic. Attributes ---------- + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['yearByYearPlayoffs'] - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['yearByYearPlayoffs'] + stat: SimpleFieldingSplit - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) -@dataclass(kw_only=True, repr=False) class FieldingWinLoss(Split): """ - A class to represent a fielding winLoss statistic + A class to represent a fielding winLoss statistic. Attributes ---------- - iswin : bool - is the game a win + is_win : bool + Is the game a win. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['winLoss'] - iswin: bool - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLoss'] + is_win: bool = Field(alias="iswin") + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingWinLossPlayoffs(Split): """ - A class to represent a fielding winLossPlayoffs statistic + A class to represent a fielding winLossPlayoffs statistic. Attributes ---------- - iswin : bool - is the game a win + is_win : bool + Is the game a win. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['winLossPlayoffs'] - iswin: bool - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLossPlayoffs'] + is_win: bool = Field(alias="iswin") + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingByDayOfWeek(Split): """ - A class to represent a fielding byDayOfWeek statistic + A class to represent a fielding byDayOfWeek statistic. Attributes ---------- + day_of_week : str + The day of the week. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['byDayOfWeek'] - dayofweek: str - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['byDayOfWeek'] + day_of_week: str = Field(alias="dayofweek") + stat: SimpleFieldingSplit - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) class FieldingByDateRangeAdvanced(Split): """ - A class to represent a fielding byDateRangeAdvanced stat + A class to represent a fielding byDateRangeAdvanced stat. Attributes ---------- + position : Position + The position. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['byDateRangeAdvanced'] - position: Union[Position, dict] = field(default_factory=dict) - - def __post_init__(self): - self.position = Position(**self.position) if self.position else self.position - stat: Union[SimpleFieldingSplit, dict] + _stat: ClassVar[List[str]] = ['byDateRangeAdvanced'] + stat: SimpleFieldingSplit + position: Optional[Position] = None - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + @field_validator('position', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v -@dataclass(kw_only=True, repr=False) class FieldingByMonth(Split): """ - A class to represent a fielding byMonth stat + A class to represent a fielding byMonth stat. Attributes ---------- month : int - the month of the stat + The month of the stat. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['byMonth'] + _stat: ClassVar[List[str]] = ['byMonth'] month: int - stat: Union[SimpleFieldingSplit, dict] + stat: SimpleFieldingSplit - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) class FieldingByMonthPlayoffs(Split): """ - A class to represent a fielding byMonthPlayoffs stat + A class to represent a fielding byMonthPlayoffs stat. Attributes ---------- month : int - the month of the stat + The month of the stat. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['byMonthPlayoffs'] + _stat: ClassVar[List[str]] = ['byMonthPlayoffs'] month: int - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingLastXGames(Split): """ - A class to represent a fielding lastXGames stat + A class to represent a fielding lastXGames stat. Attributes ---------- + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['lastXGames'] - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['lastXGames'] + stat: SimpleFieldingSplit -@dataclass(kw_only=True, repr=False) class FieldingGameLog(Split): """ - A class to represent a fielding gameLog stats + A class to represent a fielding gameLog stats. Attributes ---------- + opponent : Team + The opponent. + date : str + The date. + is_home : bool + Is home bool. + is_win : bool + Is win bool. + position : Position + The position. + game : Game + The game. + stat : SimpleFieldingSplit + The fielding split stat. """ - _stat = ['gameLog'] - opponent: Union[Team, dict] = field(default_factory=dict) + _stat: ClassVar[List[str]] = ['gameLog'] date: str - ishome: bool - iswin: bool - position: Union[Position, dict] = field(default_factory=dict) - game: Union[Game, dict] = field(default_factory=dict) - stat: Union[SimpleFieldingSplit, dict] - - def __post_init__(self): - self.stat = SimpleFieldingSplit(**self.stat) - super().__post_init__() - + is_home: bool = Field(alias="ishome") + is_win: bool = Field(alias="iswin") + stat: SimpleFieldingSplit + opponent: Optional[Team] = None + position: Optional[Position] = None + game: Optional[Game] = None + + @field_validator('opponent', 'position', 'game', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/stats/game.py b/mlbstatsapi/models/stats/game.py index ca8f27d..47268e5 100644 --- a/mlbstatsapi/models/stats/game.py +++ b/mlbstatsapi/models/stats/game.py @@ -1,72 +1,86 @@ -from dataclasses import dataclass, field -from typing import Optional, Union, List - +from typing import Optional, List, ClassVar +from pydantic import Field +from mlbstatsapi.models.base import MLBBaseModel from .stats import Split -@dataclass -class SimpleGameStats: + +class SimpleGameStats(MLBBaseModel): """ - A class to represent a simple game statistics + A class to represent simple game statistics. Attributes ---------- - firstdateplayed : str - first date of game played - gamesplayed : int - number of the games player - gamesstarted : int - number of the games started - lastdateplayed : str - last date of the game played + first_date_played : str + First date of game played. + games_played : int + Number of the games played. + games_started : int + Number of the games started. + last_date_played : str + Last date of the game played. """ - firstdateplayed: str - gamesplayed: int - gamesstarted: int - lastdateplayed: str + first_date_played: str = Field(alias="firstdateplayed") + games_played: int = Field(alias="gamesplayed") + games_started: int = Field(alias="gamesstarted") + last_date_played: str = Field(alias="lastdateplayed") - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True) class SeasonGame(Split): """ - A class to represent a game statistic + A class to represent a game statistic. Used for the following stat types: - season, career, careerRegularSeason, careerPlayoffs, statsSingleSeason + season, statsSingleSeason """ - type_ = ['season', 'statsSingleSeason'] + _type: ClassVar[List[str]] = ['season', 'statsSingleSeason'] - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True) class CareerGame(Split): """ - A class to represent a game statistic + A class to represent a career game statistic. """ - type_ = ['career'] + _type: ClassVar[List[str]] = ['career'] - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True) -class CareerRegularSeasonGame(Split, SimpleGameStats): +class CareerRegularSeasonGame(Split): """ - A class to represent a game statistic + A class to represent a career regular season game statistic. + + Attributes + ---------- + first_date_played : str + First date of game played. + games_played : int + Number of the games played. + games_started : int + Number of the games started. + last_date_played : str + Last date of the game played. """ - type_ = ['careerRegularSeason'] + _type: ClassVar[List[str]] = ['careerRegularSeason'] + first_date_played: Optional[str] = Field(default=None, alias="firstdateplayed") + games_played: Optional[int] = Field(default=None, alias="gamesplayed") + games_started: Optional[int] = Field(default=None, alias="gamesstarted") + last_date_played: Optional[str] = Field(default=None, alias="lastdateplayed") - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True) -class CareerPlayoffsGame(Split, SimpleGameStats): +class CareerPlayoffsGame(Split): """ - A class to represent a game statistic + A class to represent a career playoffs game statistic. + Attributes + ---------- + first_date_played : str + First date of game played. + games_played : int + Number of the games played. + games_started : int + Number of the games started. + last_date_played : str + Last date of the game played. """ - type_ = ['careerPlayoffs'] - - def __post_init__(self): - super().__post_init__() \ No newline at end of file + _type: ClassVar[List[str]] = ['careerPlayoffs'] + first_date_played: Optional[str] = Field(default=None, alias="firstdateplayed") + games_played: Optional[int] = Field(default=None, alias="gamesplayed") + games_started: Optional[int] = Field(default=None, alias="gamesstarted") + last_date_played: Optional[str] = Field(default=None, alias="lastdateplayed") diff --git a/mlbstatsapi/models/stats/hitting.py b/mlbstatsapi/models/stats/hitting.py index 790f5c9..a7b822a 100644 --- a/mlbstatsapi/models/stats/hitting.py +++ b/mlbstatsapi/models/stats/hitting.py @@ -1,957 +1,919 @@ -from dataclasses import dataclass, field -from typing import Optional, Union, List - +from typing import Optional, List, Any, ClassVar +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person, Position, Batter, Pitcher from mlbstatsapi.models.teams import Team from mlbstatsapi.models.game import Game -from mlbstatsapi.mlb_module import merge_keys -from mlbstatsapi.models.data import ( - PitchData, - HitData, - Count, - PlayDetails -) -from .stats import ( - Sabermetrics, - ExpectedStatistics, - Split -) -@dataclass(repr=False) -class AdvancedHittingSplit: - """ - A class to represent a advanced hitting statistics - - Attributes - ---------- - age: int - player age at the beginning of the season - plateappearances : int +from mlbstatsapi.models.data import PitchData, HitData, Count, PlayDetails +from .stats import Sabermetrics, ExpectedStatistics, Split + + +class AdvancedHittingSplit(MLBBaseModel): + """ + A class to represent advanced hitting statistics. + + Attributes + ---------- + age : int + Player age at the beginning of the season. + plate_appearances : int The number of plate appearances. - totalbases : int + total_bases : int The total number of bases. - leftonbase : int + left_on_base : int The amount of runners left on base. - sacbunts : int + sac_bunts : int The amount of sac bunts. - sacflies : int + sac_flies : int The amount of sac flies. babip : str Batting Average on Balls in Play. - see here: https://www.mlb.com/glossary/advanced-stats/babip - extrabasehits : int - The amount of extra base hits. e.g doubles, triples, homeruns - see here: https://www.mlb.com/glossary/standard-stats/extra-base-hit - hitbypitch : int + extra_base_hits : int + The amount of extra base hits. + hit_by_pitch : int The amount of times the batter has been hit by a pitch. gidp : int The amount of hits that lead to a double play. - see here: https://www.mlb.com/glossary/standard-stats/ground-into-double-play - gidpopp : int - The amount of GIDP opportunities. - numberofpitches : int + gidp_opp : int + The amount of GIDP opportunities. + number_of_pitches : int The number of pitches the batter has faced. - see here: https://www.mlb.com/glossary/standard-stats/number-of-pitches - pitchesperplateappearance : str - The avg amount of pitches per plate appearance for the hitter. - see here: https://www.mlb.com/glossary/advanced-stats/pitches-per-plate-appearance - walksperplateappearance : str + pitches_per_plate_appearance : str + The avg amount of pitches per plate appearance. + walks_per_plate_appearance : str The avg walks per plate appearance. - see here: https://www.mlb.com/glossary/advanced-stats/walk-rate - strikeoutsperplateappearance : str + strikeouts_per_plate_appearance : str The amount of strike outs per plate appearance. - see here: https://www.mlb.com/glossary/advanced-stats/plate-appearances-per-strikeout - homerunsperplateappearance : str + home_runs_per_plate_appearance : str The amount of home runs per plate appearance. - see here: https://en.wikipedia.org/wiki/At_bats_per_home_run - walksperstrikeout : str + walks_per_strikeout : str The amount of walks per strike out. - see here: https://www.mlb.com/glossary/advanced-stats/strikeout-to-walk-ratio iso : str - Isolasted power. - see also: https://www.mlb.com/glossary/advanced-stats/isolated-power - reachedonerror : int - The amount of times the batter has reached base on a error. - see also: https://www.mlb.com/glossary/standard-stats/reached-on-error + Isolated power. + reached_on_error : int + The amount of times the batter has reached base on an error. walkoffs : int The amount of times the batter has walked off a game. - see also: https://www.mlb.com/glossary/standard-stats/walk-off flyouts : int The amount of flyouts for the batter. - see also: https://www.mlb.com/glossary/standard-stats/flyout - totalswings : int - The amount of swings the batter has taken at the plate. - swingandmisses : int - The amount of swing and misses the batter has taken at the plate. - ballsinplay : int + total_swings : int + The amount of swings the batter has taken. + swing_and_misses : int + The amount of swing and misses. + balls_in_play : int The amount of balls the batter has put in play. popouts : int - The amount of popouts the batter has put in play. + The amount of popouts. lineouts : int - The amount of lineouts the batter has put in play. + The amount of lineouts. groundouts : int - The amount of groundouts the batter has hit into. - flyhits : int - The amount of fly hits the batter has hit. - pophits : int - The amount of pop hits the batter has hit. - groundhits : int - The amount of ground hits the batter has hit. - linehits : int - The amount of line hits the batter has hit. + The amount of groundouts. + fly_hits : int + The amount of fly hits. + pop_hits : int + The amount of pop hits. + ground_hits : int + The amount of ground hits. + line_hits : int + The amount of line hits. """ age: Optional[int] = None - plateappearances: Optional[int] = None - totalbases: Optional[int] = None - leftonbase: Optional[int] = None - sacbunts: Optional[int] = None - sacflies: Optional[int] = None + plate_appearances: Optional[int] = Field(default=None, alias="plateappearances") + total_bases: Optional[int] = Field(default=None, alias="totalbases") + left_on_base: Optional[int] = Field(default=None, alias="leftonbase") + sac_bunts: Optional[int] = Field(default=None, alias="sacbunts") + sac_flies: Optional[int] = Field(default=None, alias="sacflies") babip: Optional[str] = None - extrabasehits: Optional[int] = None - hitbypitch: Optional[int] = None + extra_base_hits: Optional[int] = Field(default=None, alias="extrabasehits") + hit_by_pitch: Optional[int] = Field(default=None, alias="hitbypitch") gidp: Optional[int] = None - gidpopp: Optional[int] = None - numberofpitches: Optional[int] = None - pitchesperplateappearance: Optional[str] = None - walksperplateappearance: Optional[str] = None - strikeoutsperplateappearance: Optional[str] = None - homerunsperplateappearance: Optional[str] = None - walksperstrikeout: Optional[str] = None + gidp_opp: Optional[int] = Field(default=None, alias="gidpopp") + number_of_pitches: Optional[int] = Field(default=None, alias="numberofpitches") + pitches_per_plate_appearance: Optional[str] = Field(default=None, alias="pitchesperplateappearance") + walks_per_plate_appearance: Optional[str] = Field(default=None, alias="walksperplateappearance") + strikeouts_per_plate_appearance: Optional[str] = Field(default=None, alias="strikeoutsperplateappearance") + home_runs_per_plate_appearance: Optional[str] = Field(default=None, alias="homerunsperplateappearance") + walks_per_strikeout: Optional[str] = Field(default=None, alias="walksperstrikeout") iso: Optional[str] = None - reachedonerror: Optional[int] = None + reached_on_error: Optional[int] = Field(default=None, alias="reachedonerror") walkoffs: Optional[int] = None flyouts: Optional[int] = None - totalswings: Optional[int] = None - swingandmisses: Optional[int] = None - ballsinplay: Optional[int] = None + total_swings: Optional[int] = Field(default=None, alias="totalswings") + swing_and_misses: Optional[int] = Field(default=None, alias="swingandmisses") + balls_in_play: Optional[int] = Field(default=None, alias="ballsinplay") popouts: Optional[int] = None lineouts: Optional[int] = None groundouts: Optional[int] = None - flyhits: Optional[int] = None - pophits: Optional[int] = None - groundhits: Optional[int] = None - linehits: Optional[int] = None - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(repr=False) -class SimpleHittingSplit: - """ - A class to represent a simple hitting statistics - age: int - players age at the beginning of the season - gamesplayed : int + fly_hits: Optional[int] = Field(default=None, alias="flyhits") + pop_hits: Optional[int] = Field(default=None, alias="pophits") + ground_hits: Optional[int] = Field(default=None, alias="groundhits") + line_hits: Optional[int] = Field(default=None, alias="linehits") + + +class SimpleHittingSplit(MLBBaseModel): + """ + A class to represent simple hitting statistics. + + Attributes + ---------- + age : int + Player's age at the beginning of the season. + games_played : int The number of games played by the batter. flyouts : int - The number flyouts hit by the batter + The number of flyouts hit by the batter. groundouts : int The amount of groundouts hit by the batter. airouts : int - The amount of air hits by the batter. + The amount of air outs by the batter. runs : int The amount of runs plated by the batter. doubles : int The amount of doubles hit by the batter. triples : int The amount of triples hit by the batter. - homeruns : int - The amount of homeruns hit by the batter. + home_runs : int + The amount of home runs hit by the batter. strikeouts : int The amount of strikeouts for the batter. - baseonballs : int - The amount of base on balls (walks) for the batter. - intentionalwalks : int + base_on_balls : int + The amount of base on balls (walks) for the batter. + intentional_walks : int The number of intentional walks for the batter. hits : int The number of hits for the batter. - hitbypitch : int + hit_by_pitch : int The number of pitches the batter has been hit by. avg : str The batting avg of the batter. - atbats : int + at_bats : int The number of at bats of the batter. obp : str The on base percentage of the batter. - see also: https://www.mlb.com/glossary/standard-stats/on-base-percentage slg : str The slugging percentage of the batter. - see also: https://www.mlb.com/glossary/standard-stats/slugging-percentage ops : str - The on-base plug slugging of the batter. - see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging - caughtstealing : int + The on-base plus slugging of the batter. + caught_stealing : int The amount of times the batter has been caught stealing. - stolenbases : int - The amount of stolen bases acheived by the batter. - stolenbasepercentage : int + caught_stealing_percentage : str + The caught stealing percentage. + stolen_bases : int + The amount of stolen bases achieved by the batter. + stolen_base_percentage : int The stolen base percentage of the batter. - groundintodoubleplay : int + ground_into_double_play : int The number of times the batter has hit into a double play. - groundintotripleplay : int + ground_into_triple_play : int The number of times the batter has hit into a triple play. - numberofpitches : int - The number of pitches the batter has faced. - plateappearances : int - The number of plate appearances of the batter. - totalbases : int - The number of bases acheived by batter. + number_of_pitches : int + The number of pitches the batter has faced. + plate_appearances : int + The number of plate appearances of the batter. + total_bases : int + The number of bases achieved by batter. rbi : int The number of Runs Batted In by the batter. - see also: https://www.mlb.com/glossary/standard-stats/runs-batted-in - leftonbase : int + left_on_base : int The number of runners left on base by the batter. - sacbunts : int + sac_bunts : int The number of sac bunts performed by the batter. - sacflies : int + sac_flies : int The number of sac flies performed by the batter. babip : str The batting average of balls in play of the batter. - see also: https://www.mlb.com/glossary/advanced-stats/babip - groundoutstoairouts : int + groundouts_to_airouts : int The groundout-to-airout ratio of the batter. - see also: https://www.mlb.com/glossary/advanced-stats/babip - catchersinterference : int + catchers_interference : int The number of times the batter has reached base due to catchers interference. - see also: https://www.mlb.com/glossary/rules/catcher-interference - atbatsperhomerun : int - The number of bats per home run of the batter. + at_bats_per_home_run : int + The number of at bats per home run of the batter. """ age: Optional[int] = None - gamesplayed: Optional[int] = None + games_played: Optional[int] = Field(default=None, alias="gamesplayed") flyouts: Optional[int] = None groundouts: Optional[int] = None airouts: Optional[int] = None runs: Optional[int] = None doubles: Optional[int] = None triples: Optional[int] = None - homeruns: Optional[int] = None + home_runs: Optional[int] = Field(default=None, alias="homeruns") strikeouts: Optional[int] = None - baseonballs: Optional[int] = None - intentionalwalks: Optional[int] = None + base_on_balls: Optional[int] = Field(default=None, alias="baseonballs") + intentional_walks: Optional[int] = Field(default=None, alias="intentionalwalks") hits: Optional[int] = None - hitbypitch: Optional[int] = None + hit_by_pitch: Optional[int] = Field(default=None, alias="hitbypitch") avg: Optional[str] = None - atbats: Optional[int] = None + at_bats: Optional[int] = Field(default=None, alias="atbats") obp: Optional[str] = None slg: Optional[str] = None ops: Optional[str] = None - caughtstealing: Optional[int] = None - caughtstealingpercentage: Optional[str] = None - stolenbases: Optional[int] = None - stolenbasepercentage: Optional[int] = None - groundintodoubleplay: Optional[int] = None - groundintotripleplay: Optional[int] = None - numberofpitches: Optional[int] = None - plateappearances: Optional[int] = None - totalbases: Optional[int] = None + caught_stealing: Optional[int] = Field(default=None, alias="caughtstealing") + caught_stealing_percentage: Optional[str] = Field(default=None, alias="caughtstealingpercentage") + stolen_bases: Optional[int] = Field(default=None, alias="stolenbases") + stolen_base_percentage: Optional[str] = Field(default=None, alias="stolenbasepercentage") + ground_into_double_play: Optional[int] = Field(default=None, alias="groundintodoubleplay") + ground_into_triple_play: Optional[int] = Field(default=None, alias="groundintotripleplay") + number_of_pitches: Optional[int] = Field(default=None, alias="numberofpitches") + plate_appearances: Optional[int] = Field(default=None, alias="plateappearances") + total_bases: Optional[int] = Field(default=None, alias="totalbases") rbi: Optional[int] = None - leftonbase: Optional[int] = None - sacbunts: Optional[int] = None - sacflies: Optional[int] = None + left_on_base: Optional[int] = Field(default=None, alias="leftonbase") + sac_bunts: Optional[int] = Field(default=None, alias="sacbunts") + sac_flies: Optional[int] = Field(default=None, alias="sacflies") babip: Optional[str] = None - groundoutstoairouts: Optional[int] = None - catchersinterference: Optional[int] = None - atbatsperhomerun: Optional[int] = None + groundouts_to_airouts: Optional[str] = Field(default=None, alias="groundoutstoairouts") + catchers_interference: Optional[int] = Field(default=None, alias="catchersinterference") + at_bats_per_home_run: Optional[str] = Field(default=None, alias="atbatsperhomerun") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) -@dataclass(kw_only=True, repr=False) class HittingWinLoss(Split): """ - A class to represent a hitting winLoss statistic + A class to represent a hitting winLoss statistic. Attributes ---------- - iswin : bool - the bool to hold if a win or not for hitting winLoss - stat : dict - the hitting split stat + is_win : bool + The bool to hold if a win or not. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['winLoss'] - iswin: bool - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLoss'] + is_win: bool = Field(alias="iswin") + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingWinLossPlayoffs(Split): """ - A class to represent a hitting winLossPlayoffs statistic + A class to represent a hitting winLossPlayoffs statistic. Attributes ---------- - iswin : bool - the bool to hold if a win or not for hitting winLoss - stat : dict - the hitting split stat + is_win : bool + The bool to hold if a win or not. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['winLossPlayoffs'] - iswin: bool - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLossPlayoffs'] + is_win: bool = Field(alias="iswin") + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingHomeAndAway(Split): """ - A class to represent a hitting homeAndAway statistic + A class to represent a hitting homeAndAway statistic. Attributes ---------- - ishome : bool - the bool to hold if it ishome hitting homeAndAway - stat : dict - the hitting split stat + is_home : bool + The bool to hold if it is home. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['homeAndAway'] - ishome: bool - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['homeAndAway'] + is_home: bool = Field(alias="ishome") + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingHomeAndAwayPlayoffs(Split): """ - A class to represent a hitting homeAndAway Playoff statistic + A class to represent a hitting homeAndAway Playoff statistic. Attributes ---------- - ishome : bool - the bool to hold if it ishome hitting homeAndAway - stat : dict - the hitting split stat + is_home : bool + The bool to hold if it is home. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['homeAndAwayPlayoffs'] - ishome: bool - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['homeAndAwayPlayoffs'] + is_home: bool = Field(alias="ishome") + stat: SimpleHittingSplit + -@dataclass(kw_only=True, repr=False) class HittingCareer(Split): """ - A class to represent a hitting career, careerRegularSeason or careerPlayoffs statistic + A class to represent a hitting career, careerRegularSeason or careerPlayoffs statistic. Attributes ---------- - stat : dict - the hitting split stat + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['career', 'careerRegularSeason', 'careerPlayoffs'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - super().__post_init__() - self.stat = SimpleHittingSplit(**self.stat) + _stat: ClassVar[List[str]] = ['career', 'careerRegularSeason', 'careerPlayoffs'] + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingSeason(Split): """ - A class to represent a hitting season statistic + A class to represent a hitting season statistic. Attributes ---------- - stat : dict - the hitting split stat + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['season'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - super().__post_init__() - self.stat = SimpleHittingSplit(**self.stat) + _stat: ClassVar[List[str]] = ['season'] + stat: SimpleHittingSplit + -@dataclass(kw_only=True, repr=False) class HittingSingleSeason(Split): """ - A class to represent a hitting statsSingleSeason statistic + A class to represent a hitting statsSingleSeason statistic. Attributes ---------- - stat : dict - the hitting split stat + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['statsSingleSeason'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - super().__post_init__() - self.stat = SimpleHittingSplit(**self.stat) + _stat: ClassVar[List[str]] = ['statsSingleSeason'] + stat: SimpleHittingSplit + -@dataclass(kw_only=True, repr=False) class HittingSeasonAdvanced(Split): """ - A class to represent a hitting seasonAdvanced statistic + A class to represent a hitting seasonAdvanced statistic. Attributes ---------- - stat : dict - the hitting split stat + stat : AdvancedHittingSplit + The hitting split stat. """ - _stat = ['seasonAdvanced'] - stat: Union[AdvancedHittingSplit, dict] + _stat: ClassVar[List[str]] = ['seasonAdvanced'] + stat: AdvancedHittingSplit - def __post_init__(self): - super().__post_init__() - self.stat = AdvancedHittingSplit(**self.stat) -@dataclass(kw_only=True, repr=False) class HittingCareerAdvanced(Split): """ - A class to represent a hitting season statistic + A class to represent a hitting careerAdvanced statistic. Attributes ---------- + stat : AdvancedHittingSplit + The hitting split stat. """ - _stat = ['careerAdvanced'] - stat: Union[AdvancedHittingSplit, dict] + _stat: ClassVar[List[str]] = ['careerAdvanced'] + stat: AdvancedHittingSplit - def __post_init__(self): - self.stat = AdvancedHittingSplit(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class HittingYearByYear(Split): """ - A class to represent a hitting yearbyyear or yearByYearPlayoffs statistic + A class to represent a hitting yearByYear statistic. Attributes ---------- + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['yearByYear'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYear'] + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingYearByYearPlayoffs(Split): """ - A class to represent a hitting yearByYearPlayoffs statistic + A class to represent a hitting yearByYearPlayoffs statistic. Attributes ---------- + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['yearByYearPlayoffs'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYearPlayoffs'] + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class OpponentsFacedHitting(Split): """ - A class to represent a hitting opponentsFaced statistic + A class to represent a hitting opponentsFaced statistic. Attributes ---------- - batter : Person - the batter of that stat object - fieldingteam : Team - the defence team of the stat object - pitcher : Person - the pitcher of that stat object + batter : Batter + The batter of that stat object. + fielding_team : Team + The defence team of the stat object. + pitcher : Pitcher + The pitcher of that stat object. group : str - stat group + Stat group. """ - _stat = ['opponentsFaced'] + _stat: ClassVar[List[str]] = ['opponentsFaced'] group: str - batter: Union[Batter, dict] - fieldingteam: Union[Team, dict] - pitcher: Union[Pitcher, dict] + batter: Batter + fielding_team: Team = Field(alias="fieldingteam") + pitcher: Pitcher - def __post_init__(self): - super().__post_init__() -@dataclass(kw_only=True, repr=False) class HittingSabermetrics(Split): """ - A class to represent a hitting sabermetric statistic + A class to represent a hitting sabermetric statistic. + Attributes + ---------- + stat : Sabermetrics + The sabermetric statistics. """ - _stat = ['sabermetrics'] - stat: Union[Sabermetrics, dict] + _stat: ClassVar[List[str]] = ['sabermetrics'] + stat: Sabermetrics - def __post_init__(self): - self.stat = Sabermetrics(**self.stat) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class HittingGameLog(Split): """ - A class to represent a gamelog stat for a hitter + A class to represent a gamelog stat for a hitter. Attributes ---------- - positionsplayed : List[Position] + positions_played : List[Position] + List of positions played. stat : SimpleHittingSplit - ishome : bool - bool to hold ishome - iswin : bool - bool to hold iswin + The hitting split stat. + is_home : bool + Bool to hold is home. + is_win : bool + Bool to hold is win. game : Game - Game of the log + Game of the log. date : str - date of the log - gametype : str - type of game + Date of the log. opponent : Team - Team of the opponent - sport : Sport - Sport of the stat - league : League - League of the stat - player : Person - Player of the stat - """ - ishome: bool - iswin: bool - game: Union[Game, dict] + Team of the opponent. + """ + _stat: ClassVar[List[str]] = ['gameLog'] + is_home: bool = Field(alias="ishome") + is_win: bool = Field(alias="iswin") + game: Game date: str - opponent: Union[Team, dict] - _stat = ['gameLog'] - positionsplayed: Optional[List[Position]] = field(default_factory=list) - stat: Union[SimpleHittingSplit, dict] = field(default_factory=dict) - - def __post_init__(self): - if self.positionsplayed: - self.positionsplayed = [Position(**position) for position in self.positionsplayed] - self.stat = SimpleHittingSplit(**self.stat) if self.stat else self.stat - super().__post_init__() - -@dataclass(kw_only=True, repr=False) -class HittingPlay: - """ - A class to represent a gamelog stat for a hitter - - Attributes - ---------- - """ - details: Union[PlayDetails, dict] - count: Union[Count, dict] - ispitch: bool - pitchnumber: Optional[int] = None - atbatnumber: Optional[int] = None - index: Optional[str] = None - playid: Optional[str] = None - pitchdata: Optional[Union[PitchData, dict]] = field(default_factory=dict) - hitdata: Optional[Union[HitData, dict]] = field(default_factory=dict) - starttime: Optional[str] = None - endtime: Optional[str] = None + opponent: Team + positions_played: Optional[List[Position]] = Field(default=[], alias="positionsplayed") + stat: Optional[SimpleHittingSplit] = None + + @field_validator('stat', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + +class HittingPlay(MLBBaseModel): + """ + A class to represent a play stat for a hitter. + + Attributes + ---------- + details : PlayDetails + Play details. + count : Count + The count. + is_pitch : bool + Is pitch bool. + pitch_number : int + Pitch number. + at_bat_number : int + At bat number. + index : str + Index. + play_id : str + Play ID. + pitch_data : PitchData + Pitch data. + hit_data : HitData + Hit data. + start_time : str + Start time. + end_time : str + End time. + type : str + Type. + """ + details: PlayDetails + count: Count + is_pitch: bool = Field(alias="ispitch") + pitch_number: Optional[int] = Field(default=None, alias="pitchnumber") + at_bat_number: Optional[int] = Field(default=None, alias="atbatnumber") + index: Optional[int] = None + play_id: Optional[str] = Field(default=None, alias="playid") + pitch_data: Optional[PitchData] = Field(default=None, alias="pitchdata") + hit_data: Optional[HitData] = Field(default=None, alias="hitdata") + start_time: Optional[str] = Field(default=None, alias="starttime") + end_time: Optional[str] = Field(default=None, alias="endtime") type: Optional[str] = None + @field_validator('pitch_data', 'hit_data', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - def __post_init__(self): - self.details = PlayDetails(**self.details) - self.count = Count(**self.count) - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(kw_only=True, repr=False) class HittingPlayLog(Split): """ - A class to represent a gamelog stat for a hitter + A class to represent a play log stat for a hitter. Attributes ---------- - season : str - season for the stat + stat : HittingPlay + Information regarding the play for the stat. opponent : Team - opponent + Opponent. date : str - date of log - gametype : str - game type code - ishome : bool - is the game at home bool - pitcher : Person - pitcher of the log - batter : Person - batter of the log + Date of log. + is_home : bool + Is the game at home bool. + pitcher : Pitcher + Pitcher of the log. + batter : Batter + Batter of the log. game : Game - the game of the log - + The game of the log. """ - stat: Union[HittingPlay, dict] - opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + _stat: ClassVar[List[str]] = ['playLog'] + stat: HittingPlay + opponent: Optional[Team] = None date: Optional[str] = None - ishome: Optional[bool] = None - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - game: Optional[Union[Game, dict]] = field(default_factory=dict) - - _stat = ['playLog'] + is_home: Optional[bool] = Field(default=None, alias="ishome") + pitcher: Optional[Pitcher] = None + batter: Optional[Batter] = None + game: Optional[Game] = None + + @field_validator('stat', mode='before') + @classmethod + def extract_play(cls, v: Any) -> Any: + """Extract play from stat dict.""" + if isinstance(v, dict) and 'play' in v: + return v['play'] + return v + + @field_validator('opponent', 'pitcher', 'batter', 'game', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - def __post_init__(self): - self.stat = HittingPlay(**self.stat['play']) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class HittingPitchLog(Split): """ - A class to represent a gamelog stat for a hitter + A class to represent a pitch log stat for a hitter. Attributes ---------- - season : str - season for the stat - stat : PlayLog - information regarding the play for the stat + stat : HittingPlay + Information regarding the play for the stat. opponent : Team - opponent + Opponent. date : str - date of log - gametype : str - game type code - ishome : bool - is the game at home bool - pitcher : Person - pitcher of the log - batter : Person - batter of the log + Date of log. + is_home : bool + Is the game at home bool. + pitcher : Pitcher + Pitcher of the log. + batter : Batter + Batter of the log. game : Game - the game of the log - + The game of the log. + play_id : str + Play ID. """ - stat: PlayDetails - _stat = ['pitchLog'] - opponent: Union[Team, dict] + _stat: ClassVar[List[str]] = ['pitchLog'] + stat: HittingPlay + opponent: Team date: str - ishome: bool - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - game: Union[Game, dict] - playid: Optional[str] = None + is_home: bool = Field(alias="ishome") + pitcher: Pitcher + batter: Batter + game: Game + play_id: Optional[str] = Field(default=None, alias="playid") - def __post_init__(self): - self.stat = HittingPlay(**self.stat['play']) - super().__post_init__() + @field_validator('stat', mode='before') + @classmethod + def extract_play(cls, v: Any) -> Any: + """Extract play from stat dict.""" + if isinstance(v, dict) and 'play' in v: + return v['play'] + return v -@dataclass(kw_only=True, repr=False) class HittingLastXGames(Split): """ - A class to represent a lastXGames statistic + A class to represent a lastXGames statistic. Attributes ---------- + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['lastXGames'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['lastXGames'] + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingDateRange(Split): """ - A class to represent a byDateRange statistic + A class to represent a byDateRange statistic. Attributes ---------- + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['byDateRange'] - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDateRange'] + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingDateRangeAdvanced(Split): """ - A class to represent a byDateRangeAdvanced statistic + A class to represent a byDateRangeAdvanced statistic. Attributes ---------- + stat : AdvancedHittingSplit + The hitting split stat. """ - _stat = ['byDateRangeAdvanced'] - stat: Union[AdvancedHittingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDateRangeAdvanced'] + stat: AdvancedHittingSplit -@dataclass(kw_only=True, repr=False) class HittingDateRangeAdvancedByMonth(Split): """ - A class to represent a byDateRangeAdvanced statistic + A class to represent a byDateRangeAdvanced by month statistic. Attributes ---------- + stat : AdvancedHittingSplit + The hitting split stat. """ - _stat = ['byDateRangeAdvancedbyMonth'] - stat: Union[AdvancedHittingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDateRangeAdvancedbyMonth'] + stat: AdvancedHittingSplit -@dataclass(kw_only=True, repr=False) class HittingByMonth(Split): """ - A class to represent a byMonth hitting statistic + A class to represent a byMonth hitting statistic. Attributes ---------- + month : int + The month. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['byMonth'] + _stat: ClassVar[List[str]] = ['byMonth'] month: int - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingByMonthPlayoffs(Split): """ - A class to represent a yearByYear hitting statistic + A class to represent a byMonthPlayoffs hitting statistic. Attributes ---------- - month : str - the month of the stat + month : int + The month of the stat. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['byMonthPlayoffs'] + _stat: ClassVar[List[str]] = ['byMonthPlayoffs'] month: int - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingDayOfWeek(Split): """ - A class to represent a yearByYear hitting statistic + A class to represent a byDayOfWeek hitting statistic. Attributes ---------- - dayofweek : int - the day of the week + day_of_week : int + The day of the week. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['byDayOfWeek'] - dayofweek: int - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) + _stat: ClassVar[List[str]] = ['byDayOfWeek'] + day_of_week: int = Field(alias="dayofweek") + stat: SimpleHittingSplit -@dataclass(kw_only=True, repr=False) class HittingDayOfWeekPlayoffs(Split): """ - A class to represent a yearByYear hitting statistic + A class to represent a byDayOfWeekPlayoffs hitting statistic. Attributes ---------- - dayofweek : int - the day of the week + day_of_week : int + The day of the week. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['byDayOfWeekPlayoffs'] - dayofweek: int - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDayOfWeekPlayoffs'] + day_of_week: int = Field(alias="dayofweek") + stat: SimpleHittingSplit + -@dataclass(kw_only=True, repr=False) class HittingExpectedStatistics(Split): """ - A class to represent a excepted statistics statType: expectedStatistics. + A class to represent expected statistics statType: expectedStatistics. + Attributes ---------- - avg : str - slg : str - woba : str - wobaCon : str - rank : int + stat : ExpectedStatistics + The expected statistics. """ - _stat = ['expectedStatistics'] - stat: Union[ExpectedStatistics, dict] - - def __post_init__(self): - self.stat = ExpectedStatistics(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['expectedStatistics'] + stat: ExpectedStatistics -@dataclass(kw_only=True, repr=False) class HittingVsTeam(Split): """ - A class to represent a vsTeam hitting statistic + A class to represent a vsTeam hitting statistic. + + Requires the use of the opposingTeamId parameter. - requires the use of the opposingTeamId parameter Attributes ---------- - dayofweek : int - the day of the week + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['vsTeam'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['vsTeam'] + opponent: Team + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + -@dataclass(kw_only=True, repr=False) class HittingVsTeamTotal(Split): """ - A class to represent a vsTeamTotal hitting statistic + A class to represent a vsTeamTotal hitting statistic. - requires the use of the opposingTeamId parameter + Requires the use of the opposingTeamId parameter. Attributes ---------- + opponent : Team + Opponent team. + batter : Batter + Batting person. + pitcher : Pitcher + Pitching person. + stat : SimpleHittingSplit + The hitting split stat. + """ + _stat: ClassVar[List[str]] = ['vsTeamTotal'] opponent: Team - opponent team - batter: Person - batting person - pitcher: Person - pitching person - """ - _stat = ['vsTeamTotal'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + class HittingVsTeam5Y(Split): """ - A class to represent a vsTeam5Y hitting statistic + A class to represent a vsTeam5Y hitting statistic. - requires the use of the opposingTeamId parameter + Requires the use of the opposingTeamId parameter. Attributes ---------- + opponent : Team + Opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimpleHittingSplit + The hitting split stat. """ - _stat = ['vsTeam5Y'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['vsTeam5Y'] + opponent: Team + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + -@dataclass(kw_only=True, repr=False) class HittingVsPlayer(Split): """ - A class to represent a yearByYear hitting statistic + A class to represent a vsPlayer hitting statistic. - This class is for the stat type vsPlayer* - - Requires the param opposingPlayerId set + Requires the param opposingPlayerId set. Attributes ---------- pitcher : Pitcher - The pitcher of the hitting vsplayer stat + The pitcher of the hitting vsplayer stat. batter : Batter - The batter of the hitting vsplayer stat + The batter of the hitting vsplayer stat. opponent : Team - The team of the hitting vsplayer stat - """ - _stat = ['vsPlayer'] - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - opponent: Optional[Union[Team, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) -class HittingVsPlayerTotal(Split): + The team of the hitting vsplayer stat. + stat : SimpleHittingSplit + The hitting split stat. """ - A class to represent a yearByYear hitting statistic + _stat: ClassVar[List[str]] = ['vsPlayer'] + pitcher: Pitcher + batter: Batter + stat: SimpleHittingSplit + opponent: Optional[Team] = None - This class is for the stat type vsPlayer* + @field_validator('opponent', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - Requires the param opposingPlayerId set + +class HittingVsPlayerTotal(Split): + """ + A class to represent a vsPlayerTotal hitting statistic. + + Requires the param opposingPlayerId set. Attributes ---------- pitcher : Pitcher - The pitcher of the hitting vsplayer stat + The pitcher of the hitting vsplayer stat. batter : Batter - The batter of the hitting vsplayer stat + The batter of the hitting vsplayer stat. opponent : Team - The team of the hitting vsplayer stat - """ - _stat = ['vsPlayerTotal'] - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - opponent: Optional[Union[Team, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() - -@dataclass(kw_only=True, repr=False) -class HittingVsPlayer5Y(Split): + The team of the hitting vsplayer stat. + stat : SimpleHittingSplit + The hitting split stat. """ - A class to represent a yearByYear hitting statistic + _stat: ClassVar[List[str]] = ['vsPlayerTotal'] + pitcher: Pitcher + batter: Batter + stat: SimpleHittingSplit + opponent: Optional[Team] = None - This class is for the stat type vsPlayer* + @field_validator('opponent', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - Requires the param opposingPlayerId set + +class HittingVsPlayer5Y(Split): + """ + A class to represent a vsPlayer5Y hitting statistic. + + Requires the param opposingPlayerId set. Attributes ---------- pitcher : Pitcher - The pitcher of the hitting vsplayer stat - batter : Batter - The batter of the hitting vsplayer stat + The pitcher of the hitting vsplayer stat. + batter : Person + The batter of the hitting vsplayer stat. opponent : Team - The team of the hitting vsplayer stat - """ - _stat = ['vsPlayer5Y'] - pitcher: Union[Pitcher, dict] - batter: Union[Person, dict] - opponent: Optional[Union[Team, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() + The team of the hitting vsplayer stat. + stat : SimpleHittingSplit + The hitting split stat. + """ + _stat: ClassVar[List[str]] = ['vsPlayer5Y'] + stat: SimpleHittingSplit + pitcher: Optional[Pitcher] = None + batter: Optional[Person] = None + opponent: Optional[Team] = None + + @field_validator('opponent', 'pitcher', 'batter', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/stats/pitching.py b/mlbstatsapi/models/stats/pitching.py index 2eb4a0c..92190fb 100644 --- a/mlbstatsapi/models/stats/pitching.py +++ b/mlbstatsapi/models/stats/pitching.py @@ -1,37 +1,28 @@ -from dataclasses import InitVar, dataclass, field -from typing import Optional, Union, List - +from typing import Optional, List, Any, ClassVar +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.people import Person, Pitcher, Batter from mlbstatsapi.models.teams import Team from mlbstatsapi.models.game import Game -from mlbstatsapi.mlb_module import merge_keys -from mlbstatsapi.models.data import ( - Count, - PlayDetails, - -) - +from mlbstatsapi.models.data import Count, PlayDetails from .stats import Sabermetrics, ExpectedStatistics, Split from .hitting import SimpleHittingSplit -@dataclass(repr=False) -class SimplePitchingSplit: +class SimplePitchingSplit(MLBBaseModel): """ - A class to represent a advanced pitching statistics - - attributes are all optional as there is no documentation for the stats endpoint + A class to represent simple pitching statistics. Attributes ---------- summary : str - Summary of stats for the pitcher - gamesplayed : int + Summary of stats for the pitcher. + games_played : int The games played by the pitcher. - gamesstarted : int + games_started : int The games started by the pitcher. flyouts : int - The number of flyouts for the pitcher + The number of flyouts for the pitcher. groundouts : int The number of groundouts for the pitcher. airouts : int @@ -42,42 +33,41 @@ class SimplePitchingSplit: The number of doubles given up by the pitcher. triples : int The number of triples given up by the pitcher. - homeruns : int + home_runs : int The number of home runs given up by the pitcher. strikeouts : int The number of strike outs performed by the pitcher. - baseonballs : int + base_on_balls : int The number of base on balls (walks) performed by the pitcher. - intentionalwalks : int + intentional_walks : int The number of intentional walks performed by the pitcher. hits : int The number of hits given up by the pitcher. - hitbypitch : int + hit_by_pitch : int The number of batters hit by the pitcher. - avg : int + avg : str The batting avg against the pitcher. - atbats : int + at_bats : int The at bats pitched by the pitcher. obp : str - The on base percentage again the pitcher. + The on base percentage against the pitcher. slg : str The slugging percentage against the pitcher. ops : str The on base slugging against the pitcher. - see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging - caughtstealing : int + caught_stealing : int The number of runners caught stealing against the pitcher. - stolenbases : int + stolen_bases : int The number of stolen bases while pitching. - stolenbasepercentage : int + stolen_base_percentage : str The stolen base percentage while pitching. - groundintodoubleplay : int - The number of hits against - numberofpitches : int + ground_into_double_play : int + The number of double plays hit into. + number_of_pitches : int The number of pitches thrown. era : str The earned run average of the pitcher. - inningspitched : int + innings_pitched : str The number of innings pitched by the pitcher. wins : int The number of wins by the pitcher. @@ -85,660 +75,593 @@ class SimplePitchingSplit: The number of losses by the pitcher. saves : int The number of saves by the pitcher. - saveopportunities : int + save_opportunities : int The number of save opportunities by the pitcher. holds : int The number of holds by the pitcher. - blownsaves : int + blown_saves : int The number of blown saves performed by the pitcher. - earnedruns : int + earned_runs : int The number of earned runs given up by the pitcher. whip : str The number of walks and hits per inning pitched. - see also: https://www.mlb.com/glossary/standard-stats/walks-and-hits-per-inning-pitched outs : int - The number of outs - gamespitched : int + The number of outs. + games_pitched : int The number of games pitched. - completegames : int + complete_games : int The number of complete games pitched. shutouts : int The number of shut outs pitched. strikes : int - The number of strikes thown by the pitcher. - hitbatsmen : int + The number of strikes thrown by the pitcher. + hit_batsmen : int The number of batters hit by a pitch. - strikepercentage : str + strike_percentage : str The strike percentage thrown by the pitcher. - wildpitches : int - The number of wild pitches thown by the pitcher. + wild_pitches : int + The number of wild pitches thrown by the pitcher. balks : int - The number of balks commited by the pitcher. - totalbases : int + The number of balks committed by the pitcher. + total_bases : int The total bases given up by the pitcher. pickoffs : int The number of pick offs performed by the pitcher. - winpercentage : str - The winpercentage of the pitcher. - groundoutstoairouts : str + win_percentage : str + The win percentage of the pitcher. + groundouts_to_airouts : str The groundout-to-airout ratio of the pitcher. - gamesfinished : int + games_finished : int The number of games finished by the pitcher. - pitchesperinning : str - The number of pitches thown per inning by the pitcher. - strikeoutsper9inn : str - The number of strike outs per 9 innings by the pitcher - strikeoutwalkratio : str + pitches_per_inning : str + The number of pitches thrown per inning by the pitcher. + strikeouts_per_9_inn : str + The number of strike outs per 9 innings by the pitcher. + strikeout_walk_ratio : str The strike out to walk ratio of the pitcher. - hitsper9inn : str + hits_per_9_inn : str The number of hits per 9 innings pitched. - walksper9inn : str + walks_per_9_inn : str The number of walks per 9 innings pitched. - homerunsper9 : str + home_runs_per_9 : str The number of home runs per 9 innings pitched. - runsscoredper9 : str + runs_scored_per_9 : str The number of runs scored per 9 innings pitched. - sacbunts : int + sac_bunts : int The number of sac bunts given up when pitched. - catchersinterference : int - The number of times a runner has reached from catchers interference while pitching. - battersfaced : int + catchers_interference : int + The number of times a runner has reached from catchers interference. + batters_faced : int The number of batters faced by the pitcher. - sacflies : int + sac_flies : int The number of sac flies given up by the pitcher. - inheritedrunnersscored : int + inherited_runners_scored : int The number of inherited runners scored by the pitcher. - inheritedrunners : int + inherited_runners : int The number of inherited runners for the pitcher. age : int The age of the pitcher. - caughtstealingpercentage : str + caught_stealing_percentage : str The caught stealing percentage for the pitcher. """ summary: Optional[str] = None age: Optional[int] = None - gamesplayed: Optional[int] = None - gamesstarted: Optional[int] = None + games_played: Optional[int] = Field(default=None, alias="gamesplayed") + games_started: Optional[int] = Field(default=None, alias="gamesstarted") flyouts: Optional[int] = None groundouts: Optional[int] = None airouts: Optional[int] = None runs: Optional[int] = None doubles: Optional[int] = None triples: Optional[int] = None - homeruns: Optional[int] = None + home_runs: Optional[int] = Field(default=None, alias="homeruns") strikeouts: Optional[int] = None - baseonballs: Optional[int] = None - intentionalwalks: Optional[int] = None + base_on_balls: Optional[int] = Field(default=None, alias="baseonballs") + intentional_walks: Optional[int] = Field(default=None, alias="intentionalwalks") hits: Optional[int] = None - hitbypitch: Optional[int] = None + hit_by_pitch: Optional[int] = Field(default=None, alias="hitbypitch") avg: Optional[str] = None - atbats: Optional[int] = None + at_bats: Optional[int] = Field(default=None, alias="atbats") obp: Optional[str] = None slg: Optional[str] = None ops: Optional[str] = None - caughtstealing: Optional[int] = None - caughtstealingpercentage: Optional[str]=None - stolenbases: Optional[int] = None - stolenbasepercentage: Optional[str] = None - groundintodoubleplay: Optional[int] = None - numberofpitches: Optional[int] = None + caught_stealing: Optional[int] = Field(default=None, alias="caughtstealing") + caught_stealing_percentage: Optional[str] = Field(default=None, alias="caughtstealingpercentage") + stolen_bases: Optional[int] = Field(default=None, alias="stolenbases") + stolen_base_percentage: Optional[str] = Field(default=None, alias="stolenbasepercentage") + ground_into_double_play: Optional[int] = Field(default=None, alias="groundintodoubleplay") + number_of_pitches: Optional[int] = Field(default=None, alias="numberofpitches") era: Optional[str] = None - inningspitched: Optional[str] = None + innings_pitched: Optional[str] = Field(default=None, alias="inningspitched") wins: Optional[int] = None losses: Optional[int] = None saves: Optional[int] = None - saveopportunities: Optional[int] = None + save_opportunities: Optional[int] = Field(default=None, alias="saveopportunities") holds: Optional[int] = None - blownsaves: Optional[int] = None - earnedruns: Optional[int] = None + blown_saves: Optional[int] = Field(default=None, alias="blownsaves") + earned_runs: Optional[int] = Field(default=None, alias="earnedruns") whip: Optional[str] = None outs: Optional[int] = None - gamespitched: Optional[int] = None - completegames: Optional[int] = None + games_pitched: Optional[int] = Field(default=None, alias="gamespitched") + complete_games: Optional[int] = Field(default=None, alias="completegames") shutouts: Optional[int] = None strikes: Optional[int] = None - strikepercentage: Optional[str] = None - hitbatsmen: Optional[int] = None + strike_percentage: Optional[str] = Field(default=None, alias="strikepercentage") + hit_batsmen: Optional[int] = Field(default=None, alias="hitbatsmen") balks: Optional[int] = None - wildpitches: Optional[int] = None + wild_pitches: Optional[int] = Field(default=None, alias="wildpitches") pickoffs: Optional[int] = None - totalbases: Optional[int] = None - groundoutstoairouts: Optional[str] = None - winpercentage: Optional[str] = None - pitchesperinning: Optional[str] = None - gamesfinished: Optional[int] = None - strikeoutwalkratio: Optional[str] = None - strikeoutsper9inn: Optional[str] = None - walksper9inn: Optional[str] = None - hitsper9inn: Optional[str] = None - runsscoredper9: Optional[str] = None - homerunsper9: Optional[str] = None - catchersinterference: Optional[int] = None - sacbunts: Optional[int] = None - sacflies: Optional[int] = None - battersfaced: Optional[int] = None - inheritedrunners: Optional[int] = None - inheritedrunnersscored: Optional[int] = None + total_bases: Optional[int] = Field(default=None, alias="totalbases") + groundouts_to_airouts: Optional[str] = Field(default=None, alias="groundoutstoairouts") + win_percentage: Optional[str] = Field(default=None, alias="winpercentage") + pitches_per_inning: Optional[str] = Field(default=None, alias="pitchesperinning") + games_finished: Optional[int] = Field(default=None, alias="gamesfinished") + strikeout_walk_ratio: Optional[str] = Field(default=None, alias="strikeoutwalkratio") + strikeouts_per_9_inn: Optional[str] = Field(default=None, alias="strikeoutsper9inn") + walks_per_9_inn: Optional[str] = Field(default=None, alias="walksper9inn") + hits_per_9_inn: Optional[str] = Field(default=None, alias="hitsper9inn") + runs_scored_per_9: Optional[str] = Field(default=None, alias="runsscoredper9") + home_runs_per_9: Optional[str] = Field(default=None, alias="homerunsper9") + catchers_interference: Optional[int] = Field(default=None, alias="catchersinterference") + sac_bunts: Optional[int] = Field(default=None, alias="sacbunts") + sac_flies: Optional[int] = Field(default=None, alias="sacflies") + batters_faced: Optional[int] = Field(default=None, alias="battersfaced") + inherited_runners: Optional[int] = Field(default=None, alias="inheritedrunners") + inherited_runners_scored: Optional[int] = Field(default=None, alias="inheritedrunnersscored") balls: Optional[int] = None - outspitched: Optional[int] = None + outs_pitched: Optional[int] = Field(default=None, alias="outspitched") rbi: Optional[int] = None - age: Optional[int] = None - caughtstealingpercentage: Optional[str] = None - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(repr=False) -class AdvancedPitchingSplit: +class AdvancedPitchingSplit(MLBBaseModel): """ - A class to represent a advanced pitching statistics - - winningpercentage : str + A class to represent advanced pitching statistics. + + Attributes + ---------- + age : int + The age of the pitcher. + winning_percentage : str The winning percentage of the pitcher. - runsscoredper9 : str - The number of runs scored per 9 innings - battersfaced : int - The number of batters faced + runs_scored_per_9 : str + The number of runs scored per 9 innings. + batters_faced : int + The number of batters faced. babip : str The BABIP of the pitcher. obp : str - The on base percentage again the pitcher. + The on base percentage against the pitcher. slg : str The slugging percentage against the pitcher. ops : str The on base slugging against the pitcher. - see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging - strikeoutsper9 : str - The number of strike outs per 9 innings by the pitcher - baseonballsper9 : str - The number of base on balls per 9 innings by the pitcher. - homerunsper9 : str - The number of home runs per 9 innings by the pitcher. - hitsper9 : str - The number of hits per 9 innings by the pitcher. - strikesoutstowalks : str - The strike out to walk ratio of the pitcher. - stolenbases : int - The number of stolen bases while pitching. - caughtstealing : int - The number of runners caught stealing by the pitcher. - qualitystarts : int - The number of quality starts performed by the pitcher. - gamesfinished : int - The number of games finished performed by the pitcher. + strikeouts_per_9 : str + The number of strike outs per 9 innings. + base_on_balls_per_9 : str + The number of base on balls per 9 innings. + home_runs_per_9 : str + The number of home runs per 9 innings. + hits_per_9 : str + The number of hits per 9 innings. + strikeouts_to_walks : str + The strike out to walk ratio. + stolen_bases : int + The number of stolen bases while pitching. + caught_stealing : int + The number of runners caught stealing. + quality_starts : int + The number of quality starts. + games_finished : int + The number of games finished. doubles : int - The number of doubles given up by the pitcher. + The number of doubles given up. triples : int - The number of triples given up by the pitcher. + The number of triples given up. gidp : int The amount of hits that lead to a double play. - see here: https://www.mlb.com/glossary/standard-stats/ground-into-double-play - gidpopp : int - The amount of GIDP opportunities. - wildpitches : int - The number of wild pitches thown by the pitcher. + gidp_opp : int + The amount of GIDP opportunities. + wild_pitches : int + The number of wild pitches thrown. balks : int - The number of balks commited by the pitcher. + The number of balks committed. pickoffs : int - The number of pick offs attempted by the pitcher. - totalswings : int + The number of pick offs attempted. + total_swings : int The number of swings against the pitcher. - swingandmisses : int - The number of swing and misses against the pitcher. - ballsinplay : int - The number of balls put into play against the pitcher. - runsupport : int - The number of run support - strikepercentage : str - The strike percentage thown by the pitcher. - pitchesperinning : str - The number of pitches per inning - pitchesperplateappearance : str - The avg number of pitches per plate appearance of the pitcher. - walksperplateappearance : str - The number of walks per plate appearance for the pitcher. - strikeoutsperplateappearance : str - The strike outs per plate appearance for the pitcher. - homerunsperplateappearance : str - The home runs per plate appearance for the pitcher. - walksperstrikeout : str - The walk per strike out ratio of the pitcher + swing_and_misses : int + The number of swing and misses. + balls_in_play : int + The number of balls put into play. + run_support : int + The number of run support. + strike_percentage : str + The strike percentage thrown. + pitches_per_inning : str + The number of pitches per inning. + pitches_per_plate_appearance : str + The avg number of pitches per plate appearance. + walks_per_plate_appearance : str + The number of walks per plate appearance. + strikeouts_per_plate_appearance : str + The strike outs per plate appearance. + home_runs_per_plate_appearance : str + The home runs per plate appearance. + walks_per_strikeout : str + The walk per strike out ratio. iso : str - Isolasted power. - see also: https://www.mlb.com/glossary/advanced-stats/isolated-power + Isolated power. flyouts : int - The number of ly outs given up by the pitcher. + The number of fly outs given up. popouts : int - The number of pop outs given up by the pitcher. + The number of pop outs given up. lineouts : int - The number of line outs given up by the pitcher. + The number of line outs given up. groundouts : int - The number of ground outs given up by the pitcher. - flyhits : int - The number of fly hits given up by the pitcher. - pophits : int - The number of pop hits given up by the pitcher. - linehits : int - The number of line hits given up by the pitcher. - groundhits : int - The number of ground hits given up by the pitcher. - inheritedrunners : int - The number of inherited runners for the pitcher. - inheritedrunnersscored : int - The number of inherited runners scored for the pitcher. - bequeathedrunners : int + The number of ground outs given up. + fly_hits : int + The number of fly hits given up. + pop_hits : int + The number of pop hits given up. + line_hits : int + The number of line hits given up. + ground_hits : int + The number of ground hits given up. + inherited_runners : int + The number of inherited runners. + inherited_runners_scored : int + The number of inherited runners scored. + bequeathed_runners : int The number of bequeathed runners. - see also: https://www.mlb.com/glossary/advanced-stats/bequeathed-runners - bequeathedrunnersscored : int + bequeathed_runners_scored : int The number of bequeathed runners scored. - see also: https://www.mlb.com/glossary/advanced-stats/bequeathed-runners """ age: Optional[int] = None - winningpercentage: Optional[str] = None - runsscoredper9: Optional[str] = None - battersfaced: Optional[int] = None + winning_percentage: Optional[str] = Field(default=None, alias="winningpercentage") + runs_scored_per_9: Optional[str] = Field(default=None, alias="runsscoredper9") + batters_faced: Optional[int] = Field(default=None, alias="battersfaced") babip: Optional[str] = None obp: Optional[str] = None slg: Optional[str] = None ops: Optional[str] = None - strikeoutsper9: Optional[str] = None - baseonballsper9: Optional[str] = None - homerunsper9: Optional[str] = None - hitsper9: Optional[str] = None - strikesoutstowalks: Optional[str] = None - stolenbases: Optional[int] = None - caughtstealing: Optional[int] = None - qualitystarts: Optional[int] = None - gamesfinished: Optional[int] = None + strikeouts_per_9: Optional[str] = Field(default=None, alias="strikeoutsper9") + base_on_balls_per_9: Optional[str] = Field(default=None, alias="baseonballsper9") + home_runs_per_9: Optional[str] = Field(default=None, alias="homerunsper9") + hits_per_9: Optional[str] = Field(default=None, alias="hitsper9") + strikeouts_to_walks: Optional[str] = Field(default=None, alias="strikesoutstowalks") + stolen_bases: Optional[int] = Field(default=None, alias="stolenbases") + caught_stealing: Optional[int] = Field(default=None, alias="caughtstealing") + quality_starts: Optional[int] = Field(default=None, alias="qualitystarts") + games_finished: Optional[int] = Field(default=None, alias="gamesfinished") doubles: Optional[int] = None triples: Optional[int] = None gidp: Optional[int] = None - gidpopp: Optional[int] = None - wildpitches: Optional[int] = None + gidp_opp: Optional[int] = Field(default=None, alias="gidpopp") + wild_pitches: Optional[int] = Field(default=None, alias="wildpitches") balks: Optional[int] = None pickoffs: Optional[int] = None - totalswings: Optional[int] = None - swingandmisses: Optional[int] = None - strikeoutsminuswalkspercentage: Optional[str] = None - gidppercentage: Optional[str] = None - battersfacedpergame: Optional[str] = None - buntsfailed: Optional[int] = None - buntsmissedtipped: Optional[int] = None - whiffpercentage: Optional[int] = None - ballsinplay: Optional[int] = None - runsupport: Optional[int] = None - strikepercentage: Optional[str] = None - pitchesperinning: Optional[str] = None - pitchesperplateappearance: Optional[str] = None - walksperplateappearance: Optional[str] = None - strikeoutsperplateappearance: Optional[str] = None - homerunsperplateappearance: Optional[str] = None - walksperstrikeout: Optional[str] = None + total_swings: Optional[int] = Field(default=None, alias="totalswings") + swing_and_misses: Optional[int] = Field(default=None, alias="swingandmisses") + strikeouts_minus_walks_percentage: Optional[str] = Field(default=None, alias="strikeoutsminuswalkspercentage") + gidp_percentage: Optional[str] = Field(default=None, alias="gidppercentage") + batters_faced_per_game: Optional[str] = Field(default=None, alias="battersfacedpergame") + bunts_failed: Optional[int] = Field(default=None, alias="buntsfailed") + bunts_missed_tipped: Optional[int] = Field(default=None, alias="buntsmissedtipped") + whiff_percentage: Optional[str] = Field(default=None, alias="whiffpercentage") + balls_in_play: Optional[int] = Field(default=None, alias="ballsinplay") + run_support: Optional[int] = Field(default=None, alias="runsupport") + strike_percentage: Optional[str] = Field(default=None, alias="strikepercentage") + pitches_per_inning: Optional[str] = Field(default=None, alias="pitchesperinning") + pitches_per_plate_appearance: Optional[str] = Field(default=None, alias="pitchesperplateappearance") + walks_per_plate_appearance: Optional[str] = Field(default=None, alias="walksperplateappearance") + strikeouts_per_plate_appearance: Optional[str] = Field(default=None, alias="strikeoutsperplateappearance") + home_runs_per_plate_appearance: Optional[str] = Field(default=None, alias="homerunsperplateappearance") + walks_per_strikeout: Optional[str] = Field(default=None, alias="walksperstrikeout") iso: Optional[str] = None flyouts: Optional[int] = None popouts: Optional[int] = None lineouts: Optional[int] = None groundouts: Optional[int] = None - flyhits: Optional[int] = None - pophits: Optional[int] = None - linehits: Optional[int] = None - groundhits: Optional[int] = None - inheritedrunners: Optional[int] = None - inheritedrunnersscored: Optional[int] = None - bequeathedrunners: Optional[int] = None - bequeathedrunnersscored: Optional[int] = None - inningspitchedpergame: Optional[str] = None - flyballpercentage: Optional[str] = None - - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) + fly_hits: Optional[int] = Field(default=None, alias="flyhits") + pop_hits: Optional[int] = Field(default=None, alias="pophits") + line_hits: Optional[int] = Field(default=None, alias="linehits") + ground_hits: Optional[int] = Field(default=None, alias="groundhits") + inherited_runners: Optional[int] = Field(default=None, alias="inheritedrunners") + inherited_runners_scored: Optional[int] = Field(default=None, alias="inheritedrunnersscored") + bequeathed_runners: Optional[int] = Field(default=None, alias="bequeathedrunners") + bequeathed_runners_scored: Optional[int] = Field(default=None, alias="bequeathedrunnersscored") + innings_pitched_per_game: Optional[str] = Field(default=None, alias="inningspitchedpergame") + flyball_percentage: Optional[str] = Field(default=None, alias="flyballpercentage") -@dataclass(kw_only=True, repr=False) class PitchingSabermetrics(Split): """ - A class to represent a pitching sabermetric statistics + A class to represent pitching sabermetric statistics. Attributes ---------- - fip : float - Fielding Independent Pitching - fipminus : float - Fielding Independent Pitching Minus - ra9war : float - Runs Allowed 9 innings Wins Above Replacement - rar : float - Runs Above Replacement - war : float - Wins Above Replacement - gametype : str - the gametype code of the pitching season - numteams : str - the number of teams for the pitching season - """ - _stat = ['sabermetrics'] - stat: Union[Sabermetrics, dict] - - def __post_init__(self): - self.stat = Sabermetrics(**self.stat) - super().__post_init__() - - -@dataclass(kw_only=True, repr=False) + stat : Sabermetrics + The sabermetric statistics. + """ + _stat: ClassVar[List[str]] = ['sabermetrics'] + stat: Sabermetrics + + class PitchingSeason(Split): """ - A class to represent a pitching season statistic + A class to represent a pitching season statistic. Attributes ---------- + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['season', 'statsSingleSeason'] - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['season', 'statsSingleSeason'] + stat: SimplePitchingSplit -@dataclass(kw_only=True, repr=False) class PitchingCareer(Split): """ - A class to represent a pitching season statistic + A class to represent a pitching career statistic. Attributes ---------- + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['career'] - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['career'] + stat: SimplePitchingSplit -@dataclass(kw_only=True, repr=False) class PitchingCareerAdvanced(Split): """ - A class to represent a pitching season statistic + A class to represent a pitching careerAdvanced statistic. Attributes ---------- + stat : AdvancedPitchingSplit + The pitching split stat. """ - _stat = ['careerAdvanced'] - stat: Union[AdvancedPitchingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedPitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['careerAdvanced'] + stat: AdvancedPitchingSplit -@dataclass(kw_only=True, repr=False) class PitchingYearByYear(Split): """ - A class to represent a yearByYear season statistic + A class to represent a pitching yearByYear statistic. Attributes ---------- + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['yearByYear'] - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYear'] + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingYearByYearPlayoffs(Split): """ - A class to represent a yearByYear season statistic + A class to represent a pitching yearByYearPlayoffs statistic. Attributes ---------- + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['yearByYearPlayoffs'] - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYearPlayoffs'] + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingYearByYearAdvanced(Split): """ - A class to represent a pitching yearByYear statistic + A class to represent a pitching yearByYearAdvanced statistic. Attributes ---------- + stat : AdvancedPitchingSplit + The pitching split stat. """ - _stat = ['yearByYearAdvanced'] - stat: Union[AdvancedPitchingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedPitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['yearByYearAdvanced'] + stat: AdvancedPitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingSeasonAdvanced(Split): """ - A class to represent a pitching seasonAdvanced statistic + A class to represent a pitching seasonAdvanced statistic. Attributes ---------- + stat : AdvancedPitchingSplit + The pitching split stat. """ - _stat = ['seasonAdvanced'] - stat: Union[AdvancedPitchingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedPitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['seasonAdvanced'] + stat: AdvancedPitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingSingleSeasonAdvanced(Split): """ - A class to represent a pitching seasonAdvanced statistic + A class to represent a pitching statsSingleSeasonAdvanced statistic. Attributes ---------- + stat : AdvancedPitchingSplit + The pitching split stat. """ - _stat = ['statsSingleSeasonAdvanced'] - stat: Union[AdvancedPitchingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedPitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['statsSingleSeasonAdvanced'] + stat: AdvancedPitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingGameLog(Split): """ - A class to represent a gamelog stat for a pitcher + A class to represent a gamelog stat for a pitcher. Attributes ---------- - ishome : bool - bool to hold ishome - iswin : bool - bool to hold iswin + is_home : bool + Bool to hold is home. + is_win : bool + Bool to hold is win. game : Game - Game of the log + Game of the log. date : str - date of the log - gametype : str - type of game + Date of the log. opponent : Team - Team of the opponent - """ - ishome: bool - iswin: bool - game: Union[Game, dict] + Team of the opponent. + stat : SimplePitchingSplit + The pitching split stat. + """ + _stat: ClassVar[List[str]] = ['gameLog'] + is_home: bool = Field(alias="ishome") + is_win: bool = Field(alias="iswin") + game: Game date: str - opponent: Union[Team, dict] - _stat = ['gameLog'] - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + opponent: Team + stat: SimplePitchingSplit -@dataclass -class PitchingPlay: + +class PitchingPlay(MLBBaseModel): """ - A class to represent a gamelog stat for a hitter + A class to represent a play stat for a pitcher. Attributes ---------- - details : dict - a dict containing PlayDetails - count : dict - a dict containing the pitch Count - pitchnumber : int - pitcher number - atbatnumber : int - at bat number - ispitch : bool - ispitch bool - playid : str - A play id - """ - details: Union[PlayDetails, dict] - count: Union[Count, dict] - pitchnumber: int - atbatnumber: int - ispitch: bool - playid: str - - def __post_init__(self): - self.details = PlayDetails(**self.details) - self.count = Count(**self.count) - -@dataclass(kw_only=True, repr=False) + details : PlayDetails + Play details. + count : Count + The count. + pitch_number : int + Pitch number. + at_bat_number : int + At bat number. + is_pitch : bool + Is pitch bool. + play_id : str + Play ID. + """ + details: PlayDetails + count: Count + pitch_number: int = Field(alias="pitchnumber") + at_bat_number: int = Field(alias="atbatnumber") + is_pitch: bool = Field(alias="ispitch") + play_id: str = Field(alias="playid") + + class PitchingLog(Split): """ A class to represent a pitchLog stat for a pitcher. Attributes ---------- + stat : PitchingPlay + Information regarding the play for the stat. season : str - season for the stat - stat : PlayLog - information regarding the play for the stat - team : Team - team of the stat - player : Person - player of the stat + Season for the stat. opponent : Team - opponent + Opponent. date : str - date of log - gametype : str - game type code - ishome : bool - is the game at home bool - pitcher : Person - pitcher of the log - batter : Person - batter of the log + Date of log. + is_home : bool + Is the game at home bool. + pitcher : Pitcher + Pitcher of the log. + batter : Batter + Batter of the log. game : Game - the game of the log - + The game of the log. """ - _stat = ['pitchLog'] - stat: Union[PitchingPlay, dict] + _stat: ClassVar[List[str]] = ['pitchLog'] + stat: PitchingPlay season: str - opponent: Union[Team, dict] + opponent: Team date: str - ishome: bool - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - game: Union[Game, dict] + is_home: bool = Field(alias="ishome") + pitcher: Pitcher + batter: Batter + game: Game + + @field_validator('stat', mode='before') + @classmethod + def extract_play(cls, v: Any) -> Any: + """Extract play from stat dict.""" + if isinstance(v, dict) and 'play' in v: + return v['play'] + return v - def __post_init__(self): - self.stat = PitchingPlay(**self.stat['play']) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class PitchingPlayLog(Split): """ A class to represent a playLog stat for a pitcher. Attributes ---------- + stat : PitchingPlay + Information regarding the play for the stat. season : str - season for the stat - stat : PlayLog - information regarding the play for the stat - team : Team - team of the stat - player : Person - player of the stat + Season for the stat. opponent : Team - opponent + Opponent. date : str - date of log - gametype : str - game type code - ishome : bool - is the game at home bool - pitcher : Person - pitcher of the log - batter : Person - batter of the log + Date of log. + is_home : bool + Is the game at home bool. + pitcher : Pitcher + Pitcher of the log. + batter : Batter + Batter of the log. game : Game - the game of the log - + The game of the log. """ - _stat = ['playLog'] - stat: Union[PitchingPlay, dict] + _stat: ClassVar[List[str]] = ['playLog'] + stat: PitchingPlay season: str - opponent: Union[Team, dict] + opponent: Team date: str - ishome: bool - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - game: Union[Game, dict] + is_home: bool = Field(alias="ishome") + pitcher: Pitcher + batter: Batter + game: Game + + @field_validator('stat', mode='before') + @classmethod + def extract_play(cls, v: Any) -> Any: + """Extract play from stat dict.""" + if isinstance(v, dict) and 'play' in v: + return v['play'] + return v - def __post_init__(self): - self.stat = PitchingPlay(**self.stat['play']) - super().__post_init__() -@dataclass(kw_only=True, repr=False) class PitchingByDateRange(Split): """ A class to represent a byDateRange stat for a pitcher. Attributes ---------- - dayofweek : int + day_of_week : int + The day of the week. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['byDateRange'] - dayofweek: Optional[int] = None - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDateRange'] + stat: SimplePitchingSplit + day_of_week: Optional[int] = Field(default=None, alias="dayofweek") + -@dataclass(kw_only=True, repr=False) class PitchingByDateRangeAdvanced(Split): """ A class to represent a byDateRangeAdvanced stat for a pitcher. Attributes ---------- - dayofweek : int + day_of_week : int + The day of the week. + stat : AdvancedPitchingSplit + The pitching split stat. """ - _stat = ['byDateRangeAdvanced'] - dayofweek: Optional[int] = None - stat: Union[AdvancedPitchingSplit, dict] - - def __post_init__(self): - self.stat = AdvancedPitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDateRangeAdvanced'] + stat: AdvancedPitchingSplit + day_of_week: Optional[int] = Field(default=None, alias="dayofweek") + -@dataclass(kw_only=True, repr=False) class PitchingByMonth(Split): """ A class to represent a byMonth stat for a pitcher. @@ -746,16 +669,15 @@ class PitchingByMonth(Split): Attributes ---------- month : int + The month. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['byMonth'] + _stat: ClassVar[List[str]] = ['byMonth'] month: int - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingByMonthPlayoffs(Split): """ A class to represent a byMonthPlayoffs stat for a pitcher. @@ -763,134 +685,127 @@ class PitchingByMonthPlayoffs(Split): Attributes ---------- month : int + The month. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['byMonthPlayoffs'] + _stat: ClassVar[List[str]] = ['byMonthPlayoffs'] month: int - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingByDayOfWeek(Split): """ A class to represent a byDayOfWeek stat for a pitcher. Attributes ---------- - dayofweek : int + day_of_week : int + The day of the week. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['byDayOfWeek'] - dayofweek: Optional[int] = None - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDayOfWeek'] + stat: SimplePitchingSplit + day_of_week: Optional[int] = Field(default=None, alias="dayofweek") + -@dataclass(kw_only=True, repr=False) class PitchingByDayOfWeekPlayOffs(Split): """ A class to represent a byDayOfWeekPlayoffs stat for a pitcher. Attributes ---------- - dayofweek : int + day_of_week : int + The day of the week. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['byDayOfWeekPlayoffs'] - dayofweek: Optional[int] = None - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['byDayOfWeekPlayoffs'] + stat: SimplePitchingSplit + day_of_week: Optional[int] = Field(default=None, alias="dayofweek") + -@dataclass(kw_only=True, repr=False) class PitchingHomeAndAway(Split): """ A class to represent a homeAndAway stat for a pitcher. Attributes ---------- - ishome : bool + is_home : bool + Is home bool. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['homeAndAway'] - ishome: bool - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['homeAndAway'] + is_home: bool = Field(alias="ishome") + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingHomeAndAwayPlayoffs(Split): """ A class to represent a homeAndAwayPlayoffs stat for a pitcher. Attributes ---------- - ishome : bool + is_home : bool + Is home bool. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['homeAndAwayPlayoffs'] - ishome: bool - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['homeAndAwayPlayoffs'] + is_home: bool = Field(alias="ishome") + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingWinLoss(Split): """ A class to represent a winLoss stat for a pitcher. Attributes ---------- - iswin : bool + is_win : bool + Is win bool. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['winLoss'] - iswin: bool - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLoss'] + is_win: bool = Field(alias="iswin") + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingWinLossPlayoffs(Split): """ A class to represent a winLossPlayoffs stat for a pitcher. Attributes ---------- - iswin : bool + is_win : bool + Is win bool. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['winLossPlayoffs'] - iswin: bool - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['winLossPlayoffs'] + is_win: bool = Field(alias="iswin") + stat: SimplePitchingSplit + -@dataclass(kw_only=True, repr=False) class PitchingRankings(Split): """ A class to represent a rankingsByYear stat for a pitcher. Attributes ---------- + outs_pitched : int + Outs pitched. + stat : SimplePitchingSplit + The pitching split stat. """ - _stat = ['rankingsByYear'] - outspitched: Optional[int] = None - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['rankingsByYear'] + stat: SimplePitchingSplit + outs_pitched: Optional[int] = Field(default=None, alias="outspitched") + -@dataclass(kw_only=True, repr=False) class PitchingOpponentsFaced(Split): """ A class to represent an opponentsFaced stat for a pitcher. @@ -898,178 +813,216 @@ class PitchingOpponentsFaced(Split): Attributes ---------- group : str - pitch : Person - batter : Person - battingteam : Team - """ - _stat = ['opponentsFaced'] + The stat group. + pitcher : Pitcher + The pitcher. + batter : Batter + The batter. + batting_team : Team + The batting team. + """ + _stat: ClassVar[List[str]] = ['opponentsFaced'] group: str - pitcher: Union[Pitcher, dict] - batter: Union[Batter, dict] - battingteam: Union[Team, dict] + pitcher: Pitcher + batter: Batter + batting_team: Team = Field(alias="battingteam") - def __post_init__(self): - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.battingteam = Team(**self.battingteam) if self.battingteam else self.battingteam - super().__post_init__() -@dataclass(kw_only=True, repr=False) class PitchingExpectedStatistics(Split): """ A class to represent an expectedStatistics stat for a pitcher. Attributes ---------- - avg : str - slg : str - woba : str - wobaCon : str - rank : int + stat : ExpectedStatistics + The expected statistics. """ - _stat = ['expectedStatistics'] - stat: Union[ExpectedStatistics, dict] - - def __post_init__(self): - self.stat = ExpectedStatistics(**self.stat) - super().__post_init__() + _stat: ClassVar[List[str]] = ['expectedStatistics'] + stat: ExpectedStatistics - -@dataclass(kw_only=True, repr=False) class PitchingVsPlayer5Y(Split): """ - A class to represent a vsPlayer5Y pitching statistic + A class to represent a vsPlayer5Y pitching statistic. - requires the use of opposingTeamId params + Requires the use of opposingPlayerId params. Attributes ---------- - """ - _stat = ['vsPlayer5Y'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - super().__post_init__() - - -@dataclass(kw_only=True, repr=False) + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimplePitchingSplit + The pitching split stat. + """ + _stat: ClassVar[List[str]] = ['vsPlayer5Y'] + opponent: Team + stat: SimplePitchingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + class PitchingVsPlayer(Split): """ - A class to represent a vsPlayer pitching statistic + A class to represent a vsPlayer pitching statistic. - requires the use of opposingTeamId params + Requires the use of opposingPlayerId params. Attributes ---------- - """ - _stat = ['vsPlayer'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - super().__post_init__() - - -@dataclass(kw_only=True, repr=False) + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimplePitchingSplit + The pitching split stat. + """ + _stat: ClassVar[List[str]] = ['vsPlayer'] + opponent: Team + stat: SimplePitchingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + class PitchingVsPlayerTotal(Split): """ - A class to represent a vsPlayerTotal pitching statistic + A class to represent a vsPlayerTotal pitching statistic. - requires the use of opposingTeamId params + Requires the use of opposingPlayerId params. Attributes ---------- - """ - _stat = ['vsPlayerTotal'] - opponent: Optional[Union[Team, dict]] = field(default_factory=dict) - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimplePitchingSplit, dict] - - def __post_init__(self): - self.stat = SimplePitchingSplit(**self.stat) - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - super().__post_init__() + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimplePitchingSplit + The pitching split stat. + """ + _stat: ClassVar[List[str]] = ['vsPlayerTotal'] + stat: SimplePitchingSplit + opponent: Optional[Team] = None + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('opponent', 'batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v # These stat_types return a hitting stat for a pitching stat group -# odd, but need to deal with it. -@dataclass(kw_only=True, repr=False) class PitchingVsTeam(Split): """ - A class to represent a vsTeam pitching statistic + A class to represent a vsTeam pitching statistic. Attributes ---------- - """ - _stat = ['vsTeam'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - self.stat = SimpleHittingSplit(**self.stat) - super().__post_init__() - - -@dataclass(kw_only=True, repr=False) + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimpleHittingSplit + The hitting split stat. + """ + _stat: ClassVar[List[str]] = ['vsTeam'] + opponent: Team + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + class PitchingVsTeamTotal(Split): """ - A class to represent a vsTeamTotal pitching statistic + A class to represent a vsTeamTotal pitching statistic. Attributes ---------- - """ - _stat = ['vsTeamTotal'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - super().__post_init__() - -@dataclass(kw_only=True, repr=False) + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimpleHittingSplit + The hitting split stat. + """ + _stat: ClassVar[List[str]] = ['vsTeamTotal'] + opponent: Team + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + class PitchingVsTeam5Y(Split): """ - A class to represent a vsTeam5Y pitching statistic + A class to represent a vsTeam5Y pitching statistic. Attributes ---------- - """ - _stat = ['vsTeam5Y'] - opponent: Union[Team, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) - pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) - stat: Union[SimpleHittingSplit, dict] - - def __post_init__(self): - self.stat = SimpleHittingSplit(**self.stat) - self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher - self.batter = Batter(**self.batter) if self.batter else self.batter - self.opponent = Team(**self.opponent) if self.opponent else self.opponent - super().__post_init__() + opponent : Team + The opponent team. + batter : Batter + The batter. + pitcher : Pitcher + The pitcher. + stat : SimpleHittingSplit + The hitting split stat. + """ + _stat: ClassVar[List[str]] = ['vsTeam5Y'] + opponent: Team + stat: SimpleHittingSplit + batter: Optional[Batter] = None + pitcher: Optional[Pitcher] = None + + @field_validator('batter', 'pitcher', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v diff --git a/mlbstatsapi/models/stats/running.py b/mlbstatsapi/models/stats/running.py index 261e403..6a9685e 100644 --- a/mlbstatsapi/models/stats/running.py +++ b/mlbstatsapi/models/stats/running.py @@ -1,29 +1,22 @@ -from dataclasses import dataclass, field -from typing import Optional, Union, List - +from typing import List, ClassVar from mlbstatsapi.models.people import Batter, Pitcher - from .stats import Stat -@dataclass(kw_only=True) + class RunningOpponentsFaced(Stat): """ - A class to represent a running opponentsFaced statistic + A class to represent a running opponentsFaced statistic. Attributes ---------- batter : Batter - The batter of the stat + The batter of the stat. group : str - The stat group of the stat + The stat group of the stat. pitcher : Pitcher - The pitcher of the stat + The pitcher of the stat. """ - _stat = ['opponentsFaced'] - batter: Union[Batter, dict] - group: str - pitcher: Union[Pitcher, dict] - - def __post_init__(self): - self.batter = Batter(**self.batter) if self.batter else self.batter - self.pitcher = Pitcher(**self.pitcher) if self.batter else self.batter \ No newline at end of file + _stat: ClassVar[List[str]] = ['opponentsFaced'] + batter: Batter + group: str + pitcher: Pitcher diff --git a/mlbstatsapi/models/stats/stats.py b/mlbstatsapi/models/stats/stats.py index 9403dd8..56b5bd4 100644 --- a/mlbstatsapi/models/stats/stats.py +++ b/mlbstatsapi/models/stats/stats.py @@ -1,310 +1,338 @@ -from dataclasses import dataclass, field -from typing import Optional, Union, List - +from typing import Optional, List, Any, ClassVar +from pydantic import Field, field_validator +from mlbstatsapi.models.base import MLBBaseModel from mlbstatsapi.models.teams import Team from mlbstatsapi.models.people import Person, Batter, Position from mlbstatsapi.models.sports import Sport from mlbstatsapi.models.leagues import League from mlbstatsapi.models.data import CodeDesc -@dataclass -class PitchArsenalSplit: + +class PitchArsenalSplit(MLBBaseModel): """ - A class to represent a pitching pitch arsenal split + A class to represent a pitching pitch arsenal split. Attributes ---------- percentage : float + Percentage of this pitch type. count : int - totalPitches : int - averageSpeed : float - type : Union[CodeDesc, dict] + Count of this pitch type. + total_pitches : int + Total pitches thrown. + average_speed : float + Average speed of this pitch type. + type : CodeDesc + The pitch type code and description. """ percentage: float count: int - totalpitches: int - averagespeed: float - type: Union[CodeDesc, dict] + total_pitches: int = Field(alias="totalpitches") + average_speed: float = Field(alias="averagespeed") + type: CodeDesc + -@dataclass -class ExpectedStatistics: +class ExpectedStatistics(MLBBaseModel): """ - a class to hold a code and a description + A class to hold expected statistics. Attributes ---------- + avg : str + Expected batting average. + slg : str + Expected slugging. + woba : str + Expected wOBA. + wobacon : str + Expected wOBA on contact. """ avg: str slg: str woba: str wobacon: str -@dataclass(repr=False) -class Sabermetrics: + +class Sabermetrics(MLBBaseModel): """ - a class to hold a code and a description + A class to hold sabermetric statistics. Attributes ---------- woba : float - wRaa : float - wRc : float - wRcPlus : float + Weighted on-base average. + wraa : float + Weighted runs above average. + wrc : float + Weighted runs created. + wrc_plus : float + Weighted runs created plus. rar : float + Runs above replacement. war : float + Wins above replacement. batting : float + Batting runs. fielding : float - baseRunning : float + Fielding runs. + base_running : float + Base running runs. positional : float - wLeague : float + Positional adjustment. + w_league : float + League adjustment. replacement : float + Replacement level runs. spd : float + Speed score. ubr : float - wGdp : float - wSb : float + Ultimate base running. + w_gdp : float + Weighted grounded into double play runs. + w_sb : float + Weighted stolen base runs. """ - woba: Optional[float] = None wraa: Optional[float] = None wrc: Optional[float] = None - wrcplus: Optional[float] = None + wrc_plus: Optional[float] = Field(default=None, alias="wrcplus") rar: Optional[float] = None war: Optional[float] = None batting: Optional[float] = None fielding: Optional[float] = None - baserunning: Optional[float] = None + base_running: Optional[float] = Field(default=None, alias="baserunning") positional: Optional[float] = None - wleague: Optional[float] = None + w_league: Optional[float] = Field(default=None, alias="wleague") replacement: Optional[float] = None spd: Optional[float] = None ubr: Optional[float] = None - wgdp: Optional[float] = None - wsb: Optional[float] = None + w_gdp: Optional[float] = Field(default=None, alias="wgdp") + w_sb: Optional[float] = Field(default=None, alias="wsb") - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - -@dataclass(kw_only=True, repr=False) -class Split: +class Split(MLBBaseModel): """ - Base class for splits + Base class for splits. Attributes ---------- season : str - numteams : int - numleagues : int - gametype : str + The season. + num_teams : int + Number of teams. + num_leagues : int + Number of leagues. + game_type : str + The game type. rank : int + The rank. position : Position + The position. team : Team + The team. player : Person + The player. sport : Sport + The sport. league : League + The league. """ season: Optional[str] = None - numteams: Optional[int] = None - numleagues: Optional[int] = None - gametype: Optional[str] = None + num_teams: Optional[int] = Field(default=None, alias="numteams") + num_leagues: Optional[int] = Field(default=None, alias="numleagues") + game_type: Optional[str] = Field(default=None, alias="gametype") rank: Optional[int] = None - position: Optional[Union[Position, dict]] = field(default_factory=dict) - team: Optional[Union[Team, dict]] = field(default_factory=dict) - player: Optional[Union[Person, dict]] = field(default_factory=dict) - sport: Optional[Union[Sport, dict]] = field(default_factory=dict) - league: Optional[Union[League, dict]] = field(default_factory=dict) - - def __post_init__(self): - self.position = Position(**self.position) if self.position else self.position - self.team = Team(**self.team) if self.team else self.team - self.sport = Sport(**self.sport) if self.sport else self.sport - self.league = League(**self.league) if self.league else self.league - self.player = Person(**self.player) if self.player else self.player - - def __repr__(self) -> str: - kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] - return "{}({})".format(type(self).__name__, ", ".join(kws)) - - -@dataclass(kw_only=True, repr=False) -class Stat: + position: Optional[Position] = None + team: Optional[Team] = None + player: Optional[Person] = None + sport: Optional[Sport] = None + league: Optional[League] = None + + @field_validator('position', 'team', 'player', 'sport', 'league', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v + + +class Stat(MLBBaseModel): """ - Base class for stats + Base class for stats. Attributes ---------- group : str - type of the stat group + Type of the stat group. type : str - type of the stat - totalsplits : int - The number of split objects + Type of the stat. + total_splits : int + The number of split objects. exemptions : list - not sure what this is + Exemptions list. splits : list - a list of split objects + A list of split objects. """ group: str type: str - totalsplits: int - exemptions: Optional[List] = field(default_factory=list) - splits: Optional[List] = field(default_factory=list) + total_splits: int = Field(alias="totalsplits") + exemptions: Optional[List] = [] + splits: Optional[List] = [] - def __repr__(self): - return f'Stat(group={self.group}, type={self.type})' -@dataclass(kw_only=True) class PitchArsenal(Split): """ - A class to represent a pitcharsenal stat for a hitter and pitcher + A class to represent a pitcharsenal stat for a hitter and pitcher. Attributes ---------- + stat : PitchArsenalSplit + The pitch arsenal statistics. """ - _stat = ['pitchArsenal'] - stat: Union[PitchArsenalSplit, dict] - - def __post_init__(self): - self.stat = PitchArsenalSplit(**self.stat) + _stat: ClassVar[List[str]] = ['pitchArsenal'] + stat: PitchArsenalSplit -@dataclass(kw_only=True) -class ZoneCodes: +class ZoneCodes(MLBBaseModel): """ - A class to represent a zone code statistic used in hot cold zones + A class to represent a zone code statistic used in hot cold zones. Attributes ---------- zone : str - zone code location + Zone code location. color : str - rgba code for the color of zone + RGBA code for the color of zone. temp : str - temp description of the zone + Temperature description of the zone. value : str - batting percentage of the zone + Batting percentage of the zone. """ zone: str value: str color: Optional[str] = None temp: Optional[str] = None -@dataclass(kw_only=True) -class Zones: + +class Zones(MLBBaseModel): """ - A class to represent a hot cold zone statistic + A class to represent a hot cold zone statistic. Attributes ---------- name : str - name of the hot cold zone + Name of the hot cold zone. zones : List[ZoneCodes] - a list of zone codes to describe the zone + A list of zone codes to describe the zone. """ name: str - zones: List[ZoneCodes] + zones: List[ZoneCodes] = [] - def __post_init__(self): - self.zones = [ZoneCodes(**zone) for zone in self.zones] -@dataclass(kw_only=True) class HotColdZones(Split): """ - A class to represent a hotcoldzone statistic + A class to represent a hotcoldzone statistic. Attributes ---------- stat : Zones - the holdcoldzones for the stat + The hot cold zones for the stat. """ + _stat: ClassVar[List[str]] = ['hotColdZones'] stat: Zones - _stat = ['hotColdZones'] - def __post_init__(self): - self.stat = Zones(**self.stat) -@dataclass -class Chart: +class Chart(MLBBaseModel): """ - A class to represent a chart for SprayCharts + A class to represent a chart for SprayCharts. Attributes ---------- - leftfield : int - percentage - leftcenterfield : int - percentage - centerfield : int - percentage - rightcenterfield : int - percentage - rightfield : int - percentage + left_field : int + Left field percentage. + left_center_field : int + Left center field percentage. + center_field : int + Center field percentage. + right_center_field : int + Right center field percentage. + right_field : int + Right field percentage. """ - leftfield: int - leftcenterfield: int - centerfield: int - rightcenterfield: int - rightfield: int + left_field: int = Field(alias="leftfield") + left_center_field: int = Field(alias="leftcenterfield") + center_field: int = Field(alias="centerfield") + right_center_field: int = Field(alias="rightcenterfield") + right_field: int = Field(alias="rightfield") + -@dataclass(kw_only=True) class SprayCharts(Split): + """ + A class to represent spray chart statistics. + Attributes + ---------- + stat : Chart + The spray chart data. + batter : Batter + The batter. + """ + _stat: ClassVar[List[str]] = ['sprayChart'] + stat: Chart + batter: Optional[Batter] = None - _stat = ['sprayChart'] - stat: Union[Chart, dict] - batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + @field_validator('batter', mode='before') + @classmethod + def empty_dict_to_none(cls, v: Any) -> Any: + """Convert empty dicts to None.""" + if isinstance(v, dict) and not v: + return None + return v - def __post_init__(self): - self.batter = Batter(**self.batter) if self.batter else self.batter - self.stat = Chart(**self.stat) -@dataclass(kw_only=True) class OutsAboveAverage(Split): """ - A class to represent a outs above average statistic + A class to represent an outs above average statistic. - NOTE: This stat type returns a empty list, or keys with with the value 0 + NOTE: This stat type returns an empty list, or keys with the value 0. """ - _stat = ['outsAboveAverage'] + _stat: ClassVar[List[str]] = ['outsAboveAverage'] attempts: int - totaloutsaboveaverageback: int - totaloutsaboveaveragebackunrounded: int - outsaboveaveragebackstraight: int - outsaboveaveragebackstraightunrounded: int - outsaboveaveragebackleft: int - outsaboveaveragebackleftunrounded: int - outsaboveaveragebackright: int - outsaboveaveragebackrightunrounded: int - totaloutsaboveaveragein: int - totaloutsaboveaverageinunrounded: int - outsaboveaverageinstraight: int - outsaboveaverageinstraightunrounded: int - outsaboveaverageinleft: int - outsaboveaverageinleftunrounded: int - outsaboveaverageinright: int - outsaboveaverageinrightunrounded: int - player: Union[Person, dict] - gametype: str - - -# -# These dataclasses are for the game stats end point only -# url: https://statsapi.mlb.com/api/v1/people/663728/stats/game/715757 -# The gamelog stats in this JSON have different keys set for their stat -# and group. This breaks my logic of handling stat classes -# - -@dataclass + total_outs_above_average_back: int = Field(alias="totaloutsaboveaverageback") + total_outs_above_average_back_unrounded: int = Field(alias="totaloutsaboveaveragebackunrounded") + outs_above_average_back_straight: int = Field(alias="outsaboveaveragebackstraight") + outs_above_average_back_straight_unrounded: int = Field(alias="outsaboveaveragebackstraightunrounded") + outs_above_average_back_left: int = Field(alias="outsaboveaveragebackleft") + outs_above_average_back_left_unrounded: int = Field(alias="outsaboveaveragebackleftunrounded") + outs_above_average_back_right: int = Field(alias="outsaboveaveragebackright") + outs_above_average_back_right_unrounded: int = Field(alias="outsaboveaveragebackrightunrounded") + total_outs_above_average_in: int = Field(alias="totaloutsaboveaveragein") + total_outs_above_average_in_unrounded: int = Field(alias="totaloutsaboveaverageinunrounded") + outs_above_average_in_straight: int = Field(alias="outsaboveaverageinstraight") + outs_above_average_in_straight_unrounded: int = Field(alias="outsaboveaverageinstraightunrounded") + outs_above_average_in_left: int = Field(alias="outsaboveaverageinleft") + outs_above_average_in_left_unrounded: int = Field(alias="outsaboveaverageinleftunrounded") + outs_above_average_in_right: int = Field(alias="outsaboveaverageinright") + outs_above_average_in_right_unrounded: int = Field(alias="outsaboveaverageinrightunrounded") + player: Person + game_type: str = Field(alias="gametype") + + class PlayerGameLogStat(Split): """ - A class to represent a chart for SprayCharts + A class to represent a player game log stat. Attributes ---------- + type : str + The stat type. + group : str + The stat group. + stat : dict + The stat data. """ + _stat: ClassVar[List[str]] = ['gameLog'] type: str group: str stat: dict - _stat = ['gameLog'] diff --git a/mlbstatsapi/models/stats/streak.py b/mlbstatsapi/models/stats/streak.py index 38fbb07..72974a0 100644 --- a/mlbstatsapi/models/stats/streak.py +++ b/mlbstatsapi/models/stats/streak.py @@ -1,4 +1,3 @@ -from dataclasses import dataclass, field -from typing import Optional, Union - -from .stats import Splits \ No newline at end of file +# Streak module - placeholder for future implementation +# Original file imported from .stats import Splits which doesn't exist +# This file is kept for backwards compatibility diff --git a/tests/external_tests/stats/test_catching.py b/tests/external_tests/stats/test_catching.py index 21df3f6..7ef250b 100644 --- a/tests/external_tests/stats/test_catching.py +++ b/tests/external_tests/stats/test_catching.py @@ -1,4 +1,4 @@ -import unittest +import unittest import time from mlbstatsapi.mlb_api import Mlb @@ -39,11 +39,11 @@ def test_catching_stat_attributes_player(self): season = stats['catching']['season'] career = stats['catching']['career'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'catching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'catching') self.assertEqual(career.type, 'career') @@ -70,10 +70,10 @@ def test_catching_stat_attributes_team(self): season = stats['catching']['season'] career = stats['catching']['career'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'catching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'catching') self.assertEqual(career.type, 'career') diff --git a/tests/external_tests/stats/test_fielding.py b/tests/external_tests/stats/test_fielding.py index e56d311..85b019b 100644 --- a/tests/external_tests/stats/test_fielding.py +++ b/tests/external_tests/stats/test_fielding.py @@ -1,4 +1,4 @@ -import unittest +import unittest import time from mlbstatsapi.mlb_api import Mlb @@ -45,11 +45,11 @@ def test_fielding_stat_attributes_player(self): season = stats['fielding']['season'] career = stats['fielding']['career'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'fielding') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'fielding') self.assertEqual(career.type, 'career') @@ -79,18 +79,18 @@ def test_fielding_stat_attributes_team(self): season_advanced = stats['fielding']['seasonadvanced'] career_advanced = stats['fielding']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'fielding') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'fielding') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'fielding') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'fielding') self.assertEqual(career_advanced.type, 'careerAdvanced') diff --git a/tests/external_tests/stats/test_hitting.py b/tests/external_tests/stats/test_hitting.py index 55f74c2..a198423 100644 --- a/tests/external_tests/stats/test_hitting.py +++ b/tests/external_tests/stats/test_hitting.py @@ -1,4 +1,4 @@ -import unittest +import unittest import time from mlbstatsapi.mlb_api import Mlb @@ -49,19 +49,19 @@ def test_hitting_stat_attributes_player(self): career_advanced = stats['hitting']['careeradvanced'] # check that attrs exist and contain data - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'hitting') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'hitting') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'hitting') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'hitting') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -94,19 +94,19 @@ def test_hitting_stat_attributes_team(self): season_advanced = stats['hitting']['seasonadvanced'] career_advanced = stats['hitting']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'hitting') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'hitting') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'hitting') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'hitting') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -274,7 +274,7 @@ def test_hitting_pitchlog_stats_player(self): pitchlog = stats['hitting']['pitchlog'] self.assertTrue(len(pitchlog.splits) > 1) - self.assertEqual(pitchlog.totalsplits, len(pitchlog.splits)) + self.assertEqual(pitchlog.total_splits, len(pitchlog.splits)) def test_hitting_pitchlog_stats_player(self): @@ -298,7 +298,7 @@ def test_hitting_pitchlog_stats_player(self): # playlogs should return multiple splits playlogs = stats['hitting']['playlog'] self.assertTrue(len(playlogs.splits) > 1) - self.assertEqual(playlogs.totalsplits, len(playlogs.splits)) + self.assertEqual(playlogs.total_splits, len(playlogs.splits)) def test_hitting_pitchArsenal_stats_player(self): @@ -321,7 +321,7 @@ def test_hitting_pitchArsenal_stats_player(self): pitcharsenal = stats['stats']['pitcharsenal'] self.assertTrue(len(pitcharsenal.splits) > 1) - self.assertEqual(pitcharsenal.totalsplits, len(pitcharsenal.splits)) + self.assertEqual(pitcharsenal.total_splits, len(pitcharsenal.splits)) def test_hitting_hotcoldzones_stats_player(self): """mlb get stats should return hitting stats""" @@ -344,4 +344,4 @@ def test_hitting_hotcoldzones_stats_player(self): # hotcoldzone should return 5 splits hotcoldzone = stats['stats']['hotcoldzones'] self.assertEqual(len(hotcoldzone.splits), 5) - self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) + self.assertEqual(hotcoldzone.total_splits, len(hotcoldzone.splits)) diff --git a/tests/external_tests/stats/test_pitching.py b/tests/external_tests/stats/test_pitching.py index 43bb74b..768a567 100644 --- a/tests/external_tests/stats/test_pitching.py +++ b/tests/external_tests/stats/test_pitching.py @@ -1,4 +1,4 @@ -import unittest +import unittest import time from mlbstatsapi.mlb_api import Mlb @@ -45,19 +45,19 @@ def test_pitching_stat_attributes_player(self): season_advanced = stats['pitching']['seasonadvanced'] career_advanced = stats['pitching']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'pitching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'pitching') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'pitching') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'pitching') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -89,19 +89,19 @@ def test_pitching_stat_attributes_team(self): season_advanced = stats['pitching']['seasonadvanced'] career_advanced = stats['pitching']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'pitching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'pitching') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'pitching') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'pitching') self.assertEqual(career_advanced.type, 'careerAdvanced') diff --git a/tests/mock_tests/stats/test_game_player_stats_for_game.py b/tests/mock_tests/stats/test_game_player_stats_for_game.py index 54f4517..528dcdd 100644 --- a/tests/mock_tests/stats/test_game_player_stats_for_game.py +++ b/tests/mock_tests/stats/test_game_player_stats_for_game.py @@ -1,4 +1,4 @@ -import unittest +import unittest import time from mlbstatsapi.mlb_api import Mlb @@ -156,7 +156,7 @@ def test_get_players_stats_for_archie(self, m): gamelogs = game_stats['stats']['gamelog'] self.assertEqual(len(gamelogs.splits), 3) - self.assertEqual(gamelogs.totalsplits, len(gamelogs.splits)) + self.assertEqual(gamelogs.total_splits, len(gamelogs.splits)) vsplayer5y = game_stats['pitching']['vsplayer5y'] diff --git a/tests/mock_tests/stats/test_hitting_stats_mock.py b/tests/mock_tests/stats/test_hitting_stats_mock.py index 5687e15..f78d510 100644 --- a/tests/mock_tests/stats/test_hitting_stats_mock.py +++ b/tests/mock_tests/stats/test_hitting_stats_mock.py @@ -1,4 +1,4 @@ -import unittest +import unittest import requests_mock import json import os @@ -78,19 +78,19 @@ def test_hitting_stat_attributes_player(self, m): career_advanced = stats['hitting']['careeradvanced'] # check that attrs exist and contain data - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'hitting') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'hitting') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'hitting') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'hitting') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -124,19 +124,19 @@ def test_pitching_stat_attributes_team(self, m): career_advanced = stats['hitting']['careeradvanced'] # check that attrs exist and contain data - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'hitting') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'hitting') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'hitting') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'hitting') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -159,7 +159,7 @@ def test_hitting_hotcoldzones_for_player(self, m): # hotcoldzone should return 5 splits hotcoldzone = stats['stats']['hotcoldzones'] self.assertEqual(len(hotcoldzone.splits), 5) - self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) + self.assertEqual(hotcoldzone.total_splits, len(hotcoldzone.splits)) # hot cold zone should have 13 zones for each zone type for split in hotcoldzone.splits: @@ -187,7 +187,7 @@ def test_hitting_pitchlog_for_player(self, m): # pitchlog should have 2 splits from mock pitchlogs = stats['hitting']['pitchlog'] self.assertEqual(len(pitchlogs.splits), 6) - self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + self.assertEqual(pitchlogs.total_splits, len(pitchlogs.splits)) for pitchlog in pitchlogs.splits: self.assertTrue(pitchlog.stat.details) @@ -215,7 +215,7 @@ def test_hitting_playlog_for_player(self, m): # pitchlog items should have 2 splits pitchlogs = stats['hitting']['playlog'] self.assertEqual(len(pitchlogs.splits), 2) - self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + self.assertEqual(pitchlogs.total_splits, len(pitchlogs.splits)) for pitchlog in pitchlogs.splits: self.assertTrue(pitchlog.stat) @@ -238,7 +238,7 @@ def test_hitting_spraychart_for_player(self, m): spraychart = spraychart['stats']['spraychart'] self.assertEqual(len(spraychart.splits), 1) - self.assertEqual(spraychart.totalsplits, len(spraychart.splits)) + self.assertEqual(spraychart.total_splits, len(spraychart.splits)) for pitchlog in spraychart.splits: self.assertTrue(pitchlog.stat) \ No newline at end of file diff --git a/tests/mock_tests/stats/test_pitching_stats_mock.py b/tests/mock_tests/stats/test_pitching_stats_mock.py index fd17f5f..0e66493 100644 --- a/tests/mock_tests/stats/test_pitching_stats_mock.py +++ b/tests/mock_tests/stats/test_pitching_stats_mock.py @@ -1,4 +1,4 @@ -import unittest +import unittest import requests_mock import json import os @@ -78,19 +78,19 @@ def test_pitching_stat_attributes_player(self, m): season_advanced = stats['pitching']['seasonadvanced'] career_advanced = stats['pitching']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'pitching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'pitching') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'pitching') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'pitching') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -122,19 +122,19 @@ def test_pitching_stat_attributes_team(self, m): season_advanced = stats['pitching']['seasonadvanced'] career_advanced = stats['pitching']['careeradvanced'] - self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.total_splits, len(season.splits)) self.assertEqual(season.group, 'pitching') self.assertEqual(season.type, 'season') - self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.total_splits, len(career.splits)) self.assertEqual(career.group, 'pitching') self.assertEqual(career.type, 'career') - self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.total_splits, len(season_advanced.splits)) self.assertEqual(season_advanced.group, 'pitching') self.assertEqual(season_advanced.type, 'seasonAdvanced') - self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.total_splits, len(career_advanced.splits)) self.assertEqual(career_advanced.group, 'pitching') self.assertEqual(career_advanced.type, 'careerAdvanced') @@ -163,7 +163,7 @@ def test_pitching_play_log_for_player(self, m): # hotcoldzone should return 5 splits hotcoldzone = stats['stats']['hotcoldzones'] self.assertEqual(len(hotcoldzone.splits), 5) - self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) + self.assertEqual(hotcoldzone.total_splits, len(hotcoldzone.splits)) # hot cold zone should have 13 zones for each zone type for split in hotcoldzone.splits: @@ -191,7 +191,7 @@ def test_pitching_pitchlog_for_pitcher(self, m): # pitchlog should have 2 splits from mock pitchlogs = stats['pitching']['pitchlog'] self.assertEqual(len(pitchlogs.splits), 2) - self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + self.assertEqual(pitchlogs.total_splits, len(pitchlogs.splits)) for pitchlog in pitchlogs.splits: self.assertTrue(pitchlog.stat.details) @@ -219,7 +219,7 @@ def test_pitching_playlog_for_pitcher(self, m): # pitchlog items should have 2 splits pitchlogs = stats['pitching']['playlog'] self.assertEqual(len(pitchlogs.splits), 2) - self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + self.assertEqual(pitchlogs.total_splits, len(pitchlogs.splits)) for pitchlog in pitchlogs.splits: self.assertTrue(pitchlog.stat) @@ -243,7 +243,7 @@ def test_pitching_play_log_for_player(self, m): spraychart = spraychart['stats']['spraychart'] self.assertEqual(len(spraychart.splits), 1) - self.assertEqual(spraychart.totalsplits, len(spraychart.splits)) + self.assertEqual(spraychart.total_splits, len(spraychart.splits)) for pitchlog in spraychart.splits: self.assertTrue(pitchlog.stat) \ No newline at end of file From 0734694c49afce4d8ada80a977b7b3db8afe868d Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 22:12:12 -0800 Subject: [PATCH 8/9] docs: update README for Pydantic migration - Add "Working with Pydantic Models" section with model_dump() and model_dump_json() examples - Update all examples to use snake_case field names - Simplify examples using f-strings and direct field access - Fix typos and clean up language - Add note about Pydantic in Getting Started section --- README.md | 538 ++++++++++++++++++++++-------------------------------- 1 file changed, 214 insertions(+), 324 deletions(-) diff --git a/README.md b/README.md index 15fae40..523e019 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,11 @@ This package and its authors are not affiliated with MLB or any MLB team. This A ## Getting Started -*Python-mlb-statsapi* is a Python library that provides developers with access to the MLB Stats API which allows developers to retrieve information related to MLB teams, players, stats, and more. *Python-mlb-statsapi* written in python 3.10+. +*Python-mlb-statsapi* is a Python library that provides access to the MLB Stats API, allowing developers to retrieve information related to MLB teams, players, stats, and more. Written in Python 3.10+. -To get started with the library, refer to the information provided in this README. For a more detailed explanation, check out the documentation and the Wiki section. The Wiki contains information on return objects, endpoint structure, usage examples, and more. It is a valuable resource for getting started, working with the library, and finding the information you need. +All models are built with [Pydantic](https://docs.pydantic.dev/) for robust data validation and serialization. Field names follow Python's `snake_case` convention for a more Pythonic experience. + +For detailed documentation, check out the [Wiki](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki) which contains information on return objects, endpoint structure, usage examples, and more.
@@ -37,29 +39,78 @@ To get started with the library, refer to the information provided in this READM ```python python3 -m pip install python-mlb-statsapi ``` -## Usage + +## Quick Start ```python -python3 >>> import mlbstatsapi >>> mlb = mlbstatsapi.Mlb() + >>> mlb.get_people_id("Ty France") [664034] + +>>> player = mlb.get_person(664034) +>>> print(player.full_name) +Ty France + >>> stats = ['season', 'seasonAdvanced'] >>> groups = ['hitting'] >>> params = {'season': 2022} >>> mlb.get_player_stats(664034, stats, groups, **params) {'hitting': {'season': Stat, 'seasonadvanced': Stat }} ->>> mlb.get_team_id("Oakland Athletics") -[133] +>>> mlb.get_team_id("Seattle Mariners") +[136] ->>> stats = ['season', 'seasonAdvanced'] ->>> groups = ['pitching'] ->>> params = {'season': 2022} ->>> mlb.get_team_stats(133, stats, groups, **params) -{'pitching': {'season': Stat, 'seasonadvanced': Stat }} +>>> team = mlb.get_team(136) +>>> print(team.name, team.franchise_name) +Seattle Mariners Seattle ``` +## Working with Pydantic Models + +All returned objects are Pydantic models, giving you access to powerful serialization and validation features. + +### Convert to Dictionary +```python +>>> player = mlb.get_person(664034) +>>> player.model_dump() +{'id': 664034, 'full_name': 'Ty France', 'link': '/api/v1/people/664034', ...} + +# Exclude None values +>>> player.model_dump(exclude_none=True) +{'id': 664034, 'full_name': 'Ty France', 'link': '/api/v1/people/664034', ...} + +# Include only specific fields +>>> player.model_dump(include={'id', 'full_name', 'primary_position'}) +{'id': 664034, 'full_name': 'Ty France', 'primary_position': Position(...)} +``` + +### Convert to JSON +```python +>>> player = mlb.get_person(664034) +>>> player.model_dump_json() +'{"id": 664034, "full_name": "Ty France", "link": "/api/v1/people/664034", ...}' + +# Pretty print with indentation +>>> print(player.model_dump_json(indent=2)) +{ + "id": 664034, + "full_name": "Ty France", + "link": "/api/v1/people/664034", + ... +} +``` + +### Access Fields with Snake Case Names +```python +>>> player = mlb.get_person(664034) +>>> player.full_name # Not fullName +'Ty France' +>>> player.primary_position # Not primaryPosition +Position(code='3', name='First Base', ...) +>>> player.bat_side # Not batSide +CodeDesc(code='R', description='Right') +``` ## Documentation @@ -70,7 +121,7 @@ python3 ### [Draft](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Draft(round)) * `Mlb.get_draft(self, year_id: int, **params)` - Return a draft for a given year ### [Awards](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Award) -* `Mlb.get_awards(self, award_id: int, **params)` - Return rewards recipinets for a given award +* `Mlb.get_awards(self, award_id: int, **params)` - Return award recipients for a given award ### [Teams](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Team) * `Mlb.get_team_id(self, team_name: str, search_key: str = 'name', **params)` - Return Team Id(s) from name * `Mlb.get_team(self, team_id: int, **params)` - Return Team Object from Team Id @@ -90,12 +141,12 @@ python3 * `Mlb.get_venues(self, **params)` - Return all Venues ### [Sports](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Sport) * `Mlb.get_sport(self, sport_id: int, **params)` - Return a Sport object from Id -* `Mlb.get_sports(self, **params)` - Return all teams for Sport Id +* `Mlb.get_sports(self, **params)` - Return all Sports * `Mlb.get_sport_id(self, sport_name: str, search_key: str = 'name', **params)`- Return Sport Id from name ### [Schedules](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Schedule) * `Mlb.get_schedule(self, date: str, start_date: str, end_date: str, sport_id: int, team_id: int, **params)` - Return a Schedule ### [Divisions](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Division) -* `Mlb.get_division(self, division_id: int, **params)` - Return a Divison +* `Mlb.get_division(self, division_id: int, **params)` - Return a Division * `Mlb.get_divisions(self, **params)` - Return all Divisions * `Mlb.get_division_id(self, division_name: str, search_key: str = 'name', **params)` - Return Division Id(s) from name ### [Leagues](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-League) @@ -171,11 +222,9 @@ If you notice external test failures, please check if the MLB API has changed an ## Examples -Let's show some examples of getting stat objects from the API. What is baseball with out stats right? - -### MLB Stats +Let's show some examples of getting stat objects from the API. What is baseball without stats, right? -#### Player Stats +### Player Stats Get the Id(s) of the players you want stats for and set stat types and groups. ```python >>> mlb = mlbstatsapi.Mlb() @@ -183,442 +232,276 @@ Get the Id(s) of the players you want stats for and set stat types and groups. >>> stats = ['season', 'career'] >>> groups = ['hitting', 'pitching'] >>> params = {'season': 2022} - ``` -Use player.id and stat types and groups to return a stats dictionary + +Use player id with stat types and groups to return a stats dictionary ```python >>> stat_dict = mlb.get_player_stats(player_id, stats=stats, groups=groups, **params) >>> season_hitting_stat = stat_dict['hitting']['season'] >>> career_pitching_stat = stat_dict['pitching']['career'] ``` -Print season hitting stats + +Print season hitting stats using Pydantic's `model_dump()` +```python +>>> for split in season_hitting_stat.splits: +... print(split.stat.model_dump(exclude_none=True)) +{'games_played': 140, 'groundouts': 163, 'airouts': 148, 'runs': 65, 'doubles': 27, ...} +``` + +Or access individual fields directly ```python >>> for split in season_hitting_stat.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -gamesplayed 140 -groundouts 163 -airouts 148 -runs 65 -doubles 27 -triples 1 -homeruns 20 -strikeouts 94 -baseonballs 35 -... ->>> for split in career_pitching_stat.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -gamesplayed 2 -gamesstarted 0 -groundouts 2 -airouts 4 -runs 1 -doubles 0 -triples 0 -homeruns 1 -strikeouts 0 -baseonballs 0 -intentionalwalks 0 -hits 2 -hitbypitch 0 -... - -``` -#### Team stats +... print(f"Games: {split.stat.games_played}") +... print(f"Home Runs: {split.stat.home_runs}") +... print(f"Batting Avg: {split.stat.avg}") +Games: 140 +Home Runs: 20 +Batting Avg: .274 +``` + +### Team Stats Get the Team Id(s) ```python -python3 >>> mlb = mlbstatsapi.Mlb() >>> team_id = mlb.get_team_id('Seattle Mariners')[0] ``` -Set the stat types and groups. + +Set the stat types and groups ```python >>> stats = ['season', 'seasonAdvanced'] >>> groups = ['hitting'] >>> params = {'season': 2022} - ``` -Use team.id and the stat types and groups to return season hitting stats + +Use team id and the stat types and groups to return season hitting stats ```python -stats = mlb.get_team_stats(team_id, stats=stats, groups=groups, **params) -season_hitting = stats['hitting']['season'] -advanced_hitting = stats['hitting']['seasonadvanced'] +>>> stats = mlb.get_team_stats(team_id, stats=stats, groups=groups, **params) +>>> season_hitting = stats['hitting']['season'] +>>> advanced_hitting = stats['hitting']['seasonadvanced'] ``` -Print season and seasonadvanced stats + +Print stats as JSON ```python >>> for split in season_hitting.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -gamesplayed 162 -groundouts 1273 -airouts 1523 -runs 690 -doubles 229 -triples 19 ->>> ->>> for split in advanced_hitting.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -... -plateappearances 6117 -totalbases 2094 -leftonbase 1129 -sacbunts 9 -sacflies 45 -``` -### More stats examples -#### Expected Stats -Get player Id's -```python ->>> player_id = mlb.get_people_id('Ty France')[0] +... print(split.stat.model_dump_json(indent=2, exclude_none=True)) +{ + "games_played": 162, + "groundouts": 1273, + "runs": 690, + "doubles": 229, + ... +} ``` -Set the stat type and group + +### Expected Stats ```python +>>> player_id = mlb.get_people_id('Ty France')[0] >>> stats = ['expectedStatistics'] >>> group = ['hitting'] >>> params = {'season': 2022} -``` -Get Stats -```python >>> stats = mlb.get_player_stats(player_id, stats=stats, groups=group, **params) ->>> expectedstats = stats['hitting']['expectedstatistics'] ->>> for split in expectedstats.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -avg .259 -slg .394 -woba .317 -wobacon .338 -``` -#### vsPlayer +>>> expected = stats['hitting']['expectedstatistics'] +>>> for split in expected.splits: +... print(f"Expected AVG: {split.stat.avg}") +... print(f"Expected SLG: {split.stat.slg}") +Expected AVG: .259 +Expected SLG: .394 +``` + +### vsPlayer Stats Get pitcher and batter player Ids ```python >>> ty_france_id = mlb.get_people_id('Ty France')[0] >>> shohei_ohtani_id = mlb.get_people_id('Shohei Ohtani')[0] ``` + Set stat type, stat groups, and params ```python >>> stats = ['vsPlayer'] >>> group = ['hitting'] >>> params = {'opposingPlayerId': shohei_ohtani_id, 'season': 2022} ``` + Get stats ```python >>> stats = mlb.get_player_stats(ty_france_id, stats=stats, groups=group, **params) ->>> vs_player_total = stats['hitting']['vsplayertotal'] ->>> for split in vs_player_total.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -gamesplayed 4 -groundouts 3 -airouts 4 -runs None -doubles 1 -triples 0 -homeruns 0 -... >>> vs_player = stats['hitting']['vsplayer'] >>> for split in vs_player.splits: -... for k, v in split.stat.__dict__.items(): -... print(k, v) -gamesplayed 2 -groundouts 1 -airouts 2 -runs None -doubles 1 -triples 0 -homeruns 0 -``` -#### hotColdZones -Get player Id's -```python ->>> ty_france_id = mlb.get_people_id('Ty France')[0] ->>> shohei_ohtani_id = mlb.get_people_id('Shohei Ohtani')[0] +... print(f"Games: {split.stat.games_played}, Hits: {split.stat.hits}") +Games: 2, Hits: 2 ``` -Set the stat types and groups + +### Hot/Cold Zones ```python +>>> ty_france_id = mlb.get_people_id('Ty France')[0] >>> stats = ['hotColdZones'] >>> hitting_group = ['hitting'] ->>> pitching_group = ['pitching'] ->>> params = {'season': 2022} -``` -The stat groups pitching and hitting both return hotColdZones for a pitcher and hitter. hotColdZones are not assigned to a -stat group because of issues related to the REST API. So hotColdZones will be assigned to the stat key in stats return dict. -```python ->>> hitting_hotcoldzones = mlb.get_player_stats(ty_france_id stats=stats, groups=hitting_group, **params) ->>> pitching_hotcoldzones = mlb.get_player_stats(shohei_ohtani_id, stats=stats, groups=pitching_group, **params) -``` -hotColdZones returns a list of the HotColdZones -```python ->>> ty_france_hotcoldzones = hitting_hotcoldzones['stats']['hotcoldzones'] ->>> shohei_ohtani_hotcoldzones = pitching_hotcoldzones['stats']['hotcoldzones'] -``` -Loop through hotColdZone objects for Ty France -```python ->>> for split in ty_france_hotcoldzones.splits: -... print(split.stat.name) -... -onBasePercentage -onBasePlusSlugging -sluggingPercentage -exitVelocity -battingAverage -``` -Loop through hotColdZone objects for Shoei Ohtani -```python ->>> for split in shohei_ohtani_hotcoldzones.splits: -... print(split.stat.name) -... -onBasePercentage -onBasePlusSlugging -sluggingPercentage -exitVelocity -battingAverage -``` -Print zone information for obp -```python ->>> for split in ty_france_hotcoldzones.splits: -... if split.stat.name == 'onBasePercentage': -... for zone in split.stat.zones: -... print('zone: ', zone.zone) -... print('value: ', zone.value) -zone: 01 -value: .226 -zone: 02 -value: .400 -zone: 03 -value: .375 -zone: 04 -``` -#### Passing params -Get Team Ids -```python -python3 ->>> mlb = mlbstatsapi.Mlb() ->>> team_id = mlb.get_team_id('Seattle Mariners')[0] -``` -Set the stat types and groups. -```python ->>> stats = ['season', 'seasonAdvanced'] ->>> groups = ['hitting'] >>> params = {'season': 2022} -``` -Pass season to get_team_stats() -```python -stats = mlb.get_team_stats(team_id, stats=stats, groups=groups, **params) -season_hitting = stats['hitting']['season'] -advanced_hitting = stats['hitting']['seasonadvanced'] -``` -season should be 2018 -```python ->>> for split in season_hitting.splits: -... print('Season: ', split.season) -... for k, v in split.stat.__dict__.items(): -... print(k, v) -... -Season: 2018 -gamesplayed 162 -groundouts 1535 -airouts 1425 -runs 677 -... ->>> for split in advanced_hitting.splits: -... print('Season: ', split.season) -... for k, v in split.stat.__dict__.items(): -... print(k, v) -... -Season: 2018 -plateappearances 6087 -totalbases 2250 -leftonbase 1084 -sacbunts 29 -sacflies 41 -... -``` - -### Gamepace examples -Get pace of game metrics for specific sport, league or team. -```python ->>> mlb = mlbstatsapi.Mlb() ->>> season = 2021 ->>> gamepace = mlb.get_gamepace(season) +>>> hotcoldzones = mlb.get_player_stats(ty_france_id, stats=stats, groups=hitting_group, **params) +>>> zones = hotcoldzones['stats']['hotcoldzones'] + +>>> for split in zones.splits: +... print(f"Stat: {split.stat.name}") +... for zone in split.stat.zones: +... print(f" Zone {zone.zone}: {zone.value}") +Stat: battingAverage + Zone 01: .226 + Zone 02: .400 + ... ``` ### Schedule Examples -Get a schedule for given date +Get a schedule for a given date ```python >>> mlb = mlbstatsapi.Mlb() ->>> schedule = mlb.get_schedule_date('2022-10-13') -``` -Get ScheduleDates from Schedule -```python -dates = schedule.dates -``` -Print Game status and Home and Away Teams -```python +>>> schedule = mlb.get_schedule(date='2022-10-13') +>>> dates = schedule.dates + >>> for date in dates: ... for game in date.games: -... print(game.status) -... print(game.teams.home) -... print(game.teams.away) +... print(f"Game: {game.game_pk}") +... print(f"Status: {game.status.detailed_state}") +... print(f"Home: {game.teams.home.team.name}") +... print(f"Away: {game.teams.away.team.name}") ``` + ### Game Examples Get a Game for a given game id ```python >>> mlb = mlbstatsapi.Mlb() >>> game = mlb.get_game(662242) ``` -Get the weather for a game for a given game id + +Get the weather for a game ```python ->>> mlb = mlbstatsapi.Mlb() ->>> game = mlb.get_game(662242) ->>> weather = game.gamedata.weather ->>> ->>> print(weather.condition) ->>> print(weather.temp) ->>> print(weather.wind) +>>> weather = game.game_data.weather +>>> print(f"Condition: {weather.condition}") +>>> print(f"Temperature: {weather.temp}") +>>> print(f"Wind: {weather.wind}") ``` -Get the current status of a game for a given game id + +Get the current status of a game ```python ->>> mlb = mlbstatsapi.mlb() ->>> game = mlb.get_game(662242) ->>> ->>> linescore = game.livedata.linescore ->>> hometeaminfo = game.gamedata.teams.home ->>> awayteaminfo = game.gamedata.teams.away ->>> hometeamstatus = linescore.teams.home ->>> awayteamstatus = linescore.teams.away ->>> ->>> print("home: ", hometeaminfo.franchisename, hometeaminfo.clubname) ->>> print(" runs:", hometeamstatus.runs) ->>> print(" hits:", hometeamstatus.hits) ->>> print(" errors:", hometeamstatus.errors) ->>> print("away: ", awayteaminfo.franchisename, awayteaminfo.clubname) ->>> print(" runs:", awayteamstatus.runs) ->>> print(" hits:", awayteamstatus.hits) ->>> print(" errors:", awayteamstatus.errors) ->>> print("") ->>> print("inning:", linescore.inninghalf, linescore.currentinningordinal) ->>> print("balls:", linescore.balls) ->>> print("strikes:", linescore.strikes) ->>> print("Outs:", linescore.outs) -``` -Get the play by play, line score, and box score objects from a game +>>> linescore = game.live_data.linescore +>>> home_info = game.game_data.teams.home +>>> away_info = game.game_data.teams.away +>>> home_status = linescore.teams.home +>>> away_status = linescore.teams.away + +>>> print(f"Home: {home_info.franchise_name} {home_info.club_name}") +>>> print(f" Runs: {home_status.runs}, Hits: {home_status.hits}, Errors: {home_status.errors}") +>>> print(f"Away: {away_info.franchise_name} {away_info.club_name}") +>>> print(f" Runs: {away_status.runs}, Hits: {away_status.hits}, Errors: {away_status.errors}") +>>> print(f"Inning: {linescore.inning_half} {linescore.current_inning_ordinal}") +``` + +Get play by play, line score, and box score objects ```python ->>> mlb = mlbstatsapi.Mlb() ->>> game = mlb.get_game(662242) ->>> ->>> play_by_play = game.livedata.plays ->>> line_score = game.livedata.linescore ->>> box_score = game.livedata.boxscore +>>> play_by_play = game.live_data.plays +>>> line_score = game.live_data.linescore +>>> box_score = game.live_data.boxscore ``` + #### Play by Play Get only the play by play for a given game id ```python ->>> mlb = mlbstatsapi.Mlb() ->>> playbyplay = mlb.get_play_by_play(662242) +>>> playbyplay = mlb.get_game_play_by_play(662242) ``` + #### Line Score Get only the line score for a given game id ```python ->>> mlb = mlbstatsapi.Mlb() ->>> linescore = mlb.get_line_score(662242) +>>> linescore = mlb.get_game_line_score(662242) ``` + #### Box Score Get only the box score for a given game id ```python +>>> boxscore = mlb.get_game_box_score(662242) +``` + +### Gamepace Examples +Get pace of game metrics for a specific season +```python >>> mlb = mlbstatsapi.Mlb() ->>> boxscore = mlb.get_box_score(662242) +>>> gamepace = mlb.get_gamepace(season=2021) +>>> print(f"Hits per game: {gamepace.sports[0].sport_game_pace.hits_per_game}") ``` ### People Examples Get all Players for a given sport id ```python >>> mlb = mlbstatsapi.Mlb() ->>> sport_id = mlb.get_sport_id() ->>> players = mlb.get_players(sport_id=sport_id) +>>> players = mlb.get_people(sport_id=1) >>> for player in players: -... print(player.id) +... print(f"{player.id}: {player.full_name}") ``` + Get a player id ```python >>> player_id = mlb.get_people_id("Ty France") >>> print(player_id[0]) ->>> [664034] +664034 ``` ### Team Examples Get a Team ```python >>> mlb = mlbstatsapi.Mlb() ->>> team_ids = mlb.get_team_id("Seattle Mariners") ->>> team_id = team_ids[0] ->>> team = mlb.get_team(team_id.id) ->>> print(team.id) ->>> print(team.name) +>>> team_id = mlb.get_team_id("Seattle Mariners")[0] +>>> team = mlb.get_team(team_id) +>>> print(f"{team.id}: {team.name}") +>>> print(f"Venue: {team.venue.name}") ``` + Get a Player Roster ```python >>> mlb = mlbstatsapi.Mlb() ->>> team_id = 133 ->>> players = mlb.get_team_roster(team_id) +>>> players = mlb.get_team_roster(136) >>> for player in players: - print(player.jerseynumber) +... print(f"#{player.jersey_number} {player.person.full_name}") ``` + Get a Coach Roster ```python >>> mlb = mlbstatsapi.Mlb() ->>> team_id = 133 ->>> coaches = mlb.get_team_coaches(team_id) +>>> coaches = mlb.get_team_coaches(136) >>> for coach in coaches: - print(coach.title) +... print(f"{coach.person.full_name}: {coach.title}") ``` ### Draft Examples Get a draft for a year ```python >>> mlb = mlbstatsapi.Mlb() ->>> draft_year = '2019' ->>> draft = mlb.get_draft(draft_year) +>>> draft = mlb.get_draft('2019') ``` + Get Players from Draft ```python >>> draftpicks = draft[0].picks ->>> for draftpick in draftpicks: -... print(draftpick.id) -... print(draftpick.pickround) +>>> for pick in draftpicks: +... print(f"Round {pick.pick_round}, Pick {pick.pick_number}: {pick.person.full_name}") ``` ### Award Examples Get awards for a given award id ```python >>> mlb = mlbstatsapi.Mlb() ->>> retiredjersy = self.mlb.get_awards(award_id='RETIREDUNI_108') ->>> for recipient in retiredjersy.awards: -... print (recipient.player.nameFirstLast, recipient.name, recipient.date) +>>> retired_numbers = mlb.get_awards(award_id='RETIREDUNI_108') +>>> for recipient in retired_numbers.awards: +... print(f"{recipient.player.full_name}: {recipient.name} ({recipient.date})") ``` ### Venue Examples Get a Venue ```python >>> mlb = mlbstatsapi.Mlb() ->>> vevue_ids = mlb.get_venue_id('PNC Park') ->>> venue_id = venue_ids[0] ->>> venue = mlb.get_team(venue.id) ->>> print(venue.id) ->>> print(venue.name) -``` - -### Sport Examples -Get a Sport -```python ->>> mlb = mlbstatsapi.Mlb() ->>> sport_ids = mlb.get_sport_id('Major League Baseball') ->>> sport_id = sport_ids[0] ->>> sport = mlb.get_sport(sport_id) +>>> venue_id = mlb.get_venue_id('PNC Park')[0] +>>> venue = mlb.get_venue(venue_id) +>>> print(f"{venue.name} - {venue.location.city}, {venue.location.state}") ``` ### Division Examples @@ -627,6 +510,7 @@ Get a division >>> mlb = mlbstatsapi.Mlb() >>> division = mlb.get_division(200) >>> print(division.name) +American League West ``` ### League Examples @@ -635,6 +519,7 @@ Get a league >>> mlb = mlbstatsapi.Mlb() >>> league = mlb.get_league(103) >>> print(league.name) +American League ``` ### Season Examples @@ -642,12 +527,17 @@ Get a Season ```python >>> mlb = mlbstatsapi.Mlb() >>> season = mlb.get_season(2018) ->>> print(season.seasonid) +>>> print(f"Season: {season.season_id}") +>>> print(f"Regular Season: {season.regular_season_start_date} to {season.regular_season_end_date}") ``` ### Standings Examples -Get a Standings +Get Standings ```python >>> mlb = mlbstatsapi.Mlb() >>> standings = mlb.get_standings(103, 2018) +>>> for record in standings: +... print(f"Division: {record.division.name}") +... for team in record.team_records: +... print(f" {team.team.name}: {team.wins}-{team.losses}") ``` From 99b47782f73b366e4611dc45c0e0c371b6117171 Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Mon, 12 Jan 2026 22:56:30 -0800 Subject: [PATCH 9/9] version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 254058f..ea3f4a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-mlb-statsapi" -version = "0.6.2" +version = "0.7.0" description = "mlbstatsapi python wrapper" authors = [ "Matthew Spah ",