From 20050b7df518151c34dd210204932f2c6b21bd7f Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sat, 22 Jul 2023 18:15:28 +0200 Subject: [PATCH 01/29] fixing uwsgi version --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0f7890e..dd7e6f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ ipython==8.10.0 itsdangerous==2.1.2 prettytable==0.7.2 pycparser==2.10 -uwsgi==2.0.18 +uwsgi==2.0.21 cryptography==41.0.0 pyOpenSSL==23.2.0 enum34==1.1.6 diff --git a/setup.py b/setup.py index 291f73a..9ec6158 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ 'itsdangerous==2.1.2', 'prettytable==0.7.2', 'pycparser==2.10', - 'uwsgi==2.0.18', + 'uwsgi==2.0.21', 'cryptography==41.0.0', 'pyOpenSSL==23.2.0', 'enum34==1.1.6', From 8a6dd9dcf2964ee07ac2cf437e891ff09e246342 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sun, 23 Jul 2023 05:33:29 +0200 Subject: [PATCH 02/29] removing uwsgi --- requirements.txt | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index dd7e6f5..0239420 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,6 @@ ipython==8.10.0 itsdangerous==2.1.2 prettytable==0.7.2 pycparser==2.10 -uwsgi==2.0.21 cryptography==41.0.0 pyOpenSSL==23.2.0 enum34==1.1.6 diff --git a/setup.py b/setup.py index 9ec6158..bb48567 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ 'itsdangerous==2.1.2', 'prettytable==0.7.2', 'pycparser==2.10', - 'uwsgi==2.0.21', 'cryptography==41.0.0', 'pyOpenSSL==23.2.0', 'enum34==1.1.6', From 09e01525f4b67a24278494eec1441c35a2553817 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sun, 23 Jul 2023 06:34:27 +0200 Subject: [PATCH 03/29] WIP --- frestq/app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index 42e7276..53a0f6c 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -159,6 +159,8 @@ def run(self, *args, **kwargs): if 'parse_args' in kwargs and kwargs['parse_args'] == True: del kwargs['parse_args'] self.parse_args(kwargs.get('extra_parse_func', lambda a,b: None)) + if 'extra_parse_func' in kwargs: + del kwargs['extra_parse_func'] if self.pargs is not None: if self.pargs.createdb: @@ -208,8 +210,8 @@ def run(self, *args, **kwargs): if 'port' not in kwargs: kwargs['port'] = app.config.get('SERVER_PORT', None) - return super(FrestqApp, self).run(threaded=True, use_reloader=False, - *args, **kwargs) + return super(FrestqApp, self)\ + .run(threaded=True, use_reloader=False, *args, **kwargs) app = FrestqApp(__name__) From 67f3771814e49f28043882af70a9e482cb006252 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sun, 23 Jul 2023 06:42:51 +0200 Subject: [PATCH 04/29] Revert "removing uwsgi" This reverts commit 8a6dd9dcf2964ee07ac2cf437e891ff09e246342. --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 0239420..dd7e6f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ ipython==8.10.0 itsdangerous==2.1.2 prettytable==0.7.2 pycparser==2.10 +uwsgi==2.0.21 cryptography==41.0.0 pyOpenSSL==23.2.0 enum34==1.1.6 diff --git a/setup.py b/setup.py index bb48567..9ec6158 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ 'itsdangerous==2.1.2', 'prettytable==0.7.2', 'pycparser==2.10', + 'uwsgi==2.0.21', 'cryptography==41.0.0', 'pyOpenSSL==23.2.0', 'enum34==1.1.6', From 872348c9c36845c0551cd69cb0e8cc9a95e34475 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sun, 23 Jul 2023 06:43:11 +0200 Subject: [PATCH 05/29] removing extra_run too --- frestq/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frestq/app.py b/frestq/app.py index 53a0f6c..68fa944 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -206,6 +206,9 @@ def run(self, *args, **kwargs): if 'use_reloader' in kwargs: print("use_reloader provided but ignored (always set to True): " + kwargs['use_reloader']) del kwargs['use_reloader'] + # ignore extra_run, we used already if needed + if 'extra_run' in kwargs: + del kwargs['extra_run'] if 'port' not in kwargs: kwargs['port'] = app.config.get('SERVER_PORT', None) From b2d2a1b2bd50157a448d05abc323e4c662dfc480 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 09:06:54 +0200 Subject: [PATCH 06/29] adding poetry support --- poetry.lock | 779 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 26 ++ 2 files changed, 805 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f9153d6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,779 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "apscheduler" +version = "3.7.0" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ + {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, + {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, +] + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=2.0,<3.0" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["mock", "pytest (<6)", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "enum34" +version = "1.1.10" +description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" +optional = false +python-versions = "*" +files = [ + {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, + {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, + {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, +] + +[[package]] +name = "flask" +version = "2.3.2" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, + {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.3" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-sqlalchemy" +version = "3.0.5" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.7" +files = [ + {file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"}, + {file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=1.4.18" + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "ipaddress" +version = "1.0.23" +description = "IPv4/IPv6 manipulation library" +optional = false +python-versions = "*" +files = [ + {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, + {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, +] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pyopenssl" +version = "23.2.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, +] + +[package.dependencies] +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.19" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"}, + {file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"}, + {file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "tzlocal" +version = "2.1" +description = "tzinfo object for the local timezone" +optional = false +python-versions = "*" +files = [ + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, +] + +[package.dependencies] +pytz = "*" + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "2aa425f53982a038674ab58c832c4d746fc64108946f21b71f9dd58a75d3f70f" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..54a3a55 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2023 Sequent Tech Inc +# SPDX-License-Identifier: AGPL-3.0-only + +[tool.poetry] +name = "frestq" +version = "4.0.0" +description = "" +authors = ["Eduardo Robles "] +license = "AGPL-3.0-only" +readme = "README.md" +packages = [{include = "frestq"}] + +[tool.poetry.dependencies] +python = "^3.8" +apscheduler = "3.7.0" +requests = "^2.31.0" +Flask = "^2.3.2" +flask-sqlalchemy = "^3.0.5" +cryptography = "^41.0.2" +pyOpenSSL = "23.2.0" +enum34 = "^1.1.6" +ipaddress = "^1.0.22" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From a48d4b4cf49370a28e4297a233921074fe6307ef Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 09:08:42 +0200 Subject: [PATCH 07/29] better versions constrains --- poetry.lock | 2 +- pyproject.toml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index f9153d6..9bf0a45 100644 --- a/poetry.lock +++ b/poetry.lock @@ -776,4 +776,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "2aa425f53982a038674ab58c832c4d746fc64108946f21b71f9dd58a75d3f70f" +content-hash = "19de67d2d52c43fcbe0cd2451aa518d41c2204404666828ab9ccbc037ff02c22" diff --git a/pyproject.toml b/pyproject.toml index 54a3a55..3098559 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,13 +13,13 @@ packages = [{include = "frestq"}] [tool.poetry.dependencies] python = "^3.8" apscheduler = "3.7.0" -requests = "^2.31.0" -Flask = "^2.3.2" -flask-sqlalchemy = "^3.0.5" -cryptography = "^41.0.2" +requests = "^2.31" +Flask = "^2.3" +flask-sqlalchemy = "^3.0" +cryptography = "^41.0" pyOpenSSL = "23.2.0" -enum34 = "^1.1.6" -ipaddress = "^1.0.22" +enum34 = "^1.1" +ipaddress = "^1.0" [build-system] requires = ["poetry-core"] From 02a181099a80021d0fe4c0d05922d5c7943cbbdb Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 09:11:11 +0200 Subject: [PATCH 08/29] add missing license file --- poetry.lock.license | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 poetry.lock.license diff --git a/poetry.lock.license b/poetry.lock.license new file mode 100644 index 0000000..d8b340d --- /dev/null +++ b/poetry.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2014-2021 Sequent Tech Inc + +SPDX-License-Identifier: AGPL-3.0-only \ No newline at end of file From 2d346bd501cf90668d0acd29dc4feaf288c68ea1 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 09:15:52 +0200 Subject: [PATCH 09/29] adding missing dep --- poetry.lock | 30 +++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 9bf0a45..f05ff17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -533,6 +533,23 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "prettytable" +version = "3.8.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "prettytable-3.8.0-py3-none-any.whl", hash = "sha256:03481bca25ae0c28958c8cd6ac5165c159ce89f7ccde04d5c899b24b68bb13b7"}, + {file = "prettytable-3.8.0.tar.gz", hash = "sha256:031eae6a9102017e8c7c7906460d150b7ed78b20fd1d8c8be4edaf88556c07ce"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] + [[package]] name = "pycparser" version = "2.21" @@ -741,6 +758,17 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + [[package]] name = "werkzeug" version = "2.3.6" @@ -776,4 +804,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "19de67d2d52c43fcbe0cd2451aa518d41c2204404666828ab9ccbc037ff02c22" +content-hash = "056076d474c071767540b31360efae3a76654dcedf615f0fc0d1e493dd189d01" diff --git a/pyproject.toml b/pyproject.toml index 3098559..ab7630e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ cryptography = "^41.0" pyOpenSSL = "23.2.0" enum34 = "^1.1" ipaddress = "^1.0" +prettytable = "^3.8.0" [build-system] requires = ["poetry-core"] From 0d911d96d89406518a3083a8221dca78bde145d0 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 09:23:08 +0200 Subject: [PATCH 10/29] removing enum34 --- poetry.lock | 14 +------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index f05ff17..105a303 100644 --- a/poetry.lock +++ b/poetry.lock @@ -282,18 +282,6 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -[[package]] -name = "enum34" -version = "1.1.10" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -optional = false -python-versions = "*" -files = [ - {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, - {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, - {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, -] - [[package]] name = "flask" version = "2.3.2" @@ -804,4 +792,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "056076d474c071767540b31360efae3a76654dcedf615f0fc0d1e493dd189d01" +content-hash = "6245f53fdfe1439dec85ee6c91212d118fa52c14e0cd0fc960393c190c515337" diff --git a/pyproject.toml b/pyproject.toml index ab7630e..c95cfbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ Flask = "^2.3" flask-sqlalchemy = "^3.0" cryptography = "^41.0" pyOpenSSL = "23.2.0" -enum34 = "^1.1" ipaddress = "^1.0" prettytable = "^3.8.0" From c579d2d744e998054574000fd1b4fa8f5c977ed9 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 17:45:38 +0200 Subject: [PATCH 11/29] trying to improve config handling --- examples/synchronized/server_b.py | 13 +-- frestq/api.py | 6 +- frestq/app.py | 71 ++++++------ frestq/frestq_app.py | 4 +- frestq/fscheduler.py | 10 +- frestq/models.py | 90 +++++++-------- frestq/protocol.py | 92 +++++++-------- frestq/tasks.py | 182 +++++++++++++++--------------- frestq/utils.py | 38 +++---- 9 files changed, 254 insertions(+), 252 deletions(-) diff --git a/examples/synchronized/server_b.py b/examples/synchronized/server_b.py index 9baaf46..53c5789 100644 --- a/examples/synchronized/server_b.py +++ b/examples/synchronized/server_b.py @@ -9,7 +9,6 @@ import copy from frestq import decorators -from frestq.app import db from frestq.app import app from frestq.tasks import SimpleTask, ParallelTask, SynchronizedTask from frestq.action_handlers import SynchronizedTaskHandler @@ -52,15 +51,15 @@ def new_reservation(self, subtask): self.task.task_model.reservation_data = dict() self.task.task_model.reservation_data[subtask_id] = reservation_data - db.session.add(self.task.task_model) - db.session.commit() + app.db.session.add(self.task.task_model) + app.db.session.commit() def pre_execute(self): reservation_data = self.task.get_data()["reservation_data"] for subtask in self.task.get_children(): subtask.task_model.input_data["reservation_data"] = reservation_data - db.session.add(subtask.task_model) - db.session.commit() + app.db.session.add(subtask.task_model) + app.db.session.commit() @decorators.task(action="testing.hello_world", queue="hello_world") def hello_world(task): @@ -156,8 +155,8 @@ def all_goodbyes_together(task): ] parent_model = task.get_parent().task_model task.get_parent().task_model.output_data = output_data - db.session.add(parent_model) - db.session.commit() + app.db.session.add(parent_model) + app.db.session.commit() return dict( output_data = output_data diff --git a/frestq/api.py b/frestq/api.py index 94e46ce..7891603 100644 --- a/frestq/api.py +++ b/frestq/api.py @@ -53,7 +53,7 @@ def post_message(queue_name): ''' # 1. register message in the db model - from .app import db + from .app import app from .models import Message logging.debug('RECEIVED MESSAGE in queue %s' % queue_name) @@ -110,8 +110,8 @@ def post_message(queue_name): 'output_status': 200 } msg = Message(**kwargs) - db.session.add(msg) - db.session.commit() + app.db.session.add(msg) + app.db.session.commit() action_handler = ActionHandlers.get_action_handler(msg.action, queue_name) if not action_handler: diff --git a/frestq/app.py b/frestq/app.py index 68fa944..897ea74 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -73,13 +73,19 @@ def __init__(self, *args, **kwargs): pargs = None + db = None + + def init_db(self): + logging.info("initializing app.db instance..") + self.db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) + def configure_app(self, scheduler=True, config_object=None): ''' Configures the application. It's intended to do everything to be able to - run the application except calling app.run, so that it can be reused when - using gunicorn or similar. + run the application except calling app.run, so that it can be reused + when using gunicorn or similar. ''' - self.config.from_object(__name__) + self.config.from_object(DefaultConfig()) if config_object: self.config.from_object(config_object) @@ -106,6 +112,7 @@ def configure_app(self, scheduler=True, config_object=None): return logging.info("Launching with ROOT_URL = %s", self.config['ROOT_URL']) + self.init_db() FScheduler.start_all_schedulers() def parse_args(self, extra_parse_func): @@ -165,7 +172,7 @@ def run(self, *args, **kwargs): if self.pargs is not None: if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - db.create_all() + app.db.create_all() return elif self.pargs.messages: list_messages(self.pargs) @@ -220,45 +227,41 @@ def run(self, *args, **kwargs): ### configuration -# debug, set to false on production deployment -DEBUG = True - -# see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 -SQLALCHEMY_TRACK_MODIFICATIONS = False - -# database configuration -# example: sqlite:////absolute/path/to/db.sqlite -SQLALCHEMY_DATABASE_URI = '' +class DefaultConfig(object): + # debug, set to false on production deployment + DEBUG = True -# own certificate, None if there isn't any -SSL_CERT_PATH = None -SSL_KEY_PATH = None + # see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 + SQLALCHEMY_TRACK_MODIFICATIONS = False -# queues root url -ROOT_URL = 'http://127.0.0.1:5000/api/queues' + # database configuration + # example: sqlite:////absolute/path/to/db.sqlite + SQLALCHEMY_DATABASE_URI = '' -# time a thread can be reserved in for synchronization purposes. In seconds. -RESERVATION_TIMEOUT = 60 + # own certificate, None if there isn't any + SSL_CERT_PATH = None + SSL_KEY_PATH = None -app.config.from_object(__name__) + # queues root url + ROOT_URL = 'http://127.0.0.1:5000/api/queues' -# boostrap our little application -db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) + # time a thread can be reserved in for synchronization purposes. In seconds. + RESERVATION_TIMEOUT = 60 -# set to True to get real security -ALLOW_ONLY_SSL_CONNECTIONS = False + # set to True to get real security + ALLOW_ONLY_SSL_CONNECTIONS = False -# options for each queue. example: -#QUEUES_OPTIONS = { - #'mycustom_queue': { - #'max_threads': 3, + # options for each queue. example: + #QUEUES_OPTIONS = { + #'mycustom_queue': { + #'max_threads': 3, + #} #} -#} -# thread data mapper is a function that would be called when a Synchronous task -# in this queue is going to be executed. It allows to set queue-specific -# settings, and even custom queue settings that can be used by you later. + # thread data mapper is a function that would be called when a Synchronous task + # in this queue is going to be executed. It allows to set queue-specific + # settings, and even custom queue settings that can be used by you later. -QUEUES_OPTIONS = dict() + QUEUES_OPTIONS = dict() from . import models diff --git a/frestq/frestq_app.py b/frestq/frestq_app.py index 9e99b46..6361ee5 100644 --- a/frestq/frestq_app.py +++ b/frestq/frestq_app.py @@ -164,8 +164,8 @@ def parse_args(self): def run_args(self): if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - from .app import db - db.create_all() + from .app import app + app.db.create_all() return elif self.pargs.messages: list_messages(self.pargs) diff --git a/frestq/fscheduler.py b/frestq/fscheduler.py index f44aac8..e4631ad 100644 --- a/frestq/fscheduler.py +++ b/frestq/fscheduler.py @@ -155,7 +155,7 @@ def add_now_job(self, func, args=None, kwargs=None, **options): :type date: :class:`datetime.date` :rtype: :class:`~apscheduler.job.Job` """ - from .app import db + from .app import app logging.info("adding job in sched for queue %s" % self.queue_name) trigger = NowTrigger() @@ -164,11 +164,11 @@ def add_now_job(self, func, args=None, kwargs=None, **options): def autocommit_wrapper(*args, **kwargs2): try: func(*args, **kwargs2) - db.session.commit() + app.db.session.commit() except exc.SQLAlchemyError: import traceback; traceback.print_exc() logging.info("SQLAlchemy exception, doing a rollback for recovery.") - db.session.rollback() + app.db.session.rollback() autocommit_wrapper.__name__ = func.__name__ @@ -180,12 +180,12 @@ def autocommit_wrapper(*args, **kwargs2): **options) def add_date_job(self, func, date, args=None, kwargs=None, **options): - from .app import db + from .app import app # autocommit to avoid dangling sessions def autocommit_wrapper(*args, **kwargs2): func(*args, **kwargs2) - db.session.commit() + app.db.session.commit() autocommit_wrapper.__name__ = func.__name__ diff --git a/frestq/models.py b/frestq/models.py index 92ad3ff..0ba7fc7 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -14,7 +14,7 @@ from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy -from .app import db +from .app import app from .utils import dumps, loads @@ -148,45 +148,45 @@ def remove(self, value): MutationObj.associate_with(JSONEncodedDict) -class Message(db.Model): +class Message(app.db.Model): ''' Represents an election ''' __tablename__ = 'message' - id = db.Column(db.Unicode(128), primary_key=True) + id = app.db.Column(app.db.Unicode(128), primary_key=True) - sender_url = db.Column(db.Unicode(1024)) + sender_url = app.db.Column(app.db.Unicode(1024)) - queue_name = db.Column(db.Unicode(1024)) + queue_name = app.db.Column(app.db.Unicode(1024)) - is_received = db.Column(db.Boolean) + is_received = app.db.Column(app.db.Boolean) - receiver_url = db.Column(db.Unicode(1024)) + receiver_url = app.db.Column(app.db.Unicode(1024)) - sender_ssl_cert = db.Column(db.UnicodeText) + sender_ssl_cert = app.db.Column(app.db.UnicodeText) - receiver_ssl_cert = db.Column(db.UnicodeText) + receiver_ssl_cert = app.db.Column(app.db.UnicodeText) - created_date = db.Column(db.DateTime, default=datetime.utcnow) + created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - action = db.Column(db.Unicode(1024)) + action = app.db.Column(app.db.Unicode(1024)) - input_data = db.Column(JSONEncodedDict) + input_data = app.db.Column(JSONEncodedDict) - output_status = db.Column(db.Integer) + output_status = app.db.Column(app.db.Integer) - pingback_date = db.Column(db.DateTime, default=None) + pingback_date = app.db.Column(app.db.DateTime, default=None) - expiration_date = db.Column(db.DateTime, default=None) + expiration_date = app.db.Column(app.db.DateTime, default=None) - info_text = db.Column(db.Unicode(2048)) + info_text = app.db.Column(app.db.Unicode(2048)) # fixed broken FK bug, when taskid exists in a non local db # task_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) #task = db.relationship('Task', # backref=db.backref('messages', lazy='dynamic')) - task_id = db.Column(db.Unicode(128)) + task_id = app.db.Column(app.db.Unicode(128)) def __init__(self, **kwargs): for key, value in kwargs.items(): @@ -224,65 +224,65 @@ def to_dict(self, full=False): return ret -class Task(db.Model): +class Task(app.db.Model): ''' Represents a task ''' __tablename__ = 'task' - id = db.Column(db.Unicode(128), primary_key=True) + id = app.db.Column(app.db.Unicode(128), primary_key=True) # this can be "simple", "sequential", "parallel", "external" or # "synchronized" - task_type = db.Column(db.Unicode(1024)) + task_type = app.db.Column(app.db.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = db.Column(JSONEncodedDict) + task_metadata = app.db.Column(JSONEncodedDict) - label = db.Column(db.Unicode(1024)) + label = app.db.Column(app.db.Unicode(1024)) - action = db.Column(db.Unicode(1024)) + action = app.db.Column(app.db.Unicode(1024)) - queue_name = db.Column(db.Unicode(1024)) + queue_name = app.db.Column(app.db.Unicode(1024)) - status = db.Column(db.Unicode(1024)) + status = app.db.Column(app.db.Unicode(1024)) - is_received = db.Column(db.Boolean) + is_received = app.db.Column(app.db.Boolean) - is_local = db.Column(db.Boolean, default=False) + is_local = app.db.Column(app.db.Boolean, default=False) - parent_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) + parent_id = app.db.Column(app.db.Unicode(128), app.db.ForeignKey('task.id')) - subtasks = db.relationship("Task", lazy="joined", join_depth=1) + subtasks = app.db.relationship("Task", lazy="joined", join_depth=1) # used if it's a subtask - order = db.Column(db.Integer) + order = app.db.Column(app.db.Integer) - receiver_url = db.Column(db.Unicode(1024)) + receiver_url = app.db.Column(app.db.Unicode(1024)) - sender_url = db.Column(db.Unicode(1024)) + sender_url = app.db.Column(app.db.Unicode(1024)) - sender_ssl_cert = db.Column(db.UnicodeText) + sender_ssl_cert = app.db.Column(app.db.UnicodeText) - receiver_ssl_cert = db.Column(db.UnicodeText) + receiver_ssl_cert = app.db.Column(app.db.UnicodeText) - created_date = db.Column(db.DateTime, default=datetime.utcnow) + created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - last_modified_date = db.Column(db.DateTime, default=datetime.utcnow) + last_modified_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - input_data = db.Column(JSONEncodedDict) + input_data = app.db.Column(JSONEncodedDict) - output_data = db.Column(JSONEncodedDict) + output_data = app.db.Column(JSONEncodedDict) - reservation_data = db.Column(JSONEncodedDict) + reservation_data = app.db.Column(JSONEncodedDict) - pingback_date = db.Column(db.DateTime, default=None) + pingback_date = app.db.Column(app.db.DateTime, default=None) - pingback_pending = db.Column(db.Boolean, default=False) + pingback_pending = app.db.Column(app.db.Boolean, default=False) - expiration_date = db.Column(db.DateTime, default=None) + expiration_date = app.db.Column(app.db.DateTime, default=None) - expiration_pending = db.Column(db.Boolean, default=False) + expiration_pending = app.db.Column(app.db.Boolean, default=False) # used to store scheduled jobs and remove them when they have finished # or need to be removed @@ -296,7 +296,7 @@ def __repr__(self): return '' % self.action def get_parent(self): - return db.session.query(Task).get(self.parent_id) + return app.db.session.query(Task).get(self.parent_id) def to_dict(self, full=False): ''' @@ -327,7 +327,7 @@ def to_dict(self, full=False): } if full: - parent = db.session.query(Task).get(self.parent_id) + parent = app.db.session.query(Task).get(self.parent_id) ret['parent'] = parent.to_dict() else: ret['parent_id'] = self.parent_id diff --git a/frestq/protocol.py b/frestq/protocol.py index e39df3c..d32b16a 100644 --- a/frestq/protocol.py +++ b/frestq/protocol.py @@ -57,13 +57,13 @@ class SecurityException(Exception): @decorators.message_action(action="frestq.update_task", queue=INTERNAL_SCHEDULER_NAME) def update_task(msg): - from .app import db + from .app import app from .models import Task as ModelTask from .tasks import BaseTask # fixed broken FK bug, when taskid exists in a non local db # task = msg.task - task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() logging.debug("UPDATING TASK with id %s" % task.id) if not task or\ @@ -87,8 +87,8 @@ def update_task(msg): dumps(msg.input_data[key]))) setattr(task, key, msg.input_data[key]) task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() # do next (it might be a task with a parent task) receiver_task = BaseTask.instance_by_model(task) @@ -103,12 +103,12 @@ def reserve_task(task_id): task from the receiver's point of view. When synced, this also executes the task. ''' - from .app import db, app + from .app import app from .models import Task as ModelTask from .tasks import BaseTask, send_task_update # 1. get task and check everything is ok - task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() if not task or task.status != 'syncing': # probably means we timedout or maybe there was some kind of error, so # we finish here @@ -123,8 +123,8 @@ def reserve_task(task_id): # 3. send reserved message to sender task.status = 'reserved' task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() ack_reservation(task_id) # 4. set reservation timeout @@ -137,8 +137,8 @@ def reserve_task(task_id): while True: _reserve_condition.wait() # fetch again the task to get the newly obtained status info - db.session.commit() - task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + app.db.session.commit() + task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() # we probably received a _reserve_condition that was sent to another thread # because our task didn't change state, so we ignore the signal @@ -159,18 +159,18 @@ def reserve_task(task_id): task_model = task task_model.status = 'executing' task_model.last_modified_date = datetime.utcnow() - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() task = BaseTask.instance_by_model(task_model) task_output = None try: task_output = task.run_action_handler() - db.session.commit() + app.db.session.commit() except Exception as e: task.error = e task.propagate = True - db.session.commit() + app.db.session.commit() import traceback; traceback.print_exc() if task.action_handler_object: task.action_handler_object.handle_error(e) @@ -182,13 +182,13 @@ def reserve_task(task_id): if task.propagate: task_model.status = "error" task_model.last_modified_date = datetime.utcnow() - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() elif task.auto_finish_after_handler: task_model.status = "finished" task_model.last_modified_date = datetime.utcnow() - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() if task.send_update_to_sender or task.propagate: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) @@ -211,11 +211,11 @@ def cancel_reserved_subtask(task_id): Note: this task is executed in cases where the task can be either local and not local. ''' - from .app import db + from .app import app from .models import Task as ModelTask from .tasks import BaseTask - task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() task_instance = BaseTask.instance_by_model(task) # task action_handler_object might not be present, it's not mandatory, and @@ -229,8 +229,8 @@ def cancel_reserved_subtask(task_id): task.status = "created" task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() with _reserve_condition: _reserve_condition.notify_all() @@ -238,11 +238,11 @@ def ack_reservation(task_id): ''' Sends a confirmation of a task reservation to task sender ''' - from .app import db, app + from .app import app from .models import Task as ModelTask from .tasks import send_message - task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() if not task or task.status != 'reserved': return @@ -270,11 +270,11 @@ def synchronize_task(msg): Receives a task that needs to be synchronized because its parent is a SynchronizedTask ''' - from .app import db, app + from .app import app from .models import Task as ModelTask logging.debug("SYNCING TASK with id %s" % msg.task_id) - task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if task and task.status != 'created': # error, cannot update an already finished task (unless it's an error)! # TODO: send back an error update @@ -300,8 +300,8 @@ def synchronize_task(msg): 'task_type': 'sequential' } task = ModelTask(**kwargs) - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() else: if certs_differ(task.sender_ssl_cert, msg.sender_ssl_cert): raise SecurityException() @@ -311,8 +311,8 @@ def synchronize_task(msg): task.status = 'syncing' task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() sched = FScheduler.get_scheduler(task.queue_name) sched.add_now_job(reserve_task, [task.id]) @@ -329,11 +329,11 @@ def director_confirm_task_reservation(msg): ''' Director of a synchronized task receives a task reservation ''' - from .app import db + from .app import app from .models import Task as ModelTask from .tasks import BaseTask, send_synchronization_message - task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() task_instance = BaseTask.instance_by_model(task) parent_instance = task_instance.get_parent() @@ -351,8 +351,8 @@ def director_confirm_task_reservation(msg): task.status = 'reserved' task.reservation_data = msg.input_data.get('reservation_data', None) task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() logging.debug("CONFIRMED TASK RESERVATION with id %s" % msg.task_id) @@ -390,11 +390,11 @@ def director_cancel_reserved_subtask(task_id): ''' Cancel a task because a reservation timeout in the director node ''' - from .app import db + from .app import app from .models import Task as ModelTask from .tasks import BaseTask, send_synchronization_message - task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() task_instance = BaseTask.instance_by_model(task) parent_instance = task_instance.get_parent() @@ -407,8 +407,8 @@ def director_cancel_reserved_subtask(task_id): task.status = "created" task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() #if all tasks are created, it means all tasks have expired, so we cannot #wait for a confirmation that will launch again all the expired tasks. @@ -427,7 +427,7 @@ def director_synchronized_subtask_start(task_id): ''' Launch the execution of a subtask ''' - from .app import db + from .app import app from .models import Task as ModelTask from .tasks import send_message @@ -456,7 +456,7 @@ def director_synchronized_subtask_start(task_id): @decorators.message_action(action="frestq.execute_synchronized", queue=INTERNAL_SCHEDULER_NAME) def execute_synchronized(msg): - from .app import db + from .app import app from .models import Task as ModelTask from .api import call_action_handler from .fscheduler import FScheduler @@ -475,8 +475,8 @@ def execute_synchronized(msg): task.input_data = msg.input_data['input_data'] task.status = "confirmed" task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() with _reserve_condition: _reserve_condition.notify_all() @@ -486,14 +486,14 @@ def finish_external_task(msg): ''' marks as finished an external task. This will let the task flow continue ''' - from .app import db, app + from .app import app from .models import Task as ModelTask from .api import call_action_handler from .fscheduler import FScheduler from .tasks import BaseTask, post_task, send_task_update # get the task model - task_model = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task_model = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if not task_model or task_model.task_type != "external" or\ task_model.status != 'executing' or\ task_model.sender_url != app.config.get('ROOT_URL'): @@ -507,8 +507,8 @@ def finish_external_task(msg): task = BaseTask.instance_by_model(task_model) task_model.output_data = msg.input_data task_model.status = "finished" - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() if task.send_update_to_sender: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) diff --git a/frestq/tasks.py b/frestq/tasks.py index 1fc262f..05a05d7 100644 --- a/frestq/tasks.py +++ b/frestq/tasks.py @@ -18,7 +18,7 @@ from flask import request -from .app import db, app +from .app import app from .fscheduler import FScheduler, INTERNAL_SCHEDULER_NAME from .models import Task as ModelTask, Message as ModelMessage from .utils import dumps @@ -96,8 +96,8 @@ def send(self): # update db self.task_model.status = "sent" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() send_message(msg_data, update_task_receiver_ssl_cert=True, task=self.task_model) @@ -153,7 +153,7 @@ def get_children(self): ''' Returns the ordered list of children of this task if any ''' - subtasks = db.session.query(ModelTask).with_parent(self.task_model, + subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").order_by(ModelTask.order) children_tasks = [self.instance_by_model(task) @@ -164,7 +164,7 @@ def get_child(self, label): ''' Gets a children by label ''' - task = db.session.query(ModelTask).with_parent(self.task_model, + task = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.label == label).first() if not task: return None @@ -178,7 +178,7 @@ def get_parent(self): if not self.task_model.parent_id: return None - parent = db.session.query(ModelTask).get(self.task_model.parent_id) + parent = app.db.session.query(ModelTask).get(self.task_model.parent_id) parent_task = self.instance_by_model(parent) return parent_task @@ -189,7 +189,7 @@ def get_siblings(self): if not self.task_model.parent_id: return [] - siblings = db.session.query(ModelTask)\ + siblings = app.db.session.query(ModelTask)\ .filter(ModelTask.id == self.task_model.parent_id, ModelTask.id != self.task_model.id)\ .order_by(ModelTask.order) @@ -201,7 +201,7 @@ def get_sibling(self, label): ''' Gets a children by label ''' - task = db.session.query(ModelTask).filter( + task = app.db.session.query(ModelTask).filter( ModelTask.id == self.task_model.parent_id, ModelTask.id != self.task_model.id, ModelTask.label == label).first() @@ -216,7 +216,7 @@ def get_prev(self): if self.task_model.order == 0 or not self.task_model.parent_id: return None - task = db.session.query(ModelTask)\ + task = app.db.session.query(ModelTask)\ .filter(ModelTask.parent_id == self.task_model.parent_id, ModelTask.order == self.task_model.order - 1).first() if not task: @@ -230,7 +230,7 @@ def get_next(self): if not self.task_model.parent_id: return None - task = db.session.query(ModelTask)\ + task = app.db.session.query(ModelTask)\ .filter(ModelTask.parent_id == self.task_model.parent_id, ModelTask.order == self.task_model.order + 1).first() if not task: @@ -298,7 +298,7 @@ def execute_parent(self): if not self.task_model.parent_id: return - parent = db.session.query(ModelTask).get(self.task_model.parent_id) + parent = app.db.session.query(ModelTask).get(self.task_model.parent_id) parent_task = BaseTask.instance_by_model(parent) parent_task.execute() @@ -393,8 +393,8 @@ def create(self): 'parent_id': None } self.task_model = ModelTask(**kwargs) - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() return self.task_model def execute(self): @@ -486,8 +486,8 @@ def create(self): 'parent_id': None } self.task_model = ModelTask(**kwargs) - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() return self.task_model def finish(self, data=None): @@ -511,8 +511,8 @@ def execute(self): ''' if self.task_model.status in ['created', 'sent']: self.task_model.status = "executing" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() elif self.task_model.status == "finished": self.execute_parent() @@ -561,15 +561,15 @@ def add(self, subtask): model = subtask.create() model.order = self._count_subtasks() model.parent_id = self.task_model.id - db.session.add(model) - db.session.commit() + app.db.session.add(model) + app.db.session.commit() def _count_subtasks(self): ''' Internal. Count the number of subtasks. Only meant to be executed after the sequential task has been created in the database. ''' - return db.session.query(ModelTask).with_parent(self.task_model, + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").count() def create(self): @@ -600,25 +600,25 @@ def create(self): } self.task_model = ModelTask(**kwargs) # fix broken FK bug, when child added to parent that does not yet exist - db.session.add(self.task_model) + app.db.session.add(self.task_model) # create also subtasks i = 0 for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.order = i subtask_model.parent_id = self.task_model.id - db.session.add(subtask_model) + app.db.session.add(subtask_model) i += 1 # fix broken FK bug, when child added to parent that does not yet exist # db.session.add(self.task_model) - db.session.commit() + app.db.session.commit() return self.task_model def next_subtask(self): ''' Returns next subtask if any or None ''' - return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def execute(self): @@ -628,8 +628,8 @@ def execute(self): ''' if self.task_model.status in ['created', 'sent']: self.task_model.status = "executing" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() if self.task_model.status in ['finished', 'error']: return @@ -641,8 +641,8 @@ def execute(self): # sender if it's not local if not next_subtask_model: self.task_model.status = "finished" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() # update the sender if any if not self.task_model.is_local: @@ -665,8 +665,8 @@ def execute(self): if self.propagate: self.task_model.status = "finished" if not self.propagate else "error" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() if not self.task_model.is_local: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) @@ -693,7 +693,7 @@ def execute_task(task_id): ''' Used to execute a task asynchronously ''' - task_model = db.session.query(ModelTask).get(task_id) + task_model = app.db.session.query(ModelTask).get(task_id) task = BaseTask.instance_by_model(task_model) task.execute() @@ -732,8 +732,8 @@ def add(self, subtask): model = subtask.create() model.parent_id = self.task_model.id - db.session.add(model) - db.session.commit() + app.db.session.add(model) + app.db.session.commit() def create(self): ''' @@ -764,38 +764,38 @@ def create(self): } self.task_model = ModelTask(**kwargs) # fix broken FK bug, when child added to parent that does not yet exist - db.session.add(self.task_model) + app.db.session.add(self.task_model) # create also subtasks i = 0 for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.parent_id = self.task_model.id - db.session.add(subtask_model) + app.db.session.add(subtask_model) i += 1 # fix broken FK bug, when child added to parent that does not yet exist # db.session.add(self.task_model) - db.session.commit() + app.db.session.commit() return self.task_model def count_unfinished_subtasks(self): ''' Count the number of subtasks ''' - return db.session.query(ModelTask).with_parent(self.task_model, + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status != 'finished').count() def next_subtask(self): ''' Returns next subtask if any or None ''' - return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def errored_tasks(self): ''' Count the number of subtasks with error ''' - return db.session.query(ModelTask).with_parent(self.task_model, + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status == 'error') def execute(self): @@ -809,11 +809,11 @@ def execute(self): try: self._db_lock.acquire() - db.session.commit() + app.db.session.commit() # paranoid mode, do not trust old tasks. check if task finished - db.session.expire(self.task_model) - db.session.refresh(self.task_model) + app.db.session.expire(self.task_model) + app.db.session.refresh(self.task_model) # should have been already dealt with if self.task_model.status == 'finished': return @@ -826,8 +826,8 @@ def execute(self): if errored_tasks.count() > 0: self.error = SubTasksFailed(errored_tasks) self.task_model.status = "error" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # propagate @@ -840,12 +840,12 @@ def execute(self): if self.task_model.status in ['created', 'sent'] and num_unfinished_subtasks > 0: # mark as executing this task self.task_model.status = "executing" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # start subtasks in parallel - subtasks = db.session.query(ModelTask).with_parent(self.task_model, "subtasks") + subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks") for subtask in subtasks: sched = FScheduler.get_scheduler(subtask.queue_name) sched.add_now_job(execute_task, [subtask.id]) @@ -857,8 +857,8 @@ def execute(self): if num_unfinished_subtasks == 0: # mark as finished self.task_model.status = "finished" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # check if there's a parent task, and if so execute() it @@ -895,8 +895,8 @@ def send_synchronization_message(task_id): } send_message(msg) task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() @@ -949,8 +949,8 @@ def add(self, subtask): model = subtask.create() model.parent_id = self.task_model.id - db.session.add(model) - db.session.commit() + app.db.session.add(model) + app.db.session.commit() def create(self): ''' @@ -991,31 +991,31 @@ def create(self): for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.parent_id = self.task_model.id - db.session.add(subtask_model) + app.db.session.add(subtask_model) i += 1 - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() return self.task_model def count_unfinished_subtasks(self): ''' Count the number of subtasks ''' - return db.session.query(ModelTask).with_parent(self.task_model, + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status != 'finished').count() def next_subtask(self): ''' Returns next subtask if any or None ''' - return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def errored_tasks(self): ''' Count the number of subtasks with error ''' - return db.session.query(ModelTask).with_parent(self.task_model, + return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status == 'error') def execute(self): @@ -1028,11 +1028,11 @@ def execute(self): try: self._db_lock.acquire() - db.session.commit() + app.db.session.commit() # paranoid mode, do not trust old tasks. check if task finished - db.session.expire(self.task_model) - db.session.refresh(self.task_model) + app.db.session.expire(self.task_model) + app.db.session.refresh(self.task_model) # should have been already dealt with if self.task_model.status == 'finished': return @@ -1046,8 +1046,8 @@ def execute(self): if errored_tasks.count() > 0: self.error = SubTasksFailed(errored_tasks) self.task_model.status = "error" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # propagate @@ -1073,12 +1073,12 @@ def execute(self): def _synchronize(self): # mark as executing this task self.task_model.status = "executing" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # send initial synchronization message to subtasks - subtasks = db.session.query(ModelTask).with_parent(self.task_model, "subtasks") + subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks") for subtask in subtasks: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) sched.add_now_job(send_synchronization_message, [subtask.id]) @@ -1089,8 +1089,8 @@ def _finish(self): ''' # mark as finished self.task_model.status = "finished" - db.session.add(self.task_model) - db.session.commit() + app.db.session.add(self.task_model) + app.db.session.commit() self._db_lock.release() # check if there's a parent task, and if so execute() it @@ -1141,8 +1141,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): # msg is saved before sending the message so that it's registered (it might # get even be retrieved from DB by api.py:post_message() if it's a local # message, but it's also updated when the msg is sent to update output - db.session.add(msg) - db.session.commit() + app.db.session.add(msg) + app.db.session.commit() session = requests.sessions.Session() @@ -1168,8 +1168,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): .decode('utf-8') if update_task_receiver_ssl_cert and task: task.receiver_ssl_cert = msg.receiver_ssl_cert - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() except Exception as e: pass @@ -1183,8 +1183,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): if r.status_code >= 400: print("!!! ERROR request to url = '%s' and status = '%d' answered:\n%s" % (url, r.status_code, r.text)) - db.session.add(msg) - db.session.commit() + app.db.session.add(msg) + app.db.session.commit() class TaskError(Exception): @@ -1231,12 +1231,12 @@ def send_task_update(task_id): logging.debug("update_msg.inputdata: %s" % dumps(update_msg["input_data"])) send_message(update_msg) task.last_modified_date = datetime.utcnow() - db.session.add(task) - db.session.commit() + app.db.session.add(task) + app.db.session.commit() # task finished. check if there's a parent task, and if so execute() it if task.parent_id: - parent = db.session.query(ModelTask).get(task.parent_id) + parent = app.db.session.query(ModelTask).get(task.parent_id) parent_task = BaseTask.instance_by_model(parent) parent_task.execute() @@ -1258,8 +1258,8 @@ def update_task(task, task_output): task.task_model.output_data = task_output['output_status'] if commit: - db.session.add(task.task_model) - db.session.commit() + app.db.session.add(task.task_model) + app.db.session.commit() def post_task(msg, action_handler): ''' @@ -1290,35 +1290,35 @@ def post_task(msg, action_handler): if not kwargs['id']: msg.task_id = kwargs['id'] = str(uuid4()) - db.session.add(msg) - db.session.commit() + app.db.session.add(msg) + app.db.session.commit() # if msg originated from ourselves, it might already exist. or if it's a # subtask of a synchronized task - task_model = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task_model = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if task_model: if task_model.task_type == 'simple': # this could happen if the task was created with SimpleTask task_model.task_type = 'sequential' - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() else: task_model = ModelTask(**kwargs) - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() # 2. call to the handler task = BaseTask.instance_by_model(task_model) task_output = None try: task_output = task.run_action_handler() - db.session.commit() + app.db.session.commit() except Exception as e: import traceback; traceback.print_exc() task.error = e task.propagate = True - db.session.commit() + app.db.session.commit() if task.action_handler_object: try: task.action_handler_object.handle_error(e) @@ -1332,8 +1332,8 @@ def post_task(msg, action_handler): # 3. update asynchronously the task sender if requested if task.auto_finish_after_handler or task.propagate: task_model.status = "finished" if not task.propagate else "error" - db.session.add(task_model) - db.session.commit() + app.db.session.add(task_model) + app.db.session.commit() if task.send_update_to_sender: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) diff --git a/frestq/utils.py b/frestq/utils.py index 6d12258..53894e1 100644 --- a/frestq/utils.py +++ b/frestq/utils.py @@ -56,7 +56,7 @@ def loads(obj, **kwargs): # drb def get_tasks(args): - from .app import db + from .app import app from .models import Task filters=[] @@ -65,9 +65,9 @@ def get_tasks(args): filters.append(getattr(Task, key).__eq__(value)) if filters: - tasks = db.session.query(Task).filter(*filters) + tasks = app.db.session.query(Task).filter(*filters) else: - tasks = db.session.query(Task) + tasks = app.db.session.query(Task) tasks = tasks.order_by(Task.created_date.desc()).limit(args.limit) return tasks @@ -93,7 +93,7 @@ def list_messages(args): ''' Prints the list of messages ''' - from .app import db + from .app import app from .models import Message filters=[] @@ -102,9 +102,9 @@ def list_messages(args): filters.append(getattr(Message, key).__eq__(value)) if filters: - msgs = db.session.query(Message).filter(*filters) + msgs = app.db.session.query(Message).filter(*filters) else: - msgs = db.session.query(Message) + msgs = app.db.session.query(Message) msgs = msgs.order_by(Message.created_date.desc()).limit(args.limit) table = PrettyTable(['small id', 'sender_url', 'action', 'queue', 'created_date', 'input_data']) @@ -142,10 +142,10 @@ def print_task(task, base_task_id=None, level=0, mode="full"): def traverse_tasktree(task, visitor_func, visitor_kwargs): visitor_func(task, **visitor_kwargs) - from .app import db + from .app import app from .models import Task - subtasks = db.session.query(Task)\ + subtasks = app.db.session.query(Task)\ .with_parent(task, "subtasks")\ .order_by(Task.order) for subtask in subtasks: @@ -155,10 +155,10 @@ def traverse_tasktree(task, visitor_func, visitor_kwargs): traverse_tasktree(subtask, visitor_func, vargs) def show_task(args): - from .app import db + from .app import app from .models import Task task_id = unicode(args.show_task) - task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) return @@ -258,10 +258,10 @@ def show_activity(args): def show_message(args): - from .app import db + from .app import app from .models import Message msg_id = unicode(args.show_message) - msg_model = db.session.query(Message).filter(Message.id.startswith(msg_id)).all() + msg_model = app.db.session.query(Message).filter(Message.id.startswith(msg_id)).all() if not msg_model: print("message %s not found" % msg_id) return @@ -270,11 +270,11 @@ def show_message(args): # drb def get_external_task(args): - from .app import db + from .app import app from .models import Task task_id = unicode(args.show_external) - task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() return task_model @@ -298,7 +298,7 @@ def show_external_task(args): print("info_text:\n%s" % task_model.input_data) def finish_task(args): - from .app import db + from .app import app from .models import Task from .tasks import ExternalTask @@ -309,7 +309,7 @@ def finish_task(args): print("error loading the json finish data") return - task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) @@ -329,10 +329,10 @@ def deny_task(args): pass def task_tree(args): - from .app import db + from .app import app from .models import Task task_id = unicode(args.tree) - task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) return @@ -340,7 +340,7 @@ def task_tree(args): if args.with_parents: while task_model.parent_id: try: - task_model = db.session.query(Task).get(task_model.parent_id) + task_model = app.db.session.query(Task).get(task_model.parent_id) except: print("task %s, which is the parent of %s not found" % ( str(task.parent_id)[:8], From 4fdcc490c765dc0d50f6ebc0cf91361409acdc02 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 17:54:43 +0200 Subject: [PATCH 12/29] WIP --- frestq/models.py | 89 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/frestq/models.py b/frestq/models.py index 0ba7fc7..03f0d2e 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -12,7 +12,6 @@ from sqlalchemy.ext.mutable import Mutable from sqlalchemy.types import TypeDecorator, UnicodeText from flask import Flask, jsonify -from flask_sqlalchemy import SQLAlchemy from .app import app from .utils import dumps, loads @@ -148,45 +147,45 @@ def remove(self, value): MutationObj.associate_with(JSONEncodedDict) -class Message(app.db.Model): +class Message(sqlalchemy.Model): ''' Represents an election ''' __tablename__ = 'message' - id = app.db.Column(app.db.Unicode(128), primary_key=True) + id = sqlalchemy.Column(sqlalchemy.Unicode(128), primary_key=True) - sender_url = app.db.Column(app.db.Unicode(1024)) + sender_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - queue_name = app.db.Column(app.db.Unicode(1024)) + queue_name = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - is_received = app.db.Column(app.db.Boolean) + is_received = sqlalchemy.Column(sqlalchemy.Boolean) - receiver_url = app.db.Column(app.db.Unicode(1024)) + receiver_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - sender_ssl_cert = app.db.Column(app.db.UnicodeText) + sender_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) - receiver_ssl_cert = app.db.Column(app.db.UnicodeText) + receiver_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) - created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + created_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) - action = app.db.Column(app.db.Unicode(1024)) + action = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - input_data = app.db.Column(JSONEncodedDict) + input_data = sqlalchemy.Column(JSONEncodedDict) - output_status = app.db.Column(app.db.Integer) + output_status = sqlalchemy.Column(sqlalchemy.Integer) - pingback_date = app.db.Column(app.db.DateTime, default=None) + pingback_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) - expiration_date = app.db.Column(app.db.DateTime, default=None) + expiration_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) - info_text = app.db.Column(app.db.Unicode(2048)) + info_text = sqlalchemy.Column(sqlalchemy.Unicode(2048)) # fixed broken FK bug, when taskid exists in a non local db # task_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) #task = db.relationship('Task', # backref=db.backref('messages', lazy='dynamic')) - task_id = app.db.Column(app.db.Unicode(128)) + task_id = sqlalchemy.Column(sqlalchemy.Unicode(128)) def __init__(self, **kwargs): for key, value in kwargs.items(): @@ -224,65 +223,65 @@ def to_dict(self, full=False): return ret -class Task(app.db.Model): +class Task(sqlalchemy.Model): ''' Represents a task ''' __tablename__ = 'task' - id = app.db.Column(app.db.Unicode(128), primary_key=True) + id = sqlalchemy.Column(sqlalchemy.Unicode(128), primary_key=True) # this can be "simple", "sequential", "parallel", "external" or # "synchronized" - task_type = app.db.Column(app.db.Unicode(1024)) + task_type = sqlalchemy.Column(sqlalchemy.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = app.db.Column(JSONEncodedDict) + task_metadata = sqlalchemy.Column(JSONEncodedDict) - label = app.db.Column(app.db.Unicode(1024)) + label = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - action = app.db.Column(app.db.Unicode(1024)) + action = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - queue_name = app.db.Column(app.db.Unicode(1024)) + queue_name = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - status = app.db.Column(app.db.Unicode(1024)) + status = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - is_received = app.db.Column(app.db.Boolean) + is_received = sqlalchemy.Column(sqlalchemy.Boolean) - is_local = app.db.Column(app.db.Boolean, default=False) + is_local = sqlalchemy.Column(sqlalchemy.Boolean, default=False) - parent_id = app.db.Column(app.db.Unicode(128), app.db.ForeignKey('task.id')) + parent_id = sqlalchemy.Column(sqlalchemy.Unicode(128), sqlalchemy.ForeignKey('task.id')) - subtasks = app.db.relationship("Task", lazy="joined", join_depth=1) + subtasks = sqlalchemy.relationship("Task", lazy="joined", join_depth=1) # used if it's a subtask - order = app.db.Column(app.db.Integer) + order = sqlalchemy.Column(sqlalchemy.Integer) - receiver_url = app.db.Column(app.db.Unicode(1024)) + receiver_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - sender_url = app.db.Column(app.db.Unicode(1024)) + sender_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) - sender_ssl_cert = app.db.Column(app.db.UnicodeText) + sender_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) - receiver_ssl_cert = app.db.Column(app.db.UnicodeText) + receiver_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) - created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + created_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) - last_modified_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + last_modified_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) - input_data = app.db.Column(JSONEncodedDict) + input_data = sqlalchemy.Column(JSONEncodedDict) - output_data = app.db.Column(JSONEncodedDict) + output_data = sqlalchemy.Column(JSONEncodedDict) - reservation_data = app.db.Column(JSONEncodedDict) + reservation_data = sqlalchemy.Column(JSONEncodedDict) - pingback_date = app.db.Column(app.db.DateTime, default=None) + pingback_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) - pingback_pending = app.db.Column(app.db.Boolean, default=False) + pingback_pending = sqlalchemy.Column(sqlalchemy.Boolean, default=False) - expiration_date = app.db.Column(app.db.DateTime, default=None) + expiration_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) - expiration_pending = app.db.Column(app.db.Boolean, default=False) + expiration_pending = sqlalchemy.Column(sqlalchemy.Boolean, default=False) # used to store scheduled jobs and remove them when they have finished # or need to be removed @@ -296,7 +295,7 @@ def __repr__(self): return '' % self.action def get_parent(self): - return app.db.session.query(Task).get(self.parent_id) + return sqlalchemy.session.query(Task).get(self.parent_id) def to_dict(self, full=False): ''' @@ -327,7 +326,7 @@ def to_dict(self, full=False): } if full: - parent = app.db.session.query(Task).get(self.parent_id) + parent = sqlalchemy.session.query(Task).get(self.parent_id) ret['parent'] = parent.to_dict() else: ret['parent_id'] = self.parent_id From e244a877cde52d756d6f1d64e134170f4d46f2a3 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 18:05:33 +0200 Subject: [PATCH 13/29] Revert "WIP" This reverts commit 4fdcc490c765dc0d50f6ebc0cf91361409acdc02. --- frestq/models.py | 89 ++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/frestq/models.py b/frestq/models.py index 03f0d2e..0ba7fc7 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -12,6 +12,7 @@ from sqlalchemy.ext.mutable import Mutable from sqlalchemy.types import TypeDecorator, UnicodeText from flask import Flask, jsonify +from flask_sqlalchemy import SQLAlchemy from .app import app from .utils import dumps, loads @@ -147,45 +148,45 @@ def remove(self, value): MutationObj.associate_with(JSONEncodedDict) -class Message(sqlalchemy.Model): +class Message(app.db.Model): ''' Represents an election ''' __tablename__ = 'message' - id = sqlalchemy.Column(sqlalchemy.Unicode(128), primary_key=True) + id = app.db.Column(app.db.Unicode(128), primary_key=True) - sender_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + sender_url = app.db.Column(app.db.Unicode(1024)) - queue_name = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + queue_name = app.db.Column(app.db.Unicode(1024)) - is_received = sqlalchemy.Column(sqlalchemy.Boolean) + is_received = app.db.Column(app.db.Boolean) - receiver_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + receiver_url = app.db.Column(app.db.Unicode(1024)) - sender_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) + sender_ssl_cert = app.db.Column(app.db.UnicodeText) - receiver_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) + receiver_ssl_cert = app.db.Column(app.db.UnicodeText) - created_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) + created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - action = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + action = app.db.Column(app.db.Unicode(1024)) - input_data = sqlalchemy.Column(JSONEncodedDict) + input_data = app.db.Column(JSONEncodedDict) - output_status = sqlalchemy.Column(sqlalchemy.Integer) + output_status = app.db.Column(app.db.Integer) - pingback_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) + pingback_date = app.db.Column(app.db.DateTime, default=None) - expiration_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) + expiration_date = app.db.Column(app.db.DateTime, default=None) - info_text = sqlalchemy.Column(sqlalchemy.Unicode(2048)) + info_text = app.db.Column(app.db.Unicode(2048)) # fixed broken FK bug, when taskid exists in a non local db # task_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) #task = db.relationship('Task', # backref=db.backref('messages', lazy='dynamic')) - task_id = sqlalchemy.Column(sqlalchemy.Unicode(128)) + task_id = app.db.Column(app.db.Unicode(128)) def __init__(self, **kwargs): for key, value in kwargs.items(): @@ -223,65 +224,65 @@ def to_dict(self, full=False): return ret -class Task(sqlalchemy.Model): +class Task(app.db.Model): ''' Represents a task ''' __tablename__ = 'task' - id = sqlalchemy.Column(sqlalchemy.Unicode(128), primary_key=True) + id = app.db.Column(app.db.Unicode(128), primary_key=True) # this can be "simple", "sequential", "parallel", "external" or # "synchronized" - task_type = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + task_type = app.db.Column(app.db.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = sqlalchemy.Column(JSONEncodedDict) + task_metadata = app.db.Column(JSONEncodedDict) - label = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + label = app.db.Column(app.db.Unicode(1024)) - action = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + action = app.db.Column(app.db.Unicode(1024)) - queue_name = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + queue_name = app.db.Column(app.db.Unicode(1024)) - status = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + status = app.db.Column(app.db.Unicode(1024)) - is_received = sqlalchemy.Column(sqlalchemy.Boolean) + is_received = app.db.Column(app.db.Boolean) - is_local = sqlalchemy.Column(sqlalchemy.Boolean, default=False) + is_local = app.db.Column(app.db.Boolean, default=False) - parent_id = sqlalchemy.Column(sqlalchemy.Unicode(128), sqlalchemy.ForeignKey('task.id')) + parent_id = app.db.Column(app.db.Unicode(128), app.db.ForeignKey('task.id')) - subtasks = sqlalchemy.relationship("Task", lazy="joined", join_depth=1) + subtasks = app.db.relationship("Task", lazy="joined", join_depth=1) # used if it's a subtask - order = sqlalchemy.Column(sqlalchemy.Integer) + order = app.db.Column(app.db.Integer) - receiver_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + receiver_url = app.db.Column(app.db.Unicode(1024)) - sender_url = sqlalchemy.Column(sqlalchemy.Unicode(1024)) + sender_url = app.db.Column(app.db.Unicode(1024)) - sender_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) + sender_ssl_cert = app.db.Column(app.db.UnicodeText) - receiver_ssl_cert = sqlalchemy.Column(sqlalchemy.UnicodeText) + receiver_ssl_cert = app.db.Column(app.db.UnicodeText) - created_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) + created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - last_modified_date = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) + last_modified_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) - input_data = sqlalchemy.Column(JSONEncodedDict) + input_data = app.db.Column(JSONEncodedDict) - output_data = sqlalchemy.Column(JSONEncodedDict) + output_data = app.db.Column(JSONEncodedDict) - reservation_data = sqlalchemy.Column(JSONEncodedDict) + reservation_data = app.db.Column(JSONEncodedDict) - pingback_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) + pingback_date = app.db.Column(app.db.DateTime, default=None) - pingback_pending = sqlalchemy.Column(sqlalchemy.Boolean, default=False) + pingback_pending = app.db.Column(app.db.Boolean, default=False) - expiration_date = sqlalchemy.Column(sqlalchemy.DateTime, default=None) + expiration_date = app.db.Column(app.db.DateTime, default=None) - expiration_pending = sqlalchemy.Column(sqlalchemy.Boolean, default=False) + expiration_pending = app.db.Column(app.db.Boolean, default=False) # used to store scheduled jobs and remove them when they have finished # or need to be removed @@ -295,7 +296,7 @@ def __repr__(self): return '' % self.action def get_parent(self): - return sqlalchemy.session.query(Task).get(self.parent_id) + return app.db.session.query(Task).get(self.parent_id) def to_dict(self, full=False): ''' @@ -326,7 +327,7 @@ def to_dict(self, full=False): } if full: - parent = sqlalchemy.session.query(Task).get(self.parent_id) + parent = app.db.session.query(Task).get(self.parent_id) ret['parent'] = parent.to_dict() else: ret['parent_id'] = self.parent_id From 76d008fdd4f6e49608a84c22123783aa286a49d0 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 18:05:40 +0200 Subject: [PATCH 14/29] Revert "trying to improve config handling" This reverts commit c579d2d744e998054574000fd1b4fa8f5c977ed9. --- examples/synchronized/server_b.py | 13 ++- frestq/api.py | 6 +- frestq/app.py | 71 ++++++------ frestq/frestq_app.py | 4 +- frestq/fscheduler.py | 10 +- frestq/models.py | 90 +++++++-------- frestq/protocol.py | 92 +++++++-------- frestq/tasks.py | 182 +++++++++++++++--------------- frestq/utils.py | 38 +++---- 9 files changed, 252 insertions(+), 254 deletions(-) diff --git a/examples/synchronized/server_b.py b/examples/synchronized/server_b.py index 53c5789..9baaf46 100644 --- a/examples/synchronized/server_b.py +++ b/examples/synchronized/server_b.py @@ -9,6 +9,7 @@ import copy from frestq import decorators +from frestq.app import db from frestq.app import app from frestq.tasks import SimpleTask, ParallelTask, SynchronizedTask from frestq.action_handlers import SynchronizedTaskHandler @@ -51,15 +52,15 @@ def new_reservation(self, subtask): self.task.task_model.reservation_data = dict() self.task.task_model.reservation_data[subtask_id] = reservation_data - app.db.session.add(self.task.task_model) - app.db.session.commit() + db.session.add(self.task.task_model) + db.session.commit() def pre_execute(self): reservation_data = self.task.get_data()["reservation_data"] for subtask in self.task.get_children(): subtask.task_model.input_data["reservation_data"] = reservation_data - app.db.session.add(subtask.task_model) - app.db.session.commit() + db.session.add(subtask.task_model) + db.session.commit() @decorators.task(action="testing.hello_world", queue="hello_world") def hello_world(task): @@ -155,8 +156,8 @@ def all_goodbyes_together(task): ] parent_model = task.get_parent().task_model task.get_parent().task_model.output_data = output_data - app.db.session.add(parent_model) - app.db.session.commit() + db.session.add(parent_model) + db.session.commit() return dict( output_data = output_data diff --git a/frestq/api.py b/frestq/api.py index 7891603..94e46ce 100644 --- a/frestq/api.py +++ b/frestq/api.py @@ -53,7 +53,7 @@ def post_message(queue_name): ''' # 1. register message in the db model - from .app import app + from .app import db from .models import Message logging.debug('RECEIVED MESSAGE in queue %s' % queue_name) @@ -110,8 +110,8 @@ def post_message(queue_name): 'output_status': 200 } msg = Message(**kwargs) - app.db.session.add(msg) - app.db.session.commit() + db.session.add(msg) + db.session.commit() action_handler = ActionHandlers.get_action_handler(msg.action, queue_name) if not action_handler: diff --git a/frestq/app.py b/frestq/app.py index 897ea74..68fa944 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -73,19 +73,13 @@ def __init__(self, *args, **kwargs): pargs = None - db = None - - def init_db(self): - logging.info("initializing app.db instance..") - self.db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) - def configure_app(self, scheduler=True, config_object=None): ''' Configures the application. It's intended to do everything to be able to - run the application except calling app.run, so that it can be reused - when using gunicorn or similar. + run the application except calling app.run, so that it can be reused when + using gunicorn or similar. ''' - self.config.from_object(DefaultConfig()) + self.config.from_object(__name__) if config_object: self.config.from_object(config_object) @@ -112,7 +106,6 @@ def configure_app(self, scheduler=True, config_object=None): return logging.info("Launching with ROOT_URL = %s", self.config['ROOT_URL']) - self.init_db() FScheduler.start_all_schedulers() def parse_args(self, extra_parse_func): @@ -172,7 +165,7 @@ def run(self, *args, **kwargs): if self.pargs is not None: if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - app.db.create_all() + db.create_all() return elif self.pargs.messages: list_messages(self.pargs) @@ -227,41 +220,45 @@ def run(self, *args, **kwargs): ### configuration -class DefaultConfig(object): - # debug, set to false on production deployment - DEBUG = True +# debug, set to false on production deployment +DEBUG = True + +# see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 +SQLALCHEMY_TRACK_MODIFICATIONS = False + +# database configuration +# example: sqlite:////absolute/path/to/db.sqlite +SQLALCHEMY_DATABASE_URI = '' - # see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 - SQLALCHEMY_TRACK_MODIFICATIONS = False +# own certificate, None if there isn't any +SSL_CERT_PATH = None +SSL_KEY_PATH = None - # database configuration - # example: sqlite:////absolute/path/to/db.sqlite - SQLALCHEMY_DATABASE_URI = '' +# queues root url +ROOT_URL = 'http://127.0.0.1:5000/api/queues' - # own certificate, None if there isn't any - SSL_CERT_PATH = None - SSL_KEY_PATH = None +# time a thread can be reserved in for synchronization purposes. In seconds. +RESERVATION_TIMEOUT = 60 - # queues root url - ROOT_URL = 'http://127.0.0.1:5000/api/queues' +app.config.from_object(__name__) - # time a thread can be reserved in for synchronization purposes. In seconds. - RESERVATION_TIMEOUT = 60 +# boostrap our little application +db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) - # set to True to get real security - ALLOW_ONLY_SSL_CONNECTIONS = False +# set to True to get real security +ALLOW_ONLY_SSL_CONNECTIONS = False - # options for each queue. example: - #QUEUES_OPTIONS = { - #'mycustom_queue': { - #'max_threads': 3, - #} +# options for each queue. example: +#QUEUES_OPTIONS = { + #'mycustom_queue': { + #'max_threads': 3, #} - # thread data mapper is a function that would be called when a Synchronous task - # in this queue is going to be executed. It allows to set queue-specific - # settings, and even custom queue settings that can be used by you later. +#} +# thread data mapper is a function that would be called when a Synchronous task +# in this queue is going to be executed. It allows to set queue-specific +# settings, and even custom queue settings that can be used by you later. - QUEUES_OPTIONS = dict() +QUEUES_OPTIONS = dict() from . import models diff --git a/frestq/frestq_app.py b/frestq/frestq_app.py index 6361ee5..9e99b46 100644 --- a/frestq/frestq_app.py +++ b/frestq/frestq_app.py @@ -164,8 +164,8 @@ def parse_args(self): def run_args(self): if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - from .app import app - app.db.create_all() + from .app import db + db.create_all() return elif self.pargs.messages: list_messages(self.pargs) diff --git a/frestq/fscheduler.py b/frestq/fscheduler.py index e4631ad..f44aac8 100644 --- a/frestq/fscheduler.py +++ b/frestq/fscheduler.py @@ -155,7 +155,7 @@ def add_now_job(self, func, args=None, kwargs=None, **options): :type date: :class:`datetime.date` :rtype: :class:`~apscheduler.job.Job` """ - from .app import app + from .app import db logging.info("adding job in sched for queue %s" % self.queue_name) trigger = NowTrigger() @@ -164,11 +164,11 @@ def add_now_job(self, func, args=None, kwargs=None, **options): def autocommit_wrapper(*args, **kwargs2): try: func(*args, **kwargs2) - app.db.session.commit() + db.session.commit() except exc.SQLAlchemyError: import traceback; traceback.print_exc() logging.info("SQLAlchemy exception, doing a rollback for recovery.") - app.db.session.rollback() + db.session.rollback() autocommit_wrapper.__name__ = func.__name__ @@ -180,12 +180,12 @@ def autocommit_wrapper(*args, **kwargs2): **options) def add_date_job(self, func, date, args=None, kwargs=None, **options): - from .app import app + from .app import db # autocommit to avoid dangling sessions def autocommit_wrapper(*args, **kwargs2): func(*args, **kwargs2) - app.db.session.commit() + db.session.commit() autocommit_wrapper.__name__ = func.__name__ diff --git a/frestq/models.py b/frestq/models.py index 0ba7fc7..92ad3ff 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -14,7 +14,7 @@ from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy -from .app import app +from .app import db from .utils import dumps, loads @@ -148,45 +148,45 @@ def remove(self, value): MutationObj.associate_with(JSONEncodedDict) -class Message(app.db.Model): +class Message(db.Model): ''' Represents an election ''' __tablename__ = 'message' - id = app.db.Column(app.db.Unicode(128), primary_key=True) + id = db.Column(db.Unicode(128), primary_key=True) - sender_url = app.db.Column(app.db.Unicode(1024)) + sender_url = db.Column(db.Unicode(1024)) - queue_name = app.db.Column(app.db.Unicode(1024)) + queue_name = db.Column(db.Unicode(1024)) - is_received = app.db.Column(app.db.Boolean) + is_received = db.Column(db.Boolean) - receiver_url = app.db.Column(app.db.Unicode(1024)) + receiver_url = db.Column(db.Unicode(1024)) - sender_ssl_cert = app.db.Column(app.db.UnicodeText) + sender_ssl_cert = db.Column(db.UnicodeText) - receiver_ssl_cert = app.db.Column(app.db.UnicodeText) + receiver_ssl_cert = db.Column(db.UnicodeText) - created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + created_date = db.Column(db.DateTime, default=datetime.utcnow) - action = app.db.Column(app.db.Unicode(1024)) + action = db.Column(db.Unicode(1024)) - input_data = app.db.Column(JSONEncodedDict) + input_data = db.Column(JSONEncodedDict) - output_status = app.db.Column(app.db.Integer) + output_status = db.Column(db.Integer) - pingback_date = app.db.Column(app.db.DateTime, default=None) + pingback_date = db.Column(db.DateTime, default=None) - expiration_date = app.db.Column(app.db.DateTime, default=None) + expiration_date = db.Column(db.DateTime, default=None) - info_text = app.db.Column(app.db.Unicode(2048)) + info_text = db.Column(db.Unicode(2048)) # fixed broken FK bug, when taskid exists in a non local db # task_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) #task = db.relationship('Task', # backref=db.backref('messages', lazy='dynamic')) - task_id = app.db.Column(app.db.Unicode(128)) + task_id = db.Column(db.Unicode(128)) def __init__(self, **kwargs): for key, value in kwargs.items(): @@ -224,65 +224,65 @@ def to_dict(self, full=False): return ret -class Task(app.db.Model): +class Task(db.Model): ''' Represents a task ''' __tablename__ = 'task' - id = app.db.Column(app.db.Unicode(128), primary_key=True) + id = db.Column(db.Unicode(128), primary_key=True) # this can be "simple", "sequential", "parallel", "external" or # "synchronized" - task_type = app.db.Column(app.db.Unicode(1024)) + task_type = db.Column(db.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = app.db.Column(JSONEncodedDict) + task_metadata = db.Column(JSONEncodedDict) - label = app.db.Column(app.db.Unicode(1024)) + label = db.Column(db.Unicode(1024)) - action = app.db.Column(app.db.Unicode(1024)) + action = db.Column(db.Unicode(1024)) - queue_name = app.db.Column(app.db.Unicode(1024)) + queue_name = db.Column(db.Unicode(1024)) - status = app.db.Column(app.db.Unicode(1024)) + status = db.Column(db.Unicode(1024)) - is_received = app.db.Column(app.db.Boolean) + is_received = db.Column(db.Boolean) - is_local = app.db.Column(app.db.Boolean, default=False) + is_local = db.Column(db.Boolean, default=False) - parent_id = app.db.Column(app.db.Unicode(128), app.db.ForeignKey('task.id')) + parent_id = db.Column(db.Unicode(128), db.ForeignKey('task.id')) - subtasks = app.db.relationship("Task", lazy="joined", join_depth=1) + subtasks = db.relationship("Task", lazy="joined", join_depth=1) # used if it's a subtask - order = app.db.Column(app.db.Integer) + order = db.Column(db.Integer) - receiver_url = app.db.Column(app.db.Unicode(1024)) + receiver_url = db.Column(db.Unicode(1024)) - sender_url = app.db.Column(app.db.Unicode(1024)) + sender_url = db.Column(db.Unicode(1024)) - sender_ssl_cert = app.db.Column(app.db.UnicodeText) + sender_ssl_cert = db.Column(db.UnicodeText) - receiver_ssl_cert = app.db.Column(app.db.UnicodeText) + receiver_ssl_cert = db.Column(db.UnicodeText) - created_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + created_date = db.Column(db.DateTime, default=datetime.utcnow) - last_modified_date = app.db.Column(app.db.DateTime, default=datetime.utcnow) + last_modified_date = db.Column(db.DateTime, default=datetime.utcnow) - input_data = app.db.Column(JSONEncodedDict) + input_data = db.Column(JSONEncodedDict) - output_data = app.db.Column(JSONEncodedDict) + output_data = db.Column(JSONEncodedDict) - reservation_data = app.db.Column(JSONEncodedDict) + reservation_data = db.Column(JSONEncodedDict) - pingback_date = app.db.Column(app.db.DateTime, default=None) + pingback_date = db.Column(db.DateTime, default=None) - pingback_pending = app.db.Column(app.db.Boolean, default=False) + pingback_pending = db.Column(db.Boolean, default=False) - expiration_date = app.db.Column(app.db.DateTime, default=None) + expiration_date = db.Column(db.DateTime, default=None) - expiration_pending = app.db.Column(app.db.Boolean, default=False) + expiration_pending = db.Column(db.Boolean, default=False) # used to store scheduled jobs and remove them when they have finished # or need to be removed @@ -296,7 +296,7 @@ def __repr__(self): return '' % self.action def get_parent(self): - return app.db.session.query(Task).get(self.parent_id) + return db.session.query(Task).get(self.parent_id) def to_dict(self, full=False): ''' @@ -327,7 +327,7 @@ def to_dict(self, full=False): } if full: - parent = app.db.session.query(Task).get(self.parent_id) + parent = db.session.query(Task).get(self.parent_id) ret['parent'] = parent.to_dict() else: ret['parent_id'] = self.parent_id diff --git a/frestq/protocol.py b/frestq/protocol.py index d32b16a..e39df3c 100644 --- a/frestq/protocol.py +++ b/frestq/protocol.py @@ -57,13 +57,13 @@ class SecurityException(Exception): @decorators.message_action(action="frestq.update_task", queue=INTERNAL_SCHEDULER_NAME) def update_task(msg): - from .app import app + from .app import db from .models import Task as ModelTask from .tasks import BaseTask # fixed broken FK bug, when taskid exists in a non local db # task = msg.task - task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() logging.debug("UPDATING TASK with id %s" % task.id) if not task or\ @@ -87,8 +87,8 @@ def update_task(msg): dumps(msg.input_data[key]))) setattr(task, key, msg.input_data[key]) task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() # do next (it might be a task with a parent task) receiver_task = BaseTask.instance_by_model(task) @@ -103,12 +103,12 @@ def reserve_task(task_id): task from the receiver's point of view. When synced, this also executes the task. ''' - from .app import app + from .app import db, app from .models import Task as ModelTask from .tasks import BaseTask, send_task_update # 1. get task and check everything is ok - task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() if not task or task.status != 'syncing': # probably means we timedout or maybe there was some kind of error, so # we finish here @@ -123,8 +123,8 @@ def reserve_task(task_id): # 3. send reserved message to sender task.status = 'reserved' task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() ack_reservation(task_id) # 4. set reservation timeout @@ -137,8 +137,8 @@ def reserve_task(task_id): while True: _reserve_condition.wait() # fetch again the task to get the newly obtained status info - app.db.session.commit() - task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + db.session.commit() + task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() # we probably received a _reserve_condition that was sent to another thread # because our task didn't change state, so we ignore the signal @@ -159,18 +159,18 @@ def reserve_task(task_id): task_model = task task_model.status = 'executing' task_model.last_modified_date = datetime.utcnow() - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() task = BaseTask.instance_by_model(task_model) task_output = None try: task_output = task.run_action_handler() - app.db.session.commit() + db.session.commit() except Exception as e: task.error = e task.propagate = True - app.db.session.commit() + db.session.commit() import traceback; traceback.print_exc() if task.action_handler_object: task.action_handler_object.handle_error(e) @@ -182,13 +182,13 @@ def reserve_task(task_id): if task.propagate: task_model.status = "error" task_model.last_modified_date = datetime.utcnow() - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() elif task.auto_finish_after_handler: task_model.status = "finished" task_model.last_modified_date = datetime.utcnow() - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() if task.send_update_to_sender or task.propagate: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) @@ -211,11 +211,11 @@ def cancel_reserved_subtask(task_id): Note: this task is executed in cases where the task can be either local and not local. ''' - from .app import app + from .app import db from .models import Task as ModelTask from .tasks import BaseTask - task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() task_instance = BaseTask.instance_by_model(task) # task action_handler_object might not be present, it's not mandatory, and @@ -229,8 +229,8 @@ def cancel_reserved_subtask(task_id): task.status = "created" task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() with _reserve_condition: _reserve_condition.notify_all() @@ -238,11 +238,11 @@ def ack_reservation(task_id): ''' Sends a confirmation of a task reservation to task sender ''' - from .app import app + from .app import db, app from .models import Task as ModelTask from .tasks import send_message - task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() if not task or task.status != 'reserved': return @@ -270,11 +270,11 @@ def synchronize_task(msg): Receives a task that needs to be synchronized because its parent is a SynchronizedTask ''' - from .app import app + from .app import db, app from .models import Task as ModelTask logging.debug("SYNCING TASK with id %s" % msg.task_id) - task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if task and task.status != 'created': # error, cannot update an already finished task (unless it's an error)! # TODO: send back an error update @@ -300,8 +300,8 @@ def synchronize_task(msg): 'task_type': 'sequential' } task = ModelTask(**kwargs) - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() else: if certs_differ(task.sender_ssl_cert, msg.sender_ssl_cert): raise SecurityException() @@ -311,8 +311,8 @@ def synchronize_task(msg): task.status = 'syncing' task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() sched = FScheduler.get_scheduler(task.queue_name) sched.add_now_job(reserve_task, [task.id]) @@ -329,11 +329,11 @@ def director_confirm_task_reservation(msg): ''' Director of a synchronized task receives a task reservation ''' - from .app import app + from .app import db from .models import Task as ModelTask from .tasks import BaseTask, send_synchronization_message - task = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() task_instance = BaseTask.instance_by_model(task) parent_instance = task_instance.get_parent() @@ -351,8 +351,8 @@ def director_confirm_task_reservation(msg): task.status = 'reserved' task.reservation_data = msg.input_data.get('reservation_data', None) task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() logging.debug("CONFIRMED TASK RESERVATION with id %s" % msg.task_id) @@ -390,11 +390,11 @@ def director_cancel_reserved_subtask(task_id): ''' Cancel a task because a reservation timeout in the director node ''' - from .app import app + from .app import db from .models import Task as ModelTask from .tasks import BaseTask, send_synchronization_message - task = app.db.session.query(ModelTask).filter(ModelTask.id == task_id).first() + task = db.session.query(ModelTask).filter(ModelTask.id == task_id).first() task_instance = BaseTask.instance_by_model(task) parent_instance = task_instance.get_parent() @@ -407,8 +407,8 @@ def director_cancel_reserved_subtask(task_id): task.status = "created" task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() #if all tasks are created, it means all tasks have expired, so we cannot #wait for a confirmation that will launch again all the expired tasks. @@ -427,7 +427,7 @@ def director_synchronized_subtask_start(task_id): ''' Launch the execution of a subtask ''' - from .app import app + from .app import db from .models import Task as ModelTask from .tasks import send_message @@ -456,7 +456,7 @@ def director_synchronized_subtask_start(task_id): @decorators.message_action(action="frestq.execute_synchronized", queue=INTERNAL_SCHEDULER_NAME) def execute_synchronized(msg): - from .app import app + from .app import db from .models import Task as ModelTask from .api import call_action_handler from .fscheduler import FScheduler @@ -475,8 +475,8 @@ def execute_synchronized(msg): task.input_data = msg.input_data['input_data'] task.status = "confirmed" task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() with _reserve_condition: _reserve_condition.notify_all() @@ -486,14 +486,14 @@ def finish_external_task(msg): ''' marks as finished an external task. This will let the task flow continue ''' - from .app import app + from .app import db, app from .models import Task as ModelTask from .api import call_action_handler from .fscheduler import FScheduler from .tasks import BaseTask, post_task, send_task_update # get the task model - task_model = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task_model = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if not task_model or task_model.task_type != "external" or\ task_model.status != 'executing' or\ task_model.sender_url != app.config.get('ROOT_URL'): @@ -507,8 +507,8 @@ def finish_external_task(msg): task = BaseTask.instance_by_model(task_model) task_model.output_data = msg.input_data task_model.status = "finished" - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() if task.send_update_to_sender: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) diff --git a/frestq/tasks.py b/frestq/tasks.py index 05a05d7..1fc262f 100644 --- a/frestq/tasks.py +++ b/frestq/tasks.py @@ -18,7 +18,7 @@ from flask import request -from .app import app +from .app import db, app from .fscheduler import FScheduler, INTERNAL_SCHEDULER_NAME from .models import Task as ModelTask, Message as ModelMessage from .utils import dumps @@ -96,8 +96,8 @@ def send(self): # update db self.task_model.status = "sent" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() send_message(msg_data, update_task_receiver_ssl_cert=True, task=self.task_model) @@ -153,7 +153,7 @@ def get_children(self): ''' Returns the ordered list of children of this task if any ''' - subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, + subtasks = db.session.query(ModelTask).with_parent(self.task_model, "subtasks").order_by(ModelTask.order) children_tasks = [self.instance_by_model(task) @@ -164,7 +164,7 @@ def get_child(self, label): ''' Gets a children by label ''' - task = app.db.session.query(ModelTask).with_parent(self.task_model, + task = db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.label == label).first() if not task: return None @@ -178,7 +178,7 @@ def get_parent(self): if not self.task_model.parent_id: return None - parent = app.db.session.query(ModelTask).get(self.task_model.parent_id) + parent = db.session.query(ModelTask).get(self.task_model.parent_id) parent_task = self.instance_by_model(parent) return parent_task @@ -189,7 +189,7 @@ def get_siblings(self): if not self.task_model.parent_id: return [] - siblings = app.db.session.query(ModelTask)\ + siblings = db.session.query(ModelTask)\ .filter(ModelTask.id == self.task_model.parent_id, ModelTask.id != self.task_model.id)\ .order_by(ModelTask.order) @@ -201,7 +201,7 @@ def get_sibling(self, label): ''' Gets a children by label ''' - task = app.db.session.query(ModelTask).filter( + task = db.session.query(ModelTask).filter( ModelTask.id == self.task_model.parent_id, ModelTask.id != self.task_model.id, ModelTask.label == label).first() @@ -216,7 +216,7 @@ def get_prev(self): if self.task_model.order == 0 or not self.task_model.parent_id: return None - task = app.db.session.query(ModelTask)\ + task = db.session.query(ModelTask)\ .filter(ModelTask.parent_id == self.task_model.parent_id, ModelTask.order == self.task_model.order - 1).first() if not task: @@ -230,7 +230,7 @@ def get_next(self): if not self.task_model.parent_id: return None - task = app.db.session.query(ModelTask)\ + task = db.session.query(ModelTask)\ .filter(ModelTask.parent_id == self.task_model.parent_id, ModelTask.order == self.task_model.order + 1).first() if not task: @@ -298,7 +298,7 @@ def execute_parent(self): if not self.task_model.parent_id: return - parent = app.db.session.query(ModelTask).get(self.task_model.parent_id) + parent = db.session.query(ModelTask).get(self.task_model.parent_id) parent_task = BaseTask.instance_by_model(parent) parent_task.execute() @@ -393,8 +393,8 @@ def create(self): 'parent_id': None } self.task_model = ModelTask(**kwargs) - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() return self.task_model def execute(self): @@ -486,8 +486,8 @@ def create(self): 'parent_id': None } self.task_model = ModelTask(**kwargs) - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() return self.task_model def finish(self, data=None): @@ -511,8 +511,8 @@ def execute(self): ''' if self.task_model.status in ['created', 'sent']: self.task_model.status = "executing" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() elif self.task_model.status == "finished": self.execute_parent() @@ -561,15 +561,15 @@ def add(self, subtask): model = subtask.create() model.order = self._count_subtasks() model.parent_id = self.task_model.id - app.db.session.add(model) - app.db.session.commit() + db.session.add(model) + db.session.commit() def _count_subtasks(self): ''' Internal. Count the number of subtasks. Only meant to be executed after the sequential task has been created in the database. ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").count() def create(self): @@ -600,25 +600,25 @@ def create(self): } self.task_model = ModelTask(**kwargs) # fix broken FK bug, when child added to parent that does not yet exist - app.db.session.add(self.task_model) + db.session.add(self.task_model) # create also subtasks i = 0 for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.order = i subtask_model.parent_id = self.task_model.id - app.db.session.add(subtask_model) + db.session.add(subtask_model) i += 1 # fix broken FK bug, when child added to parent that does not yet exist # db.session.add(self.task_model) - app.db.session.commit() + db.session.commit() return self.task_model def next_subtask(self): ''' Returns next subtask if any or None ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def execute(self): @@ -628,8 +628,8 @@ def execute(self): ''' if self.task_model.status in ['created', 'sent']: self.task_model.status = "executing" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() if self.task_model.status in ['finished', 'error']: return @@ -641,8 +641,8 @@ def execute(self): # sender if it's not local if not next_subtask_model: self.task_model.status = "finished" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() # update the sender if any if not self.task_model.is_local: @@ -665,8 +665,8 @@ def execute(self): if self.propagate: self.task_model.status = "finished" if not self.propagate else "error" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() if not self.task_model.is_local: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) @@ -693,7 +693,7 @@ def execute_task(task_id): ''' Used to execute a task asynchronously ''' - task_model = app.db.session.query(ModelTask).get(task_id) + task_model = db.session.query(ModelTask).get(task_id) task = BaseTask.instance_by_model(task_model) task.execute() @@ -732,8 +732,8 @@ def add(self, subtask): model = subtask.create() model.parent_id = self.task_model.id - app.db.session.add(model) - app.db.session.commit() + db.session.add(model) + db.session.commit() def create(self): ''' @@ -764,38 +764,38 @@ def create(self): } self.task_model = ModelTask(**kwargs) # fix broken FK bug, when child added to parent that does not yet exist - app.db.session.add(self.task_model) + db.session.add(self.task_model) # create also subtasks i = 0 for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.parent_id = self.task_model.id - app.db.session.add(subtask_model) + db.session.add(subtask_model) i += 1 # fix broken FK bug, when child added to parent that does not yet exist # db.session.add(self.task_model) - app.db.session.commit() + db.session.commit() return self.task_model def count_unfinished_subtasks(self): ''' Count the number of subtasks ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status != 'finished').count() def next_subtask(self): ''' Returns next subtask if any or None ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def errored_tasks(self): ''' Count the number of subtasks with error ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status == 'error') def execute(self): @@ -809,11 +809,11 @@ def execute(self): try: self._db_lock.acquire() - app.db.session.commit() + db.session.commit() # paranoid mode, do not trust old tasks. check if task finished - app.db.session.expire(self.task_model) - app.db.session.refresh(self.task_model) + db.session.expire(self.task_model) + db.session.refresh(self.task_model) # should have been already dealt with if self.task_model.status == 'finished': return @@ -826,8 +826,8 @@ def execute(self): if errored_tasks.count() > 0: self.error = SubTasksFailed(errored_tasks) self.task_model.status = "error" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # propagate @@ -840,12 +840,12 @@ def execute(self): if self.task_model.status in ['created', 'sent'] and num_unfinished_subtasks > 0: # mark as executing this task self.task_model.status = "executing" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # start subtasks in parallel - subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks") + subtasks = db.session.query(ModelTask).with_parent(self.task_model, "subtasks") for subtask in subtasks: sched = FScheduler.get_scheduler(subtask.queue_name) sched.add_now_job(execute_task, [subtask.id]) @@ -857,8 +857,8 @@ def execute(self): if num_unfinished_subtasks == 0: # mark as finished self.task_model.status = "finished" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # check if there's a parent task, and if so execute() it @@ -895,8 +895,8 @@ def send_synchronization_message(task_id): } send_message(msg) task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() @@ -949,8 +949,8 @@ def add(self, subtask): model = subtask.create() model.parent_id = self.task_model.id - app.db.session.add(model) - app.db.session.commit() + db.session.add(model) + db.session.commit() def create(self): ''' @@ -991,31 +991,31 @@ def create(self): for subtask in self._subtasks: subtask_model = subtask.create() subtask_model.parent_id = self.task_model.id - app.db.session.add(subtask_model) + db.session.add(subtask_model) i += 1 - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() return self.task_model def count_unfinished_subtasks(self): ''' Count the number of subtasks ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status != 'finished').count() def next_subtask(self): ''' Returns next subtask if any or None ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").\ filter(ModelTask.status != 'finished').order_by(ModelTask.order).first() def errored_tasks(self): ''' Count the number of subtasks with error ''' - return app.db.session.query(ModelTask).with_parent(self.task_model, + return db.session.query(ModelTask).with_parent(self.task_model, "subtasks").filter(ModelTask.status == 'error') def execute(self): @@ -1028,11 +1028,11 @@ def execute(self): try: self._db_lock.acquire() - app.db.session.commit() + db.session.commit() # paranoid mode, do not trust old tasks. check if task finished - app.db.session.expire(self.task_model) - app.db.session.refresh(self.task_model) + db.session.expire(self.task_model) + db.session.refresh(self.task_model) # should have been already dealt with if self.task_model.status == 'finished': return @@ -1046,8 +1046,8 @@ def execute(self): if errored_tasks.count() > 0: self.error = SubTasksFailed(errored_tasks) self.task_model.status = "error" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # propagate @@ -1073,12 +1073,12 @@ def execute(self): def _synchronize(self): # mark as executing this task self.task_model.status = "executing" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # send initial synchronization message to subtasks - subtasks = app.db.session.query(ModelTask).with_parent(self.task_model, "subtasks") + subtasks = db.session.query(ModelTask).with_parent(self.task_model, "subtasks") for subtask in subtasks: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) sched.add_now_job(send_synchronization_message, [subtask.id]) @@ -1089,8 +1089,8 @@ def _finish(self): ''' # mark as finished self.task_model.status = "finished" - app.db.session.add(self.task_model) - app.db.session.commit() + db.session.add(self.task_model) + db.session.commit() self._db_lock.release() # check if there's a parent task, and if so execute() it @@ -1141,8 +1141,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): # msg is saved before sending the message so that it's registered (it might # get even be retrieved from DB by api.py:post_message() if it's a local # message, but it's also updated when the msg is sent to update output - app.db.session.add(msg) - app.db.session.commit() + db.session.add(msg) + db.session.commit() session = requests.sessions.Session() @@ -1168,8 +1168,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): .decode('utf-8') if update_task_receiver_ssl_cert and task: task.receiver_ssl_cert = msg.receiver_ssl_cert - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() except Exception as e: pass @@ -1183,8 +1183,8 @@ def send_message(msg_data, update_task_receiver_ssl_cert=False, task=None): if r.status_code >= 400: print("!!! ERROR request to url = '%s' and status = '%d' answered:\n%s" % (url, r.status_code, r.text)) - app.db.session.add(msg) - app.db.session.commit() + db.session.add(msg) + db.session.commit() class TaskError(Exception): @@ -1231,12 +1231,12 @@ def send_task_update(task_id): logging.debug("update_msg.inputdata: %s" % dumps(update_msg["input_data"])) send_message(update_msg) task.last_modified_date = datetime.utcnow() - app.db.session.add(task) - app.db.session.commit() + db.session.add(task) + db.session.commit() # task finished. check if there's a parent task, and if so execute() it if task.parent_id: - parent = app.db.session.query(ModelTask).get(task.parent_id) + parent = db.session.query(ModelTask).get(task.parent_id) parent_task = BaseTask.instance_by_model(parent) parent_task.execute() @@ -1258,8 +1258,8 @@ def update_task(task, task_output): task.task_model.output_data = task_output['output_status'] if commit: - app.db.session.add(task.task_model) - app.db.session.commit() + db.session.add(task.task_model) + db.session.commit() def post_task(msg, action_handler): ''' @@ -1290,35 +1290,35 @@ def post_task(msg, action_handler): if not kwargs['id']: msg.task_id = kwargs['id'] = str(uuid4()) - app.db.session.add(msg) - app.db.session.commit() + db.session.add(msg) + db.session.commit() # if msg originated from ourselves, it might already exist. or if it's a # subtask of a synchronized task - task_model = app.db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() + task_model = db.session.query(ModelTask).filter(ModelTask.id == msg.task_id).first() if task_model: if task_model.task_type == 'simple': # this could happen if the task was created with SimpleTask task_model.task_type = 'sequential' - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() else: task_model = ModelTask(**kwargs) - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() # 2. call to the handler task = BaseTask.instance_by_model(task_model) task_output = None try: task_output = task.run_action_handler() - app.db.session.commit() + db.session.commit() except Exception as e: import traceback; traceback.print_exc() task.error = e task.propagate = True - app.db.session.commit() + db.session.commit() if task.action_handler_object: try: task.action_handler_object.handle_error(e) @@ -1332,8 +1332,8 @@ def post_task(msg, action_handler): # 3. update asynchronously the task sender if requested if task.auto_finish_after_handler or task.propagate: task_model.status = "finished" if not task.propagate else "error" - app.db.session.add(task_model) - app.db.session.commit() + db.session.add(task_model) + db.session.commit() if task.send_update_to_sender: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) diff --git a/frestq/utils.py b/frestq/utils.py index 53894e1..6d12258 100644 --- a/frestq/utils.py +++ b/frestq/utils.py @@ -56,7 +56,7 @@ def loads(obj, **kwargs): # drb def get_tasks(args): - from .app import app + from .app import db from .models import Task filters=[] @@ -65,9 +65,9 @@ def get_tasks(args): filters.append(getattr(Task, key).__eq__(value)) if filters: - tasks = app.db.session.query(Task).filter(*filters) + tasks = db.session.query(Task).filter(*filters) else: - tasks = app.db.session.query(Task) + tasks = db.session.query(Task) tasks = tasks.order_by(Task.created_date.desc()).limit(args.limit) return tasks @@ -93,7 +93,7 @@ def list_messages(args): ''' Prints the list of messages ''' - from .app import app + from .app import db from .models import Message filters=[] @@ -102,9 +102,9 @@ def list_messages(args): filters.append(getattr(Message, key).__eq__(value)) if filters: - msgs = app.db.session.query(Message).filter(*filters) + msgs = db.session.query(Message).filter(*filters) else: - msgs = app.db.session.query(Message) + msgs = db.session.query(Message) msgs = msgs.order_by(Message.created_date.desc()).limit(args.limit) table = PrettyTable(['small id', 'sender_url', 'action', 'queue', 'created_date', 'input_data']) @@ -142,10 +142,10 @@ def print_task(task, base_task_id=None, level=0, mode="full"): def traverse_tasktree(task, visitor_func, visitor_kwargs): visitor_func(task, **visitor_kwargs) - from .app import app + from .app import db from .models import Task - subtasks = app.db.session.query(Task)\ + subtasks = db.session.query(Task)\ .with_parent(task, "subtasks")\ .order_by(Task.order) for subtask in subtasks: @@ -155,10 +155,10 @@ def traverse_tasktree(task, visitor_func, visitor_kwargs): traverse_tasktree(subtask, visitor_func, vargs) def show_task(args): - from .app import app + from .app import db from .models import Task task_id = unicode(args.show_task) - task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) return @@ -258,10 +258,10 @@ def show_activity(args): def show_message(args): - from .app import app + from .app import db from .models import Message msg_id = unicode(args.show_message) - msg_model = app.db.session.query(Message).filter(Message.id.startswith(msg_id)).all() + msg_model = db.session.query(Message).filter(Message.id.startswith(msg_id)).all() if not msg_model: print("message %s not found" % msg_id) return @@ -270,11 +270,11 @@ def show_message(args): # drb def get_external_task(args): - from .app import app + from .app import db from .models import Task task_id = unicode(args.show_external) - task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() return task_model @@ -298,7 +298,7 @@ def show_external_task(args): print("info_text:\n%s" % task_model.input_data) def finish_task(args): - from .app import app + from .app import db from .models import Task from .tasks import ExternalTask @@ -309,7 +309,7 @@ def finish_task(args): print("error loading the json finish data") return - task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) @@ -329,10 +329,10 @@ def deny_task(args): pass def task_tree(args): - from .app import app + from .app import db from .models import Task task_id = unicode(args.tree) - task_model = app.db.session.query(Task).filter(Task.id.startswith(task_id)).all() + task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) return @@ -340,7 +340,7 @@ def task_tree(args): if args.with_parents: while task_model.parent_id: try: - task_model = app.db.session.query(Task).get(task_model.parent_id) + task_model = db.session.query(Task).get(task_model.parent_id) except: print("task %s, which is the parent of %s not found" % ( str(task.parent_id)[:8], From b7c45006bbaa6fedf31293698b96b3e967af0512 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 18:18:56 +0200 Subject: [PATCH 15/29] WIP --- frestq/app.py | 74 +++++++++++++++------------- poetry.lock | 130 +++++++++++++++++++++---------------------------- pyproject.toml | 3 +- 3 files changed, 97 insertions(+), 110 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index 68fa944..fa6bf3f 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -20,6 +20,9 @@ logging.basicConfig(level=logging.DEBUG) +# boostrap our little application +db = SQLAlchemy(engine_options={"pool_pre_ping": True}) + class FrestqRequest(Request): ''' We have to customize request so that by default it can overload the json @@ -73,13 +76,17 @@ def __init__(self, *args, **kwargs): pargs = None + def init_db(self): + logging.info("initializing app.db instance..") + db.init_app(self) + def configure_app(self, scheduler=True, config_object=None): ''' Configures the application. It's intended to do everything to be able to - run the application except calling app.run, so that it can be reused when - using gunicorn or similar. + run the application except calling app.run, so that it can be reused + when using gunicorn or similar. ''' - self.config.from_object(__name__) + self.config.from_object(DefaultConfig()) if config_object: self.config.from_object(config_object) @@ -106,6 +113,7 @@ def configure_app(self, scheduler=True, config_object=None): return logging.info("Launching with ROOT_URL = %s", self.config['ROOT_URL']) + self.init_db() FScheduler.start_all_schedulers() def parse_args(self, extra_parse_func): @@ -165,7 +173,7 @@ def run(self, *args, **kwargs): if self.pargs is not None: if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - db.create_all() + app.db.create_all() return elif self.pargs.messages: list_messages(self.pargs) @@ -220,45 +228,41 @@ def run(self, *args, **kwargs): ### configuration -# debug, set to false on production deployment -DEBUG = True +class DefaultConfig(object): + # debug, set to false on production deployment + DEBUG = True -# see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 -SQLALCHEMY_TRACK_MODIFICATIONS = False + # see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 + SQLALCHEMY_TRACK_MODIFICATIONS = False -# database configuration -# example: sqlite:////absolute/path/to/db.sqlite -SQLALCHEMY_DATABASE_URI = '' + # database configuration + # example: sqlite:////absolute/path/to/db.sqlite + SQLALCHEMY_DATABASE_URI = '' -# own certificate, None if there isn't any -SSL_CERT_PATH = None -SSL_KEY_PATH = None + # own certificate, None if there isn't any + SSL_CERT_PATH = None + SSL_KEY_PATH = None -# queues root url -ROOT_URL = 'http://127.0.0.1:5000/api/queues' + # queues root url + ROOT_URL = 'http://127.0.0.1:5000/api/queues' -# time a thread can be reserved in for synchronization purposes. In seconds. -RESERVATION_TIMEOUT = 60 - -app.config.from_object(__name__) - -# boostrap our little application -db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) + # time a thread can be reserved in for synchronization purposes. In seconds. + RESERVATION_TIMEOUT = 60 -# set to True to get real security -ALLOW_ONLY_SSL_CONNECTIONS = False + # set to True to get real security + ALLOW_ONLY_SSL_CONNECTIONS = False -# options for each queue. example: -#QUEUES_OPTIONS = { - #'mycustom_queue': { - #'max_threads': 3, + # options for each queue. example: + #QUEUES_OPTIONS = { + #'mycustom_queue': { + #'max_threads': 3, + #} #} -#} -# thread data mapper is a function that would be called when a Synchronous task -# in this queue is going to be executed. It allows to set queue-specific -# settings, and even custom queue settings that can be used by you later. + # thread data mapper is a function that would be called when a Synchronous task + # in this queue is going to be executed. It allows to set queue-specific + # settings, and even custom queue settings that can be used by you later. -QUEUES_OPTIONS = dict() + QUEUES_OPTIONS = dict() from . import models @@ -271,4 +275,4 @@ def run(self, *args, **kwargs): show_activity) if __name__ == "__main__": - app.run() + app.run() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 105a303..eb4930d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -307,18 +307,18 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-sqlalchemy" -version = "3.0.5" -description = "Add SQLAlchemy support to your Flask application." +version = "2.5.1" +description = "Adds SQLAlchemy support to your Flask application." optional = false -python-versions = ">=3.7" +python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*" files = [ - {file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"}, - {file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"}, + {file = "Flask-SQLAlchemy-2.5.1.tar.gz", hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912"}, + {file = "Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl", hash = "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390"}, ] [package.dependencies] -flask = ">=2.2.5" -sqlalchemy = ">=1.4.18" +Flask = ">=0.10" +SQLAlchemy = ">=0.8.0" [[package]] name = "greenlet" @@ -628,93 +628,75 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.19" +version = "1.4.49" description = "Database Abstraction Library" optional = false -python-versions = ">=3.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"}, - {file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"}, - {file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, + {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} -typing-extensions = ">=4.2.0" +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"} [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] +oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] +pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3-binary"] -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, -] - [[package]] name = "tzlocal" version = "2.1" @@ -792,4 +774,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "6245f53fdfe1439dec85ee6c91212d118fa52c14e0cd0fc960393c190c515337" +content-hash = "0191646ba7c237dee90d7503b8060c159c7b90d3c3d30f09aba40d6d02bdf046" diff --git a/pyproject.toml b/pyproject.toml index c95cfbb..9035449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,11 +15,12 @@ python = "^3.8" apscheduler = "3.7.0" requests = "^2.31" Flask = "^2.3" -flask-sqlalchemy = "^3.0" +flask-sqlalchemy = "^2.5" cryptography = "^41.0" pyOpenSSL = "23.2.0" ipaddress = "^1.0" prettytable = "^3.8.0" +sqlalchemy = "^1.4" [build-system] requires = ["poetry-core"] From e2e9d746f0215aa8289a49a9e412725977e71e95 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 18:27:15 +0200 Subject: [PATCH 16/29] WIP --- frestq/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frestq/app.py b/frestq/app.py index fa6bf3f..4aa3e05 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -68,6 +68,7 @@ def get_json(self, force=False, silent=False, cache=True): return rv + class FrestqApp(Flask): def __init__(self, *args, **kwargs): super(FrestqApp, self).__init__(*args, **kwargs) @@ -224,7 +225,6 @@ def run(self, *args, **kwargs): return super(FrestqApp, self)\ .run(threaded=True, use_reloader=False, *args, **kwargs) -app = FrestqApp(__name__) ### configuration @@ -264,6 +264,10 @@ class DefaultConfig(object): QUEUES_OPTIONS = dict() + +app = FrestqApp(__name__) +app.config.from_object(DefaultConfig()) + from . import models from .api import api From 99dab67e8f840c1c403741ccd57083dd6d929613 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 20:39:50 +0200 Subject: [PATCH 17/29] WIP --- frestq/app.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index 4aa3e05..a40b747 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -87,34 +87,25 @@ def configure_app(self, scheduler=True, config_object=None): run the application except calling app.run, so that it can be reused when using gunicorn or similar. ''' - self.config.from_object(DefaultConfig()) if config_object: self.config.from_object(config_object) - frestq_settings = os.environ.get('FRESTQ_SETTINGS', None) - if frestq_settings is not None: - if not os.path.isabs(frestq_settings): - os.environ['FRESTQ_SETTINGS'] = os.path.abspath(frestq_settings) - logging.debug("FRESTQ_SETTINGS = %s" % os.environ['FRESTQ_SETTINGS']) - self.config.from_envvar('FRESTQ_SETTINGS', silent=False) - else: - logging.warning("FRESTQ_SETTINGS not set") - # store cert in - if self.config.get('SSL_CERT_PATH', None) and\ - self.config.get('SSL_KEY_PATH', None): - + if ( + self.config.get('SSL_CERT_PATH', None) and + self.config.get('SSL_KEY_PATH', None) + ): with open(self.config.get('SSL_CERT_PATH', ''), 'r') as f: self.config['SSL_CERT_STRING'] = f.read() else: self.config['SSL_CERT_STRING'] = '' logging.warning("You are NOT using SSL in this instance") + self.init_db() if not scheduler: return logging.info("Launching with ROOT_URL = %s", self.config['ROOT_URL']) - self.init_db() FScheduler.start_all_schedulers() def parse_args(self, extra_parse_func): From 7c29255ccf945f32d9d3fa023ace3f69271ce958 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 20:42:21 +0200 Subject: [PATCH 18/29] WIP --- frestq/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index a40b747..aaa2d26 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -78,7 +78,7 @@ def __init__(self, *args, **kwargs): pargs = None def init_db(self): - logging.info("initializing app.db instance..") + logging.info("initializing db instance..") db.init_app(self) def configure_app(self, scheduler=True, config_object=None): @@ -165,7 +165,7 @@ def run(self, *args, **kwargs): if self.pargs is not None: if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) - app.db.create_all() + db.create_all() return elif self.pargs.messages: list_messages(self.pargs) From b5c942ea684da4b1feee0a7606216742b8e22198 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 20:49:43 +0200 Subject: [PATCH 19/29] WIP --- frestq/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frestq/app.py b/frestq/app.py index aaa2d26..1418210 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -165,6 +165,7 @@ def run(self, *args, **kwargs): if self.pargs is not None: if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) + self.init_db() db.create_all() return elif self.pargs.messages: From 2a588c17b142d8c514902805cf52caf2926dcb54 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Wed, 26 Jul 2023 20:54:47 +0200 Subject: [PATCH 20/29] WIP --- frestq/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frestq/app.py b/frestq/app.py index 1418210..80e4242 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -166,7 +166,7 @@ def run(self, *args, **kwargs): if self.pargs.createdb: print("creating the database: " + self.config.get('SQLALCHEMY_DATABASE_URI', '')) self.init_db() - db.create_all() + db.create_all(app=self) return elif self.pargs.messages: list_messages(self.pargs) From 0d58ee349fb18c9abb2842e0aaf6b6b6929de996 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Thu, 27 Jul 2023 05:36:28 +0200 Subject: [PATCH 21/29] remove old build systems --- requirements.txt | 24 ------------------------ setup.py | 46 ---------------------------------------------- 2 files changed, 70 deletions(-) delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index dd7e6f5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-FileCopyrightText: 2014-2021 Sequent Tech Inc -# -# SPDX-License-Identifier: AGPL-3.0-only - -apscheduler==3.7.0 -requests==2.31.0 -Flask==2.3.2 -Flask-SQLAlchemy==2.5.1 -Jinja2==3.1.2 -MarkupSafe==2.1.1 -SQLAlchemy==1.3.23 -Werkzeug==2.3.3 -argparse==1.2.1 -cffi==1.14.4 -ipdb==0.13.9 -ipython==8.10.0 -itsdangerous==2.1.2 -prettytable==0.7.2 -pycparser==2.10 -uwsgi==2.0.21 -cryptography==41.0.0 -pyOpenSSL==23.2.0 -enum34==1.1.6 -ipaddress==1.0.22 diff --git a/setup.py b/setup.py deleted file mode 100644 index 9ec6158..0000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (C) 2014-2021 Sequent Tech Inc -# -# SPDX-License-Identifier: AGPL-3.0-only - -from setuptools import setup - -setup( - name='frestq', - version='4.0.0', - author='Sequent Tech Inc', - author_email='legal@sequentech.io', - packages=['frestq'], - scripts=[], - url='http://pypi.python.org/pypi/frestq/', - license='AGPL-3.0', - description='simple federated rest task queue', - long_description=open('README.md').read(), - install_requires=[ - 'apscheduler==3.7.0', - 'requests==2.31.0', - 'Flask==2.3.2', - 'Flask-SQLAlchemy==2.5.1', - 'Jinja2==3.1.2', - 'MarkupSafe==2.1.1', - 'SQLAlchemy==1.3.23', - 'Werkzeug==2.3.3', - 'argparse==1.2.1', - 'cffi==1.14.4', - 'ipdb==0.13.9', - 'ipython==8.10.0', - 'itsdangerous==2.1.2', - 'prettytable==0.7.2', - 'pycparser==2.10', - 'uwsgi==2.0.21', - 'cryptography==41.0.0', - 'pyOpenSSL==23.2.0', - 'enum34==1.1.6', - 'ipaddress==1.0.22', - ], - classifiers=[ - "Programming Language :: Python :: 3", - "OSI Approved :: GNU Affero General Public License v3" - ], - python_requires='>=3.5', - dependency_links = [] -) From 6afbf4209acb85ee7d513ffff95f6ed1bcf27abe Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 04:12:16 +0200 Subject: [PATCH 22/29] configurable ssl header name --- frestq/api.py | 3 ++- frestq/app.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frestq/api.py b/frestq/api.py index 94e46ce..4379b9c 100644 --- a/frestq/api.py +++ b/frestq/api.py @@ -72,7 +72,8 @@ def post_message(queue_name): req['isinstance']): return error(400, "invalid/notfound %s parameter" % req['name']) - sender_ssl_cert = request.environ.get('X-Sender-SSL-Certificate', None) + header_name = current_app.config['SSL_HEADER_NAME'] + sender_ssl_cert = request.environ.get(header_name, None) # NOTE: nginx adds \t to the certificate because otherwise it would be not # possible to send it as a proxy header, so we have to remove those tabs. # A PEM certificate does never contain tabs, so this replace is safe anyway. diff --git a/frestq/app.py b/frestq/app.py index 80e4242..275034a 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -235,6 +235,10 @@ class DefaultConfig(object): SSL_CERT_PATH = None SSL_KEY_PATH = None + # with uwsgi this would be "X-Sender-SSL-Certificate" and in unicorn it's + # "HTTP_X_SENDER_SSL_CERTIFICATE" + SSL_HEADER_NAME = "X-Sender-SSL-Certificate" + # queues root url ROOT_URL = 'http://127.0.0.1:5000/api/queues' From ed94a4393b015e6b2fbbfbaf4139280a72002cbb Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 05:03:32 +0200 Subject: [PATCH 23/29] WIP --- frestq/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index 275034a..f40bff2 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -20,8 +20,6 @@ logging.basicConfig(level=logging.DEBUG) -# boostrap our little application -db = SQLAlchemy(engine_options={"pool_pre_ping": True}) class FrestqRequest(Request): ''' @@ -264,6 +262,9 @@ class DefaultConfig(object): app = FrestqApp(__name__) app.config.from_object(DefaultConfig()) +# boostrap our little application +db = SQLAlchemy(app, engine_options={"pool_pre_ping": True}) + from . import models from .api import api From 88285677cb054801fae1d713ee98b716aba0361f Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 05:23:32 +0200 Subject: [PATCH 24/29] WIP --- frestq/models.py | 145 +++-------------------------------------------- poetry.lock | 16 +++++- pyproject.toml | 1 + 3 files changed, 24 insertions(+), 138 deletions(-) diff --git a/frestq/models.py b/frestq/models.py index 92ad3ff..58c0112 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -9,145 +9,16 @@ from datetime import datetime import sqlalchemy -from sqlalchemy.ext.mutable import Mutable -from sqlalchemy.types import TypeDecorator, UnicodeText +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy_json import mutable_json_type from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy + from .app import db from .utils import dumps, loads -class JSONEncodedDict(TypeDecorator): - impl = UnicodeText - - def process_bind_param(self, value, dialect): - if value is None: - return None - return dumps(value) - - def process_result_value(self, value, dialect): - if not value: - return None - return loads(value) - - -class MutationObj(Mutable): - @classmethod - def coerce(cls, key, value): - if isinstance(value, dict) and not isinstance(value, MutationDict): - return MutationDict.coerce(key, value) - if isinstance(value, list) and not isinstance(value, MutationList): - return MutationList.coerce(key, value) - return value - - @classmethod - def _listen_on_attribute(cls, attribute, coerce, parent_cls): - key = attribute.key - if parent_cls is not attribute.class_: - return - - # rely on "propagate" here - parent_cls = attribute.class_ - - def load(state, *args): - val = state.dict.get(key, None) - if coerce: - val = cls.coerce(key, val) - state.dict[key] = val - if isinstance(val, cls): - val._parents[state.obj()] = key - - def set(target, value, oldvalue, initiator): - if not isinstance(value, cls): - value = cls.coerce(key, value) - if isinstance(value, cls): - value._parents[target.obj()] = key - if isinstance(oldvalue, cls): - oldvalue._parents.pop(target.obj(), None) - return value - - def pickle(state, state_dict): - val = state.dict.get(key, None) - if isinstance(val, cls): - if 'ext.mutable.values' not in state_dict: - state_dict['ext.mutable.values'] = [] - state_dict['ext.mutable.values'].append(val) - - def unpickle(state, state_dict): - if 'ext.mutable.values' in state_dict: - for val in state_dict['ext.mutable.values']: - val._parents[state.obj()] = key - - sqlalchemy.event.listen(parent_cls, 'load', load, raw=True, propagate=True) - sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True, propagate=True) - sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True, propagate=True) - sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True, propagate=True) - sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True, propagate=True) - -class MutationDict(MutationObj, dict): - @classmethod - def coerce(cls, key, value): - """Convert plain dictionary to MutationDict""" - self = MutationDict((k,MutationObj.coerce(key,v)) for (k,v) in value.items()) - self._key = key - return self - - def __setitem__(self, key, value): - dict.__setitem__(self, key, MutationObj.coerce(self._key, value)) - self.changed() - - def __delitem__(self, key): - dict.__delitem__(self, key) - self.changed() - -class MutationList(MutationObj, list): - @classmethod - def coerce(cls, key, value): - """Convert plain list to MutationList""" - self = MutationList((MutationObj.coerce(key, v) for v in value)) - self._key = key - return self - - def __setitem__(self, idx, value): - list.__setitem__(self, idx, MutationObj.coerce(self._key, value)) - self.changed() - - def __setslice__(self, start, stop, values): - list.__setslice__(self, start, stop, (MutationObj.coerce(self._key, v) for v in values)) - self.changed() - - def __delitem__(self, idx): - list.__delitem__(self, idx) - self.changed() - - def __delslice__(self, start, stop): - list.__delslice__(self, start, stop) - self.changed() - - def append(self, value): - list.append(self, MutationObj.coerce(self._key, value)) - self.changed() - - def insert(self, idx, value): - list.insert(self, idx, MutationObj.coerce(self._key, value)) - self.changed() - - def extend(self, values): - list.extend(self, (MutationObj.coerce(self._key, v) for v in values)) - self.changed() - - def pop(self, *args, **kw): - value = list.pop(self, *args, **kw) - self.changed() - return value - - def remove(self, value): - list.remove(self, value) - self.changed() - -MutationObj.associate_with(JSONEncodedDict) - class Message(db.Model): ''' Represents an election @@ -172,7 +43,7 @@ class Message(db.Model): action = db.Column(db.Unicode(1024)) - input_data = db.Column(JSONEncodedDict) + input_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) output_status = db.Column(db.Integer) @@ -237,7 +108,7 @@ class Task(db.Model): task_type = db.Column(db.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = db.Column(JSONEncodedDict) + task_metadata = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) label = db.Column(db.Unicode(1024)) @@ -270,11 +141,11 @@ class Task(db.Model): last_modified_date = db.Column(db.DateTime, default=datetime.utcnow) - input_data = db.Column(JSONEncodedDict) + input_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) - output_data = db.Column(JSONEncodedDict) + output_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) - reservation_data = db.Column(JSONEncodedDict) + reservation_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) pingback_date = db.Column(db.DateTime, default=None) diff --git a/poetry.lock b/poetry.lock index eb4930d..f8d6e3a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -697,6 +697,20 @@ postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3-binary"] +[[package]] +name = "sqlalchemy-json" +version = "0.6.0" +description = "JSON type with nested change tracking for SQLAlchemy" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "sqlalchemy-json-0.6.0.tar.gz", hash = "sha256:94785325716f0649ec6f4d0304ce8803a5bf25ff8aa2d998ce6b6334192f7448"}, + {file = "sqlalchemy_json-0.6.0-py3-none-any.whl", hash = "sha256:06f124c093368903859367c7ab22416c4428e90c09ae14def8391f06a2373c1f"}, +] + +[package.dependencies] +sqlalchemy = ">=0.7" + [[package]] name = "tzlocal" version = "2.1" @@ -774,4 +788,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0191646ba7c237dee90d7503b8060c159c7b90d3c3d30f09aba40d6d02bdf046" +content-hash = "5c062e16e9b423aa767579e2c13779e550fbc7114c0c059e4f334e34946d6b0e" diff --git a/pyproject.toml b/pyproject.toml index 9035449..fd74aaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ pyOpenSSL = "23.2.0" ipaddress = "^1.0" prettytable = "^3.8.0" sqlalchemy = "^1.4" +sqlalchemy-json = "^0.6.0" [build-system] requires = ["poetry-core"] From d20b29ba70047754a8672b7a6f0ce9e441a76b61 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 12:16:19 +0200 Subject: [PATCH 25/29] Revert "WIP" This reverts commit 88285677cb054801fae1d713ee98b716aba0361f. --- frestq/models.py | 145 ++++++++++++++++++++++++++++++++++++++++++++--- poetry.lock | 16 +----- pyproject.toml | 1 - 3 files changed, 138 insertions(+), 24 deletions(-) diff --git a/frestq/models.py b/frestq/models.py index 58c0112..92ad3ff 100644 --- a/frestq/models.py +++ b/frestq/models.py @@ -9,16 +9,145 @@ from datetime import datetime import sqlalchemy -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy_json import mutable_json_type +from sqlalchemy.ext.mutable import Mutable +from sqlalchemy.types import TypeDecorator, UnicodeText from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy - from .app import db from .utils import dumps, loads +class JSONEncodedDict(TypeDecorator): + impl = UnicodeText + + def process_bind_param(self, value, dialect): + if value is None: + return None + return dumps(value) + + def process_result_value(self, value, dialect): + if not value: + return None + return loads(value) + + +class MutationObj(Mutable): + @classmethod + def coerce(cls, key, value): + if isinstance(value, dict) and not isinstance(value, MutationDict): + return MutationDict.coerce(key, value) + if isinstance(value, list) and not isinstance(value, MutationList): + return MutationList.coerce(key, value) + return value + + @classmethod + def _listen_on_attribute(cls, attribute, coerce, parent_cls): + key = attribute.key + if parent_cls is not attribute.class_: + return + + # rely on "propagate" here + parent_cls = attribute.class_ + + def load(state, *args): + val = state.dict.get(key, None) + if coerce: + val = cls.coerce(key, val) + state.dict[key] = val + if isinstance(val, cls): + val._parents[state.obj()] = key + + def set(target, value, oldvalue, initiator): + if not isinstance(value, cls): + value = cls.coerce(key, value) + if isinstance(value, cls): + value._parents[target.obj()] = key + if isinstance(oldvalue, cls): + oldvalue._parents.pop(target.obj(), None) + return value + + def pickle(state, state_dict): + val = state.dict.get(key, None) + if isinstance(val, cls): + if 'ext.mutable.values' not in state_dict: + state_dict['ext.mutable.values'] = [] + state_dict['ext.mutable.values'].append(val) + + def unpickle(state, state_dict): + if 'ext.mutable.values' in state_dict: + for val in state_dict['ext.mutable.values']: + val._parents[state.obj()] = key + + sqlalchemy.event.listen(parent_cls, 'load', load, raw=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True, propagate=True) + sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True, propagate=True) + sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True, propagate=True) + +class MutationDict(MutationObj, dict): + @classmethod + def coerce(cls, key, value): + """Convert plain dictionary to MutationDict""" + self = MutationDict((k,MutationObj.coerce(key,v)) for (k,v) in value.items()) + self._key = key + return self + + def __setitem__(self, key, value): + dict.__setitem__(self, key, MutationObj.coerce(self._key, value)) + self.changed() + + def __delitem__(self, key): + dict.__delitem__(self, key) + self.changed() + +class MutationList(MutationObj, list): + @classmethod + def coerce(cls, key, value): + """Convert plain list to MutationList""" + self = MutationList((MutationObj.coerce(key, v) for v in value)) + self._key = key + return self + + def __setitem__(self, idx, value): + list.__setitem__(self, idx, MutationObj.coerce(self._key, value)) + self.changed() + + def __setslice__(self, start, stop, values): + list.__setslice__(self, start, stop, (MutationObj.coerce(self._key, v) for v in values)) + self.changed() + + def __delitem__(self, idx): + list.__delitem__(self, idx) + self.changed() + + def __delslice__(self, start, stop): + list.__delslice__(self, start, stop) + self.changed() + + def append(self, value): + list.append(self, MutationObj.coerce(self._key, value)) + self.changed() + + def insert(self, idx, value): + list.insert(self, idx, MutationObj.coerce(self._key, value)) + self.changed() + + def extend(self, values): + list.extend(self, (MutationObj.coerce(self._key, v) for v in values)) + self.changed() + + def pop(self, *args, **kw): + value = list.pop(self, *args, **kw) + self.changed() + return value + + def remove(self, value): + list.remove(self, value) + self.changed() + +MutationObj.associate_with(JSONEncodedDict) + class Message(db.Model): ''' Represents an election @@ -43,7 +172,7 @@ class Message(db.Model): action = db.Column(db.Unicode(1024)) - input_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) + input_data = db.Column(JSONEncodedDict) output_status = db.Column(db.Integer) @@ -108,7 +237,7 @@ class Task(db.Model): task_type = db.Column(db.Unicode(1024)) # for example used in synchronous tasks to store the algorithm - task_metadata = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) + task_metadata = db.Column(JSONEncodedDict) label = db.Column(db.Unicode(1024)) @@ -141,11 +270,11 @@ class Task(db.Model): last_modified_date = db.Column(db.DateTime, default=datetime.utcnow) - input_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) + input_data = db.Column(JSONEncodedDict) - output_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) + output_data = db.Column(JSONEncodedDict) - reservation_data = db.Column(mutable_json_type(dbtype=JSONB, nested=True)) + reservation_data = db.Column(JSONEncodedDict) pingback_date = db.Column(db.DateTime, default=None) diff --git a/poetry.lock b/poetry.lock index f8d6e3a..eb4930d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -697,20 +697,6 @@ postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3-binary"] -[[package]] -name = "sqlalchemy-json" -version = "0.6.0" -description = "JSON type with nested change tracking for SQLAlchemy" -optional = false -python-versions = ">= 3.6" -files = [ - {file = "sqlalchemy-json-0.6.0.tar.gz", hash = "sha256:94785325716f0649ec6f4d0304ce8803a5bf25ff8aa2d998ce6b6334192f7448"}, - {file = "sqlalchemy_json-0.6.0-py3-none-any.whl", hash = "sha256:06f124c093368903859367c7ab22416c4428e90c09ae14def8391f06a2373c1f"}, -] - -[package.dependencies] -sqlalchemy = ">=0.7" - [[package]] name = "tzlocal" version = "2.1" @@ -788,4 +774,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "5c062e16e9b423aa767579e2c13779e550fbc7114c0c059e4f334e34946d6b0e" +content-hash = "0191646ba7c237dee90d7503b8060c159c7b90d3c3d30f09aba40d6d02bdf046" diff --git a/pyproject.toml b/pyproject.toml index fd74aaa..9035449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ pyOpenSSL = "23.2.0" ipaddress = "^1.0" prettytable = "^3.8.0" sqlalchemy = "^1.4" -sqlalchemy-json = "^0.6.0" [build-system] requires = ["poetry-core"] From 2b759e289fc85605cb460a1d60495c52b07f946f Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 12:17:38 +0200 Subject: [PATCH 26/29] WIP --- poetry.lock | 171 ++++++++++++------------------------------------- pyproject.toml | 2 +- 2 files changed, 42 insertions(+), 131 deletions(-) diff --git a/poetry.lock b/poetry.lock index eb4930d..c607a13 100644 --- a/poetry.lock +++ b/poetry.lock @@ -320,79 +320,6 @@ files = [ Flask = ">=0.10" SQLAlchemy = ">=0.8.0" -[[package]] -name = "greenlet" -version = "2.0.2" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] - [[package]] name = "idna" version = "3.4" @@ -628,74 +555,58 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.49" +version = "1.3.24" description = "Database Abstraction Library" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, ] -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"} - [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +mysql = ["mysqlclient"] +oracle = ["cx-oracle"] +postgresql = ["psycopg2"] +postgresql-pg8000 = ["pg8000 (<1.16.6)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] [[package]] name = "tzlocal" @@ -774,4 +685,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0191646ba7c237dee90d7503b8060c159c7b90d3c3d30f09aba40d6d02bdf046" +content-hash = "b3196e9e370b33f7dfe8581509bb2a23fc24d22a1f332469ff9646f0fc4b7f3e" diff --git a/pyproject.toml b/pyproject.toml index 9035449..711d133 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ cryptography = "^41.0" pyOpenSSL = "23.2.0" ipaddress = "^1.0" prettytable = "^3.8.0" -sqlalchemy = "^1.4" +sqlalchemy = "~1.3" [build-system] requires = ["poetry-core"] From 0fc4d34fd51d236bbc2c9c45c5cf87dd7ca90de2 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 12:29:27 +0200 Subject: [PATCH 27/29] WIP --- frestq/app.py | 4 ++-- frestq/protocol.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frestq/app.py b/frestq/app.py index f40bff2..d4cf4c2 100755 --- a/frestq/app.py +++ b/frestq/app.py @@ -241,10 +241,10 @@ class DefaultConfig(object): ROOT_URL = 'http://127.0.0.1:5000/api/queues' # time a thread can be reserved in for synchronization purposes. In seconds. - RESERVATION_TIMEOUT = 60 + RESERVATION_TIMEOUT = "60" # set to True to get real security - ALLOW_ONLY_SSL_CONNECTIONS = False + ALLOW_ONLY_SSL_CONNECTIONS = "False" # options for each queue. example: #QUEUES_OPTIONS = { diff --git a/frestq/protocol.py b/frestq/protocol.py index e39df3c..245eddb 100644 --- a/frestq/protocol.py +++ b/frestq/protocol.py @@ -28,7 +28,7 @@ def certs_differ(cert_a, cert_b): if cert_b is None: cert_b = u'' - if app.config.get('ALLOW_ONLY_SSL_CONNECTIONS') and\ + if app.config.get('ALLOW_ONLY_SSL_CONNECTIONS') != "False" and\ (not len(cert_a) or not len(cert_b)): raise SecurityException() @@ -129,7 +129,7 @@ def reserve_task(task_id): # 4. set reservation timeout sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) - date = datetime.utcnow() + timedelta(seconds=app.config.get('RESERVATION_TIMEOUT')) + date = datetime.utcnow() + timedelta(seconds=int(app.config.get('RESERVATION_TIMEOUT'))) sched.add_date_job(cancel_reserved_subtask, date, [task.id]) # 5. wait for a cancel or execute message @@ -249,7 +249,7 @@ def ack_reservation(task_id): # reservation_timeout is also sent logging.debug("SENDING ACK RESERVATION TO SENDER of task with id %s", task_id) - expire_secs = app.config.get('RESERVATION_TIMEOUT') + expire_secs = int(app.config.get('RESERVATION_TIMEOUT')) task = ModelTask.query.get(task_id) msg = { "action": "frestq.confirm_task_reservation", @@ -320,7 +320,10 @@ def synchronize_task(msg): # schedule expiration if task.expiration_date: sched = FScheduler.get_scheduler(INTERNAL_SCHEDULER_NAME) - date = datetime.utcnow() + timedelta(seconds=app.config.get('RESERVATION_TIMEOUT')) + date = ( + datetime.utcnow() + + timedelta(seconds=int(app.config.get('RESERVATION_TIMEOUT'))) + ) sched.add_date_job(cancel_reserved_subtask, date, [task.id]) From 0e98c135231af445dbbd45a2df4ef20684720512 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Mon, 31 Jul 2023 12:41:18 +0200 Subject: [PATCH 28/29] WIP --- frestq/protocol.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frestq/protocol.py b/frestq/protocol.py index 245eddb..ec765c1 100644 --- a/frestq/protocol.py +++ b/frestq/protocol.py @@ -28,8 +28,10 @@ def certs_differ(cert_a, cert_b): if cert_b is None: cert_b = u'' - if app.config.get('ALLOW_ONLY_SSL_CONNECTIONS') != "False" and\ - (not len(cert_a) or not len(cert_b)): + if app.config.get('ALLOW_ONLY_SSL_CONNECTIONS') == "False": + return False + + if (not len(cert_a) or not len(cert_b)): raise SecurityException() if not len(cert_a) and not len(cert_b): From 1c9de47aa5b8f2d3beaacc3821cf5e8d483c12bf Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Tue, 1 Aug 2023 06:14:04 +0200 Subject: [PATCH 29/29] WIP --- frestq/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frestq/utils.py b/frestq/utils.py index 6d12258..88444e2 100644 --- a/frestq/utils.py +++ b/frestq/utils.py @@ -157,7 +157,7 @@ def traverse_tasktree(task, visitor_func, visitor_kwargs): def show_task(args): from .app import db from .models import Task - task_id = unicode(args.show_task) + task_id = str(args.show_task) task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) @@ -260,7 +260,7 @@ def show_activity(args): def show_message(args): from .app import db from .models import Message - msg_id = unicode(args.show_message) + msg_id = str(args.show_message) msg_model = db.session.query(Message).filter(Message.id.startswith(msg_id)).all() if not msg_model: print("message %s not found" % msg_id) @@ -273,7 +273,7 @@ def get_external_task(args): from .app import db from .models import Task - task_id = unicode(args.show_external) + task_id = str(args.show_external) task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() return task_model @@ -281,7 +281,7 @@ def get_external_task(args): # drb def show_external_task(args): - task_id = unicode(args.show_external) + task_id = str(args.show_external) task_model = get_external_task(args) if not task_model: @@ -302,9 +302,9 @@ def finish_task(args): from .models import Task from .tasks import ExternalTask - task_id = unicode(args.finish[0]) + task_id = str(args.finish[0]) try: - finish_data = loads(unicode(args.finish[1])) + finish_data = loads(str(args.finish[1])) except: print("error loading the json finish data") return @@ -331,7 +331,7 @@ def deny_task(args): def task_tree(args): from .app import db from .models import Task - task_id = unicode(args.tree) + task_id = str(args.tree) task_model = db.session.query(Task).filter(Task.id.startswith(task_id)).all() if not task_model: print("task %s not found" % task_id) @@ -343,8 +343,8 @@ def task_tree(args): task_model = db.session.query(Task).get(task_model.parent_id) except: print("task %s, which is the parent of %s not found" % ( - str(task.parent_id)[:8], - str(task.id)[:8], + str(task_model.parent_id)[:8], + str(task_model.id)[:8], )) break