From b13167a26298634b8156b71e129e1ebb93fceaf7 Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Wed, 10 Apr 2024 10:45:43 +0200
Subject: [PATCH] Fixed the model validation

---
 .jupyter/default.ipynb              |  18 +--
 lib/python/Pipfile                  |   1 +
 lib/python/Pipfile.lock             | 211 +++++++++++++++++++++-------
 lib/python/dbrepo/RestClient.py     |  91 ++++++------
 lib/python/debug.py                 |   5 -
 lib/python/pyproject.toml           |   3 +-
 lib/python/tests/test_analyse.py    |   2 +-
 lib/python/tests/test_container.py  |   2 +-
 lib/python/tests/test_database.py   |  14 +-
 lib/python/tests/test_identifier.py |   4 +-
 lib/python/tests/test_query.py      |  22 ++-
 lib/python/tests/test_table.py      |  25 +++-
 lib/python/tests/test_user.py       |  20 +--
 lib/python/tests/test_view.py       |  21 ++-
 14 files changed, 299 insertions(+), 140 deletions(-)
 delete mode 100644 lib/python/debug.py

diff --git a/.jupyter/default.ipynb b/.jupyter/default.ipynb
index 002ee7a160..0d51aa7188 100644
--- a/.jupyter/default.ipynb
+++ b/.jupyter/default.ipynb
@@ -3,8 +3,8 @@
   {
    "metadata": {
     "ExecuteTime": {
-     "end_time": "2024-04-09T18:28:47.537714Z",
-     "start_time": "2024-04-09T18:28:45.600736Z"
+     "end_time": "2024-04-10T06:10:08.974108Z",
+     "start_time": "2024-04-10T06:10:07.365075Z"
     }
    },
    "cell_type": "code",
@@ -48,8 +48,8 @@
   {
    "metadata": {
     "ExecuteTime": {
-     "end_time": "2024-04-09T18:28:48.010311Z",
-     "start_time": "2024-04-09T18:28:47.541176Z"
+     "end_time": "2024-04-10T06:10:09.860353Z",
+     "start_time": "2024-04-10T06:10:08.981192Z"
     }
    },
    "cell_type": "code",
@@ -64,11 +64,11 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2024-04-09 20:28:47,867 root         DEBUG  method: get\n",
-      "2024-04-09 20:28:47,867 root         DEBUG  url: https://dbrepo1.ec.tuwien.ac.at/api/database/license\n",
-      "2024-04-09 20:28:47,868 root         DEBUG  stream: False\n",
-      "2024-04-09 20:28:47,868 root         DEBUG  secure: True\n",
-      "2024-04-09 20:28:47,868 root         DEBUG  username: foo, password: (hidden)\n"
+      "2024-04-10 08:10:09,327 root         DEBUG  method: get\n",
+      "2024-04-10 08:10:09,328 root         DEBUG  url: https://test.dbrepo.tuwien.ac.at/api/database/license\n",
+      "2024-04-10 08:10:09,328 root         DEBUG  stream: False\n",
+      "2024-04-10 08:10:09,329 root         DEBUG  secure: True\n",
+      "2024-04-10 08:10:09,329 root         DEBUG  username: foo, password: (hidden)\n"
      ]
     }
    ],
diff --git a/lib/python/Pipfile b/lib/python/Pipfile
index a6f3abd4b3..b52daf9ec4 100644
--- a/lib/python/Pipfile
+++ b/lib/python/Pipfile
@@ -8,6 +8,7 @@ requests = "~=2.31"
 pika = "*"
 pydantic = "*"
 tuspy = "*"
+pandas = "*"
 
 [dev-packages]
 build = "*"
diff --git a/lib/python/Pipfile.lock b/lib/python/Pipfile.lock
index d1c478e174..554a33747d 100644
--- a/lib/python/Pipfile.lock
+++ b/lib/python/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "8cc220d86727c0bc277f084e48a33c5a487e40639ac8ac36ef8f5d824784261d"
+            "sha256": "075c662404a333e1251df1222db0602a779feb803e123e80df47260cb2e4ebf9"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -413,6 +413,84 @@
             "markers": "python_version >= '3.7'",
             "version": "==6.0.5"
         },
+        "numpy": {
+            "hashes": [
+                "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b",
+                "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818",
+                "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20",
+                "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0",
+                "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010",
+                "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a",
+                "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea",
+                "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c",
+                "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71",
+                "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110",
+                "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be",
+                "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a",
+                "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a",
+                "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5",
+                "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed",
+                "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd",
+                "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c",
+                "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e",
+                "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0",
+                "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c",
+                "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a",
+                "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b",
+                "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0",
+                "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6",
+                "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2",
+                "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a",
+                "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30",
+                "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218",
+                "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5",
+                "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07",
+                "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2",
+                "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4",
+                "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764",
+                "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef",
+                "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3",
+                "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"
+            ],
+            "markers": "python_version == '3.11'",
+            "version": "==1.26.4"
+        },
+        "pandas": {
+            "hashes": [
+                "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee",
+                "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e",
+                "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572",
+                "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944",
+                "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403",
+                "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89",
+                "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab",
+                "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6",
+                "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb",
+                "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9",
+                "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019",
+                "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be",
+                "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd",
+                "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c",
+                "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88",
+                "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0",
+                "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397",
+                "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc",
+                "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2",
+                "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7",
+                "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06",
+                "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51",
+                "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0",
+                "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a",
+                "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16",
+                "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02",
+                "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359",
+                "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b",
+                "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"
+            ],
+            "index": "pypi",
+            "markers": "python_version >= '3.9'",
+            "version": "==2.2.1"
+        },
         "pika": {
             "hashes": [
                 "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f",
@@ -516,6 +594,21 @@
             "markers": "python_version >= '3.8'",
             "version": "==2.16.3"
         },
+        "python-dateutil": {
+            "hashes": [
+                "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
+                "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.9.0.post0"
+        },
+        "pytz": {
+            "hashes": [
+                "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
+                "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
+            ],
+            "version": "==2024.1"
+        },
         "requests": {
             "hashes": [
                 "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
@@ -525,6 +618,14 @@
             "markers": "python_version >= '3.7'",
             "version": "==2.31.0"
         },
+        "six": {
+            "hashes": [
+                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.16.0"
+        },
         "tinydb": {
             "hashes": [
                 "sha256:30c06d12383d7c332e404ca6a6103fb2b32cbf25712689648c39d9a6bd34bd3d",
@@ -544,11 +645,19 @@
         },
         "typing-extensions": {
             "hashes": [
-                "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
-                "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
+                "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0",
+                "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==4.10.0"
+            "version": "==4.11.0"
+        },
+        "tzdata": {
+            "hashes": [
+                "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
+                "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
+            ],
+            "markers": "python_version >= '2'",
+            "version": "==2024.1"
         },
         "urllib3": {
             "hashes": [
@@ -672,6 +781,14 @@
             "markers": "python_version >= '3.7'",
             "version": "==2.14.0"
         },
+        "backports.tarfile": {
+            "hashes": [
+                "sha256:2688f159c21afd56a07b75f01306f9f52c79aebcc5f4a117fb8fbb4445352c75",
+                "sha256:bcd36290d9684beb524d3fe74f4a2db056824c47746583f090b8e55daf0776e4"
+            ],
+            "markers": "python_version < '3.12'",
+            "version": "==1.0.0"
+        },
         "beautifulsoup4": {
             "hashes": [
                 "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051",
@@ -682,12 +799,12 @@
         },
         "build": {
             "hashes": [
-                "sha256:8ed0851ee76e6e38adce47e4bee3b51c771d86c64cf578d0c2245567ee200e73",
-                "sha256:8eea65bb45b1aac2e734ba2cc8dad3a6d97d97901a395bd0ed3e7b46953d2a31"
+                "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d",
+                "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"
             ],
             "index": "pypi",
-            "markers": "python_version >= '3.7'",
-            "version": "==1.1.1"
+            "markers": "python_version >= '3.8'",
+            "version": "==1.2.1"
         },
         "certifi": {
             "hashes": [
@@ -999,19 +1116,19 @@
         },
         "jaraco.classes": {
             "hashes": [
-                "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206",
-                "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"
+                "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd",
+                "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==3.3.1"
+            "version": "==3.4.0"
         },
         "jaraco.context": {
             "hashes": [
-                "sha256:4dad2404540b936a20acedec53355bdaea223acb88fd329fa6de9261c941566e",
-                "sha256:5d9e95ca0faa78943ed66f6bc658dd637430f16125d86988e77844c741ff2f11"
+                "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266",
+                "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"
             ],
-            "markers": "python_version >= '3.7'",
-            "version": "==4.3.0"
+            "markers": "python_version >= '3.8'",
+            "version": "==5.3.0"
         },
         "jaraco.functools": {
             "hashes": [
@@ -1039,11 +1156,11 @@
         },
         "keyring": {
             "hashes": [
-                "sha256:9a15cd280338920388e8c1787cb8792b9755dabb3e7c61af5ac1f8cd437cefde",
-                "sha256:fc024ed53c7ea090e30723e6bd82f58a39dc25d9a6797d866203ecd0ee6306cb"
+                "sha256:26fc12e6a329d61d24aa47b22a7c5c3f35753df7d8f2860973cf94f4e1fb3427",
+                "sha256:7230ea690525133f6ad536a9b5def74a4bd52642abe594761028fc044d7c7893"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==25.0.0"
+            "version": "==25.1.0"
         },
         "markdown-it-py": {
             "hashes": [
@@ -1137,24 +1254,24 @@
         },
         "nh3": {
             "hashes": [
-                "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770",
-                "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf",
-                "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305",
-                "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601",
-                "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28",
-                "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7",
-                "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3",
-                "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911",
-                "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf",
-                "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0",
-                "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5",
-                "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97",
-                "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d",
-                "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e",
-                "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3",
-                "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"
-            ],
-            "version": "==0.2.15"
+                "sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a",
+                "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911",
+                "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb",
+                "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a",
+                "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc",
+                "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028",
+                "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9",
+                "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3",
+                "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351",
+                "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10",
+                "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71",
+                "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f",
+                "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b",
+                "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a",
+                "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062",
+                "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a"
+            ],
+            "version": "==0.2.17"
         },
         "packaging": {
             "hashes": [
@@ -1182,10 +1299,11 @@
         },
         "pycparser": {
             "hashes": [
-                "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
-                "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
+                "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
+                "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
             ],
-            "version": "==2.21"
+            "markers": "python_version >= '3.8'",
+            "version": "==2.22"
         },
         "pygments": {
             "hashes": [
@@ -1231,11 +1349,12 @@
         },
         "requests-mock": {
             "hashes": [
-                "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4",
-                "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"
+                "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563",
+                "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"
             ],
             "index": "pypi",
-            "version": "==1.11.0"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.12.1"
         },
         "requests-toolbelt": {
             "hashes": [
@@ -1278,14 +1397,6 @@
             "markers": "python_version >= '3.8'",
             "version": "==69.2.0"
         },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.16.0"
-        },
         "snowballstemmer": {
             "hashes": [
                 "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
diff --git a/lib/python/dbrepo/RestClient.py b/lib/python/dbrepo/RestClient.py
index a114dcc988..619fec9a66 100644
--- a/lib/python/dbrepo/RestClient.py
+++ b/lib/python/dbrepo/RestClient.py
@@ -5,6 +5,7 @@ import logging
 import requests
 from pydantic import TypeAdapter
 from tusclient.client import TusClient
+from pandas import DataFrame
 
 from dbrepo.api.dto import *
 from dbrepo.api.exceptions import ResponseCodeError, UsernameExistsError, EmailExistsError, NotExistsError, \
@@ -124,7 +125,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return User.parse_raw(body)
+            return User.model_validate(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to find user with id {user_id}')
         raise ResponseCodeError(
@@ -151,7 +152,7 @@ class RestClient:
                                  payload=CreateUser(username=username, password=password, email=email))
         if response.status_code == 201:
             body = response.json()
-            return UserBrief.parse_raw(body)
+            return UserBrief.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to update user password: not allowed')
         if response.status_code == 404:
@@ -186,7 +187,7 @@ class RestClient:
                                                     orcid=orcid))
         if response.status_code == 202:
             body = response.json()
-            return User.parse_raw(body)
+            return User.model_validate(body)
         if response.status_code == 400:
             raise ResponseCodeError(f'Failed to update user: invalid values')
         if response.status_code == 403:
@@ -215,7 +216,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=UpdateUserTheme(theme=theme))
         if response.status_code == 202:
             body = response.json()
-            return User.parse_raw(body)
+            return User.model_validate(body)
         if response.status_code == 400:
             raise ResponseCodeError(f'Failed to update user theme: invalid values')
         if response.status_code == 403:
@@ -244,7 +245,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=UpdateUserPassword(password=password))
         if response.status_code == 202:
             body = response.json()
-            return User.parse_raw(body)
+            return User.model_validate(body)
         if response.status_code == 400:
             raise ResponseCodeError(f'Failed to update user password: invalid values')
         if response.status_code == 403:
@@ -286,7 +287,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return Container.parse_raw(body)
+            return Container.model_validate(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to get container: not found')
         raise ResponseCodeError(f'Failed to get container: response code: {response.status_code} is not 200 (OK)')
@@ -333,7 +334,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return Database.parse_raw(body)
+            return Database.model_validate(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to find database with id {database_id}')
         raise ResponseCodeError(
@@ -359,7 +360,7 @@ class RestClient:
                                  payload=CreateDatabase(name=name, container_id=container_id, is_public=is_public))
         if response.status_code == 201:
             body = response.json()
-            return Database.parse_raw(body)
+            return Database.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to create database: not allowed')
         if response.status_code == 404:
@@ -385,7 +386,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=ModifyVisibility(is_public=is_public))
         if response.status_code == 202:
             body = response.json()
-            return Database.parse_raw(body)
+            return Database.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to update database visibility: not allowed')
         if response.status_code == 404:
@@ -409,7 +410,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=ModifyOwner(id=user_id))
         if response.status_code == 202:
             body = response.json()
-            return Database.parse_raw(body)
+            return Database.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to update database visibility: not allowed')
         if response.status_code == 404:
@@ -442,7 +443,7 @@ class RestClient:
                                                      columns=columns, constraints=constraints))
         if response.status_code == 201:
             body = response.json()
-            return Table.parse_raw(body)
+            return Table.model_validate(body)
         if response.status_code == 400:
             raise MalformedError(f'Failed to create table: service rejected malformed payload')
         if response.status_code == 403:
@@ -488,7 +489,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return Table.parse_raw(body)
+            return Table.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to find table: not allowed')
         if response.status_code == 404:
@@ -556,7 +557,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return View.parse_raw(body)
+            return View.model_validate(body)
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to find view: not allowed')
         if response.status_code == 404:
@@ -584,7 +585,7 @@ class RestClient:
                                  payload=CreateView(name=name, query=query, is_public=is_public))
         if response.status_code == 201:
             body = response.json()
-            return View.parse_raw(body)
+            return View.model_validate(body)
         if response.status_code == 400 or response.status_code == 423:
             raise MalformedError(f'Failed to create view: service rejected malformed payload')
         if response.status_code == 403 or response.status_code == 405:
@@ -616,7 +617,8 @@ class RestClient:
             raise NotExistsError(f'Failed to delete view: not found')
         raise ResponseCodeError(f'Failed to delete view: response code: {response.status_code} is not 202 (ACCEPTED)')
 
-    def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10) -> Result:
+    def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10,
+                      df: bool = False) -> Result | DataFrame:
         """
         Get data of a view in a database with given database id and view id.
 
@@ -624,6 +626,7 @@ class RestClient:
         :param view_id: The view id.
         :param page: The result pagination number. Optional. Default: 0.
         :param size: The result pagination size. Optional. Default: 10.
+        :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
 
         :returns: The result of the view query, if successful.
 
@@ -639,7 +642,10 @@ class RestClient:
         response = self._wrapper(method="get", url=url, params=params)
         if response.status_code == 200:
             body = response.json()
-            return Result.parse_raw(body)
+            res = Result.model_validate(body)
+            if df:
+                return DataFrame.from_records(res.result)
+            return res
         if response.status_code == 400:
             raise MalformedError(f'Failed to get view data: service rejected malformed payload')
         if response.status_code == 403:
@@ -649,7 +655,7 @@ class RestClient:
         raise ResponseCodeError(f'Failed to get view data: response code: {response.status_code} is not 200 (OK)')
 
     def get_table_data(self, database_id: int, table_id: int, page: int = 0, size: int = 10,
-                       timestamp: datetime.datetime = None) -> Result:
+                       timestamp: datetime.datetime = None, df: bool = False) -> Result | DataFrame:
         """
         Get data of a table in a database with given database id and table id.
 
@@ -658,6 +664,7 @@ class RestClient:
         :param page: The result pagination number. Optional. Default: 0.
         :param size: The result pagination size. Optional. Default: 10.
         :param timestamp: The query execution time. Optional.
+        :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
 
         :returns: The result of the view query, if successful.
 
@@ -676,7 +683,10 @@ class RestClient:
         response = self._wrapper(method="get", url=url, params=params)
         if response.status_code == 200:
             body = response.json()
-            return Result.parse_raw(body)
+            res = Result.model_validate(body)
+            if df:
+                return DataFrame.from_records(res.result)
+            return res
         if response.status_code == 400:
             raise MalformedError(f'Failed to get table data: service rejected malformed payload')
         if response.status_code == 403:
@@ -790,7 +800,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url, params=params)
         if response.status_code == 202:
             body = response.json()
-            return DatatypeAnalysis.parse_raw(body)
+            return DatatypeAnalysis.model_validate(body)
         if response.status_code == 400 or response.status_code == 500:
             raise MalformedError(f'Failed to analyse data types: service rejected malformed payload')
         if response.status_code == 404:
@@ -826,7 +836,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url, params=params)
         if response.status_code == 202:
             body = response.json()
-            return KeyAnalysis.parse_raw(body)
+            return KeyAnalysis.model_validate(body)
         if response.status_code == 400 or response.status_code == 500:
             raise MalformedError(f'Failed to analyse data types: service rejected malformed payload')
         if response.status_code == 404:
@@ -851,7 +861,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 202:
             body = response.json()
-            return TableStatistics.parse_raw(body)
+            return TableStatistics.model_validate(body)
         if response.status_code == 400:
             raise MalformedError(f'Failed to analyse table statistics: service rejected malformed payload')
         if response.status_code == 404:
@@ -994,7 +1004,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return DatabaseAccess.parse_raw(body).type
+            return DatabaseAccess.model_validate(body).type
         if response.status_code == 403:
             raise ForbiddenError(f'Failed to get database access: not allowed')
         if response.status_code == 404:
@@ -1020,7 +1030,7 @@ class RestClient:
         response = self._wrapper(method="post", url=url, force_auth=True, payload=CreateAccess(type=type))
         if response.status_code == 202:
             body = response.json()
-            return DatabaseAccess.parse_raw(body).type
+            return DatabaseAccess.model_validate(body).type
         if response.status_code == 400:
             raise MalformedError(f'Failed to create database access: service rejected malformed payload')
         if response.status_code == 403 or response.status_code == 405:
@@ -1049,7 +1059,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=UpdateAccess(type=type))
         if response.status_code == 202:
             body = response.json()
-            return DatabaseAccess.parse_raw(body).type
+            return DatabaseAccess.model_validate(body).type
         if response.status_code == 400:
             raise MalformedError(f'Failed to update database access: service rejected malformed payload')
         if response.status_code == 403 or response.status_code == 405:
@@ -1113,7 +1123,7 @@ class RestClient:
                                  payload=ExecuteQuery(statement=query, timestamp=timestamp))
         if response.status_code == 202:
             body = response.json()
-            return Result.parse_raw(body)
+            return Result.model_validate(body)
         if response.status_code == 400:
             raise MalformedError(f'Failed to execute query: service rejected malformed payload')
         if response.status_code == 403:
@@ -1128,7 +1138,7 @@ class RestClient:
             f'Failed to execute query: response code: {response.status_code} is not 202 (ACCEPTED)')
 
     def get_query_data(self, database_id: int, query_id: int, page: int = 0, size: int = 10,
-                       file_path: str = None) -> Result:
+                       df: bool = False) -> Result | DataFrame:
         """
         Re-executes a query in a database with given database id and query id.
 
@@ -1137,7 +1147,7 @@ class RestClient:
         :param page: The result pagination number. Optional. Default: 0.
         :param size: The result pagination size. Optional. Default: 10.
         :param size: The result pagination size. Optional. Default: 10.
-        :param file_path: The file path where the result should be saved. Optional.
+        :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
 
         :returns: The result set, if successful.
 
@@ -1148,22 +1158,17 @@ class RestClient:
         :raises QueryStoreError: The query store rejected the query.
         :raises MetadataConsistencyError: The service failed to parse columns from the metadata database.
         """
-        stream = False
         headers = {}
         url = f'/api/database/{database_id}/query/{query_id}/data'
         if page is not None and size is not None:
             url += f'?page={page}&size={size}'
-        if file_path is not None:
-            stream = True
-            headers = {'Accept': 'text/csv'}
-        response = self._wrapper(method="get", url=url, headers=headers, stream=stream)
+        response = self._wrapper(method="get", url=url, headers=headers)
         if response.status_code == 200:
-            if file_path is None:
-                body = response.json()
-                return Result.parse_raw(body)
-            else:
-                with open(file_path, "w") as f:
-                    f.write(response.content.decode("utf-8"))
+            body = response.json()
+            res = Result.model_validate(body)
+            if df:
+                return DataFrame.from_records(res.result)
+            return res
         if response.status_code == 400:
             raise MalformedError(f'Failed to re-execute query: service rejected malformed payload')
         if response.status_code == 403 or response.status_code == 405:
@@ -1233,7 +1238,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return Query.parse_raw(body)
+            return Query.model_validate(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to find query: not found')
         if response.status_code == 403 or response.status_code == 405:
@@ -1295,7 +1300,7 @@ class RestClient:
         response = self._wrapper(method="put", url=url, force_auth=True, payload=UpdateQuery(persist=persist))
         if response.status_code == 202:
             body = response.json()
-            return Query.parse_raw(body)
+            return Query.model_validate(body)
         if response.status_code == 403 or response.status_code == 405:
             raise ForbiddenError(f'Failed to update query: not allowed')
         if response.status_code == 404:
@@ -1349,7 +1354,7 @@ class RestClient:
         response = self._wrapper(method="post", url=url, force_auth=True, payload=payload)
         if response.status_code == 201:
             body = response.json()
-            return Identifier.parse_raw(body)
+            return Identifier.model_validate(body)
         if response.status_code == 400:
             raise MalformedError(f'Failed to create identifier: service rejected malformed payload')
         if response.status_code == 403 or response.status_code == 405:
@@ -1376,7 +1381,7 @@ class RestClient:
         response = self._wrapper(method="get", url=url)
         if response.status_code == 200:
             body = response.json()
-            return Identifier.parse_raw(body)
+            return Identifier.model_validate(body)
         if response.status_code == 404:
             raise NotExistsError(f'Failed to suggest identifier: not found or not supported')
         raise ResponseCodeError(f'Failed to suggest identifier: response code: {response.status_code} is not 200 (OK)')
@@ -1442,7 +1447,7 @@ class RestClient:
                                  payload=UpdateColumn(concept_uri=concept_uri, unit_uri=unit_uri))
         if response.status_code == 202:
             body = response.json()
-            return Column.parse_raw(body)
+            return Column.model_validate(body)
         if response.status_code == 400:
             raise MalformedError(f'Failed to update column: service rejected malformed payload')
         if response.status_code == 403:
diff --git a/lib/python/debug.py b/lib/python/debug.py
deleted file mode 100644
index afcb586090..0000000000
--- a/lib/python/debug.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from dbrepo.RestClient import RestClient
-
-client = RestClient(endpoint="https://dbrepo1.ec.tuwien.ac.at", username="foo",
-                    password="bar")
-client.get_licenses()
diff --git a/lib/python/pyproject.toml b/lib/python/pyproject.toml
index 6dfae10925..43baf9a5f1 100644
--- a/lib/python/pyproject.toml
+++ b/lib/python/pyproject.toml
@@ -23,7 +23,8 @@ dependencies = [
     "requests >= 2.31",
     "pika",
     "pydantic",
-    "tuspy"
+    "tuspy",
+    "pandas"
 ]
 
 [build-system]
diff --git a/lib/python/tests/test_analyse.py b/lib/python/tests/test_analyse.py
index c87cb22018..a4668fedc5 100644
--- a/lib/python/tests/test_analyse.py
+++ b/lib/python/tests/test_analyse.py
@@ -13,7 +13,7 @@ class AnalyseTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = KeyAnalysis(keys={'id': 0, 'firstname': 1, 'lastname': 2})
             # mock
-            mock.get('/api/analyse/keys', json=exp.model_dump_json(), status_code=202)
+            mock.get('/api/analyse/keys', json=exp.model_dump(), status_code=202)
             # test
             response = RestClient().analyse_keys(file_path='f705a7bd0cb2d5e37ab2b425036810a2', separator=',',
                                                  upload=False)
diff --git a/lib/python/tests/test_container.py b/lib/python/tests/test_container.py
index d948e337db..e9988f19ca 100644
--- a/lib/python/tests/test_container.py
+++ b/lib/python/tests/test_container.py
@@ -94,7 +94,7 @@ class ContainerTest(unittest.TestCase):
                                         ]),
                             hash="f829dd8a884182d0da846f365dee1221fd16610a14c81b8f9f295ff162749e50")
             # mock
-            mock.get('/api/container/1', json=exp.model_dump_json())
+            mock.get('/api/container/1', json=exp.model_dump())
             # test
             response = RestClient().get_container(container_id=1)
             self.assertEqual(exp, response)
diff --git a/lib/python/tests/test_database.py b/lib/python/tests/test_database.py
index 9df3a11d5a..017a17445a 100644
--- a/lib/python/tests/test_database.py
+++ b/lib/python/tests/test_database.py
@@ -110,7 +110,7 @@ class DatabaseTest(unittest.TestCase):
         )
         with requests_mock.Mocker() as mock:
             # mock
-            mock.get('/api/database/1', json=exp.model_dump_json())
+            mock.get('/api/database/1', json=exp.model_dump())
             # test
             response = RestClient().get_database(1)
             self.assertEqual(exp, response)
@@ -178,7 +178,7 @@ class DatabaseTest(unittest.TestCase):
         )
         with requests_mock.Mocker() as mock:
             # mock
-            mock.post('/api/database', json=exp.model_dump_json(), status_code=201)
+            mock.post('/api/database', json=exp.model_dump(), status_code=201)
             # test
             client = RestClient(username="a", password="b")
             response = client.create_database(name='test', container_id=1, is_public=True)
@@ -253,7 +253,7 @@ class DatabaseTest(unittest.TestCase):
         )
         with requests_mock.Mocker() as mock:
             # mock
-            mock.put('/api/database/1', json=exp.model_dump_json(), status_code=202)
+            mock.put('/api/database/1', json=exp.model_dump(), status_code=202)
             # test
             client = RestClient(username="a", password="b")
             response = client.update_database_visibility(database_id=1, is_public=True)
@@ -328,7 +328,7 @@ class DatabaseTest(unittest.TestCase):
         )
         with requests_mock.Mocker() as mock:
             # mock
-            mock.put('/api/database/1/owner', json=exp.model_dump_json(), status_code=202)
+            mock.put('/api/database/1/owner', json=exp.model_dump(), status_code=202)
             # test
             client = RestClient(username="a", password="b")
             response = client.update_database_owner(database_id=1, user_id='abdbf897-e599-4e5a-a3f0-7529884ea011')
@@ -376,7 +376,7 @@ class DatabaseTest(unittest.TestCase):
                                        attributes=UserAttributes(theme='light')))
         with requests_mock.Mocker() as mock:
             # mock
-            mock.get('/api/database/1/access', json=exp.model_dump_json())
+            mock.get('/api/database/1/access', json=exp.model_dump())
             # test
             response = RestClient().get_database_access(database_id=1)
             self.assertEqual(response, AccessType.READ)
@@ -408,7 +408,7 @@ class DatabaseTest(unittest.TestCase):
                                        attributes=UserAttributes(theme='light')))
         with requests_mock.Mocker() as mock:
             # mock
-            mock.post('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', json=exp.model_dump_json(),
+            mock.post('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', json=exp.model_dump(),
                       status_code=202)
             # test
             client = RestClient(username="a", password="b")
@@ -470,7 +470,7 @@ class DatabaseTest(unittest.TestCase):
                                        attributes=UserAttributes(theme='light')))
         with requests_mock.Mocker() as mock:
             # mock
-            mock.put('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', json=exp.model_dump_json(),
+            mock.put('/api/database/1/access/abdbf897-e599-4e5a-a3f0-7529884ea011', json=exp.model_dump(),
                      status_code=202)
             # test
             client = RestClient(username="a", password="b")
diff --git a/lib/python/tests/test_identifier.py b/lib/python/tests/test_identifier.py
index c39e7a99a5..64ebfe1f51 100644
--- a/lib/python/tests/test_identifier.py
+++ b/lib/python/tests/test_identifier.py
@@ -34,7 +34,7 @@ class IdentifierTest(unittest.TestCase):
                                                    type=RelatedIdentifierType.DOI)],
                              creators=[IdentifierCreator(id=5, creator_name='Carberry, Josiah')])
             # mock
-            mock.post('/api/identifier', json=exp.model_dump_json(), status_code=201)
+            mock.post('/api/identifier', json=exp.model_dump(), status_code=201)
             # test
             client = RestClient(username="a", password="b")
             response = client.create_identifier(database_id=1, type=IdentifierType.VIEW,
@@ -137,7 +137,7 @@ class IdentifierTest(unittest.TestCase):
                              creators=[IdentifierCreator(id=5, creator_name='Carberry, Josiah',
                                                          name_identifier='https://orcid.org/0000-0002-1825-0097')])
             # mock
-            mock.get('/api/identifier?url=https://orcid.org/0000-0002-1825-0097', json=exp.model_dump_json())
+            mock.get('/api/identifier?url=https://orcid.org/0000-0002-1825-0097', json=exp.model_dump())
             # test
             response = RestClient().suggest_identifier("https://orcid.org/0000-0002-1825-0097")
             self.assertEqual(exp, response)
diff --git a/lib/python/tests/test_query.py b/lib/python/tests/test_query.py
index 76bd95d8d3..0d75b8afc5 100644
--- a/lib/python/tests/test_query.py
+++ b/lib/python/tests/test_query.py
@@ -1,9 +1,12 @@
 import unittest
+from json import dumps
+from typing import Any
 
 import requests_mock
 import datetime
 
 from dbrepo.RestClient import RestClient
+from pandas import DataFrame
 
 from dbrepo.api.dto import Result, Query, User, UserAttributes, QueryType
 from dbrepo.api.exceptions import MalformedError, NotExistsError, ForbiddenError, QueryStoreError, \
@@ -18,7 +21,7 @@ class QueryTest(unittest.TestCase):
                          headers=[{'id': 0, 'username': 1}],
                          id=None)
             # mock
-            mock.post('/api/database/1/query', json=exp.model_dump_json(), status_code=202)
+            mock.post('/api/database/1/query', json=exp.model_dump(), status_code=202)
             # test
             client = RestClient(username="a", password="b")
             response = client.execute_query(database_id=1, page=0, size=10,
@@ -114,7 +117,7 @@ class QueryTest(unittest.TestCase):
                         result_number=None,
                         identifiers=[])
             # mock
-            mock.get('/api/database/1/query/6', json=exp.model_dump_json())
+            mock.get('/api/database/1/query/6', json=exp.model_dump())
             # test
             response = RestClient().get_query(database_id=1, query_id=6)
             self.assertEqual(exp, response)
@@ -237,11 +240,24 @@ class QueryTest(unittest.TestCase):
                          headers=[{'id': 0, 'username': 1}],
                          id=6)
             # mock
-            mock.get('/api/database/1/query/6/data', json=exp.model_dump_json())
+            mock.get('/api/database/1/query/6/data', json=exp.model_dump())
             # test
             response = RestClient().get_query_data(database_id=1, query_id=6)
             self.assertEqual(exp, response)
 
+    def test_get_query_data_dataframe_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}],
+                         headers=[{'id': 0, 'username': 1}],
+                         id=6)
+            exp = DataFrame.from_records(res.model_dump()['result'])
+            # mock
+            mock.get('/api/database/1/query/6/data', json=res.model_dump())
+            # test
+            response = RestClient().get_query_data(database_id=1, query_id=6, df=True)
+            self.assertEqual(exp.shape, response.shape)
+            self.assertTrue(DataFrame.equals(exp, response))
+
     def test_get_query_data_not_allowed_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
diff --git a/lib/python/tests/test_table.py b/lib/python/tests/test_table.py
index 68f947386e..286d908c91 100644
--- a/lib/python/tests/test_table.py
+++ b/lib/python/tests/test_table.py
@@ -1,9 +1,11 @@
 import unittest
+from json import dumps
 
 import requests_mock
 import datetime
 
 from dbrepo.RestClient import RestClient
+from pandas import DataFrame
 
 from dbrepo.api.dto import Table, CreateTableConstraints, UserAttributes, User, Column, Constraints, ColumnType, Result, \
     Concept, Unit, TableStatistics, ColumnStatistic
@@ -42,7 +44,7 @@ class TableTest(unittest.TestCase):
                                     is_null_allowed=False)])
         with requests_mock.Mocker() as mock:
             # mock
-            mock.post('/api/database/1/table', json=exp.model_dump_json(), status_code=201)
+            mock.post('/api/database/1/table', json=exp.model_dump(), status_code=201)
             # test
             client = RestClient(username="a", password="b")
             response = client.create_table(database_id=1, name="Test", description="Test Table", columns=[],
@@ -179,7 +181,7 @@ class TableTest(unittest.TestCase):
                                         is_public=True,
                                         is_null_allowed=False)])
             # mock
-            mock.get('/api/database/1/table/2', json=exp.model_dump_json())
+            mock.get('/api/database/1/table/2', json=exp.model_dump())
             # test
             response = RestClient().get_table(database_id=1, table_id=2)
             self.assertEqual(exp, response)
@@ -250,11 +252,24 @@ class TableTest(unittest.TestCase):
                          headers=[{'id': 0, 'username': 1}],
                          id=None)
             # mock
-            mock.get('/api/database/1/table/9/data', json=exp.model_dump_json())
+            mock.get('/api/database/1/table/9/data', json=exp.model_dump())
             # test
             response = RestClient().get_table_data(database_id=1, table_id=9)
             self.assertEqual(exp, response)
 
+    def test_get_table_data_dataframe_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}],
+                         headers=[{'id': 0, 'username': 1}],
+                         id=None)
+            exp = DataFrame.from_records(res.model_dump()['result'])
+            # mock
+            mock.get('/api/database/1/table/9/data', json=res.model_dump())
+            # test
+            response = RestClient().get_table_data(database_id=1, table_id=9, df=True)
+            self.assertEqual(exp.shape, response.shape)
+            self.assertTrue(DataFrame.equals(exp, response))
+
     def test_get_table_data_malformed_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
@@ -558,7 +573,7 @@ class TableTest(unittest.TestCase):
                                    name="liters per square meter"),
                          is_null_allowed=False)
             # mock
-            mock.put('/api/database/1/table/2/column/1', json=exp.model_dump_json(), status_code=202)
+            mock.put('/api/database/1/table/2/column/1', json=exp.model_dump(), status_code=202)
             # test
             client = RestClient(username="a", password="b")
             response = client.update_table_column(database_id=1, table_id=2, column_id=1,
@@ -622,7 +637,7 @@ class TableTest(unittest.TestCase):
             exp = TableStatistics(
                 columns={"id": ColumnStatistic(val_min=1.0, val_max=9.0, mean=5.0, median=5.0, std_dev=2.73)})
             # mock
-            mock.get('/api/analyse/database/1/table/2/statistics', json=exp.model_dump_json(), status_code=202)
+            mock.get('/api/analyse/database/1/table/2/statistics', json=exp.model_dump(), status_code=202)
             # test
             response = RestClient().analyse_table_statistics(database_id=1, table_id=2)
             self.assertEqual(exp, response)
diff --git a/lib/python/tests/test_user.py b/lib/python/tests/test_user.py
index d5afaf2d01..67698a8fd5 100644
--- a/lib/python/tests/test_user.py
+++ b/lib/python/tests/test_user.py
@@ -53,7 +53,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=201)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=201)
             # test
             response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
             self.assertEqual(exp, response)
@@ -62,7 +62,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=400)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=400)
             # test
             try:
                 response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
@@ -73,7 +73,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=403)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=403)
             # test
             try:
                 response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
@@ -84,7 +84,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=409)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=409)
             # test
             try:
                 response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
@@ -95,7 +95,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=404)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=404)
             # test
             try:
                 response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
@@ -106,7 +106,7 @@ class UserTest(unittest.TestCase):
         with requests_mock.Mocker() as mock:
             exp = UserBrief(id='8638c043-5145-4be8-a3e4-4b79991b0a16', username='mweise')
             # mock
-            mock.post('http://gateway-service/api/user', json=exp.model_dump_json(), status_code=417)
+            mock.post('http://gateway-service/api/user', json=exp.model_dump(), status_code=417)
             # test
             try:
                 response = RestClient().create_user(username='mweise', password='s3cr3t', email='mweise@example.com')
@@ -119,7 +119,7 @@ class UserTest(unittest.TestCase):
                        attributes=UserAttributes(theme='dark'))
             # mock
             mock.get('http://gateway-service/api/user/8638c043-5145-4be8-a3e4-4b79991b0a16',
-                     json=exp.model_dump_json())
+                     json=exp.model_dump())
             # test
             response = RestClient().get_user(user_id='8638c043-5145-4be8-a3e4-4b79991b0a16')
             self.assertEqual(exp, response)
@@ -140,7 +140,7 @@ class UserTest(unittest.TestCase):
                        attributes=UserAttributes(theme='dark'))
             # mock
             mock.put('http://gateway-service/api/user/8638c043-5145-4be8-a3e4-4b79991b0a16', status_code=202,
-                     json=exp.model_dump_json())
+                     json=exp.model_dump())
             # test
             client = RestClient(username="a", password="b")
             response = client.update_user(user_id='8638c043-5145-4be8-a3e4-4b79991b0a16', firstname='Martin')
@@ -195,7 +195,7 @@ class UserTest(unittest.TestCase):
                        attributes=UserAttributes(theme='dark'))
             # mock
             mock.put('http://gateway-service/api/user/8638c043-5145-4be8-a3e4-4b79991b0a16/theme', status_code=202,
-                     json=exp.model_dump_json())
+                     json=exp.model_dump())
             # test
             client = RestClient(username="a", password="b")
             response = client.update_user_theme(user_id='8638c043-5145-4be8-a3e4-4b79991b0a16', theme='dark')
@@ -250,7 +250,7 @@ class UserTest(unittest.TestCase):
                        attributes=UserAttributes(theme='dark'))
             # mock
             mock.put('http://gateway-service/api/user/8638c043-5145-4be8-a3e4-4b79991b0a16/password', status_code=202,
-                     json=exp.model_dump_json())
+                     json=exp.model_dump())
             # test
             client = RestClient(username="a", password="b")
             response = client.update_user_password(user_id='8638c043-5145-4be8-a3e4-4b79991b0a16',
diff --git a/lib/python/tests/test_view.py b/lib/python/tests/test_view.py
index 16eb6f4c51..2a61cab89e 100644
--- a/lib/python/tests/test_view.py
+++ b/lib/python/tests/test_view.py
@@ -1,9 +1,11 @@
 import unittest
+from json import dumps
 
 import requests_mock
 import datetime
 
 from dbrepo.RestClient import RestClient
+from pandas import DataFrame
 
 from dbrepo.api.dto import UserAttributes, User, View, Result
 from dbrepo.api.exceptions import ForbiddenError, NotExistsError, MalformedError, AuthenticationError
@@ -76,7 +78,7 @@ class ViewTest(unittest.TestCase):
                        last_modified=datetime.datetime(2024, 1, 1, 0, 0, 0, 0, datetime.timezone.utc),
                        identifiers=[])
             # mock
-            mock.get('/api/database/1/view/3', json=exp.model_dump_json())
+            mock.get('/api/database/1/view/3', json=exp.model_dump())
             # test
             response = RestClient().get_view(database_id=1, view_id=3)
             self.assertEqual(exp, response)
@@ -117,7 +119,7 @@ class ViewTest(unittest.TestCase):
                        last_modified=datetime.datetime(2024, 1, 1, 0, 0, 0, 0, datetime.timezone.utc),
                        identifiers=[])
             # mock
-            mock.post('/api/database/1/view', json=exp.model_dump_json(), status_code=201)
+            mock.post('/api/database/1/view', json=exp.model_dump(), status_code=201)
             # test
             client = RestClient(username="a", password="b")
             response = client.create_view(database_id=1, name="Data", is_public=True,
@@ -217,11 +219,24 @@ class ViewTest(unittest.TestCase):
                          headers=[{'id': 0, 'username': 1}],
                          id=None)
             # mock
-            mock.get('/api/database/1/view/3/data', json=exp.model_dump_json())
+            mock.get('/api/database/1/view/3/data', json=exp.model_dump())
             # test
             response = RestClient().get_view_data(database_id=1, view_id=3)
             self.assertEqual(exp, response)
 
+    def test_get_view_data_dataframe_succeeds(self):
+        with requests_mock.Mocker() as mock:
+            res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}],
+                         headers=[{'id': 0, 'username': 1}],
+                         id=None)
+            exp = DataFrame.from_records(res.model_dump()['result'])
+            # mock
+            mock.get('/api/database/1/view/3/data', json=res.model_dump())
+            # test
+            response: DataFrame = RestClient().get_view_data(database_id=1, view_id=3, df=True)
+            self.assertEqual(exp.shape, response.shape)
+            self.assertTrue(DataFrame.equals(exp, response))
+
     def test_get_view_data_malformed_fails(self):
         with requests_mock.Mocker() as mock:
             # mock
-- 
GitLab