From 6f0724a3e05c2df2b7922704a156edb4f5191c01 Mon Sep 17 00:00:00 2001 From: sam57719 Date: Thu, 25 Apr 2024 00:31:59 +0100 Subject: [PATCH 01/11] Testing MongoDB Need to work out abstraction whilst still keeping it plugin based Looking at mongogettersetter library --- poetry.lock | 105 +++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/pymongo_test.py | 43 +++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 tests/pymongo_test.py diff --git a/poetry.lock b/poetry.lock index d9eb4fc..d1eee1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -129,6 +129,27 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "fastapi" version = "0.110.1" @@ -545,6 +566,88 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pymongo" +version = "4.7.0" +description = "Python driver for MongoDB " +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8449b6af19cac09cce9d0834c196b29b72b29e05724f4ea208b3f602fdd47086"}, + {file = "pymongo-4.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb00787bed1939ef21ffcb09b3034b193c3c6e9838724e2c05ef881cb2b03a33"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8c4cbe5a1258b9f3a49f83781c8b2fb58f39a682779a3c81dc444a609cb15ba"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12db8e8768bd0d4a433eea3463f05648c3f65f262776c777a0e19e7c55f27a73"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7be2e57df38fa9b1b6f9ebe5bedd38118b511d3bdf0d9e77158c476542c9153d"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2b49670b32df8cf6650133cf439593f0291228ce971094c62c3a478024c7d1"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5366f28b2115120611536914540b0d247a89b09bb80bbc78893f246a584165b9"}, + {file = "pymongo-4.7.0-cp310-cp310-win32.whl", hash = "sha256:6c993fff4c110f6de4d76b76af97733efecae83b688cb27d1a3c5431415e3803"}, + {file = "pymongo-4.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:66b490775aa4542e0585ffdff1d0c6c4279536c852334f34a6a9a5c882beafd4"}, + {file = "pymongo-4.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9584be3d20ee26b53c0b1e25ba38196b7f65f594f48211b5ab3fa12b428ec6a9"}, + {file = "pymongo-4.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db2885773af0c10420e6bb86e84ee780bc3817d45a29ef24d8f6376ae2351eec"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8af3de7fea21b1ced0770766ec37a5900a62b45fe4b8f1dfa521226d591dbf66"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78b0ba6d60c7f2ac779909ac53383c83584826a304206559599c46a33366622a"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c82105c91cf95821039aca48350630435e7be18989496b6292aaa8779fa5fb6"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44eb2a3adaa0916f2fb6812d4d805956fd376b7fceae3b62f5dfae5e29330786"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2161278182f3163d15afc3c578097ec20c844ac7180e41134a2a2b5c9ae77b9d"}, + {file = "pymongo-4.7.0-cp311-cp311-win32.whl", hash = "sha256:98cb932ab936d702e28cf8da1982dcf5e7cfc35736b7516c0df7aaa46c63e0e2"}, + {file = "pymongo-4.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:3f1d57edc2a4bd96ae5741e4d83d3d54695174fd9068c88c89e12f7262be4de4"}, + {file = "pymongo-4.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:36d05d1ff861dda7c9e84d9848ea6f2b5d2245ae1093865d14597de29ba95b37"}, + {file = "pymongo-4.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ad32bb7e5f889fc5994001f7bb8bf945b52e10e428a563dfce0661961eae224"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8885f825203fa14ce863b462effcd93e07bfc6e582b3b93cfcde5ae42ccc9923"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf4187bc91bd10e29857775651101d0ec26e580d6b46a8c5cbf93928358ac3c3"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aebd99aaea95c48fba24bc3d7b72e7bf70e06df4c647de938c4d3dce2fd25a1c"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52facf98dcba501b2ae337d21f065cc30ceb25b97ce8f17878c1ae9d781f7f26"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f807dadc8030a5b55915f78fac25393af47bee8ccb62b5a6c5c622274ff4adf1"}, + {file = "pymongo-4.7.0-cp312-cp312-win32.whl", hash = "sha256:7a3c9218c5bc4384fa079f41b744473ada6a5f549fc11a4ae0fe7287746acc04"}, + {file = "pymongo-4.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:97ccb53d9310d5963df1a4543f1cfabdfd914638a5c8438234f6ed70d9303222"}, + {file = "pymongo-4.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:41d647fdaedba2f5b5c92299575814c164af44696fed3a4fc0d0df4f29eabcb2"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f53cf5bf65dda3fc1b5ec5f760233a41b282db3157d135e9272101f0492825f"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6673daf8fc23a96934cbb7a3626dcfa3ae21510492047e6003dfe3f26e62886b"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d7fc4891f5482e42c35be6931e9cf6b635d7d95056ff45b56bae5f0384830f"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc34b4d92d5d8671be6b728076f275ccfe8495c7e6b74750b634190e17ede68"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4d584b249c79acae86729d216a5185d833a90477d566f094b47d39620493870"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3784063fa43a0019b6a73e1e63b7fcbff4ded4d0ec5442202aa3caa12be9ef8"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bd514420eb09bba897016b7f1a2c17f9f3f1a7bc320c0505c59c3225e024b51c"}, + {file = "pymongo-4.7.0-cp37-cp37m-win32.whl", hash = "sha256:31ed6426fc68d500e2f27346e4ce3cc4fd3438adc99a3aaae41578c8a3b1f467"}, + {file = "pymongo-4.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69865d5739822c277d075a50601077767706e9f0862562e116ef13969d09fc9e"}, + {file = "pymongo-4.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbad9290b32ff1fc38bcac42699b8ea6a7c49cab081ba54761f3109bc5703248"}, + {file = "pymongo-4.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5307bfda4f39d9f1b3df9ab96b22d44bca458e44286ce806d716a2ffed2c46da"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f1a2ee91a97904cd21bddfce58d1868b6ea67b99bdd81dfe9cebfe35d0d751b"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cefa4e9be8bffa80de1bd70ae5ee79973e5db10befabcb25289fb52231a0dcff"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7b8bd94c63cef8f5bfbb29568934213d9730381db94f467f979c9e5aaa27130"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8ff95728965e633591862bfc197018d25bc349b5cd8da080acb52a2d17a6e95"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07265c14aa40259771255dbf59f9160a3690e82522ed02ab07e0e5c3045bad5b"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7214b7599a9f2e4ed01ecdc034cbe8f2926954bfdad9277390dd1bccf9fd6553"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1864f224b1793ef8698f779a7808e2b8c4a8f26bd0612c578412f62d6e99be46"}, + {file = "pymongo-4.7.0-cp38-cp38-win32.whl", hash = "sha256:2bfaf7a7eb6a91dfe58f384be16fd895e040d17236ee82217d1be9fc56869dc8"}, + {file = "pymongo-4.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2545c2be5ed25b1e9419cde4269d6a744076f80eaf86695d2dd888bddac29dd7"}, + {file = "pymongo-4.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7a00cee5b7a4160eed9cb43a2539037f572f01ed7261c2d1b4f7217060dba61"}, + {file = "pymongo-4.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c85f9824a7e90bf49aeed953e63942bff499116312e555ccb51bd3bf7ebe9342"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030dba8b3e1cb29f874739247e1eba1d01118a11583c62145c707a6e725d416a"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0dc2e365b14cb768898429e4331c58587be7143ad230858d19e8dd032f0adadc"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50865177882df0badc879c5b20f20cdc9c73494f0e2b19a40534af9c90018b4e"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b0d8393fb991b3dd934e891e064ae804e9267fce9d01d2f16b25e20564e3d"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7530ea1da6fe0bb1960390ba6523483dfdb2a6239d0e8058b1505cc2a79c75f8"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36536a41f08180adc647a21ca12dba859a23d841d28ca8fd3976c8781ed8290b"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b3a49be20a403d86eb1c559350fb56f28a859041756159eeb00e89f59b6e1288"}, + {file = "pymongo-4.7.0-cp39-cp39-win32.whl", hash = "sha256:a292ee4babdd632531effaac95da5f211caafa6a039c097a1b18a4dc0d52488b"}, + {file = "pymongo-4.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:cb809ff53ab3110ebc43a5e47aa945bb97e4ed9bc9beb07f935f5c83d9077e67"}, + {file = "pymongo-4.7.0.tar.gz", hash = "sha256:431093ef808944a14698b2a719b739fa7721778769e80c08423568991aa29c42"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] +encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + [[package]] name = "python-decouple" version = "3.8" @@ -893,4 +996,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "6938a3e3057c91bcf37a65d20a4ca934393d63e6cc52879096ec3a58247f368d" +content-hash = "05cb62359c1948e96948275ad30efb0282433d2c83635398d3d9d907e431c60e" diff --git a/pyproject.toml b/pyproject.toml index 8304347..d250d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ apscheduler = "^3.10.4" fastapi = "^0.110.0" uvicorn = "^0.29.0" dataclasses-json = "^0.6.4" +pymongo = {extras = ["srv"], version = "^4.7.0"} [build-system] diff --git a/tests/pymongo_test.py b/tests/pymongo_test.py new file mode 100644 index 0000000..b3ce990 --- /dev/null +++ b/tests/pymongo_test.py @@ -0,0 +1,43 @@ +from pymongo import MongoClient + +# Step 1: Connect to MongoDB +# Default connection url for local MongoDB instance. +client = MongoClient('mongodb://localhost:27017/') + +# Step 2: Create database +db = client['smib'] + +# Step 3: Create collection +collection = db['users'] + +# Step 4: Insert a document into the collection +new_user = {"name": "John Doe", "email": "john@example.com", "space_open": True} +insert_result = collection.insert_one(new_user) +print(f"Added user with id: {insert_result.inserted_id}") + +# Step 5: Retrieve all documents from the collection +for user in collection.find(): + print(user) + +from pymongo import MongoClient + +class MongoDBWrapper: + def __init__(self, db_name, collection_name): + self.client = MongoClient("mongodb://localhost:27017/") + self.db = self.client[db_name] + self.collection = self.db[collection_name] + + def __getattr__(self, field_name): + def method(value=None): + if value is None: + return self.collection.find({field_name: {"$exists": True}}) + else: + return self.collection.find({field_name: value}) + + return method + + +# Usage: +wrapper = MongoDBWrapper("smib", "users") +for doc in wrapper.space_open(): + print(next(wrapper.space_open())) From 60e827d92fbd09f00c2f7d7fa489482dc438f651 Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Sat, 27 Apr 2024 01:12:54 +0100 Subject: [PATCH 02/11] Added mongo-db container Playing around with structure for MongoDB interface for the plugins --- docker-compose.yml | 30 ++++ poetry.lock | 364 ++++++++++++++++++++++++++++++------------ pyproject.toml | 3 + tests/pymongo_test.py | 101 ++++++++---- 4 files changed, 361 insertions(+), 137 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b1158e7..69cd826 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,9 +6,13 @@ services: dockerfile: smib-fast.Dockerfile ports: - "4123:4123" + depends_on: + - smib-db environment: - WEBSERVER_HOST=smib-webserver - WEBSOCKET_ALLOWED_HOSTS=smib-webserver,smib-webserver.smib-bridge-network + - SLACK_APP_TOKEN + - SLACK_BOT_TOKEN networks: - smib-bridge-network command: "python -m smib.slack" @@ -24,10 +28,36 @@ services: - smib-slack environment: - WEBSOCKET_HOST=smib-slack + - SLACK_APP_TOKEN + - SLACK_BOT_TOKEN networks: - smib-bridge-network command: "python -m smib.webserver" + smib-db: + image: mongo + container_name: smib-db + restart: always + ports: + - 27017:27017 + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + + smib-db-ui: + image: mongo-express + container_name: smib-db-ui + depends_on: + - smib-db + restart: always + ports: + - 8082:8081 + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: example + ME_CONFIG_MONGODB_URL: mongodb://root:example@smib-db:27017/ + ME_CONFIG_BASICAUTH: false + networks: smib-bridge-network: name: smib-bridge-network diff --git a/poetry.lock b/poetry.lock index f2e9125..62d7668 100644 --- a/poetry.lock +++ b/poetry.lock @@ -111,18 +111,6 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" -[[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] - [[package]] name = "anyio" version = "4.3.0" @@ -244,6 +232,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + [[package]] name = "dataclasses-json" version = "0.6.4" @@ -260,6 +263,27 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "fastapi" version = "0.110.1" @@ -600,6 +624,21 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +name = "mongogettersetter" +version = "1.5.0" +description = "A clean way to handle MongoDB documents in Pythonic way" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mongogettersetter-1.5.0-py3-none-any.whl", hash = "sha256:e6bc4e8d024fdf76ec99a6618f18a63b39319d4252e4681e853559dc1933dda9"}, + {file = "mongogettersetter-1.5.0.tar.gz", hash = "sha256:f2100c43b145587b679b2eabeabae0a7f96c487169d06be519f3a13d787a24ea"}, +] + +[package.dependencies] +pymongo = "*" + [[package]] name = "multidict" version = "6.0.5" @@ -753,115 +792,173 @@ files = [ [[package]] name = "pydantic" -version = "2.7.0" -description = "Data validation using Python type hints" +version = "1.10.15" +description = "Data validation and settings management using python type hints" category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"}, - {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, + {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, + {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, + {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, + {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, + {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, + {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, + {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, + {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.18.1" -typing-extensions = ">=4.6.1" +typing-extensions = ">=4.2.0" [package.extras] -email = ["email-validator (>=2.0.0)"] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] [[package]] -name = "pydantic-core" -version = "2.18.1" -description = "Core functionality for Pydantic validation and serialization" +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"}, - {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"}, - {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"}, - {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"}, - {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"}, - {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"}, - {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"}, - {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"}, - {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"}, - {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"}, - {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"}, - {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"}, - {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"}, - {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"}, - {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"}, - {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"}, - {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"}, - {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"}, - {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"}, - {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"}, - {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"}, - {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"}, - {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"}, - {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"}, - {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"}, - {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"}, - {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"}, - {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"}, - {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"}, - {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"}, - {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"}, - {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"}, - {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pymongo" +version = "4.7.0" +description = "Python driver for MongoDB " +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8449b6af19cac09cce9d0834c196b29b72b29e05724f4ea208b3f602fdd47086"}, + {file = "pymongo-4.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb00787bed1939ef21ffcb09b3034b193c3c6e9838724e2c05ef881cb2b03a33"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8c4cbe5a1258b9f3a49f83781c8b2fb58f39a682779a3c81dc444a609cb15ba"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12db8e8768bd0d4a433eea3463f05648c3f65f262776c777a0e19e7c55f27a73"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7be2e57df38fa9b1b6f9ebe5bedd38118b511d3bdf0d9e77158c476542c9153d"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2b49670b32df8cf6650133cf439593f0291228ce971094c62c3a478024c7d1"}, + {file = "pymongo-4.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5366f28b2115120611536914540b0d247a89b09bb80bbc78893f246a584165b9"}, + {file = "pymongo-4.7.0-cp310-cp310-win32.whl", hash = "sha256:6c993fff4c110f6de4d76b76af97733efecae83b688cb27d1a3c5431415e3803"}, + {file = "pymongo-4.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:66b490775aa4542e0585ffdff1d0c6c4279536c852334f34a6a9a5c882beafd4"}, + {file = "pymongo-4.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9584be3d20ee26b53c0b1e25ba38196b7f65f594f48211b5ab3fa12b428ec6a9"}, + {file = "pymongo-4.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db2885773af0c10420e6bb86e84ee780bc3817d45a29ef24d8f6376ae2351eec"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8af3de7fea21b1ced0770766ec37a5900a62b45fe4b8f1dfa521226d591dbf66"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78b0ba6d60c7f2ac779909ac53383c83584826a304206559599c46a33366622a"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c82105c91cf95821039aca48350630435e7be18989496b6292aaa8779fa5fb6"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44eb2a3adaa0916f2fb6812d4d805956fd376b7fceae3b62f5dfae5e29330786"}, + {file = "pymongo-4.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2161278182f3163d15afc3c578097ec20c844ac7180e41134a2a2b5c9ae77b9d"}, + {file = "pymongo-4.7.0-cp311-cp311-win32.whl", hash = "sha256:98cb932ab936d702e28cf8da1982dcf5e7cfc35736b7516c0df7aaa46c63e0e2"}, + {file = "pymongo-4.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:3f1d57edc2a4bd96ae5741e4d83d3d54695174fd9068c88c89e12f7262be4de4"}, + {file = "pymongo-4.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:36d05d1ff861dda7c9e84d9848ea6f2b5d2245ae1093865d14597de29ba95b37"}, + {file = "pymongo-4.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ad32bb7e5f889fc5994001f7bb8bf945b52e10e428a563dfce0661961eae224"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8885f825203fa14ce863b462effcd93e07bfc6e582b3b93cfcde5ae42ccc9923"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf4187bc91bd10e29857775651101d0ec26e580d6b46a8c5cbf93928358ac3c3"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aebd99aaea95c48fba24bc3d7b72e7bf70e06df4c647de938c4d3dce2fd25a1c"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52facf98dcba501b2ae337d21f065cc30ceb25b97ce8f17878c1ae9d781f7f26"}, + {file = "pymongo-4.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f807dadc8030a5b55915f78fac25393af47bee8ccb62b5a6c5c622274ff4adf1"}, + {file = "pymongo-4.7.0-cp312-cp312-win32.whl", hash = "sha256:7a3c9218c5bc4384fa079f41b744473ada6a5f549fc11a4ae0fe7287746acc04"}, + {file = "pymongo-4.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:97ccb53d9310d5963df1a4543f1cfabdfd914638a5c8438234f6ed70d9303222"}, + {file = "pymongo-4.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:41d647fdaedba2f5b5c92299575814c164af44696fed3a4fc0d0df4f29eabcb2"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f53cf5bf65dda3fc1b5ec5f760233a41b282db3157d135e9272101f0492825f"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6673daf8fc23a96934cbb7a3626dcfa3ae21510492047e6003dfe3f26e62886b"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d7fc4891f5482e42c35be6931e9cf6b635d7d95056ff45b56bae5f0384830f"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc34b4d92d5d8671be6b728076f275ccfe8495c7e6b74750b634190e17ede68"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4d584b249c79acae86729d216a5185d833a90477d566f094b47d39620493870"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3784063fa43a0019b6a73e1e63b7fcbff4ded4d0ec5442202aa3caa12be9ef8"}, + {file = "pymongo-4.7.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bd514420eb09bba897016b7f1a2c17f9f3f1a7bc320c0505c59c3225e024b51c"}, + {file = "pymongo-4.7.0-cp37-cp37m-win32.whl", hash = "sha256:31ed6426fc68d500e2f27346e4ce3cc4fd3438adc99a3aaae41578c8a3b1f467"}, + {file = "pymongo-4.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69865d5739822c277d075a50601077767706e9f0862562e116ef13969d09fc9e"}, + {file = "pymongo-4.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbad9290b32ff1fc38bcac42699b8ea6a7c49cab081ba54761f3109bc5703248"}, + {file = "pymongo-4.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5307bfda4f39d9f1b3df9ab96b22d44bca458e44286ce806d716a2ffed2c46da"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f1a2ee91a97904cd21bddfce58d1868b6ea67b99bdd81dfe9cebfe35d0d751b"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cefa4e9be8bffa80de1bd70ae5ee79973e5db10befabcb25289fb52231a0dcff"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7b8bd94c63cef8f5bfbb29568934213d9730381db94f467f979c9e5aaa27130"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8ff95728965e633591862bfc197018d25bc349b5cd8da080acb52a2d17a6e95"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07265c14aa40259771255dbf59f9160a3690e82522ed02ab07e0e5c3045bad5b"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7214b7599a9f2e4ed01ecdc034cbe8f2926954bfdad9277390dd1bccf9fd6553"}, + {file = "pymongo-4.7.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1864f224b1793ef8698f779a7808e2b8c4a8f26bd0612c578412f62d6e99be46"}, + {file = "pymongo-4.7.0-cp38-cp38-win32.whl", hash = "sha256:2bfaf7a7eb6a91dfe58f384be16fd895e040d17236ee82217d1be9fc56869dc8"}, + {file = "pymongo-4.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2545c2be5ed25b1e9419cde4269d6a744076f80eaf86695d2dd888bddac29dd7"}, + {file = "pymongo-4.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7a00cee5b7a4160eed9cb43a2539037f572f01ed7261c2d1b4f7217060dba61"}, + {file = "pymongo-4.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c85f9824a7e90bf49aeed953e63942bff499116312e555ccb51bd3bf7ebe9342"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030dba8b3e1cb29f874739247e1eba1d01118a11583c62145c707a6e725d416a"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0dc2e365b14cb768898429e4331c58587be7143ad230858d19e8dd032f0adadc"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50865177882df0badc879c5b20f20cdc9c73494f0e2b19a40534af9c90018b4e"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b0d8393fb991b3dd934e891e064ae804e9267fce9d01d2f16b25e20564e3d"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7530ea1da6fe0bb1960390ba6523483dfdb2a6239d0e8058b1505cc2a79c75f8"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36536a41f08180adc647a21ca12dba859a23d841d28ca8fd3976c8781ed8290b"}, + {file = "pymongo-4.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b3a49be20a403d86eb1c559350fb56f28a859041756159eeb00e89f59b6e1288"}, + {file = "pymongo-4.7.0-cp39-cp39-win32.whl", hash = "sha256:a292ee4babdd632531effaac95da5f211caafa6a039c097a1b18a4dc0d52488b"}, + {file = "pymongo-4.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:cb809ff53ab3110ebc43a5e47aa945bb97e4ed9bc9beb07f935f5c83d9077e67"}, + {file = "pymongo-4.7.0.tar.gz", hash = "sha256:431093ef808944a14698b2a719b739fa7721778769e80c08423568991aa29c42"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] +encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + +[[package]] +name = "pymongodantic" +version = "0.1.0" +description = "`pymongodantic` is a MongoDB ORM working with pydantic thus being directly compatible with tools like FastAPI" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pymongodantic-0.1.0-py3-none-any.whl", hash = "sha256:859defea7186e0b032ec5ef2d641b7f16ced02e3e10816f86ce0f911b56d4f8e"}, + {file = "pymongodantic-0.1.0.tar.gz", hash = "sha256:88f549827d4af2d5c0307d77907ae5399d6560a81977ebb9ebc1fcb7d9c9431b"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +dnspython = ">=2.2.1,<3.0.0" +pydantic = ">=1.9.1,<2.0.0" +pymongo = ">=4.1.1,<5.0.0" +rich = ">=10.14.0,<11.0.0" +typer = {version = ">=0.4.0,<0.5.0", extras = ["all"]} [[package]] name = "python-decouple" @@ -902,6 +999,38 @@ files = [ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] +[[package]] +name = "rich" +version = "10.16.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "rich-10.16.2-py3-none-any.whl", hash = "sha256:c59d73bd804c90f747c8d7b1d023b88f2a9ac2454224a4aeaf959b21eeb42d03"}, + {file = "rich-10.16.2.tar.gz", hash = "sha256:720974689960e06c2efdb54327f8bf0cdbdf4eae4ad73b6c94213cad405c371b"}, +] + +[package.dependencies] +colorama = ">=0.4.0,<0.5.0" +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + [[package]] name = "simple-websocket-server" version = "0.4.4" @@ -1014,6 +1143,29 @@ files = [ {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] +[[package]] +name = "typer" +version = "0.4.2" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.4.2-py3-none-any.whl", hash = "sha256:023bae00d1baf358a6cc7cea45851639360bb716de687b42b0a4641cd99173f1"}, + {file = "typer-0.4.2.tar.gz", hash = "sha256:b8261c6c0152dd73478b5ba96ba677e5d6948c715c310f7c91079f311f62ec03"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} +shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + [[package]] name = "typing-extensions" version = "4.11.0" @@ -1315,4 +1467,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "b3bac82ec10c2f773971328f60030029b2ca9fde1078ed3e844a4b52d2511764" +content-hash = "cc7bb20db62efd1a3906a73b0b5f44145d536f508e77231f128f9abaeda2b653" diff --git a/pyproject.toml b/pyproject.toml index f4ad318..2a2a944 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,9 @@ fastapi = "^0.110.0" uvicorn = "^0.29.0" dataclasses-json = "^0.6.4" aiohttp = "^3.9.5" +pymongo = "^4.7.0" +mongogettersetter = "^1.5.0" +pymongodantic = "^0.1.0" [build-system] diff --git a/tests/pymongo_test.py b/tests/pymongo_test.py index b3ce990..aa6b187 100644 --- a/tests/pymongo_test.py +++ b/tests/pymongo_test.py @@ -1,43 +1,82 @@ -from pymongo import MongoClient +import functools -# Step 1: Connect to MongoDB -# Default connection url for local MongoDB instance. -client = MongoClient('mongodb://localhost:27017/') +from pymongo import MongoClient +from dataclasses import dataclass, field, fields +import datetime -# Step 2: Create database +# MongoDB initialization +client = MongoClient('mongodb://root:example@localhost:27017/') db = client['smib'] -# Step 3: Create collection -collection = db['users'] -# Step 4: Insert a document into the collection -new_user = {"name": "John Doe", "email": "john@example.com", "space_open": True} -insert_result = collection.insert_one(new_user) -print(f"Added user with id: {insert_result.inserted_id}") +def database(collection_name): + collection = db[collection_name] -# Step 5: Retrieve all documents from the collection -for user in collection.find(): - print(user) + def decorator(cls): + cls = dataclass(cls) -from pymongo import MongoClient + class Wrapper: + def __init__(self, *args, **kwargs): + document = collection.find_one() or {} + + default_instance = cls(*args, **kwargs) + + for f in fields(cls): + value = document.get(f.name, getattr(default_instance, f.name)) + setattr(self, f.name, value) + + self.wrapped = default_instance + + def save(self): + # get a dictionary of all instance variables excluding 'wrapped' + obj_dict = {k: v for k, v in self.__dict__.items() if k != 'wrapped'} + collection.update_one({}, {'$set': obj_dict}, upsert=True) + + def __getattr__(self, name): + # Get the latest document from the database + document = collection.find_one() or {} + self.__dict__ = document + # if document has updated value of name, return it. Otherwise return the value from dataclass instance + return document.get(name, getattr(self.wrapped, name)) + + def __setattr__(self, field, value): + if field != 'wrapped': + collection.update_one({}, {'$set': {field: value, "_updated_at": datetime.datetime.now()}}, upsert=True) + super().__setattr__(field, value) + + def reset(self): + obj = cls.__new__(cls) # Create new instance without calling __init__ + cls.__init__(obj) # Call __init__ to initialize the new instance + collection.update_one({}, {'$set': obj.__dict__}, upsert=True) + + + # Copying class name and docstring + __repr__ = cls.__repr__ + __name__ = cls.__name__ + __doc__ = cls.__doc__ + __qualname__ = cls.__qualname__ + + return Wrapper + + return decorator + + +@database('SpaceStateCollection') +class SpaceState: + open: bool = False + motion: bool = True + _updated_at: datetime.datetime = field(default_factory=datetime.datetime.now) + + +state = SpaceState() +state.open = True -class MongoDBWrapper: - def __init__(self, db_name, collection_name): - self.client = MongoClient("mongodb://localhost:27017/") - self.db = self.client[db_name] - self.collection = self.db[collection_name] +print(state.open) - def __getattr__(self, field_name): - def method(value=None): - if value is None: - return self.collection.find({field_name: {"$exists": True}}) - else: - return self.collection.find({field_name: value}) +state.open = False +state.motion = False - return method +print(state) -# Usage: -wrapper = MongoDBWrapper("smib", "users") -for doc in wrapper.space_open(): - print(next(wrapper.space_open())) +print(state) From b4ab358a0b7cd2a7d853f858d0c20de53734185e Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Sat, 27 Apr 2024 16:14:44 +0100 Subject: [PATCH 03/11] Playing around with ideas - seems like mngo_contexct.py is the best idea so far! --- tests/mngo_contexct.py | 131 +++++++++++++++++++++++++++++++++++++++ tests/mongo_context_2.py | 65 +++++++++++++++++++ tests/mongo_repo.py | 79 +++++++++++++++++++++++ tests/mongotest.py | 49 +++++++++++++++ tests/pymongo_test.py | 87 +++++++++----------------- 5 files changed, 354 insertions(+), 57 deletions(-) create mode 100644 tests/mngo_contexct.py create mode 100644 tests/mongo_context_2.py create mode 100644 tests/mongo_repo.py create mode 100644 tests/mongotest.py diff --git a/tests/mngo_contexct.py b/tests/mngo_contexct.py new file mode 100644 index 0000000..4846c7d --- /dev/null +++ b/tests/mngo_contexct.py @@ -0,0 +1,131 @@ +from enum import StrEnum, auto +from typing import Type, TypeVar + +from pymongo import MongoClient +from pymongo.client_session import ClientSession +from datetime import datetime +from dataclasses import dataclass, field, fields, asdict + +from pymongo.collection import Collection + +# mongo_client = MongoClient("mongodb://root:example@localhost:27017/") +# db = mongo_client["your_database"] +# collection = db["your_collection"] + +global_cache = {} +_sync = True + + +class SingletonMongoDB: + + collection: Collection + client: MongoClient + session: ClientSession + + @classmethod + def set_collection(cls, name): + mongo_client = MongoClient("mongodb://root:example@localhost:27017/") + db = mongo_client['smib_plugins'] + cls.collection = db[name] + cls.client = mongo_client + return cls + + def __post_init__(self): + global global_cache + global_cache = asdict(self) + document = self.collection.find_one() + if document is None: + document = self.collection.update_one({}, {"$set": asdict(self)}, upsert=True) + document = self.collection.find_one() + + global_cache.update({k: v for k, v in document.items() if k in global_cache}) + + def __setattr__(self, key, value): + global global_cache, _sync + if key in global_cache and _sync: + self.collection.update_one({}, {"$set": {key: value}}) + elif not _sync: + global_cache[key] = value + + super().__setattr__(key, value) + + def __getattribute__(self, item): + # Check for methods + if callable(method := super().__getattribute__(item)): + return method + + global global_cache, _sync + if item in global_cache and _sync: + doc = self.collection.find_one() + if doc: + return doc.get(item) + elif not _sync: + return global_cache.get(item, None) + + return super().__getattribute__(item) + + def __enter__(self): + global _sync, global_cache + + self.session = self.client.start_session() + self.session.start_transaction() + + # On entering the context, update the local cache with contents from db + doc = self.collection.find_one() + for field_name in global_cache.keys(): + if field_name in doc: + global_cache[field_name] = doc[field_name] + _sync = False + return self + + def __exit__(self, *args): + global _sync, global_cache + _sync = True + # On exiting the context, update the db with contents from the local cache + self.collection.update_one({}, {"$set": global_cache}, upsert=True) + self.session.commit_transaction() + + +Database = SingletonMongoDB.set_collection('space_openclose') + + +class Trigger(StrEnum): + TOGGLE = auto() + BUTTON = auto() + + +@dataclass +class SpaceState(Database): + open: bool = False + last_updated: datetime = datetime.now() + motion: bool = False + open_closed_last_trigger: str = None + + def toggle_open(self): + self.open = not self.open + self.open_closed_last_trigger = Trigger.TOGGLE.value + + def set_space_open(self): + self.open = True + self.open_closed_last_trigger = Trigger.BUTTON.value + + def set_space_closed(self): + self.open = False + self.open_closed_last_trigger = Trigger.BUTTON.value + + def __setattr__(self, name, value): + super().__setattr__(name, value) + if name != "last_updated": + self.last_updated = datetime.now() + + +if __name__ == '__main__': + with SpaceState() as space: + space.toggle_open() + print(space) + + space = SpaceState() + space.set_space_open() + print(space) + space.set_space_closed() + print(space) diff --git a/tests/mongo_context_2.py b/tests/mongo_context_2.py new file mode 100644 index 0000000..1690601 --- /dev/null +++ b/tests/mongo_context_2.py @@ -0,0 +1,65 @@ +from pymongo import MongoClient +from datetime import datetime +from dataclasses import dataclass, field, asdict +from abc import ABCMeta, ABC + +mongo_client = MongoClient("mongodb://root:example@localhost:27017/") +db = mongo_client["your_database"] +collection = db["your_collection"] + + +def init_collection_with_sample_doc(): + collection.insert_one({"open": False, "last_updated": datetime.now().isoformat()}) + + +init_collection_with_sample_doc() + + +class CacheMeta(ABCMeta): + def __new__(cls, name, bases, dct): + new_class = super().__new__(cls, name, bases, dct) + new_class._cache = {} + new_class._sync = True + return new_class + + def __enter__(cls): + doc = cls.collection.find_one() + for key in doc.keys(): + setattr(cls, key, doc[key]) + cls._sync = False + return cls + + def __exit__(cls, *_args): + cls._sync = True + cls.collection.update_one({}, {"$set": cls._cache}, upsert=True) + + def __setattr__(cls, key, value): + if key == "_sync": + super().__setattr__(key, value) + elif cls._sync: + cls.collection.update_one({}, {"$set": {key: value}}) + cls._cache[key] = value + else: + cls._cache[key] = value + + def __getattribute__(cls, item): + if item in cls._cache and cls._sync: + doc = cls.collection.find_one() + return doc.get(item) + else: + return super().__getattribute__(item) + + +@dataclass +class SpaceState(metaclass=CacheMeta): + collection = collection + open: bool = False + last_updated: str = field(default=None) + + +print(SpaceState.open) + +with SpaceState: + SpaceState.open = True + +print(SpaceState.open) diff --git a/tests/mongo_repo.py b/tests/mongo_repo.py new file mode 100644 index 0000000..1f89b19 --- /dev/null +++ b/tests/mongo_repo.py @@ -0,0 +1,79 @@ +from bson import ObjectId +from pymongo import MongoClient +from datetime import datetime, timezone +from dataclasses import dataclass, field, fields +from typing import TypeVar, Type, Optional + +# MongoDB initialization +client = MongoClient('mongodb://root:example@localhost:27017/') +db = client[''] +collection = db[''] + +T = TypeVar('T') + + +class RepositoryMeta(type): + + _id: ObjectId = None + + def get(cls: Type[T]) -> T: + document = collection.find_one({}) + if not document: + _ = collection.insert_one({}) + document = collection.find_one({}) + document.pop('_id', None) + return cls(**document) + + def update(cls: Type[T], item: T) -> T | None: + document = vars(item) + document.pop('_id', None) # Exclude the '_id' field + document["last_updated"] = datetime.now(timezone.utc) + updated_document = collection.find_one_and_update({}, {"$set": document}, return_document=True) + if updated_document is None: + return None + updated_document.pop('_id', None) + result = cls(**updated_document) + return result + + +@dataclass +class SpaceState(metaclass=RepositoryMeta): + open: bool = None + last_updated: datetime | None = field(default_factory=lambda: datetime.now(timezone.utc)) + _sync: bool = field(default=False, init=False, repr=False) + _collection = collection + + def __enter__(self): + self._sync = True + return self + + def __exit__(self, *args): + self._sync = False + + def __getattribute__(self, name): + sync = super().__getattribute__('_sync') + # Check if the name is a field in the dataclass + if sync and any(f.name == name for f in fields(self)): + document = self._collection.find_one({}) + return document.get(name) if document else None + return super().__getattribute__(name) + + def __setattr__(self, name, value): + sync = super().__getattribute__('_sync') + if sync and name in fields(self): + self._collection.find_one_and_update({}, {"$set": {name: value}}) + super().__setattr__(name, value) + + def __getitem__(self, name): + return self.__getattribute__(name) + + def __setitem__(self, name, value): + return self.__setattr__(name, value) + + +# Usage: +with SpaceState() as state: + print(state.open) # This will fetch from database + state.open = False # This will update in database + +print(SpaceState()) diff --git a/tests/mongotest.py b/tests/mongotest.py new file mode 100644 index 0000000..0f24e90 --- /dev/null +++ b/tests/mongotest.py @@ -0,0 +1,49 @@ +from bson import ObjectId +from pymongo import MongoClient +from dataclasses import dataclass, field +from datetime import datetime, timezone + +# MongoDB initialization +client = MongoClient('mongodb://root:example@localhost:27017/') +db = client[''] +collection = db[''] + + +@dataclass +class SpaceState: + _id: ObjectId = None + open: bool = None + last_updated: datetime | None = field(default_factory=lambda: datetime.now(timezone.utc)) + + +class SpaceStateRepository: + + @staticmethod + def get() -> SpaceState: + """Retrieve a single SpaceState document by its unique id""" + document = collection.find_one({}) + if not document: + _ = collection.insert_one({}) + document = collection.find_one({}) + return SpaceState(**document) + + @staticmethod + def update(space_state: SpaceState): + """Update a SpaceState document by giving only the fields to update""" + document = vars(space_state) + document.pop('_id', None) + document["last_updated"] = datetime.now() + result = collection.update_one({}, {"$set": document}, upsert=True) + if not result.matched_count: + raise Exception("Failed to update SpaceState.") + + +initial_state = SpaceStateRepository.get() +print(initial_state) # Initial state + +# Update the state +updated_state = SpaceState(open=True) +SpaceStateRepository.update(updated_state) + +final_state = SpaceStateRepository.get() +print(final_state) # Updated state diff --git a/tests/pymongo_test.py b/tests/pymongo_test.py index aa6b187..29f45b7 100644 --- a/tests/pymongo_test.py +++ b/tests/pymongo_test.py @@ -4,79 +4,52 @@ from dataclasses import dataclass, field, fields import datetime +import time + # MongoDB initialization client = MongoClient('mongodb://root:example@localhost:27017/') -db = client['smib'] - - -def database(collection_name): - collection = db[collection_name] - - def decorator(cls): - cls = dataclass(cls) - - class Wrapper: - def __init__(self, *args, **kwargs): - document = collection.find_one() or {} - - default_instance = cls(*args, **kwargs) - - for f in fields(cls): - value = document.get(f.name, getattr(default_instance, f.name)) - setattr(self, f.name, value) - self.wrapped = default_instance - - def save(self): - # get a dictionary of all instance variables excluding 'wrapped' - obj_dict = {k: v for k, v in self.__dict__.items() if k != 'wrapped'} - collection.update_one({}, {'$set': obj_dict}, upsert=True) - - def __getattr__(self, name): - # Get the latest document from the database - document = collection.find_one() or {} - self.__dict__ = document - # if document has updated value of name, return it. Otherwise return the value from dataclass instance - return document.get(name, getattr(self.wrapped, name)) +db = client['smib'] - def __setattr__(self, field, value): - if field != 'wrapped': - collection.update_one({}, {'$set': {field: value, "_updated_at": datetime.datetime.now()}}, upsert=True) - super().__setattr__(field, value) +collection = db['state-test'] - def reset(self): - obj = cls.__new__(cls) # Create new instance without calling __init__ - cls.__init__(obj) # Call __init__ to initialize the new instance - collection.update_one({}, {'$set': obj.__dict__}, upsert=True) +@dataclass +class BaseDocument: + _id: any = None - # Copying class name and docstring - __repr__ = cls.__repr__ - __name__ = cls.__name__ - __doc__ = cls.__doc__ - __qualname__ = cls.__qualname__ + def __post_init__(self): + document = collection.find_one() + if document: + self.__dict__.update({k:v for k, v in document.items() if k != '_id'}) - return Wrapper + def refresh(self): + self.__post_init__() - return decorator + def save(self): + collection.update_one({}, {'$set': {k:v for k, v in self.__dict__.items() if k != '_id'}}, upsert=True) -@database('SpaceStateCollection') -class SpaceState: - open: bool = False - motion: bool = True - _updated_at: datetime.datetime = field(default_factory=datetime.datetime.now) +class SpaceState(BaseDocument): + open: bool | None = None + last_updated: datetime.datetime | None = field(default_factory=lambda: datetime.datetime.now(datetime.timezone.utc)) state = SpaceState() -state.open = True - -print(state.open) +print(state) state.open = False -state.motion = False +state.last_updated = datetime.datetime.now() +state.save() print(state) - -print(state) +# document = SpaceState(**collection.find_one() or {}) +# +# document.open = True +# +# collection.update_one({}, {'$set': {**document.__dict__}}, upsert=True) +# +# document = SpaceState(**collection.find_one({}) or {}) +# +# print(document) \ No newline at end of file From 8f88cdb0466a2ecda53133134a19705969eff1d3 Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Sat, 27 Apr 2024 16:18:26 +0100 Subject: [PATCH 04/11] Use UTC timezone for timestamps in mngo_contexct.py Datetime handling has been updated to use UTC timezone in 'last_updated' attribute of 'SpaceState' class and in __setattr__ method. This ensures all generated timestamps are in UTC. --- tests/mngo_contexct.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mngo_contexct.py b/tests/mngo_contexct.py index 4846c7d..f48bb50 100644 --- a/tests/mngo_contexct.py +++ b/tests/mngo_contexct.py @@ -3,7 +3,7 @@ from pymongo import MongoClient from pymongo.client_session import ClientSession -from datetime import datetime +from datetime import datetime, timezone from dataclasses import dataclass, field, fields, asdict from pymongo.collection import Collection @@ -97,7 +97,7 @@ class Trigger(StrEnum): @dataclass class SpaceState(Database): open: bool = False - last_updated: datetime = datetime.now() + last_updated: datetime = datetime.now(tz=timezone.utc) motion: bool = False open_closed_last_trigger: str = None @@ -116,7 +116,7 @@ def set_space_closed(self): def __setattr__(self, name, value): super().__setattr__(name, value) if name != "last_updated": - self.last_updated = datetime.now() + self.last_updated = datetime.now(tz=timezone.utc) if __name__ == '__main__': From f702c7af4281213301731e86431824f1c3411b61 Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Sat, 27 Apr 2024 16:22:49 +0100 Subject: [PATCH 05/11] Update mongo context with no value check Refactor mongo context in tests to ignore the return value of collection update. Also, the state of 'SpaceState' object has been printed at the beginning to help understand its initial state better. --- tests/mngo_contexct.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mngo_contexct.py b/tests/mngo_contexct.py index f48bb50..e4bee3d 100644 --- a/tests/mngo_contexct.py +++ b/tests/mngo_contexct.py @@ -35,7 +35,7 @@ def __post_init__(self): global_cache = asdict(self) document = self.collection.find_one() if document is None: - document = self.collection.update_one({}, {"$set": asdict(self)}, upsert=True) + _ = self.collection.update_one({}, {"$set": asdict(self)}, upsert=True) document = self.collection.find_one() global_cache.update({k: v for k, v in document.items() if k in global_cache}) @@ -121,6 +121,7 @@ def __setattr__(self, name, value): if __name__ == '__main__': with SpaceState() as space: + print(space) space.toggle_open() print(space) From 673f3d189fdfe9666cd450bafb178b4da6dcffff Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Sun, 28 Apr 2024 12:52:45 +0100 Subject: [PATCH 06/11] Playing --- poetry.lock | 248 +++++++++++++++++----------------------- pyproject.toml | 1 - tests/mngo_contexct.py | 4 +- tests/mongo_test.py | 33 ++++++ tests/pydantic-mongo.py | 20 ++++ 5 files changed, 162 insertions(+), 144 deletions(-) create mode 100644 tests/mongo_test.py create mode 100644 tests/pydantic-mongo.py diff --git a/poetry.lock b/poetry.lock index 62d7668..37ef84c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -111,6 +111,18 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "anyio" version = "4.3.0" @@ -232,21 +244,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - [[package]] name = "dataclasses-json" version = "0.6.4" @@ -792,72 +789,115 @@ files = [ [[package]] name = "pydantic" -version = "1.10.15" -description = "Data validation and settings management using python type hints" +version = "2.7.1" +description = "Data validation using Python type hints" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.18.2" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] [[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." +name = "pydantic-core" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pymongo" @@ -941,25 +981,6 @@ snappy = ["python-snappy"] test = ["pytest (>=7)"] zstd = ["zstandard"] -[[package]] -name = "pymongodantic" -version = "0.1.0" -description = "`pymongodantic` is a MongoDB ORM working with pydantic thus being directly compatible with tools like FastAPI" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "pymongodantic-0.1.0-py3-none-any.whl", hash = "sha256:859defea7186e0b032ec5ef2d641b7f16ced02e3e10816f86ce0f911b56d4f8e"}, - {file = "pymongodantic-0.1.0.tar.gz", hash = "sha256:88f549827d4af2d5c0307d77907ae5399d6560a81977ebb9ebc1fcb7d9c9431b"}, -] - -[package.dependencies] -dnspython = ">=2.2.1,<3.0.0" -pydantic = ">=1.9.1,<2.0.0" -pymongo = ">=4.1.1,<5.0.0" -rich = ">=10.14.0,<11.0.0" -typer = {version = ">=0.4.0,<0.5.0", extras = ["all"]} - [[package]] name = "python-decouple" version = "3.8" @@ -999,38 +1020,6 @@ files = [ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] -[[package]] -name = "rich" -version = "10.16.2" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.6.2,<4.0.0" -files = [ - {file = "rich-10.16.2-py3-none-any.whl", hash = "sha256:c59d73bd804c90f747c8d7b1d023b88f2a9ac2454224a4aeaf959b21eeb42d03"}, - {file = "rich-10.16.2.tar.gz", hash = "sha256:720974689960e06c2efdb54327f8bf0cdbdf4eae4ad73b6c94213cad405c371b"}, -] - -[package.dependencies] -colorama = ">=0.4.0,<0.5.0" -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - [[package]] name = "simple-websocket-server" version = "0.4.4" @@ -1143,29 +1132,6 @@ files = [ {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] -[[package]] -name = "typer" -version = "0.4.2" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typer-0.4.2-py3-none-any.whl", hash = "sha256:023bae00d1baf358a6cc7cea45851639360bb716de687b42b0a4641cd99173f1"}, - {file = "typer-0.4.2.tar.gz", hash = "sha256:b8261c6c0152dd73478b5ba96ba677e5d6948c715c310f7c91079f311f62ec03"}, -] - -[package.dependencies] -click = ">=7.1.1,<9.0.0" -colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} -shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} - -[package.extras] -all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] -dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)"] -test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "shellingham (>=1.3.0,<2.0.0)"] - [[package]] name = "typing-extensions" version = "4.11.0" @@ -1467,4 +1433,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cc7bb20db62efd1a3906a73b0b5f44145d536f508e77231f128f9abaeda2b653" +content-hash = "1fd09434902599f6855c9324eb7bc6506b5c2b2e17988c8d5edc48d9928e1d79" diff --git a/pyproject.toml b/pyproject.toml index 2a2a944..319f7f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ dataclasses-json = "^0.6.4" aiohttp = "^3.9.5" pymongo = "^4.7.0" mongogettersetter = "^1.5.0" -pymongodantic = "^0.1.0" [build-system] diff --git a/tests/mngo_contexct.py b/tests/mngo_contexct.py index e4bee3d..cfacd21 100644 --- a/tests/mngo_contexct.py +++ b/tests/mngo_contexct.py @@ -16,7 +16,7 @@ _sync = True -class SingletonMongoDB: +class SingleMongoDocument: collection: Collection client: MongoClient @@ -86,7 +86,7 @@ def __exit__(self, *args): self.session.commit_transaction() -Database = SingletonMongoDB.set_collection('space_openclose') +Database = SingleMongoDocument.set_collection('space_openclose') class Trigger(StrEnum): diff --git a/tests/mongo_test.py b/tests/mongo_test.py new file mode 100644 index 0000000..2c60af0 --- /dev/null +++ b/tests/mongo_test.py @@ -0,0 +1,33 @@ +import asyncio +import datetime +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from pymongo import MongoClient + +from pydantic_mongo_document import Document + +uri = "mongodb://root:example@localhost:27017/" + +client = MongoClient(uri) +ping_result = client.admin.command("ping") + +Document.set_mongo_uri(uri) +Document.__database__ = "smib_plugins" +Document.__collection__ = "space_state_2" + + +class SpaceState(Document): + open: bool = None + last_updated: datetime.datetime = None + + +async def main(): + state = SpaceState() + state.open = True + + await state.insert() + + print(state) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/pydantic-mongo.py b/tests/pydantic-mongo.py new file mode 100644 index 0000000..a10c7e3 --- /dev/null +++ b/tests/pydantic-mongo.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel +from pymongo import MongoClient +from pymongodantic import Model, MongoModel + +client = MongoClient("mongodb://localhost:27017/") +db = client["database_name"] + + +class SpaceState(BaseModel, MongoModel): + open: bool + motion: bool + + +# Insert data +new_state = SpaceState(open=False, motion=True) +new_state.save(db.space_state) + +# Fetch data +db_state = SpaceState.fetch(db.space_state, new_state.id) +print(db_state) From 806669e56570ad8c41008e723f8ecc673fcd56c8 Mon Sep 17 00:00:00 2001 From: Sam Cork Date: Tue, 30 Apr 2024 00:27:10 +0100 Subject: [PATCH 07/11] Integrate MongoDB via Mogo for data persistence Implemented MongoDB for persisting plugin data through the Mogo wrapper. Refactored Slack and Common code configurations, including the relocation of functions and addition of MongoDB environment variables. This also led to new database related files creation, as well as Docker Compose and Python project environment alterations. --- docker-compose.yml | 9 +--- poetry.lock | 17 +++++++- pyproject.toml | 1 + smib/.env | 4 +- smib/common/config.py | 13 ++++++ smib/common/logging_/setup.py | 13 +----- smib/common/utils.py | 14 ++++++ smib/logging.json | 3 ++ smib/slack/__main__.py | 5 +-- smib/slack/db/__init__.py | 27 ++++++++++++ smib/slack/logging_injector.py | 4 +- .../slack/plugins/space/openclose/__init__.py | 8 +++- smib/slack/plugins/space/openclose/models.py | 20 +++++++++ tests/mogo_test.py | 43 +++++++++++++++++++ 14 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 smib/slack/db/__init__.py create mode 100644 smib/slack/plugins/space/openclose/models.py create mode 100644 tests/mogo_test.py diff --git a/docker-compose.yml b/docker-compose.yml index 69cd826..5f13e85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,9 +40,6 @@ services: restart: always ports: - 27017:27017 - environment: - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: example smib-db-ui: image: mongo-express @@ -53,10 +50,8 @@ services: ports: - 8082:8081 environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: root - ME_CONFIG_MONGODB_ADMINPASSWORD: example - ME_CONFIG_MONGODB_URL: mongodb://root:example@smib-db:27017/ - ME_CONFIG_BASICAUTH: false + ME_CONFIG_MONGODB_URL: mongodb://smib-db:27017/ + ME_CONFIG_BASICAUTH: true networks: smib-bridge-network: diff --git a/poetry.lock b/poetry.lock index 37ef84c..60b5a9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -621,6 +621,21 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +name = "mogo" +version = "0.6.0" +description = "Simple PyMongo \"schema-less\" object wrapper" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mogo-0.6.0-py3-none-any.whl", hash = "sha256:3ca31c1a15cc1add3b1407cefe50c335f72ea166b974758230f5121927216dea"}, + {file = "mogo-0.6.0.tar.gz", hash = "sha256:ec53fdb19637168f7eef92555ebd4292e2030d15266885286e3696ebc375e550"}, +] + +[package.dependencies] +pymongo = ">=4.1" + [[package]] name = "mongogettersetter" version = "1.5.0" @@ -1433,4 +1448,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "1fd09434902599f6855c9324eb7bc6506b5c2b2e17988c8d5edc48d9928e1d79" +content-hash = "fd0744dbb91c9f89e9b402fcc4118d86aa9d0bb37c2eeeda99deca8f8a583a05" diff --git a/pyproject.toml b/pyproject.toml index 319f7f6..830c97c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dataclasses-json = "^0.6.4" aiohttp = "^3.9.5" pymongo = "^4.7.0" mongogettersetter = "^1.5.0" +mogo = "^0.6.0" [build-system] diff --git a/smib/.env b/smib/.env index 67c036a..6b60e12 100644 --- a/smib/.env +++ b/smib/.env @@ -1,2 +1,4 @@ SLACK_BOT_TOKEN=xoxb-4366637215524-4349706455447-t4r91O4KnQM8jlkypZfb8o8n -SLACK_APP_TOKEN=xapp-1-A04BDSFTCMN-4376840887521-e4e6f2f1426234b2cc75939b191cc4b10ad5f3b39daa1a8a65198da370f84ba9 \ No newline at end of file +SLACK_APP_TOKEN=xapp-1-A04BDSFTCMN-4376840887521-e4e6f2f1426234b2cc75939b191cc4b10ad5f3b39daa1a8a65198da370f84ba9 +MONGO_DB_ADMIN_USER=admin +MONGO_DB_ADMIN_PASSWORD=password \ No newline at end of file diff --git a/smib/common/config.py b/smib/common/config.py index c7da49e..e3da352 100644 --- a/smib/common/config.py +++ b/smib/common/config.py @@ -39,6 +39,19 @@ cast=urlparse) WEBSOCKET_ALLOWED_HOSTS = config('WEBSOCKET_ALLOWED_HOSTS', default='localhost,127.0.0.1,::1', cast=Csv()) +MONGO_DB_HOST = config('MONGO_DB_HOST', default='localhost') +MONGO_DB_PORT = config('MONGO_DB_PORT', default=27017, cast=int) + +MONGO_DB_URL = f"mongodb://{MONGO_DB_HOST}:{MONGO_DB_PORT}/" + +MONGO_DB_PLUGINS_USER = config('MONGO_DB_PLUGINS_USER', default='plugins') +MONGO_DB_PLUGINS_PASSWORD = config('MONGO_DB_PLUGINS_PASSWORD', default='plug1n5') + +MONGO_DB_PLUGINS_NAME = config('MONGO_DB_PLUGINS_NAME', default='smib_plugins') + +MONGO_DB_ADMIN_USER = config('MONGO_DB_ADMIN_USER') +MONGO_DB_ADMIN_PASSWORD = config('MONGO_DB_ADMIN_PASSWORD') + PLUGINS_DIRECTORY = config('PLUGINS_DIRECTORY', default=ROOT_DIRECTORY / 'slack' / 'plugins', cast=Path) SPACE_OPEN_ANNOUNCE_CHANNEL_ID = config('SPACE_OPEN_ANNOUNCE_CHANNEL_ID', default='C06UDPLQRP1') diff --git a/smib/common/logging_/setup.py b/smib/common/logging_/setup.py index 047ccce..2360eb2 100644 --- a/smib/common/logging_/setup.py +++ b/smib/common/logging_/setup.py @@ -3,6 +3,7 @@ import logging from smib.common.config import ROOT_DIRECTORY +from smib.common.utils import _get_module_name from injectable import injectable_factory, load_injection_container, inject @@ -16,18 +17,6 @@ def setup_logging(path=ROOT_DIRECTORY / 'logging.json'): logging.config.dictConfig(read_logging_json(path)) -def _get_module_name(stack_num: int = 4): - frame = inspect.stack()[stack_num] - module = inspect.getmodule(frame[0]) - module_name = module.__name__ - return module_name - - -@injectable_factory(logging.Logger, qualifier="logger") -def logger_factory(): - return logging.getLogger(_get_module_name()) - - @injectable_factory(logging.Logger, qualifier="plugin_logger") def plugin_logger_factory(): return logging.getLogger(_get_module_name(2)) diff --git a/smib/common/utils.py b/smib/common/utils.py index 13a67f2..832d063 100644 --- a/smib/common/utils.py +++ b/smib/common/utils.py @@ -1,3 +1,4 @@ +import inspect import logging import pickle from pathlib import Path @@ -65,3 +66,16 @@ def wrapper(*args, **kwargs): return BoltResponse(status=response[0], body=json.dumps(response[1])) return wrapper + + +def _get_module_name(stack_num: int = 4): + frame = inspect.stack()[stack_num] + module = inspect.getmodule(frame[0]) + module_name = module.__name__ + return module_name + + +def _get_module_file(stack_num: int = 4) -> Path: + frame = inspect.stack()[stack_num] + file = inspect.getfile(frame[0]) + return Path(file) diff --git a/smib/logging.json b/smib/logging.json index 20a2155..f0d18fb 100644 --- a/smib/logging.json +++ b/smib/logging.json @@ -48,6 +48,9 @@ "tzlocal": { "level": "WARNING", "propagate": false + }, + "pymongo": { + "level": "WARNING" } } } \ No newline at end of file diff --git a/smib/slack/__main__.py b/smib/slack/__main__.py index 6917feb..610f6b1 100644 --- a/smib/slack/__main__.py +++ b/smib/slack/__main__.py @@ -1,10 +1,7 @@ import logging -from pathlib import Path -from simple_websocket_server import WebSocketServer from slack_bolt.adapter.socket_mode import SocketModeHandler - -from smib.slack.logging_injector import inject_logger_to_slack_context +from slack.logging_injector import inject_logger_to_slack_context from smib.common.config import SLACK_APP_TOKEN, SLACK_BOT_TOKEN, APPLICATION_NAME, ROOT_DIRECTORY from smib.slack.websocket import server as websocket_server from smib.slack.error import handle_errors diff --git a/smib/slack/db/__init__.py b/smib/slack/db/__init__.py new file mode 100644 index 0000000..74cb6c5 --- /dev/null +++ b/smib/slack/db/__init__.py @@ -0,0 +1,27 @@ +import logging +from functools import partial +from logging import Logger + +import pymongo.errors +from injectable import injectable_factory, inject, load_injection_container +from mogo import Model, Field, connect +from pymongo import MongoClient + +from slack.plugin import PluginManager +from smib.common.utils import _get_module_name, _get_module_file +from smib.common.config import MONGO_DB_URL, MONGO_DB_ADMIN_PASSWORD, MONGO_DB_ADMIN_USER + + +def connect_to_plugin_database(plugin_id: str): + return connect(plugin_id, uri=MONGO_DB_URL) + + +def get_plugin_database(): + plugin_manager: PluginManager = inject("PluginManager") + plugin_name = plugin_manager.get_plugin_from_file(_get_module_file(2)).id + return connect_to_plugin_database(plugin_name) + + +@injectable_factory(Logger, qualifier="logger") +def logger_factory(): + return logging.getLogger(__name__) diff --git a/smib/slack/logging_injector.py b/smib/slack/logging_injector.py index 5e1f163..6cfc671 100644 --- a/smib/slack/logging_injector.py +++ b/smib/slack/logging_injector.py @@ -1,6 +1,6 @@ from injectable import inject -from smib.common.logging_.setup import plugin_logger_factory + def inject_logger_to_slack_context(context, next): context['logger'] = inject("plugin_logger", lazy=True) - next() \ No newline at end of file + next() diff --git a/smib/slack/plugins/space/openclose/__init__.py b/smib/slack/plugins/space/openclose/__init__.py index 5491750..551ed0b 100644 --- a/smib/slack/plugins/space/openclose/__init__.py +++ b/smib/slack/plugins/space/openclose/__init__.py @@ -46,16 +46,22 @@ def app_home_opened(client: WebClient, event: dict): @app.action('space_open') @app.event('http_put_space_open') def space_open(say, context, ack): + from .models import Space + ack() context['logger'].debug("Space Open!") say(text='Space Open!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID) + Space.single().set_open() + @app.action('space_closed') @app.event('http_put_space_closed') def space_closed(say, context, ack): + from .models import Space + ack() context['logger'].debug("Space Closed!") say(text='Space Closed!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID) - + Space.single().set_closed() diff --git a/smib/slack/plugins/space/openclose/models.py b/smib/slack/plugins/space/openclose/models.py new file mode 100644 index 0000000..1686cb1 --- /dev/null +++ b/smib/slack/plugins/space/openclose/models.py @@ -0,0 +1,20 @@ +from smib.slack.db import Model, Field, get_plugin_database +from injectable import inject + +client = get_plugin_database() + + +class Space(Model): + open: bool = Field[bool](default=None) + + @classmethod + def single(cls): + return cls.find_one() or cls() + + def set_open(self): + self.open = True + self.save() + + def set_closed(self): + self.open = False + self.save() diff --git a/tests/mogo_test.py b/tests/mogo_test.py new file mode 100644 index 0000000..c95ba22 --- /dev/null +++ b/tests/mogo_test.py @@ -0,0 +1,43 @@ +from datetime import datetime +from typing import TypeVar + +import tzlocal +from mogo import connect, Model, Field + +uri = "mongodb://root:example@localhost:27017/" + +# Connect to MongoDB +connect("smib_plugins", uri=uri, tz_aware=True) + + +class SpaceState(Model): + open: bool | None = Field[bool](default=None) + last_updated: datetime | None = Field[datetime](default=datetime.utcnow()) + + def set_open(self): + self.open = True + self.save() + + def set_closed(self): + self.open = False + self.save() + + def __setattr__(self, name, value): + super().__setattr__(name, value) + if name != "last_updated": + self.last_updated = datetime.utcnow() + + +def get_single_document(cls): + single = cls.find_one() or cls() + single.save() + return single + + +# state = SpaceState.find_one() or SpaceState().save() + +state = get_single_document(SpaceState) +print(state.last_updated) + +# state.set_open() +# state.set_closed() From c3dd60c0fdeeec579112d3944c10551d4019e502 Mon Sep 17 00:00:00 2001 From: sam57719 Date: Thu, 2 May 2024 00:58:14 +0100 Subject: [PATCH 08/11] Improved Database Integration and Enhanced Decorators Major changes: 1. Apply `@database` decorator to `space_open`, `space_closed` and `get_space_state` methods in 'smib/slack/plugins/space/openclose/__init__.py' to streamline connection to DB. 2. Implemented `database()` decorator use in 'smib/slack/db/__init__.py', configured to automatically get the current plugin id for the DB if input argument is not provided. 3. Removed unused methods `connect_to_plugin_database` and `get_plugin_database` from 'smib/slack/db/__init__.py'. 4. Moved `get_current_plugin_id()` method to 'smib/slack/db/__init__.py' to manage plugin id for DB operations. 5. Updated import statements and DB connection details in various other files. Minor modifications: 1. Adjusted import statements and logging configuration across files for consistency. 2. Handled the connection timeout setting for the MongoDB connection within the `database()` decorator. These changes aim to enhance the clarity and robustness of database operations within the codebase. --- smib/common/config.py | 11 ++--- smib/common/logging_/setup.py | 9 +++- smib/common/utils.py | 10 ++-- smib/slack/__main__.py | 2 +- smib/slack/db/__init__.py | 48 ++++++++++++------- smib/slack/logging_injector.py | 1 + .../plugin/loaders/abstract_plugin_loader.py | 2 +- .../slack/plugins/space/openclose/__init__.py | 26 ++++++++-- smib/slack/plugins/space/openclose/models.py | 5 +- 9 files changed, 72 insertions(+), 42 deletions(-) diff --git a/smib/common/config.py b/smib/common/config.py index e3da352..6ebe104 100644 --- a/smib/common/config.py +++ b/smib/common/config.py @@ -42,15 +42,10 @@ MONGO_DB_HOST = config('MONGO_DB_HOST', default='localhost') MONGO_DB_PORT = config('MONGO_DB_PORT', default=27017, cast=int) -MONGO_DB_URL = f"mongodb://{MONGO_DB_HOST}:{MONGO_DB_PORT}/" - -MONGO_DB_PLUGINS_USER = config('MONGO_DB_PLUGINS_USER', default='plugins') -MONGO_DB_PLUGINS_PASSWORD = config('MONGO_DB_PLUGINS_PASSWORD', default='plug1n5') +MONGO_DB_DEFAULT_DB = config("MONGO_DB_DEFAULT_DB", default="smib_default") +MONGO_DB_CONNECT_TIMEOUT_SECONDS = config("MONGO_DB_CONNECT_TIMEOUT_SECONDS", default=5, cast=int) -MONGO_DB_PLUGINS_NAME = config('MONGO_DB_PLUGINS_NAME', default='smib_plugins') - -MONGO_DB_ADMIN_USER = config('MONGO_DB_ADMIN_USER') -MONGO_DB_ADMIN_PASSWORD = config('MONGO_DB_ADMIN_PASSWORD') +MONGO_DB_URL = f"mongodb://{MONGO_DB_HOST}:{MONGO_DB_PORT}/" PLUGINS_DIRECTORY = config('PLUGINS_DIRECTORY', default=ROOT_DIRECTORY / 'slack' / 'plugins', cast=Path) diff --git a/smib/common/logging_/setup.py b/smib/common/logging_/setup.py index 2360eb2..beba5ae 100644 --- a/smib/common/logging_/setup.py +++ b/smib/common/logging_/setup.py @@ -3,7 +3,7 @@ import logging from smib.common.config import ROOT_DIRECTORY -from smib.common.utils import _get_module_name +from smib.common.utils import get_module_name from injectable import injectable_factory, load_injection_container, inject @@ -19,7 +19,12 @@ def setup_logging(path=ROOT_DIRECTORY / 'logging.json'): @injectable_factory(logging.Logger, qualifier="plugin_logger") def plugin_logger_factory(): - return logging.getLogger(_get_module_name(2)) + return logging.getLogger(get_module_name(2)) + + +@injectable_factory(logging.Logger, qualifier="logger") +def logger_factory(): + return logging.getLogger(get_module_name(4)) if __name__ == '__main__': diff --git a/smib/common/utils.py b/smib/common/utils.py index 832d063..78a024b 100644 --- a/smib/common/utils.py +++ b/smib/common/utils.py @@ -68,14 +68,16 @@ def wrapper(*args, **kwargs): return wrapper -def _get_module_name(stack_num: int = 4): - frame = inspect.stack()[stack_num] +def get_module_name(stack_num: int = 4): + stack = inspect.stack() + frame = stack[stack_num] module = inspect.getmodule(frame[0]) module_name = module.__name__ return module_name -def _get_module_file(stack_num: int = 4) -> Path: - frame = inspect.stack()[stack_num] +def get_module_file(stack_num: int = 4) -> Path: + stack = inspect.stack() + frame = stack[stack_num] file = inspect.getfile(frame[0]) return Path(file) diff --git a/smib/slack/__main__.py b/smib/slack/__main__.py index 610f6b1..9da3b97 100644 --- a/smib/slack/__main__.py +++ b/smib/slack/__main__.py @@ -1,7 +1,7 @@ import logging from slack_bolt.adapter.socket_mode import SocketModeHandler -from slack.logging_injector import inject_logger_to_slack_context +from smib.slack.logging_injector import inject_logger_to_slack_context from smib.common.config import SLACK_APP_TOKEN, SLACK_BOT_TOKEN, APPLICATION_NAME, ROOT_DIRECTORY from smib.slack.websocket import server as websocket_server from smib.slack.error import handle_errors diff --git a/smib/slack/db/__init__.py b/smib/slack/db/__init__.py index 74cb6c5..1d2e311 100644 --- a/smib/slack/db/__init__.py +++ b/smib/slack/db/__init__.py @@ -1,27 +1,39 @@ -import logging -from functools import partial -from logging import Logger +import functools +import inspect +from pathlib import Path -import pymongo.errors -from injectable import injectable_factory, inject, load_injection_container +from injectable import inject from mogo import Model, Field, connect -from pymongo import MongoClient -from slack.plugin import PluginManager -from smib.common.utils import _get_module_name, _get_module_file -from smib.common.config import MONGO_DB_URL, MONGO_DB_ADMIN_PASSWORD, MONGO_DB_ADMIN_USER +from smib.slack.plugin import PluginManager +from smib.common.utils import get_module_file +from smib.common.config import MONGO_DB_URL, MONGO_DB_CONNECT_TIMEOUT_SECONDS -def connect_to_plugin_database(plugin_id: str): - return connect(plugin_id, uri=MONGO_DB_URL) +def get_current_plugin_id() -> str: + plugin_manager: PluginManager = inject("PluginManager") + plugin_name = plugin_manager.get_plugin_from_file(get_module_file(2)).id + return plugin_name -def get_plugin_database(): - plugin_manager: PluginManager = inject("PluginManager") - plugin_name = plugin_manager.get_plugin_from_file(_get_module_file(2)).id - return connect_to_plugin_database(plugin_name) +def database(database_name: str = None): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + db_name = database_name + + # If no database_name parameter name passed in, get current plugin id and use that + if db_name is None: + plugin_file = Path(inspect.getfile(func)) + plugin_manager: PluginManager = inject("PluginManager") + db_name = plugin_manager.get_plugin_from_file(plugin_file).id + + inject("logger").debug(f"Database name: {db_name}") + + # Connect to DB and close it afterward + with connect(db_name, uri=MONGO_DB_URL, timeoutMs=1000*MONGO_DB_CONNECT_TIMEOUT_SECONDS): + return func(*args, **kwargs) + return wrapper -@injectable_factory(Logger, qualifier="logger") -def logger_factory(): - return logging.getLogger(__name__) + return decorator diff --git a/smib/slack/logging_injector.py b/smib/slack/logging_injector.py index 6cfc671..54f6728 100644 --- a/smib/slack/logging_injector.py +++ b/smib/slack/logging_injector.py @@ -1,6 +1,7 @@ from injectable import inject + def inject_logger_to_slack_context(context, next): context['logger'] = inject("plugin_logger", lazy=True) next() diff --git a/smib/slack/plugin/loaders/abstract_plugin_loader.py b/smib/slack/plugin/loaders/abstract_plugin_loader.py index 28cd830..323e2f3 100644 --- a/smib/slack/plugin/loaders/abstract_plugin_loader.py +++ b/smib/slack/plugin/loaders/abstract_plugin_loader.py @@ -79,7 +79,7 @@ def load_plugin(self, plugin_path: Path) -> Plugin: self.unload_plugin(plugin) if plugin.error: - logger.error(plugin.error) + logger.error(f"Plugin {plugin.id} failed to load: {plugin.error}") return returned_plugin diff --git a/smib/slack/plugins/space/openclose/__init__.py b/smib/slack/plugins/space/openclose/__init__.py index 551ed0b..657ec27 100644 --- a/smib/slack/plugins/space/openclose/__init__.py +++ b/smib/slack/plugins/space/openclose/__init__.py @@ -3,12 +3,17 @@ __author__ = "Sam Cork" from injectable import inject + +from smib.common.utils import http_bolt_response from smib.slack.custom_app import CustomApp from slack_sdk import WebClient from slack_sdk.models.views import View from slack_sdk.models.blocks import ActionsBlock, PlainTextObject, HeaderBlock, ButtonElement +from .models import Space + from smib.common.config import SPACE_OPEN_ANNOUNCE_CHANNEL_ID +from smib.slack.db import database app: CustomApp = inject("SlackApp") @@ -45,23 +50,36 @@ def app_home_opened(client: WebClient, event: dict): @app.action('space_open') @app.event('http_put_space_open') +@database() def space_open(say, context, ack): - from .models import Space ack() context['logger'].debug("Space Open!") say(text='Space Open!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID) - Space.single().set_open() + space: Space = Space.single() + space.set_open() @app.action('space_closed') @app.event('http_put_space_closed') +@database() def space_closed(say, context, ack): - from .models import Space ack() context['logger'].debug("Space Closed!") + say(text='Space Closed!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID) - Space.single().set_closed() + space: Space = Space.single() + space.set_closed() + + +@app.event("http_get_space_state") +@http_bolt_response +@database() +def get_space_state(): + space = Space.single() + return { + "open": space.open + } diff --git a/smib/slack/plugins/space/openclose/models.py b/smib/slack/plugins/space/openclose/models.py index 1686cb1..3ac0035 100644 --- a/smib/slack/plugins/space/openclose/models.py +++ b/smib/slack/plugins/space/openclose/models.py @@ -1,7 +1,4 @@ -from smib.slack.db import Model, Field, get_plugin_database -from injectable import inject - -client = get_plugin_database() +from smib.slack.db import Model, Field class Space(Model): From 30051549c6ba194d5450b9020cecab536812ba74 Mon Sep 17 00:00:00 2001 From: sam57719 Date: Thu, 2 May 2024 01:19:31 +0100 Subject: [PATCH 09/11] Added is_json_encodable function and refined get_space_state method Added a new function `is_json_encodable` to utils.py for checking if a value can be serialized to JSON, and updated the `get_space_state` method in the openclose plugin. The updated method now returns a dictionary of objects from the Space class that are JSON-encodable, shifting the focus on ensuring data compatibility with JSON serialization. --- smib/common/utils.py | 7 +++++++ smib/slack/plugins/space/openclose/__init__.py | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/smib/common/utils.py b/smib/common/utils.py index 78a024b..aef441e 100644 --- a/smib/common/utils.py +++ b/smib/common/utils.py @@ -17,6 +17,13 @@ def is_pickleable(obj): except (pickle.PicklingError, AttributeError, TypeError): return False +def is_json_encodable(value): + try: + json.dumps(value) + return True + except TypeError: + return False + def to_path(x): path = Path(f"/{x.lstrip('/')}").as_posix().lstrip('/') diff --git a/smib/slack/plugins/space/openclose/__init__.py b/smib/slack/plugins/space/openclose/__init__.py index 657ec27..5c8dcae 100644 --- a/smib/slack/plugins/space/openclose/__init__.py +++ b/smib/slack/plugins/space/openclose/__init__.py @@ -4,7 +4,7 @@ from injectable import inject -from smib.common.utils import http_bolt_response +from smib.common.utils import http_bolt_response, is_json_encodable from smib.slack.custom_app import CustomApp from slack_sdk import WebClient from slack_sdk.models.views import View @@ -80,6 +80,4 @@ def space_closed(say, context, ack): @database() def get_space_state(): space = Space.single() - return { - "open": space.open - } + return {k: v for k, v in space.copy().items() if is_json_encodable(v)} From 86a423348bebf07b200c4787c57947227136ba39 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 19:50:24 +0200 Subject: [PATCH 10/11] Changed docker-compose.yml to pin mongo-db version, ensure all containers on the same network. Removed timezone stuff from smib-fast.Dockerfile. This needs to be looked at again as wasnt running on my Pi. --- docker-compose.yml | 12 ++++++++++-- smib-fast.Dockerfile | 12 ++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index aa866c1..6f6706e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: environment: - WEBSERVER_HOST=smib-webserver - WEBSOCKET_ALLOWED_HOSTS=smib-webserver,smib-webserver.smib-bridge-network + - MONGO_DB_HOST=smib-db # Passed in from HOST - SLACK_APP_TOKEN @@ -39,11 +40,15 @@ services: command: "python -m smib.webserver" smib-db: - image: mongo + + # Specific version - latest that works on a pi + image: mongo:4.4.18 container_name: smib-db restart: always ports: - 27017:27017 + networks: + - smib-bridge-network smib-db-ui: image: mongo-express @@ -57,7 +62,10 @@ services: ME_CONFIG_MONGODB_URL: mongodb://smib-db:27017/ ME_CONFIG_BASICAUTH: true + networks: + - smib-bridge-network + networks: smib-bridge-network: name: smib-bridge-network - driver: bridge \ No newline at end of file + driver: bridge diff --git a/smib-fast.Dockerfile b/smib-fast.Dockerfile index 53d57e6..4206ab0 100644 --- a/smib-fast.Dockerfile +++ b/smib-fast.Dockerfile @@ -4,13 +4,13 @@ FROM python:3.11-buster as builder RUN pip install poetry==1.4.2 # Install tzdata, curl, and jq. -RUN apt-get update && apt-get install -y tzdata curl jq +# RUN apt-get update && apt-get install -y tzdata curl jq # Fetch the timezone using the API, set the TZ environment variable to the fetched timezone. -RUN TIMEZONE=$(curl -s http://worldtimeapi.org/api/ip | jq -r .timezone) && \ - ln -fs /usr/share/zoneinfo/$TIMEZONE /etc/localtime && \ - dpkg-reconfigure -f noninteractive tzdata && \ - echo "TZ=$TIMEZONE" >> /etc/environment +#RUN TIMEZONE=$(curl -s http://worldtimeapi.org/api/ip | jq -r .timezone) && \ + # ln -fs /usr/share/zoneinfo/$TIMEZONE /etc/localtime && \ + # dpkg-reconfigure -f noninteractive tzdata && \ + # echo "TZ=$TIMEZONE" >> /etc/environment ENV POETRY_NO_INTERACTION=1 \ POETRY_VIRTUALENVS_IN_PROJECT=1 \ @@ -40,4 +40,4 @@ COPY --from=builder /etc/localtime /etc/localtime WORKDIR /app -COPY smib ./smib \ No newline at end of file +COPY smib ./smib From 157c2d41d0549119e26b78e94c80d075c87d6d5b Mon Sep 17 00:00:00 2001 From: sam57719 Date: Wed, 8 May 2024 19:18:49 +0100 Subject: [PATCH 11/11] Removed mongogettersetter dependency from the project The mongogettersetter dependency is removed from the pyproject.toml file and the poetry.lock file. As the project no longer requires this package, it has been removed to keep the dependencies simpler and cleaner. --- poetry.lock | 17 +---------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 60b5a9f..ba037af 100644 --- a/poetry.lock +++ b/poetry.lock @@ -636,21 +636,6 @@ files = [ [package.dependencies] pymongo = ">=4.1" -[[package]] -name = "mongogettersetter" -version = "1.5.0" -description = "A clean way to handle MongoDB documents in Pythonic way" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "mongogettersetter-1.5.0-py3-none-any.whl", hash = "sha256:e6bc4e8d024fdf76ec99a6618f18a63b39319d4252e4681e853559dc1933dda9"}, - {file = "mongogettersetter-1.5.0.tar.gz", hash = "sha256:f2100c43b145587b679b2eabeabae0a7f96c487169d06be519f3a13d787a24ea"}, -] - -[package.dependencies] -pymongo = "*" - [[package]] name = "multidict" version = "6.0.5" @@ -1448,4 +1433,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "fd0744dbb91c9f89e9b402fcc4118d86aa9d0bb37c2eeeda99deca8f8a583a05" +content-hash = "31953d972f1d3f3be17cd9c22fe467080cc69144cb751d54c434599fd904ef5b" diff --git a/pyproject.toml b/pyproject.toml index 830c97c..3052c87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ uvicorn = "^0.29.0" dataclasses-json = "^0.6.4" aiohttp = "^3.9.5" pymongo = "^4.7.0" -mongogettersetter = "^1.5.0" mogo = "^0.6.0"