From 0b6243d7c94f4702426196a8689e0a699bffd5d9 Mon Sep 17 00:00:00 2001 From: Javier Luna Molina Date: Sun, 6 Oct 2019 20:47:37 +0200 Subject: [PATCH] Release 0.0.0a6 (#17) * Update license to MIT * Update license to MIT * Update license to MIT * DS-1: Add issue templates * DS-1: Add feature request template * DS-1: Add question template * DS-1: Add bug report template * DS-12: Add Code of Conduct (#13) * WIP: DS-14: Add serialization and deserialization to fields. (#15) * DS-14: Add serialization and deserialization to fields. * DS-14: Add pytest. Change test layout. * DS-14: Add mapper. Fix PR bug. * DS-14: Fix field_name not setting right with new mapper. * Bump version to 0.0.0a6 * Add better badges --- .circleci/config.yml | 2 +- CODE_OF_CONDUCT.md | 77 ++++++++ Makefile | 26 ++- Pipfile | 3 + Pipfile.lock | 243 +++++++++++++++++-------- README.md | 2 +- datastorm/__init__.py | 2 +- datastorm/entity.py | 46 +++-- datastorm/fields.py | 80 ++++++++- datastorm/mapper.py | 37 ++++ datastorm/query.py | 8 +- docs/Datastorm/fields.md | 1 + setup.py | 3 +- tests/e2e/__init__.py | 0 tests/e2e/conftest.py | 28 +++ tests/e2e/test_create_fetch.py | 14 ++ tests/e2e/test_mappings.py | 15 ++ tests/legacy/__init__.py | 0 tests/{ => legacy}/test_base.py | 0 tests/{ => legacy}/test_datastorm.py | 2 +- tests/{ => legacy}/test_operations.py | 2 +- tests/{ => legacy}/test_queries.py | 2 +- tests/test_fields.py | 250 -------------------------- tests/unit/__init__.py | 0 tests/unit/fields/__init__.py | 0 tests/unit/fields/test_base.py | 43 +++++ tests/unit/fields/test_boolean.py | 20 +++ tests/unit/fields/test_dict.py | 16 ++ tests/unit/fields/test_float.py | 20 +++ tests/unit/fields/test_int.py | 20 +++ tests/unit/fields/test_json.py | 27 +++ tests/unit/fields/test_list.py | 16 ++ tests/unit/fields/test_string.py | 20 +++ 33 files changed, 659 insertions(+), 366 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 datastorm/mapper.py create mode 100644 tests/e2e/__init__.py create mode 100644 tests/e2e/conftest.py create mode 100644 tests/e2e/test_create_fetch.py create mode 100644 tests/e2e/test_mappings.py create mode 100644 tests/legacy/__init__.py rename tests/{ => legacy}/test_base.py (100%) rename tests/{ => legacy}/test_datastorm.py (96%) rename tests/{ => legacy}/test_operations.py (96%) rename tests/{ => legacy}/test_queries.py (99%) delete mode 100644 tests/test_fields.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/fields/__init__.py create mode 100644 tests/unit/fields/test_base.py create mode 100644 tests/unit/fields/test_boolean.py create mode 100644 tests/unit/fields/test_dict.py create mode 100644 tests/unit/fields/test_float.py create mode 100644 tests/unit/fields/test_int.py create mode 100644 tests/unit/fields/test_json.py create mode 100644 tests/unit/fields/test_list.py create mode 100644 tests/unit/fields/test_string.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 9711290..a2146b5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,4 +23,4 @@ jobs: name: Run tests command: | . env/bin/activate - make test + make tests diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e94e33f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,77 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [jlunadevel@gmail.com](mailto:jlunadevel@gmail.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + diff --git a/Makefile b/Makefile index ce1512f..84422cf 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,17 @@ -.PHONY: test +.PHONY: tests -test: export DATASTORE_EMULATOR_HOST=0.0.0.0:8081 -test: - python -m unittest discover tests/ +e2e-tests: export DATASTORE_EMULATOR_HOST=0.0.0.0:8081 +e2e-tests: + pipenv run py.test tests/e2e + +unit-tests: + pipenv run py.test tests/unit + +legacy-tests: export DATASTORE_EMULATOR_HOST=0.0.0.0:8081 +legacy-tests: + pipenv run py.test tests/legacy + +tests: unit-tests legacy-tests e2e-tests coverage: PYTHONPATH=. coverage run --source datastorm setup.py test @@ -13,7 +22,7 @@ docker-build: docker build -t datastorm-test-env:255.0.0-3.6.9 .circleci/images docker-run: docker-build - docker run --name datastorm-test-env --publish 8081:8081 datastorm-test-env:255.0.0-3.6.9 + docker run --rm --name datastorm-test-env --publish 8081:8081 datastorm-test-env:255.0.0-3.6.9 docker-tag: docker tag datastorm-test-env:255.0.0-3.6.9 javierluna/datastorm-test-env:255.0.0-3.6.9 @@ -25,11 +34,10 @@ docker-clean: docker stop datastore-test-env docker rm datastore-test-env -docker-test: docker-build - docker run -d --name datastore-test-env --publish 8081:8081 datastorm-test-env:255.0.0-3.6.9 +docker-tests: docker-build + docker run --rm -d --name datastore-test-env --publish 8081:8081 datastorm-test-env:255.0.0-3.6.9 sleep 5 - $(MAKE) test - $(MAKE) docker-clean + $(MAKE) tests clean: diff --git a/Pipfile b/Pipfile index fa698b2..20aa3c6 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,9 @@ google-cloud-datastore = "*" twine = "*" coverage = "*" mkdocs = "*" +pytest = "*" +pytest-mock = "*" +pytest-cov = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index d9364ca..b3e8868 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "95c2feb3c2be3801e3e73c73d27924c1e43e1a738d91c37b5dd5e12b0634c36c" + "sha256": "b0e18bf0fa8ebe1b87f0cc583e22203f1474e4eca5ebcbf56f24fadecee98c11" }, "pipfile-spec": 6, "requires": { @@ -25,10 +25,10 @@ }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "chardet": { "hashes": [ @@ -77,40 +77,40 @@ }, "grpcio": { "hashes": [ - "sha256:1303578092f1f6e4bfbc354c04ac422856c393723d3ffa032fff0f7cb5cfd693", - "sha256:229c6b313cd82bec8f979b059d87f03cc1a48939b543fe170b5a9c5cf6a6bc69", - "sha256:3cd3d99a8b5568d0d186f9520c16121a0f2a4bcad8e2b9884b76fb88a85a7774", - "sha256:41cfb222db358227521f9638a6fbc397f310042a4db5539a19dea01547c621cd", - "sha256:43330501660f636fd6547d1e196e395cd1e2c2ae57d62219d6184a668ffebda0", - "sha256:45d7a2bd8b4f25a013296683f4140d636cdbb507d94a382ea5029a21e76b1648", - "sha256:47dc935658a13b25108823dabd010194ddea9610357c5c1ef1ad7b3f5157ebee", - "sha256:480aa7e2b56238badce0b9413a96d5b4c90c3bfbd79eba5a0501e92328d9669e", - "sha256:4a0934c8b0f97e1d8c18e76c45afc0d02d33ab03125258179f2ac6c7a13f3626", - "sha256:5624dab19e950f99e560400c59d87b685809e4cfcb2c724103f1ab14c06071f7", - "sha256:60515b1405bb3dadc55e6ca99429072dad3e736afcf5048db5452df5572231ff", - "sha256:610f97ebae742a57d336a69b09a9c7d7de1f62aa54aaa8adc635b38f55ba4382", - "sha256:64ea189b2b0859d1f7b411a09185028744d494ef09029630200cc892e366f169", - "sha256:686090c6c1e09e4f49585b8508d0a31d58bc3895e4049ea55b197d1381e9f70f", - "sha256:7745c365195bb0605e3d47b480a2a4d1baa8a41a5fd0a20de5fa48900e2c886a", - "sha256:79491e0d2b77a1c438116bf9e5f9e2e04e78b78524615e2ce453eff62db59a09", - "sha256:825177dd4c601c487836b7d6b4ba268db59787157911c623ba59a7c03c8d3adc", - "sha256:8a060e1f72fb94eee8a035ed29f1201ce903ad14cbe27bda56b4a22a8abda045", - "sha256:90168cc6353e2766e47b650c963f21cfff294654b10b3a14c67e26a4e3683634", - "sha256:94b7742734bceeff6d8db5edb31ac844cb68fc7f13617eca859ff1b78bb20ba1", - "sha256:962aebf2dd01bbb2cdb64580e61760f1afc470781f9ecd5fe8f3d8dcd8cf4556", - "sha256:9c8d9eacdce840b72eee7924c752c31b675f8aec74790e08cff184a4ea8aa9c1", - "sha256:af5b929debc336f6bab9b0da6915f9ee5e41444012aed6a79a3c7e80d7662fdf", - "sha256:b9cdb87fc77e9a3eabdc42a512368538d648fa0760ad30cf97788076985c790a", - "sha256:c5e6380b90b389454669dc67d0a39fb4dc166416e01308fcddd694236b8329ef", - "sha256:d60c90fe2bfbee735397bf75a2f2c4e70c5deab51cd40c6e4fa98fae018c8db6", - "sha256:d8582c8b1b1063249da1588854251d8a91df1e210a328aeb0ece39da2b2b763b", - "sha256:ddbf86ba3aa0ad8fed2867910d2913ee237d55920b55f1d619049b3399f04efc", - "sha256:e46bc0664c5c8a0545857aa7a096289f8db148e7f9cca2d0b760113e8994bddc", - "sha256:f6437f70ec7fed0ca3a0eef1146591bb754b418bb6c6b21db74f0333d624e135", - "sha256:f71693c3396530c6b00773b029ea85e59272557e9bd6077195a6593e4229892a", - "sha256:f79f7455f8fbd43e8e9d61914ecf7f48ba1c8e271801996fef8d6a8f3cc9f39f" - ], - "version": "==1.23.0" + "sha256:0337debec20fe385bcd49048d6917270efbc17a5119857466559b4db91f8995b", + "sha256:164f82a99e08797ea786283b66b45ebe76772d321577d1674ba6fe0200155892", + "sha256:172dfba8d9621048c2cbc1d1cf7a02244e9a9a8cff5bb79bb30bcb0c13c7fd31", + "sha256:18f4b536d8a9cfa15b3214e0bb628071def94160699e91798f0a954c3b2db88d", + "sha256:2283b56bda49b068b0f08d006fffc7dd46eae72322f1a5dec87fc9c218f1dc2d", + "sha256:26b33f488a955bf49262d2ce3423d3a8174108506d8f819e8150aca21bdd3b99", + "sha256:31cc9b6f70bdd0d9ff53df2d563ea1fb278601d5c625932d8a82d03b08ff3de0", + "sha256:37dd8684fbc2bc00766ae6784bcbd7f874bc96527636a341411db811d04ff650", + "sha256:424c01189ef51a808669f020368b01204e0f1fa0bf2adab7c8d0d13166f92e9e", + "sha256:431c099f20a1f1d97def98f87bb74fa752e8819c2bab23d79089353aed1acc9b", + "sha256:4c2f1d0b27bcef301e5d5c1da05ffd7d174f807f61889c006b8e708b16bc978e", + "sha256:59b8d738867b59c5daaff5df242b5f3f9c58b47862f603c6ee530964b897b69b", + "sha256:8d4f1ee2a67cf8f792d4fc9b8c7bb2148174e838d935f175653aec234752828b", + "sha256:97ab9e35b47bda0441332204960f95c1169c55ec8e989381bedd32bdb9f78b05", + "sha256:9cf93e185507bfdaa7ed45a90049bd3f1ed3f6357ad3772b31e993ff723cf67d", + "sha256:a5a81472c0ca6181492b9291c316ff60c6c94dd3f21c1e8c481f21923d899af0", + "sha256:aaa1feb0fdd094af6db0a16cbd446ed94285a50e320aede5971152d9ea022df8", + "sha256:b36bf4408f8400ee9ab13ff129e71f2e4c72ce2d8886b744aeab77ce50a55cf6", + "sha256:bb345f7e98b38a2c1ef33ff1145687234f78dfeedf308b41b3e41f4b42eba099", + "sha256:c13ae15695e0eb4ba2db920d6a197171d2398c675afa4f27460b6381d20a6884", + "sha256:c4a233a00cc5b64543e97733902151bc6738396931b3c166aad03a3aaadbd479", + "sha256:c521c5f8a95baabba69c68dd0f5e34f37c8adf1a9691f9884dba3eab4ebadc29", + "sha256:c772edd094fe3e54d6d54fdecb90c51cb8d07b55e9c1cda2d33e9615e33d07e8", + "sha256:cebfba6542855403b29e4bc95bbcd5ab444f21137b440f2fb7c7925ca0e55bfd", + "sha256:d7490e013c4bad3e8db804fc6483b47125dc8df0ebcfd6e419bd25df35025301", + "sha256:dfb6063619f297cbd22c67530d7465d98348b35d0424bbc1756b36c5ef9f99d4", + "sha256:e80a15b48a66f35c7c33db2a7df4034a533b362269d0e60e0036e23f14bac7b5", + "sha256:ea444fa1c1ec4f8d2ce965bb01e06148ef9ceb398fb2f627511d50f137eac35b", + "sha256:ec986cbf8837a49f9612cc1cfc2a8ccb54875cfce5355a121279de35124ea1db", + "sha256:fb641df6de8c4a55c784c24d334d53096954d9b30679d3ce5eb6a4d25c1020a3", + "sha256:fb88bd791c8efbcb36de12f0aa519ceec0b7806d3decff16e412e097d4725d44", + "sha256:ffa1be3d566a9cbd21a5f2d95fd9262ec6c337c499291bfeb51547b8de18942e" + ], + "version": "==1.24.0" }, "idna": { "hashes": [ @@ -121,31 +121,31 @@ }, "protobuf": { "hashes": [ - "sha256:00a1b0b352dc7c809749526d1688a64b62ea400c5b05416f93cfb1b11a036295", - "sha256:01acbca2d2c8c3f7f235f1842440adbe01bbc379fa1cbdd80753801432b3fae9", - "sha256:0a795bca65987b62d6b8a2d934aa317fd1a4d06a6dd4df36312f5b0ade44a8d9", - "sha256:0ec035114213b6d6e7713987a759d762dd94e9f82284515b3b7331f34bfaec7f", - "sha256:31b18e1434b4907cb0113e7a372cd4d92c047ce7ba0fa7ea66a404d6388ed2c1", - "sha256:32a3abf79b0bef073c70656e86d5bd68a28a1fbb138429912c4fc07b9d426b07", - "sha256:55f85b7808766e5e3f526818f5e2aeb5ba2edcc45bcccede46a3ccc19b569cb0", - "sha256:64ab9bc971989cbdd648c102a96253fdf0202b0c38f15bd34759a8707bdd5f64", - "sha256:64cf847e843a465b6c1ba90fb6c7f7844d54dbe9eb731e86a60981d03f5b2e6e", - "sha256:917c8662b585470e8fd42f052661fc66d59fccaae450a60044307dcbf82a3335", - "sha256:afed9003d7f2be2c3df20f64220c30faec441073731511728a2cb4cab4cd46a6", - "sha256:bf8e05d638b585d1752c5a84247134a0350d3a8b73d3632489a014a9f6f1e758", - "sha256:d831b047bd69becaf64019a47179eb22118a50dd008340655266a906c69c6417", - "sha256:de2760583ed28749ff885789c1cbc6c9c06d6de92fc825740ab99deb2f25ea4d", - "sha256:eabc4cf1bc19689af8022ba52fd668564a8d96e0d08f3b4732d26a64255216a4", - "sha256:fcff6086c86fb1628d94ea455c7b9de898afc50378042927a59df8065a79a549" - ], - "version": "==3.9.1" + "sha256:26c0d756c7ad6823fccbc3b5f84c619b9cc7ac281496fe0a9d78e32023c45034", + "sha256:3200046e4d4f6c42ed66257dbe15e2e5dc76072c280e9b3d69dc8f3a4fa3fbbc", + "sha256:368f1bae6dd22d04fd2254d30cd301863408a96ff604422e3ddd8ab601f095a4", + "sha256:3902fa1920b4ef9f710797496b309efc5ccd0faeba44dc82ed6a711a244764a0", + "sha256:3a7a8925ba6481b9241cdb5d69cd0b0700f23efed6bb691dc9543faa4aa25d6f", + "sha256:4bc33d49f43c6e9916fb56b7377cb4478cbf25824b4d2bedfb8a4e3df31c12ca", + "sha256:568b434a36e31ed30d60d600b2227666ce150b8b5275948f50411481a4575d6d", + "sha256:5c393cd665d03ce6b29561edd6b0cc4bcb3fb8e2a7843e8f223d693f07f61b40", + "sha256:80072e9ba36c73cf89c01f669c7b123733fc2de1780b428082a850f53cc7865f", + "sha256:843f498e98ad1469ad54ecb4a7ccf48605a1c5d2bd26ae799c7a2cddab4a37ec", + "sha256:aa45443035651cbfae74c8deb53358ba660d8e7a5fbab3fc4beb33fb3e3ca4be", + "sha256:aaab817d9d038dd5f56a6fb2b2e8ae68caf1fd28cc6a963c755fa73268495c13", + "sha256:e6f68b9979dc8f75299293d682f67fecb72d78f98652da2eeb85c85edef1ca94", + "sha256:e7366cabddff3441d583fdc0176ab42eba4ee7090ef857d50c4dd59ad124003a", + "sha256:f0144ad97cd28bfdda0567b9278d25061ada5ad2b545b538cd3577697b32bda3", + "sha256:f655338491481f482042f19016647e50365ab41b75b486e0df56e0dcc425abf4" + ], + "version": "==3.9.2" }, "pyasn1": { "hashes": [ - "sha256:3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", - "sha256:b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86" + "sha256:62cdade8b5530f0b185e09855dd422bc05c0bbff6b72ff61381c09dac7befd8c", + "sha256:a9495356ca1d66ed197a0f72b41eb1823cf7ea8b5bd07191673e8147aecf8604" ], - "version": "==0.4.6" + "version": "==0.4.7" }, "pyasn1-modules": { "hashes": [ @@ -184,13 +184,27 @@ }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", + "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" ], - "version": "==1.25.3" + "version": "==1.25.6" } }, "develop": { + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, "bleach": { "hashes": [ "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", @@ -200,10 +214,10 @@ }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "chardet": { "hashes": [ @@ -272,6 +286,14 @@ ], "version": "==2.8" }, + "importlib-metadata": { + "hashes": [ + "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", + "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + ], + "markers": "python_version < '3.8'", + "version": "==0.23" + }, "jinja2": { "hashes": [ "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", @@ -334,6 +356,20 @@ "index": "pypi", "version": "==1.0.4" }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, + "packaging": { + "hashes": [ + "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", + "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + ], + "version": "==19.2" + }, "pkginfo": { "hashes": [ "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", @@ -341,6 +377,20 @@ ], "version": "==1.5.0.1" }, + "pluggy": { + "hashes": [ + "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", + "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + ], + "version": "==0.13.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, "pygments": { "hashes": [ "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", @@ -348,6 +398,37 @@ ], "version": "==2.4.2" }, + "pyparsing": { + "hashes": [ + "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", + "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + ], + "version": "==2.4.2" + }, + "pytest": { + "hashes": [ + "sha256:13c1c9b22127a77fc684eee24791efafcef343335d855e3573791c68588fe1a5", + "sha256:d8ba7be9466f55ef96ba203fc0f90d0cf212f2f927e69186e1353e30bc7f62e5" + ], + "index": "pypi", + "version": "==5.2.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", + "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a" + ], + "index": "pypi", + "version": "==2.7.1" + }, + "pytest-mock": { + "hashes": [ + "sha256:3fd5ffb33b7041aea60a77f77b98e05d5acd577d53a01bf2ff0ca9780c6e3d84", + "sha256:a89104018a4083b5c402e23b15b855924b74d9a3511e94f230a33621b72e35e1" + ], + "index": "pypi", + "version": "==1.11.0" + }, "pyyaml": { "hashes": [ "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", @@ -408,25 +489,32 @@ }, "tqdm": { "hashes": [ - "sha256:1dc82f87a8726602fa7177a091b5e8691d6523138a8f7acd08e58088f51e389f", - "sha256:47220a4f2aeebbc74b0ab317584264ea44c745e1fd5ff316b675cd0aff8afad8" + "sha256:abc25d0ce2397d070ef07d8c7e706aede7920da163c64997585d42d3537ece3d", + "sha256:dd3fcca8488bb1d416aa7469d2f277902f26260c45aa86b667b074cd44b3b115" ], - "version": "==4.33.0" + "version": "==4.36.1" }, "twine": { "hashes": [ - "sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446", - "sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc" + "sha256:5319dd3e02ac73fcddcd94f035b9631589ab5d23e1f4699d57365199d85261e1", + "sha256:9fe7091715c7576df166df8ef6654e61bada39571783f2fd415bdcba867c6993" ], "index": "pypi", - "version": "==1.13.0" + "version": "==2.0.0" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", + "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" + ], + "version": "==1.25.6" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" ], - "version": "==1.25.3" + "version": "==0.1.7" }, "webencodings": { "hashes": [ @@ -434,6 +522,13 @@ "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" ], "version": "==0.5.1" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + ], + "version": "==0.6.0" } } } diff --git a/README.md b/README.md index 937a4f6..3f5b541 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Datastorm -![Python versions](https://img.shields.io/badge/Python-3.5%2C%203.6%2C%203.7-green.svg) ![PyPi](https://img.shields.io/badge/PyPi-0.0.0a5-brightgreen.svg) ![coverage](https://img.shields.io/badge/coverage-95%25-green.svg) +![Python versions](https://img.shields.io/badge/Python-3.5%2C%203.6%2C%203.7-green.svg) [![PyPI version](https://badge.fury.io/py/datastorm.svg)](https://badge.fury.io/py/datastorm) [![Documentation Status](https://readthedocs.org/projects/datastorm/badge/?version=latest)](https://datastorm.readthedocs.io/en/latest/?badge=latest) [![CircleCI](https://circleci.com/gh/JavierLuna/datastorm/tree/master.svg?style=svg)](https://circleci.com/gh/JavierLuna/datastorm/tree/master) ## What is it? diff --git a/datastorm/__init__.py b/datastorm/__init__.py index 086528a..f7bdc0c 100644 --- a/datastorm/__init__.py +++ b/datastorm/__init__.py @@ -1,3 +1,3 @@ from .datastorm import DataStorm -__version__ = '0.0.0a5' +__version__ = '0.0.0a6' diff --git a/datastorm/entity.py b/datastorm/entity.py index b54e955..ea90f1c 100644 --- a/datastorm/entity.py +++ b/datastorm/entity.py @@ -1,10 +1,11 @@ import inspect -from typing import Optional, Union +from typing import Optional, Union, Any from google.cloud import datastore from google.cloud.datastore import Key -from datastorm.fields import BaseField, AnyField +from datastorm.fields import BaseField +from datastorm.mapper import FieldMapper from datastorm.query import QueryBuilder @@ -29,19 +30,26 @@ class BaseEntity: def __init__(self, key: Union[Key, str], **kwargs): self.key = key if type(key) is not str else self.generate_key(key) - self.__datastorm_fields = self.__resolve_mappings() + self._datastorm_mapper = self.__resolve_mappings() + self.__set_defaults() - [self.set(name, field.default) for name, field in self.__datastorm_fields.items()] [self.set(name, value) for name, value in kwargs.items()] def save(self): self._datastore_client.put(self.get_datastore_entity()) - def set(self, name, value, field=None): - field = field or self.__datastorm_fields.get(value, AnyField) - self.__datastorm_fields[name] = field if field in self.__datastorm_fields or \ - not inspect.isclass(field) else field() - setattr(self, name, value) + def set(self, field_name: str, value: Any, field: BaseField = None): + if field: + self._map_field(field_name, field) + if field_name not in self._datastorm_mapper.fields: + self._datastorm_mapper.set_field(field_name, self._datastorm_mapper.get_field(field_name)) + + + setattr(self, field_name, value) + + def _map_field(self, field_name: str, field: Union[BaseField, type]): + field = field() if inspect.isclass(field) else field + self._datastorm_mapper.set_field(field_name, field) def sync(self): buffer = self.get_datastore_entity() @@ -60,20 +68,26 @@ def generate_key(cls, identifier: str, parent_key: Optional[Key] = None): def get_datastore_entity(self): entity = datastore.Entity(key=self.key) - entity_dict = {field.field_name or field_name: field.dumps(getattr(self, field_name)) for field_name, field in - self.__datastorm_fields.items()} - entity.update(entity_dict) + for field_name in self._datastorm_mapper.fields: + field = self._datastorm_mapper.get_field(field_name) + + entity_dict = {field.field_name or field_name: field.dumps(getattr(self, field_name))} + entity.update(entity_dict) return entity - def __resolve_mappings(self): - field_mapping = {} + def __resolve_mappings(self) -> FieldMapper: + field_mapper = FieldMapper() for attribute_name in dir(self): attribute = getattr(self, attribute_name) if inspect.isclass(attribute) and issubclass(attribute, BaseField): attribute = attribute() if isinstance(attribute, BaseField): - field_mapping[attribute_name] = attribute - return field_mapping + field_mapper.set_field(attribute_name, attribute) + return field_mapper + + def __set_defaults(self): + for field_name in self._datastorm_mapper.fields: + self.set(field_name, self._datastorm_mapper.default(field_name)) def __repr__(self): return "< {name} >".format(name=self.__kind__) # pragma: no cover diff --git a/datastorm/fields.py b/datastorm/fields.py index 155f00c..6ea7263 100644 --- a/datastorm/fields.py +++ b/datastorm/fields.py @@ -1,3 +1,4 @@ +import json from typing import Union, Any from datastorm.filter import Filter from datastorm.base import FieldABC @@ -16,6 +17,10 @@ def loads(self, serialized_value) -> Any: def dumps(self, value) -> Any: if self.enforce_type and not self.check_type(value): raise ValueError("Type mismatch for value {} in field {}".format(value, self.__class__.__name__)) + return self._dumps(value) + + @classmethod + def _dumps(cls, value) -> Any: return value def check_type(self, value) -> bool: @@ -25,23 +30,29 @@ def check_type(self, value) -> bool: def default(self): return self._default() + def _generate_filter(self, op: str, other: Union[str, int, float, bool]): + if self.enforce_type and not self.check_type(other): + raise ValueError( + "Comparing field {} with '{}' of type {}".format(self.__class__.__name__, other, type(other))) + return Filter(self.field_name, op, other) + def __eq__(self, other: Union[str, int, float, bool]): - return Filter(self.field_name, "=", other) + return self._generate_filter("=", other) def __lt__(self, other: Union[str, int, float, bool]): - return Filter(self.field_name, "<", other) + return self._generate_filter("<", other) def __gt__(self, other: Union[str, int, float, bool]): - return Filter(self.field_name, ">", other) + return self._generate_filter(">", other) def __le__(self, other: Union[str, int, float, bool]): - return Filter(self.field_name, "<=", other) + return self._generate_filter("<=", other) def __ge__(self, other: Union[str, int, float, bool]): - return Filter(self.field_name, ">=", other) + return self._generate_filter(">=", other) def __repr__(self): - return "< {field_type} {field_name} >".format(field_type=self.__class__.__name__, + return "< {field_type} name={field_name} >".format(field_type=self.__class__.__name__, field_name=self.field_name) # pragma: no cover @@ -54,31 +65,84 @@ class BooleanField(BaseField): def check_type(self, value): return isinstance(value, bool) + @classmethod + def _dumps(cls, value) -> bool: + return bool(value) + + @property + def default(self): + return super().default or bool() + class IntField(BaseField): def check_type(self, value): return isinstance(value, int) and not isinstance(value, bool) + @classmethod + def _dumps(cls, value) -> int: + return int(value) + + @property + def default(self): + return super().default or int() + class FloatField(BaseField): def check_type(self, value): return isinstance(value, float) + @classmethod + def _dumps(cls, value) -> float: + return float(value) + + @property + def default(self): + return super().default or float() + class StringField(BaseField): def check_type(self, value): return isinstance(value, str) + @classmethod + def _dumps(cls, value) -> str: + return str(value) -class DictField(BaseField): + @property + def default(self): + return super().default or str() + + +class JSONField(BaseField): + def check_type(self, value): + json_types = [bool, int, float, str, list, dict] + return any(isinstance(value, json_type) for json_type in json_types) + + @classmethod + def _dumps(cls, value) -> str: + return json.dumps(value) + + def loads(self, serialized_value: str) -> dict: + return json.loads(serialized_value) + + @property + def default(self): + return super().default or dict() + + +class DictField(JSONField): def check_type(self, value): return isinstance(value, dict) -class ListField(BaseField): +class ListField(JSONField): def check_type(self, value): return isinstance(value, list) + + @property + def default(self): + return super().default or list() diff --git a/datastorm/mapper.py b/datastorm/mapper.py new file mode 100644 index 0000000..b96ab4d --- /dev/null +++ b/datastorm/mapper.py @@ -0,0 +1,37 @@ +from typing import Any, Optional, Dict, List + +from datastorm.fields import AnyField, BaseField + + +class FieldMapper: + + def __init__(self): + self.__mappings: Dict[str, BaseField] = {} + self.__datastore_mappings : Dict[str, str] = {} + + def get_field(self, field_name: str) -> BaseField: + field_name = self.__datastore_mappings.get(field_name, field_name) + return self.__mappings.get(field_name, AnyField()) + + def set_field(self, field_name: str, field: BaseField): + field.field_name = field.field_name or field_name + self.__mappings[field_name] = field + self.__datastore_mappings[field.field_name] = field_name + + def resolve_datastore_alias(self, datastore_alias: str) -> str: + return self.__datastore_mappings.get(datastore_alias, datastore_alias) + + def default(self, field_name: str) -> Any: + return self.__mappings[field_name].default + + def dumps(self, field_name: str, value: Any, field: Optional[BaseField] = None) -> Any: + field = field or self.get_field(field_name) + return field.dumps(value) + + def loads(self, field_name: str, value: Any, field: Optional[BaseField] = None) -> Any: + field = field or self.get_field(field_name) + return field.loads(value) + + @property + def fields(self) -> List[str]: + return list(self.__mappings) diff --git a/datastorm/query.py b/datastorm/query.py index ee3e68f..44725d8 100644 --- a/datastorm/query.py +++ b/datastorm/query.py @@ -68,7 +68,11 @@ def first(self): return result def _make_entity_instance(self, key: Key, attr_data: dict): - return self._entity_class(key, **attr_data) + entity = self._entity_class(key) + for datastore_field_name, serialized_data in attr_data.items(): + datastorm_field_name = entity._datastorm_mapper.resolve_datastore_alias(datastore_field_name) + entity.set(datastorm_field_name, entity._datastorm_mapper.get_field(datastorm_field_name).loads(serialized_data)) + return entity def __repr__(self): return "< QueryBuilder filters: {}, ordered by: {}>".format(self._filters or "No filters", @@ -110,4 +114,4 @@ def all(self, page_size: int = 500, parent_key: Key = None): yield last_yielded_entity cursor = query_iter.next_page_token if not cursor or last_yielded_entity is None: - break \ No newline at end of file + break diff --git a/docs/Datastorm/fields.md b/docs/Datastorm/fields.md index 42281a2..7d2f2b1 100644 --- a/docs/Datastorm/fields.md +++ b/docs/Datastorm/fields.md @@ -62,6 +62,7 @@ Checks the input's type. * `IntField` * `FloatField` * `StringField` +* `JSONField` * `DictField` * `ListField` diff --git a/setup.py b/setup.py index f982422..3653ce2 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ from setuptools import setup, find_packages +import datastorm setup( name='datastorm', - version='0.0.0a5', + version=datastorm.__version__, description='Simple and easy to use ODM for Google Datastore. Documentation: https://datastorm-docs.rtfd.io', url='https://github.com/JavierLuna/datastorm', author='Javier Luna Molina', diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py new file mode 100644 index 0000000..b5e147a --- /dev/null +++ b/tests/e2e/conftest.py @@ -0,0 +1,28 @@ +import pytest +from google.auth.credentials import AnonymousCredentials + +from datastorm import DataStorm, fields + + +@pytest.fixture(scope='session') +def test_project_id(): + return "datastorm-test-env" + + +@pytest.fixture(scope='session') +def datastorm_client(test_project_id): + return DataStorm(test_project_id, credentials=AnonymousCredentials()) + + +@pytest.fixture +def ds_entity(datastorm_client): + class TestEntity1(datastorm_client.DSEntity): + __kind__ = "TestEntity1" + + dict_field = fields.DictField() + list_field = fields.ListField() + int_field = fields.IntField() + + yield TestEntity1 + + [entity.delete() for entity in TestEntity1.query.all()] diff --git a/tests/e2e/test_create_fetch.py b/tests/e2e/test_create_fetch.py new file mode 100644 index 0000000..c441bfc --- /dev/null +++ b/tests/e2e/test_create_fetch.py @@ -0,0 +1,14 @@ +def test_create_fetch_entity_with_list(ds_entity): + entity_key = "test_entity" + list_values = [1, 2, 3] + dict_values = {"test": "test"} + int_value = 3 + test_entity = ds_entity(entity_key, list_field=list_values, dict_field=dict_values, int_field=int_value) + test_entity.save() + fetched_test_entity = ds_entity.query.get(entity_key) + + assert fetched_test_entity.list_field == list_values + + assert fetched_test_entity.dict_field == dict_values + + assert fetched_test_entity.int_field == int_value diff --git a/tests/e2e/test_mappings.py b/tests/e2e/test_mappings.py new file mode 100644 index 0000000..08fc05f --- /dev/null +++ b/tests/e2e/test_mappings.py @@ -0,0 +1,15 @@ +from datastorm import fields + + +def test_create_fetch_entity_with_list(ds_entity): + entity_key = "test_entity" + datastore_field_name = "datastore" + + class CustomEntity(ds_entity): + aliased_field = fields.AnyField(field_name=datastore_field_name) + + test_entity = CustomEntity(entity_key) + test_entity.save() + fetched_test_entity = CustomEntity.query.get(entity_key) + + assert not hasattr(fetched_test_entity, datastore_field_name) diff --git a/tests/legacy/__init__.py b/tests/legacy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_base.py b/tests/legacy/test_base.py similarity index 100% rename from tests/test_base.py rename to tests/legacy/test_base.py diff --git a/tests/test_datastorm.py b/tests/legacy/test_datastorm.py similarity index 96% rename from tests/test_datastorm.py rename to tests/legacy/test_datastorm.py index a23dde3..c78e84c 100644 --- a/tests/test_datastorm.py +++ b/tests/legacy/test_datastorm.py @@ -1,5 +1,5 @@ from uuid import uuid4 -from tests.test_base import TestBase +from tests.legacy.test_base import TestBase class TestDatastorm(TestBase): diff --git a/tests/test_operations.py b/tests/legacy/test_operations.py similarity index 96% rename from tests/test_operations.py rename to tests/legacy/test_operations.py index 4123570..c5bca06 100644 --- a/tests/test_operations.py +++ b/tests/legacy/test_operations.py @@ -1,6 +1,6 @@ from uuid import uuid4 -from tests.test_base import TestBase +from tests.legacy.test_base import TestBase class TestOperations(TestBase): diff --git a/tests/test_queries.py b/tests/legacy/test_queries.py similarity index 99% rename from tests/test_queries.py rename to tests/legacy/test_queries.py index 0564f4d..c8ca7b4 100644 --- a/tests/test_queries.py +++ b/tests/legacy/test_queries.py @@ -1,7 +1,7 @@ from uuid import uuid4 from datastorm.fields import IntField, FloatField, StringField, BooleanField -from tests.test_base import TestBase +from tests.legacy.test_base import TestBase class TestQuery(TestBase): diff --git a/tests/test_fields.py b/tests/test_fields.py deleted file mode 100644 index 116931e..0000000 --- a/tests/test_fields.py +++ /dev/null @@ -1,250 +0,0 @@ -import unittest - -from datastorm.fields import BaseField, AnyField, BooleanField, IntField, FloatField, StringField, DictField, ListField - - -class TestBaseField(unittest.TestCase): - - - def test_base_field_loads(self): - field = BaseField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_base_field_dumps_no_enforce_type(self): - field = BaseField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_base_field_dumps_enforce_type(self): - field = BaseField(enforce_type=True) - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_base_field_default_default_None(self): - field = BaseField() - self.assertIsNone(field.default) - - def test_base_field_default_value(self): - field = BaseField(default=1) - self.assertEqual(1, field.default) - - def test_base_field_default_callable(self): - field = BaseField(default=lambda : 1) - self.assertEqual(1, field.default) - - -class TestAnyField(unittest.TestCase): - - def test_any_field_loads(self): - field = AnyField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_any_field_dumps_no_enforce_type(self): - field = AnyField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_any_field_dumps_enforce_type(self): - field = AnyField(enforce_type=True) - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_any_field_default_default_None(self): - field = AnyField() - self.assertIsNone(field.default) - - def test_any_field_default_value(self): - field = AnyField(default=1) - self.assertEqual(1, field.default) - - def test_any_field_default_callable(self): - field = AnyField(default=lambda: 1) - self.assertEqual(1, field.default) - -class TestBooleanField(unittest.TestCase): - - def test_boolean_field_loads(self): - field = BooleanField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_boolean_field_dumps_no_enforce_type(self): - field = BooleanField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_boolean_field_dumps_enforce_type(self): - field = BooleanField(enforce_type=True) - for value in [1, 1.2, "foo", object(), {}, []]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual(True, field.dumps(True)) - - def test_boolean_field_default_default_None(self): - field = BooleanField() - self.assertIsNone(field.default) - - def test_boolean_field_default_value(self): - field = BooleanField(default=True) - self.assertEqual(True, field.default) - - def test_boolean_field_default_callable(self): - field = BooleanField(default=bool) - self.assertEqual(bool(), field.default) - -class TestIntField(unittest.TestCase): - - def test_int_field_loads(self): - field = IntField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_int_field_dumps_no_enforce_type(self): - field = IntField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_int_field_dumps_enforce_type(self): - field = IntField(enforce_type=True) - for value in [1.2, True, "foo", object(), {}, []]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual(1, field.dumps(1)) - - def test_int_field_default_default_None(self): - field = IntField() - self.assertIsNone(field.default) - - def test_int_field_default_value(self): - field = IntField(default=1) - self.assertEqual(1, field.default) - - def test_int_field_default_callable(self): - field = IntField(default=int) - self.assertEqual(int(), field.default) - -class TestFloatField(unittest.TestCase): - - def test_float_field_loads(self): - field = FloatField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_float_field_dumps_no_enforce_type(self): - field = FloatField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_float_field_dumps_enforce_type(self): - field = FloatField(enforce_type=True) - for value in [1, True, "foo", object(), {}, []]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual(1.2, field.dumps(1.2)) - - def test_float_field_default_default_None(self): - field = FloatField() - self.assertIsNone(field.default) - - def test_float_field_default_value(self): - field = FloatField(default=1.2) - self.assertEqual(1.2, field.default) - - def test_float_field_default_callable(self): - field = FloatField(default=float) - self.assertEqual(float(), field.default) - -class TestStringField(unittest.TestCase): - - def test_string_field_loads(self): - field = StringField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_string_field_dumps_no_enforce_type(self): - field = StringField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_string_field_dumps_enforce_type(self): - field = StringField(enforce_type=True) - for value in [1, 1.2, True, object(), {}, []]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual("foo", field.dumps("foo")) - - def test_string_field_default_default_None(self): - field = StringField() - self.assertIsNone(field.default) - - def test_string_field_default_value(self): - field = StringField(default="foo") - self.assertEqual("foo", field.default) - - def test_string_field_default_callable(self): - field = StringField(default=str) - self.assertEqual(str(), field.default) - -class TestDictField(unittest.TestCase): - - def test_dict_field_loads(self): - field = DictField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_dict_field_dumps_no_enforce_type(self): - field = DictField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_dict_field_dumps_enforce_type(self): - field = DictField(enforce_type=True) - for value in [1, 1.2, True, "foo", object(), []]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual({}, field.dumps({})) - - def test_dict_field_default_default_None(self): - field = DictField() - self.assertIsNone(field.default) - - def test_dict_field_default_value(self): - field = DictField(default={}) - self.assertEqual({}, field.default) - - def test_dict_field_default_callable(self): - field = DictField(default=dict) - self.assertEqual(dict(), field.default) - -class TestListField(unittest.TestCase): - - def test_list_field_loads(self): - field = ListField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.loads(value)) - - def test_list_field_dumps_no_enforce_type(self): - field = ListField() - for value in [1, 1.2, True, "foo", object(), {}, []]: - self.assertEqual(value, field.dumps(value)) - - def test_list_field_dumps_enforce_type(self): - field = ListField(enforce_type=True) - for value in [1, 1.2, True, "foo", object(), {}]: - with self.assertRaises(ValueError): - field.dumps(value) - self.assertEqual([], field.dumps([])) - - def test_list_field_default_default_None(self): - field = ListField() - self.assertIsNone(field.default) - - def test_list_field_default_value(self): - field = ListField(default=[]) - self.assertEqual([], field.default) - - def test_list_field_default_callable(self): - field = ListField(default=list) - self.assertEqual(list(), field.default) \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/fields/__init__.py b/tests/unit/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/fields/test_base.py b/tests/unit/fields/test_base.py new file mode 100644 index 0000000..0d9b59e --- /dev/null +++ b/tests/unit/fields/test_base.py @@ -0,0 +1,43 @@ +import pytest + +from datastorm.fields import BaseField + + +def test_callable_default(): + default_value = "test" + field = BaseField(default=lambda: default_value) + assert field.default == default_value + + +def test_value_default(): + default_value = "test" + field = BaseField(default=default_value) + assert field.default == default_value + + +def test_enforce_variable_checks_variable_type(mocker): + mock_check_type = mocker.patch('datastorm.fields.BaseField.check_type', return_value=True) + field = BaseField(enforce_type=True) + field.dumps("test") + assert mock_check_type.called + + +def test_no_enforce_variable_no_checks_variable_type(mocker): + mock_check_type = mocker.patch('datastorm.fields.BaseField.check_type') + field = BaseField(enforce_type=False) + field.dumps("test") + assert not mock_check_type.called + + +def test_enforce_variable_incorrect_type_raises_value_error(mocker): + mocker.patch('datastorm.fields.BaseField.check_type', return_value=False) + field = BaseField(enforce_type=True) + with pytest.raises(ValueError) as e: + field.dumps("test") + + +def test_enforce_variable_incorrect_comparison_raises_value_error(mocker): + mocker.patch('datastorm.fields.BaseField.check_type', return_value=False) + field = BaseField(enforce_type=True) + with pytest.raises(ValueError) as e: + field == "test" diff --git a/tests/unit/fields/test_boolean.py b/tests/unit/fields/test_boolean.py new file mode 100644 index 0000000..bf0fc92 --- /dev/null +++ b/tests/unit/fields/test_boolean.py @@ -0,0 +1,20 @@ +import pytest + +from datastorm.fields import BooleanField + + +@pytest.fixture +def field(): + return BooleanField() + + +def test_check_type_boolean_ok(field): + assert field.check_type(True) + + +def test_check_type_no_boolean_ko(field): + assert not field.check_type("test") + + +def test_dumps_booleans(field): + assert type(field.dumps(1)) is bool diff --git a/tests/unit/fields/test_dict.py b/tests/unit/fields/test_dict.py new file mode 100644 index 0000000..e056403 --- /dev/null +++ b/tests/unit/fields/test_dict.py @@ -0,0 +1,16 @@ +import pytest + +from datastorm.fields import DictField + + +@pytest.fixture +def field(): + return DictField() + + +def test_check_type_dict_ok(field): + assert field.check_type({}) + + +def test_check_type_no_dict_ko(field): + assert not field.check_type("test") diff --git a/tests/unit/fields/test_float.py b/tests/unit/fields/test_float.py new file mode 100644 index 0000000..42dd2af --- /dev/null +++ b/tests/unit/fields/test_float.py @@ -0,0 +1,20 @@ +import pytest + +from datastorm.fields import FloatField + + +@pytest.fixture +def field(): + return FloatField() + + +def test_check_type_float_ok(field): + assert field.check_type(1.03) + + +def test_check_type_no_float_ko(field): + assert not field.check_type("test") + + +def test_dumps_floats(field): + assert type(field.dumps(1)) is float diff --git a/tests/unit/fields/test_int.py b/tests/unit/fields/test_int.py new file mode 100644 index 0000000..7d41fe0 --- /dev/null +++ b/tests/unit/fields/test_int.py @@ -0,0 +1,20 @@ +import pytest + +from datastorm.fields import IntField + + +@pytest.fixture +def field(): + return IntField() + + +def test_check_type_int_ok(field): + assert field.check_type(3) + + +def test_check_type_no_int_ko(field): + assert not field.check_type("test") + + +def test_dumps_integers(field): + assert type(field.dumps(1.03)) is int diff --git a/tests/unit/fields/test_json.py b/tests/unit/fields/test_json.py new file mode 100644 index 0000000..0579172 --- /dev/null +++ b/tests/unit/fields/test_json.py @@ -0,0 +1,27 @@ +import pytest + +from datastorm.fields import JSONField + + +@pytest.fixture +def field(): + return JSONField() + + +@pytest.mark.parametrize("value", [bool(), int(), str(), dict(), list()]) +def test_check_type_ok(field, value): + assert field.check_type(value) + + +def test_check_type_ko(field): + assert not field.check_type(set()) + + +@pytest.mark.parametrize("value", [bool(), int(), str(), dict(), list()]) +def test_dumps_json_str(field, value): + assert type(field.dumps(value)) == str + + +@pytest.mark.parametrize("value", [bool(), int(), str(), dict(), list()]) +def test_dumps_loads_same_value(field, value): + assert field.loads(field.dumps(value)) == value diff --git a/tests/unit/fields/test_list.py b/tests/unit/fields/test_list.py new file mode 100644 index 0000000..cbc173e --- /dev/null +++ b/tests/unit/fields/test_list.py @@ -0,0 +1,16 @@ +import pytest + +from datastorm.fields import ListField + + +@pytest.fixture +def field(): + return ListField() + + +def test_check_type_list_ok(field): + assert field.check_type([]) + + +def test_check_type_no_list_ko(field): + assert not field.check_type("test") diff --git a/tests/unit/fields/test_string.py b/tests/unit/fields/test_string.py new file mode 100644 index 0000000..00d8710 --- /dev/null +++ b/tests/unit/fields/test_string.py @@ -0,0 +1,20 @@ +import pytest + +from datastorm.fields import StringField + + +@pytest.fixture +def field(): + return StringField() + + +def test_check_type_string_ok(field): + assert field.check_type("test") + + +def test_check_type_no_string_ko(field): + assert not field.check_type(1) + + +def test_dumps_strings(field): + assert type(field.dumps(1)) is str