From 4808cabe3eaf10f1e69a01aad1ae3fe073aa689e Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 12:49:00 +0100 Subject: [PATCH 1/8] :sparkles: Add CSVDialectValidator. --- csvy/validators.py | 54 ++++++++++++++++++ poetry.lock | 139 ++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 csvy/validators.py diff --git a/csvy/validators.py b/csvy/validators.py new file mode 100644 index 0000000..c4a056c --- /dev/null +++ b/csvy/validators.py @@ -0,0 +1,54 @@ +import csv + +from pydantic import BaseModel, Field + + +class CSVDialectValidator(BaseModel): + """Implements a validator for CSV Dialects. + + This class is used to validate the CSV Dialects in the CSVY file. It is based on the + `csv.Dialect` class from the Python Standard Library. It does not include the + 'quoting' attribute, as it is not serializable as JSON or easy to understand by + other tools, but rather a python specific thing. + + Attributes: + delimiter: A one-character string used to separate fields. + doublequote: Controls how instances of quotechar appearing inside a field should + themselves be quoted. When True, the character is doubled. When False, the + escapechar is used as a prefix to the quotechar. It defaults to True. + escapechar: A one-character string used by the writer to escape the delimiter. + It defaults to None. + lineterminator: The string used to terminate lines produced by the writer. It + defaults to '\\r\\n'. + quotechar: A one-character string used to quote fields containing special + characters, such as the delimiter or quotechar, or which contain new-line + characters. It defaults to '"'. + skipinitialspace: When True, whitespace immediately following the delimiter is + ignored. It defaults to False. + strict: When True, raise exception Error on bad CSV input. It defaults to False. + """ + + delimiter: str = Field(default=",") + doublequote: bool = Field(default=True) + escapechar: str = Field(default=None) + lineterminator: str = Field(default="\r\n") + quotechar: str = Field(default='"') + skipinitialspace: bool = Field(default=False) + strict: bool = Field(default=False) + + def to_dialect(self) -> csv.Dialect: + """Converts the validator to a custom csv.Dialect object.""" + dialect = type( + "CustomDialect", + (csv.Dialect,), + { + "delimiter": self.delimiter, + "doublequote": self.doublequote, + "escapechar": self.escapechar, + "lineterminator": self.lineterminator, + "quotechar": self.quotechar, + "skipinitialspace": self.skipinitialspace, + "strict": self.strict, + }, + ) + return dialect() diff --git a/poetry.lock b/poetry.lock index ba0b263..3477d1c 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 = "attrs" version = "24.2.0" @@ -954,9 +965,9 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -1058,6 +1069,130 @@ files = [ {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyflakes" version = "2.4.0" @@ -1456,4 +1591,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "27623a749bb883c01bebf8bec0b913c9254cd4279808f3ee6afddbc1e095c02f" +content-hash = "0ce7e317656ee6d042e2f9a711047b5c05599b47d2f8ffa6a6ae3d97d82a8624" diff --git a/pyproject.toml b/pyproject.toml index 7d0ff4f..ffd86c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ packages = [ [tool.poetry.dependencies] python = "^3.9" PyYAML = "^6.0" +pydantic = "^2.9.2" [tool.poetry.group.dev] optional = true From 2ceedca77200d4686010f16a7fefdf20ce17ffe5 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 13:04:51 +0100 Subject: [PATCH 2/8] :sparkles: Add constructors based on known Dialects. --- csvy/validators.py | 76 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/csvy/validators.py b/csvy/validators.py index c4a056c..434139a 100644 --- a/csvy/validators.py +++ b/csvy/validators.py @@ -1,7 +1,11 @@ import csv +from typing import Type, TypeVar from pydantic import BaseModel, Field +# Create a generic variable that can be 'Parent', or any subclass. +T = TypeVar("T", bound="CSVDialectValidator") + class CSVDialectValidator(BaseModel): """Implements a validator for CSV Dialects. @@ -37,7 +41,14 @@ class CSVDialectValidator(BaseModel): strict: bool = Field(default=False) def to_dialect(self) -> csv.Dialect: - """Converts the validator to a custom csv.Dialect object.""" + """Converts the validator to a custom csv.Dialect object. + + This method converts the validator to a custom csv.Dialect object that can be + used to read or write CSV files with the specified dialect. + + Returns: + A custom csv.Dialect object with the specified attributes. + """ dialect = type( "CustomDialect", (csv.Dialect,), @@ -52,3 +63,66 @@ def to_dialect(self) -> csv.Dialect: }, ) return dialect() + + @classmethod + def excel(cls: Type[T]) -> T: + """Returns a validator for the Excel CSV Dialect. + + This method returns a validator for the Excel CSV Dialect, which is a common + dialect used in Excel files. + + Returns: + A validator for the Excel CSV Dialect. + """ + excel = csv.excel() + return cls( + delimiter=excel.delimiter, + doublequote=excel.doublequote, + escapechar=excel.escapechar, + lineterminator=excel.lineterminator, + quotechar=excel.quotechar, + skipinitialspace=excel.skipinitialspace, + strict=excel.strict, + ) + + @classmethod + def excel_tab(cls: Type[T]) -> T: + """Returns a validator for the Excel Tab CSV Dialect. + + This method returns a validator for the Excel Tab CSV Dialect, which is a common + dialect used in Excel files with tab delimiters. + + Returns: + A validator for the Excel Tab CSV Dialect. + """ + excel_tab = csv.excel_tab() + return cls( + delimiter=excel_tab.delimiter, + doublequote=excel_tab.doublequote, + escapechar=excel_tab.escapechar, + lineterminator=excel_tab.lineterminator, + quotechar=excel_tab.quotechar, + skipinitialspace=excel_tab.skipinitialspace, + strict=excel_tab.strict, + ) + + @classmethod + def unix(cls: Type[T]) -> T: + """Returns a validator for the Unix CSV Dialect. + + This method returns a validator for the Unix CSV Dialect, which is a common + dialect used in Unix files. + + Returns: + A validator for the Unix CSV Dialect. + """ + unix = csv.unix_dialect() + return cls( + delimiter=unix.delimiter, + doublequote=unix.doublequote, + escapechar=unix.escapechar, + lineterminator=unix.lineterminator, + quotechar=unix.quotechar, + skipinitialspace=unix.skipinitialspace, + strict=unix.strict, + ) From 0d3e802930a5dbd323a6b07b18731e06893b4908 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 13:56:25 +0100 Subject: [PATCH 3/8] :construction_worker: Run CI also in py3.9, so it runs in the oldest and newest python versions. --- .github/workflows/ci_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_template.yml b/.github/workflows/ci_template.yml index 3f21ef5..00477d3 100644 --- a/.github/workflows/ci_template.yml +++ b/.github/workflows/ci_template.yml @@ -9,7 +9,7 @@ on: type: string python-version: description: 'Python version' - default: '["3.12"]' + default: '["3.9", "3.12"]' type: string jobs: From 6b8b58bc73588c53a727844fe58eee76b752498b Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 14:11:22 +0100 Subject: [PATCH 4/8] :white_check_mark: Add tests for the CSVDialectValidator. --- csvy/validators.py | 17 ++++++++--------- tests/test_validators.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 tests/test_validators.py diff --git a/csvy/validators.py b/csvy/validators.py index 434139a..62bc9fa 100644 --- a/csvy/validators.py +++ b/csvy/validators.py @@ -1,5 +1,5 @@ import csv -from typing import Type, TypeVar +from typing import Optional, Type, TypeVar from pydantic import BaseModel, Field @@ -29,16 +29,14 @@ class CSVDialectValidator(BaseModel): characters. It defaults to '"'. skipinitialspace: When True, whitespace immediately following the delimiter is ignored. It defaults to False. - strict: When True, raise exception Error on bad CSV input. It defaults to False. """ delimiter: str = Field(default=",") doublequote: bool = Field(default=True) - escapechar: str = Field(default=None) + escapechar: Optional[str] = Field(default=None) lineterminator: str = Field(default="\r\n") quotechar: str = Field(default='"') skipinitialspace: bool = Field(default=False) - strict: bool = Field(default=False) def to_dialect(self) -> csv.Dialect: """Converts the validator to a custom csv.Dialect object. @@ -46,6 +44,8 @@ def to_dialect(self) -> csv.Dialect: This method converts the validator to a custom csv.Dialect object that can be used to read or write CSV files with the specified dialect. + For 'quoting', the default value is used, as it is not serializable. + Returns: A custom csv.Dialect object with the specified attributes. """ @@ -59,7 +59,7 @@ def to_dialect(self) -> csv.Dialect: "lineterminator": self.lineterminator, "quotechar": self.quotechar, "skipinitialspace": self.skipinitialspace, - "strict": self.strict, + "quoting": csv.QUOTE_MINIMAL, # This is not serializable. }, ) return dialect() @@ -82,7 +82,6 @@ def excel(cls: Type[T]) -> T: lineterminator=excel.lineterminator, quotechar=excel.quotechar, skipinitialspace=excel.skipinitialspace, - strict=excel.strict, ) @classmethod @@ -92,6 +91,8 @@ def excel_tab(cls: Type[T]) -> T: This method returns a validator for the Excel Tab CSV Dialect, which is a common dialect used in Excel files with tab delimiters. + `excel` has not parameter `strict` so that one is ignored. + Returns: A validator for the Excel Tab CSV Dialect. """ @@ -103,11 +104,10 @@ def excel_tab(cls: Type[T]) -> T: lineterminator=excel_tab.lineterminator, quotechar=excel_tab.quotechar, skipinitialspace=excel_tab.skipinitialspace, - strict=excel_tab.strict, ) @classmethod - def unix(cls: Type[T]) -> T: + def unix_dialect(cls: Type[T]) -> T: """Returns a validator for the Unix CSV Dialect. This method returns a validator for the Unix CSV Dialect, which is a common @@ -124,5 +124,4 @@ def unix(cls: Type[T]) -> T: lineterminator=unix.lineterminator, quotechar=unix.quotechar, skipinitialspace=unix.skipinitialspace, - strict=unix.strict, ) diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..a6f3de4 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,19 @@ +import pytest + + +@pytest.mark.parametrize("shortcut", ["excel", "excel_tab", "unix_dialect"]) +def test_shortcut_dialects_roundtrip(shortcut): + import csv + + from csvy.validators import CSVDialectValidator + + validator = getattr(CSVDialectValidator, shortcut)() + dialect = validator.to_dialect() + actual = getattr(csv, shortcut)() + + assert dialect.delimiter == actual.delimiter + assert dialect.doublequote == actual.doublequote + assert dialect.escapechar == actual.escapechar + assert dialect.lineterminator == actual.lineterminator + assert dialect.quotechar == actual.quotechar + assert dialect.skipinitialspace == actual.skipinitialspace From 298692b2a41bb4516ab7be4e0796e9dc4a83578c Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 14:19:34 +0100 Subject: [PATCH 5/8] Update vscode settings --- .vscode/settings.json | 5 +++++ setup.cfg | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..809a1de --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.testing.pytestArgs": ["--no-cov"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 52c4052..a2b04b4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ test = pytest [tool:pytest] addopts = - -v --mypy -p no:warnings + -v -p no:warnings --cov=csvy --cov-report=html:reports/coverage/html --cov-report=xml:reports/coverage/coverage.xml --doctest-modules --ignore=docs/ From 4ef3895617bbb3fcca97c3068cf38d6166522e77 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Tue, 1 Oct 2024 15:50:30 +0100 Subject: [PATCH 6/8] :rotating_light: Fix linting issues. --- csvy/validators.py | 12 +++++++----- tests/test_validators.py | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/csvy/validators.py b/csvy/validators.py index 62bc9fa..103f18c 100644 --- a/csvy/validators.py +++ b/csvy/validators.py @@ -1,5 +1,7 @@ +"""This module contains validators for the CSVY file format.""" + import csv -from typing import Optional, Type, TypeVar +from typing import Optional, TypeVar from pydantic import BaseModel, Field @@ -8,7 +10,7 @@ class CSVDialectValidator(BaseModel): - """Implements a validator for CSV Dialects. + r"""Implements a validator for CSV Dialects. This class is used to validate the CSV Dialects in the CSVY file. It is based on the `csv.Dialect` class from the Python Standard Library. It does not include the @@ -65,7 +67,7 @@ def to_dialect(self) -> csv.Dialect: return dialect() @classmethod - def excel(cls: Type[T]) -> T: + def excel(cls: type[T]) -> T: """Returns a validator for the Excel CSV Dialect. This method returns a validator for the Excel CSV Dialect, which is a common @@ -85,7 +87,7 @@ def excel(cls: Type[T]) -> T: ) @classmethod - def excel_tab(cls: Type[T]) -> T: + def excel_tab(cls: type[T]) -> T: """Returns a validator for the Excel Tab CSV Dialect. This method returns a validator for the Excel Tab CSV Dialect, which is a common @@ -107,7 +109,7 @@ def excel_tab(cls: Type[T]) -> T: ) @classmethod - def unix_dialect(cls: Type[T]) -> T: + def unix_dialect(cls: type[T]) -> T: """Returns a validator for the Unix CSV Dialect. This method returns a validator for the Unix CSV Dialect, which is a common diff --git a/tests/test_validators.py b/tests/test_validators.py index a6f3de4..a71d58c 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,8 +1,11 @@ +"""Tests for the validators module.""" + import pytest @pytest.mark.parametrize("shortcut", ["excel", "excel_tab", "unix_dialect"]) def test_shortcut_dialects_roundtrip(shortcut): + """Test that the shortcut dialects roundtrip to the actual dialects.""" import csv from csvy.validators import CSVDialectValidator From 6ec849cf957656d205cd823f8d7d2953269b59fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Alonso=20=C3=81lvarez?= <6095790+dalonsoa@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:50:50 +0100 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Alex Dewar --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 809a1de..e137fad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "python.testing.pytestArgs": ["--no-cov"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true } \ No newline at end of file From a8fd84a3687124ac57509012e67958deeb42d760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Alonso=20=C3=81lvarez?= <6095790+dalonsoa@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:39:48 +0100 Subject: [PATCH 8/8] Update setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index a2b04b4..52c4052 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ test = pytest [tool:pytest] addopts = - -v -p no:warnings + -v --mypy -p no:warnings --cov=csvy --cov-report=html:reports/coverage/html --cov-report=xml:reports/coverage/coverage.xml --doctest-modules --ignore=docs/