Repository: SneaksAndData/adapta (language: python)

Issue:
[FEATURE] Standalone Delta API
**Is your feature request related to a problem? Please describe.**
We are currently forced into Spark runtime when working with Delta tables.

**Describe the solution you'd like**
Would be nice to have standalone reader that supports conversion to Pandas dataframes. Like this one https://github.com/delta-io/delta-rs


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 6a47b760..4e8985cd 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -42,7 +42,12 @@ jobs:
           pypath=$(pwd)
           export PYTHONPATH="$pypath:$PYTHONPATH"
 
-          poetry run pytest ./tests
+          poetry run pytest ./tests --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=term-missing:skip-covered | tee pytest-coverage.txt
+      - name: Publish Code Coverage
+        uses: MishaKav/pytest-coverage-comment@main
+        with:
+          pytest-coverage-path: ./pytest-coverage.txt
+          junitxml-path: ./junit/test-results.xml
 
   create_release:
     name: Create Release
@@ -56,7 +61,7 @@ jobs:
       - name: Create Release
         env:
           MAJOR_V: "0"
-          MINOR_V: "1"
+          MINOR_V: "2"
           GITHUB_TOKEN: ${{ github.token }}
         run: |
           set -e
diff --git a/README.md b/README.md
index d52e746b..96b9832e 100644
--- a/README.md
+++ b/README.md
@@ -5,3 +5,9 @@ This project aim at providing tools needed for everyday activities of data scien
 - Logging framework
 - Metrics reporting framework
 - Storage drivers for various clouds and storage types
+
+## Delta Lake
+
+This module provides basic Delta Lake operations without Spark session, based on [delta-rs](https://github.com/delta-io/delta-rs) project.
+
+Please refer to the [module](proteus/storage/delta_lake/README.md) documentation for examples.
diff --git a/poetry.lock b/poetry.lock
index df7baeb5..98ade9ce 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -34,11 +34,19 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
 tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
 tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
 
+[[package]]
+name = "azure-common"
+version = "1.1.28"
+description = "Microsoft Azure Client Library for Python (Common)"
+category = "main"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "azure-core"
 version = "1.23.0"
 description = "Microsoft Azure Core Library for Python"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 
@@ -51,7 +59,7 @@ typing-extensions = ">=4.0.1"
 name = "azure-identity"
 version = "1.7.1"
 description = "Microsoft Azure Identity Library for Python"
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -62,11 +70,48 @@ msal = ">=1.12.0,<2.0.0"
 msal-extensions = ">=0.3.0,<0.4.0"
 six = ">=1.12.0"
 
+[[package]]
+name = "azure-mgmt-core"
+version = "1.3.0"
+description = "Microsoft Azure Management Core Library for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+azure-core = ">=1.15.0,<2.0.0"
+
+[[package]]
+name = "azure-mgmt-storage"
+version = "19.1.0"
+description = "Microsoft Azure Storage Management Client Library for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+azure-common = ">=1.1,<2.0"
+azure-mgmt-core = ">=1.3.0,<2.0.0"
+msrest = ">=0.6.21"
+
+[[package]]
+name = "azure-storage-blob"
+version = "12.10.0"
+description = "Microsoft Azure Blob Storage Client Library for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+azure-core = ">=1.15.0,<2.0.0"
+cryptography = ">=2.1.4"
+msrest = ">=0.6.21"
+
 [[package]]
 name = "certifi"
 version = "2021.10.8"
 description = "Python package for providing Mozilla's CA Bundle."
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -74,7 +119,7 @@ python-versions = "*"
 name = "cffi"
 version = "1.15.0"
 description = "Foreign Function Interface for Python calling C code."
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -85,7 +130,7 @@ pycparser = "*"
 name = "charset-normalizer"
 version = "2.0.12"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.5.0"
 
@@ -100,11 +145,22 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
+[[package]]
+name = "coverage"
+version = "6.3.2"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+toml = ["tomli"]
+
 [[package]]
 name = "cryptography"
-version = "36.0.1"
+version = "36.0.2"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 
@@ -119,11 +175,26 @@ sdist = ["setuptools_rust (>=0.11.4)"]
 ssh = ["bcrypt (>=3.1.5)"]
 test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
 
+[[package]]
+name = "deltalake"
+version = "0.5.6"
+description = "Native Delta Lake Python binding based on delta-rs with Pandas integration"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyarrow = ">=4"
+
+[package.extras]
+pandas = ["types-dataclasses", "pandas"]
+devel = ["mypy", "black", "isort", "pytest", "pytest-mock", "pytest-cov", "pytest-timeout", "sphinx", "sphinx-rtd-theme", "toml", "pandas"]
+
 [[package]]
 name = "idna"
 version = "3.3"
 description = "Internationalized Domain Names in Applications (IDNA)"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.5"
 
@@ -135,6 +206,17 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "isodate"
+version = "0.6.1"
+description = "An ISO 8601 date/time/duration parser and formatter"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
 [[package]]
 name = "isort"
 version = "5.10.1"
@@ -169,7 +251,7 @@ python-versions = "*"
 name = "msal"
 version = "1.17.0"
 description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect."
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -182,7 +264,7 @@ requests = ">=2.0.0,<3"
 name = "msal-extensions"
 version = "0.3.1"
 description = ""
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -193,6 +275,44 @@ portalocker = [
     {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""},
 ]
 
+[[package]]
+name = "msrest"
+version = "0.6.21"
+description = "AutoRest swagger generator Python client runtime."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+isodate = ">=0.6.0"
+requests = ">=2.16,<3.0"
+requests-oauthlib = ">=0.5.0"
+
+[package.extras]
+async = ["aiohttp (>=3.0)", "aiodns"]
+
+[[package]]
+name = "numpy"
+version = "1.22.3"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[[package]]
+name = "oauthlib"
+version = "3.2.0"
+description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+rsa = ["cryptography (>=3.0.0)"]
+signals = ["blinker (>=1.4.0)"]
+signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
+
 [[package]]
 name = "packaging"
 version = "21.3"
@@ -204,6 +324,27 @@ python-versions = ">=3.6"
 [package.dependencies]
 pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
+[[package]]
+name = "pandas"
+version = "1.4.1"
+description = "Powerful data structures for data analysis, time series, and statistics"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = [
+    {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
+    {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""},
+    {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""},
+    {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
+]
+python-dateutil = ">=2.8.1"
+pytz = ">=2020.1"
+
+[package.extras]
+test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
+
 [[package]]
 name = "platformdirs"
 version = "2.5.1"
@@ -232,7 +373,7 @@ testing = ["pytest", "pytest-benchmark"]
 name = "portalocker"
 version = "2.4.0"
 description = "Wraps the portalocker recipe for easy usage"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.5"
 
@@ -252,11 +393,22 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
+[[package]]
+name = "pyarrow"
+version = "7.0.0"
+description = "Python library for Apache Arrow"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+numpy = ">=1.16.6"
+
 [[package]]
 name = "pycparser"
 version = "2.21"
 description = "C parser in Python"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
@@ -264,7 +416,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 name = "pyjwt"
 version = "2.3.0"
 description = "JSON Web Token implementation in Python"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 
@@ -326,11 +478,46 @@ toml = "*"
 [package.extras]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
+[[package]]
+name = "pytest-cov"
+version = "2.12.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+coverage = ">=5.2.1"
+pytest = ">=4.6"
+toml = "*"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2022.1"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "pywin32"
 version = "303"
 description = "Python for Window Extensions"
-category = "dev"
+category = "main"
 optional = false
 python-versions = "*"
 
@@ -338,7 +525,7 @@ python-versions = "*"
 name = "requests"
 version = "2.27.1"
 description = "Python HTTP for Humans."
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 
@@ -352,9 +539,24 @@ urllib3 = ">=1.21.1,<1.27"
 socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 
+[[package]]
+name = "requests-oauthlib"
+version = "1.3.1"
+description = "OAuthlib authentication support for Requests."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+oauthlib = ">=3.0.0"
+requests = ">=2.0.0"
+
+[package.extras]
+rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
+
 [[package]]
 name = "setuptools"
-version = "60.9.3"
+version = "60.10.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "dev"
 optional = false
@@ -362,14 +564,14 @@ python-versions = ">=3.7"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier", "furo"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-enabler (>=1.0.1)", "pytest-perf", "mock", "flake8-2020", "virtualenv (>=13.0.0)", "wheel", "pip (>=19.1)", "jaraco.envs (>=2.2)", "pytest-xdist", "sphinx (>=4.3.2)", "jaraco.path (>=3.2.0)", "build", "filelock (>=3.4.0)", "pip-run (>=8.8)", "pytest-black (>=0.3.7)", "pytest-cov", "pytest-mypy (>=0.9.1)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-enabler (>=1.0.1)", "pytest-perf", "mock", "flake8-2020", "virtualenv (>=13.0.0)", "wheel", "pip (>=19.1)", "jaraco.envs (>=2.2)", "pytest-xdist", "jaraco.path (>=3.2.0)", "build", "filelock (>=3.4.0)", "pip-run (>=8.8)", "pytest-black (>=0.3.7)", "pytest-cov", "pytest-mypy (>=0.9.1)"]
 testing-integration = ["pytest", "pytest-xdist", "pytest-enabler", "virtualenv (>=13.0.0)", "tomli", "wheel", "jaraco.path (>=3.2.0)", "jaraco.envs (>=2.2)", "build", "filelock (>=3.4.0)"]
 
 [[package]]
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 
@@ -385,20 +587,20 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 name = "typing-extensions"
 version = "4.1.1"
 description = "Backported and Experimental Type Hints for Python 3.6+"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 
 [[package]]
 name = "urllib3"
-version = "1.26.8"
+version = "1.26.9"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
 
 [package.extras]
-brotli = ["brotlipy (>=0.6.0)"]
+brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
 secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
@@ -413,7 +615,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "e66b1ee3b539c0f5f66c4bf37ae768180f43445fd27b85e80ae2d1bb8bd98a09"
+content-hash = "b347803fec8eb801a555248394e0f71b9e7b2470d63be4b29772235039a99d23"
 
 [metadata.files]
 astroid = [
@@ -428,6 +630,10 @@ attrs = [
     {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
     {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
+azure-common = [
+    {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"},
+    {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"},
+]
 azure-core = [
     {file = "azure-core-1.23.0.zip", hash = "sha256:a56a6f720d0948d3f3e4a25a5fe46df2f1b7f865c358d74e2ce47dbb49262608"},
     {file = "azure_core-1.23.0-py3-none-any.whl", hash = "sha256:23c1389a115c328878c4eface3ca3899c2468313ea6f883f2347d6924cd887b2"},
@@ -436,6 +642,18 @@ azure-identity = [
     {file = "azure-identity-1.7.1.zip", hash = "sha256:7f22cd0c7a9b92ed297dd67ae79d9bb9a866e404061c02cec709ad10c4c88e19"},
     {file = "azure_identity-1.7.1-py2.py3-none-any.whl", hash = "sha256:454e16ed1152b4fd3fb463f4b4e2f7a3fc3a862b0ca28010bff6d5c6b2b0c50f"},
 ]
+azure-mgmt-core = [
+    {file = "azure-mgmt-core-1.3.0.zip", hash = "sha256:3ffb7352b39e5495dccc2d2b47254f4d82747aff4735e8bf3267c335b0c9bb40"},
+    {file = "azure_mgmt_core-1.3.0-py2.py3-none-any.whl", hash = "sha256:7b7fa952aeb9d3eaa13eff905880f3d3b62200f7be7a8ba5a50c8b2e7295322a"},
+]
+azure-mgmt-storage = [
+    {file = "azure-mgmt-storage-19.1.0.zip", hash = "sha256:49ea22f00e0965a3550af34a41c1a1d3a481690f6500c78e85408802f56d7416"},
+    {file = "azure_mgmt_storage-19.1.0-py3-none-any.whl", hash = "sha256:61c7a55395e7410a24bfc8def353429eb772a105dd8268dce91f5ee38e4fc04e"},
+]
+azure-storage-blob = [
+    {file = "azure-storage-blob-12.10.0.zip", hash = "sha256:3c7dc2c93e7ff2a731acd66a36a1f0a6266072b4154deba4894dab891285ea3a"},
+    {file = "azure_storage_blob-12.10.0-py3-none-any.whl", hash = "sha256:a70995c4f9310eb704594f30505d1499286b4caac5543a2ebfe84431c4a38b0b"},
+]
 certifi = [
     {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
     {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
@@ -500,27 +718,77 @@ colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
+coverage = [
+    {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"},
+    {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"},
+    {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"},
+    {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"},
+    {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"},
+    {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"},
+    {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"},
+    {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"},
+    {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"},
+    {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"},
+    {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"},
+    {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"},
+    {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"},
+    {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"},
+    {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"},
+    {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"},
+    {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"},
+    {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"},
+    {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"},
+    {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"},
+    {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"},
+    {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"},
+    {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"},
+    {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"},
+    {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"},
+    {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"},
+    {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"},
+    {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"},
+    {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"},
+    {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"},
+    {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"},
+    {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"},
+    {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"},
+    {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"},
+    {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"},
+    {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"},
+    {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"},
+    {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"},
+    {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"},
+    {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"},
+    {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"},
+]
 cryptography = [
-    {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
-    {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"},
-    {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"},
-    {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"},
-    {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"},
-    {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"},
-    {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"},
-    {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"},
-    {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"},
-    {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"},
-    {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"},
-    {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"},
-    {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"},
-    {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"},
-    {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"},
-    {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"},
-    {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"},
-    {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"},
-    {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"},
-    {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"},
+    {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
+    {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"},
+    {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"},
+    {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"},
+    {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"},
+    {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"},
+    {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"},
+    {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"},
+    {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"},
+    {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"},
+    {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"},
+    {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"},
+    {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"},
+    {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"},
+    {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"},
+    {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"},
+    {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"},
+    {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"},
+    {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"},
+    {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
+]
+deltalake = [
+    {file = "deltalake-0.5.6-cp36-abi3-macosx_10_7_x86_64.whl", hash = "sha256:eb419eac9382fd01390f091215e31c5c3bb4d29e421ffc94a55cd306805b1dfa"},
+    {file = "deltalake-0.5.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:3128d6df02ea2a82b2227195743f305f3bac66f308848bb58ba10a79b8565d7a"},
+    {file = "deltalake-0.5.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af2753e5c5f5213c77bb0b0cb18865fb69cf68be500bac3839219fe846d935b4"},
+    {file = "deltalake-0.5.6-cp36-abi3-win_amd64.whl", hash = "sha256:7f9029a741522d8c8ef58e41d6ecc4541d8fa2f253248793fc24db4fc755739c"},
+    {file = "deltalake-0.5.6.tar.gz", hash = "sha256:e36f45bfa7477ce16809f1e0baddd2672bf02f5c85d7d136a5d4508ed521054b"},
 ]
 idna = [
     {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
@@ -530,6 +798,10 @@ iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
+isodate = [
+    {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"},
+    {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"},
+]
 isort = [
     {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
     {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
@@ -585,10 +857,62 @@ msal-extensions = [
     {file = "msal-extensions-0.3.1.tar.gz", hash = "sha256:d9029af70f2cbdc5ad7ecfed61cb432ebe900484843ccf72825445dbfe62d311"},
     {file = "msal_extensions-0.3.1-py2.py3-none-any.whl", hash = "sha256:89df9c0237e1adf16938fa58575db59c2bb9de04a83ffb0452c8dfc79031f717"},
 ]
+msrest = [
+    {file = "msrest-0.6.21-py2.py3-none-any.whl", hash = "sha256:c840511c845330e96886011a236440fafc2c9aff7b2df9c0a92041ee2dee3782"},
+    {file = "msrest-0.6.21.tar.gz", hash = "sha256:72661bc7bedc2dc2040e8f170b6e9ef226ee6d3892e01affd4d26b06474d68d8"},
+]
+numpy = [
+    {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"},
+    {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"},
+    {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"},
+    {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"},
+    {file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"},
+    {file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"},
+    {file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"},
+    {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"},
+    {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"},
+    {file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"},
+    {file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"},
+    {file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"},
+    {file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"},
+    {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"},
+    {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"},
+    {file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"},
+    {file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"},
+    {file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"},
+    {file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"},
+]
+oauthlib = [
+    {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"},
+    {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"},
+]
 packaging = [
     {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
     {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
+pandas = [
+    {file = "pandas-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3dfb32ed50122fe8c5e7f2b8d97387edd742cc78f9ec36f007ee126cd3720907"},
+    {file = "pandas-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0259cd11e7e6125aaea3af823b80444f3adad6149ff4c97fef760093598b3e34"},
+    {file = "pandas-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96e9ece5759f9b47ae43794b6359bbc54805d76e573b161ae770c1ea59393106"},
+    {file = "pandas-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508c99debccd15790d526ce6b1624b97a5e1e4ca5b871319fb0ebfd46b8f4dad"},
+    {file = "pandas-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6a7bbbb7950063bfc942f8794bc3e31697c020a14f1cd8905fc1d28ec674a01"},
+    {file = "pandas-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:c614001129b2a5add5e3677c3a213a9e6fd376204cb8d17c04e84ff7dfc02a73"},
+    {file = "pandas-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4e1176f45981c8ccc8161bc036916c004ca51037a7ed73f2d2a9857e6dbe654f"},
+    {file = "pandas-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bbb15ad79050e8b8d39ec40dd96a30cd09b886a2ae8848d0df1abba4d5502a67"},
+    {file = "pandas-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6d6ad1da00c7cc7d8dd1559a6ba59ba3973be6b15722d49738b2be0977eb8a0c"},
+    {file = "pandas-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:358b0bc98a5ff067132d23bf7a2242ee95db9ea5b7bbc401cf79205f11502fd3"},
+    {file = "pandas-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6105af6533f8b63a43ea9f08a2ede04e8f43e49daef0209ab0d30352bcf08bee"},
+    {file = "pandas-1.4.1-cp38-cp38-win32.whl", hash = "sha256:04dd15d9db538470900c851498e532ef28d4e56bfe72c9523acb32042de43dfb"},
+    {file = "pandas-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b384516dbb4e6aae30e3464c2e77c563da5980440fbdfbd0968e3942f8f9d70"},
+    {file = "pandas-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f02e85e6d832be37d7f16cf6ac8bb26b519ace3e5f3235564a91c7f658ab2a43"},
+    {file = "pandas-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0b1a13f647e4209ed7dbb5da3497891d0045da9785327530ab696417ef478f84"},
+    {file = "pandas-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19f7c632436b1b4f84615c3b127bbd7bc603db95e3d4332ed259dc815c9aaa26"},
+    {file = "pandas-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ea47ba1d6f359680130bd29af497333be6110de8f4c35b9211eec5a5a9630fa"},
+    {file = "pandas-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e5a7a1e0ecaac652326af627a3eca84886da9e667d68286866d4e33f6547caf"},
+    {file = "pandas-1.4.1-cp39-cp39-win32.whl", hash = "sha256:1d85d5f6be66dfd6d1d8d13b9535e342a2214260f1852654b19fa4d7b8d1218b"},
+    {file = "pandas-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3129a35d9dad1d80c234dd78f8f03141b914395d23f97cf92a366dcd19f8f8bf"},
+    {file = "pandas-1.4.1.tar.gz", hash = "sha256:8db93ec98ac7cb5f8ac1420c10f5e3c43533153f253fe7fb6d891cf5aa2b80d2"},
+]
 platformdirs = [
     {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
     {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
@@ -605,6 +929,38 @@ py = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
+pyarrow = [
+    {file = "pyarrow-7.0.0-cp310-cp310-macosx_10_13_universal2.whl", hash = "sha256:0f15213f380539c9640cb2413dc677b55e70f04c9e98cfc2e1d8b36c770e1036"},
+    {file = "pyarrow-7.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:29c4e3b3be0b94d07ff4921a5e410fc690a3a066a850a302fc504de5fc638495"},
+    {file = "pyarrow-7.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a9bfc8a016bcb8f9a8536d2fa14a890b340bc7a236275cd60fd4fb8b93ff405"},
+    {file = "pyarrow-7.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:49d431ed644a3e8f53ae2bbf4b514743570b495b5829548db51610534b6eeee7"},
+    {file = "pyarrow-7.0.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa6442a321c1e49480b3d436f7d631c895048a16df572cf71c23c6b53c45ed66"},
+    {file = "pyarrow-7.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b01a23cb401750092c6f7c4dcae67cd8fd6b99ae710e26f654f23508f25f25"},
+    {file = "pyarrow-7.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f10928745c6ff66e121552731409803bed86c66ac79c64c90438b053b5242c5"},
+    {file = "pyarrow-7.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:759090caa1474cafb5e68c93a9bd6cb45d8bb8e4f2cad2f1a0cc9439bae8ae88"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e3fe34bcfc28d9c4a747adc3926d2307a04c5c50b89155946739515ccfe5eab0"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:040dce5345603e4e621bcf4f3b21f18d557852e7b15307e559bb14c8951c8714"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ed4b647c3345ae3463d341a9d28d0260cd302fb92ecf4e2e3e0f1656d6e0e55c"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7fecd5d5604f47e003f50887a42aee06cb8b7bf8e8bf7dc543a22331d9ba832"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f2d00b892fe865e43346acb78761ba268f8bb1cbdba588816590abcb780ee3d"},
+    {file = "pyarrow-7.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f439f7d77201681fd31391d189aa6b1322d27c9311a8f2fce7d23972471b02b6"},
+    {file = "pyarrow-7.0.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:3e06b0e29ce1e32f219c670c6b31c33d25a5b8e29c7828f873373aab78bf30a5"},
+    {file = "pyarrow-7.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:13dc05bcf79dbc1bd2de1b05d26eb64824b85883d019d81ca3c2eca9b68b5a44"},
+    {file = "pyarrow-7.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06183a7ff2b0c030ec0413fc4dc98abad8cf336c78c280a0b7f4bcbebb78d125"},
+    {file = "pyarrow-7.0.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:702c5a9f960b56d03569eaaca2c1a05e8728f05ea1a2138ef64234aa53cd5884"},
+    {file = "pyarrow-7.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7313038203df77ec4092d6363dbc0945071caa72635f365f2b1ae0dd7469865"},
+    {file = "pyarrow-7.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e87d1f7dc7a0b2ecaeb0c7a883a85710f5b5626d4134454f905571c04bc73d5a"},
+    {file = "pyarrow-7.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ba69488ae25c7fde1a2ae9ea29daf04d676de8960ffd6f82e1e13ca945bb5861"},
+    {file = "pyarrow-7.0.0-cp39-cp39-macosx_10_13_universal2.whl", hash = "sha256:11a591f11d2697c751261c9d57e6e5b0d38fdc7f0cc57f4fd6edc657da7737df"},
+    {file = "pyarrow-7.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:6183c700877852dc0f8a76d4c0c2ffd803ba459e2b4a452e355c2d58d48cf39f"},
+    {file = "pyarrow-7.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1748154714b543e6ae8452a68d4af85caf5298296a7e5d4d00f1b3021838ac6"},
+    {file = "pyarrow-7.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcc8f934c7847a88f13ec35feecffb61fe63bb7a3078bd98dd353762e969ce60"},
+    {file = "pyarrow-7.0.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:759f59ac77b84878dbd54d06cf6df74ff781b8e7cf9313eeffbb5ec97b94385c"},
+    {file = "pyarrow-7.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3e3f93ac2993df9c5e1922eab7bdea047b9da918a74e52145399bc1f0099a3"},
+    {file = "pyarrow-7.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:306120af554e7e137895254a3b4741fad682875a5f6403509cd276de3fe5b844"},
+    {file = "pyarrow-7.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:087769dac6e567d58d59b94c4f866b3356c00d3db5b261387ece47e7324c2150"},
+    {file = "pyarrow-7.0.0.tar.gz", hash = "sha256:da656cad3c23a2ebb6a307ab01d35fce22f7850059cffafcb90d12590f8f4f38"},
+]
 pycparser = [
     {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
     {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
@@ -625,6 +981,18 @@ pytest = [
     {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
     {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
 ]
+pytest-cov = [
+    {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
+    {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"},
+]
+python-dateutil = [
+    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+pytz = [
+    {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
+    {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
+]
 pywin32 = [
     {file = "pywin32-303-cp310-cp310-win32.whl", hash = "sha256:6fed4af057039f309263fd3285d7b8042d41507343cd5fa781d98fcc5b90e8bb"},
     {file = "pywin32-303-cp310-cp310-win_amd64.whl", hash = "sha256:51cb52c5ec6709f96c3f26e7795b0bf169ee0d8395b2c1d7eb2c029a5008ed51"},
@@ -643,9 +1011,13 @@ requests = [
     {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
     {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
 ]
+requests-oauthlib = [
+    {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
+    {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
+]
 setuptools = [
-    {file = "setuptools-60.9.3-py3-none-any.whl", hash = "sha256:e4f30b9f84e5ab3decf945113119649fec09c1fc3507c6ebffec75646c56e62b"},
-    {file = "setuptools-60.9.3.tar.gz", hash = "sha256:2347b2b432c891a863acadca2da9ac101eae6169b1d3dfee2ec605ecd50dbfe5"},
+    {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"},
+    {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"},
 ]
 six = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -660,8 +1032,8 @@ typing-extensions = [
     {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
 ]
 urllib3 = [
-    {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
-    {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
+    {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
+    {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
 ]
 wrapt = [
     {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"},
diff --git a/proteus/connectors/beast/_models.py b/proteus/connectors/beast/_models.py
index 7e1ae899..bb878b71 100644
--- a/proteus/connectors/beast/_models.py
+++ b/proteus/connectors/beast/_models.py
@@ -23,6 +23,13 @@ class JobSocket:
     data_path: str
     data_format: str
 
+    def to_utils_format(self) -> str:
+        """
+         Converts JobSocket
+        :return:
+        """
+        return f"{self.alias}|{self.data_path}|{self.data_format}"
+
 
 class JobSize(Enum):
     """
diff --git a/proteus/security/__init__.py b/proteus/security/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/proteus/security/clients/__init__.py b/proteus/security/clients/__init__.py
new file mode 100644
index 00000000..55320386
--- /dev/null
+++ b/proteus/security/clients/__init__.py
@@ -0,0 +1,7 @@
+"""
+ Import index.
+"""
+
+from proteus.security.clients._azure_client import AzureClient
+from proteus.security.clients._local_client import LocalClient
+from proteus.security.clients._base import ProteusClient
diff --git a/proteus/security/clients/_azure_client.py b/proteus/security/clients/_azure_client.py
new file mode 100644
index 00000000..aeb2cb0c
--- /dev/null
+++ b/proteus/security/clients/_azure_client.py
@@ -0,0 +1,71 @@
+"""
+ Azure Cloud implementation of Proteus Client.
+"""
+import os
+from typing import Optional, List, Dict, Tuple
+
+from azure.mgmt.storage.v2021_08_01.models import StorageAccountKey, StorageAccount
+from azure.mgmt.storage import StorageManagementClient
+from azure.identity import DefaultAzureCredential
+
+from proteus.security.clients._base import ProteusClient
+from proteus.storage.models.azure import AdlsGen2Path
+from proteus.storage.models.base import DataPath
+
+
+class AzureClient(ProteusClient):
+    """
+     Azure Credentials provider for various Azure resources.
+    """
+
+    def __init__(self, *, subscription_id: str):
+        self.subscription_id = subscription_id
+
+    def get_access_token(self, scope: Optional[str] = None) -> str:
+        return DefaultAzureCredential(exclude_shared_token_cache_credential=True) \
+            .get_token(scope or "https://management.core.windows.net/.default").token
+
+    def connect_account(self):
+        """
+         Not used in Azure.
+        :return:
+        """
+
+    def connect_storage(self, path: DataPath, set_env: bool = False) -> Optional[Dict]:
+        def get_resource_group(account: StorageAccount) -> str:
+            return account.id.split('/')[account.id.split('/').index('resourceGroups') + 1]
+
+        assert isinstance(path, AdlsGen2Path), 'Azure Client only works with proteus.storage.models.azure.AdlsGen2Path'
+
+        adls_path: AdlsGen2Path = path
+
+        if not os.environ.get('AZURE_STORAGE_ACCOUNT_NAME', None):
+            cred = DefaultAzureCredential(exclude_shared_token_cache_credential=True)
+            storage_client = StorageManagementClient(cred, self.subscription_id)
+
+            accounts: List[Tuple[str, str]] = list(
+                map(lambda result: (get_resource_group(result), result.name), storage_client.storage_accounts.list()))
+
+            for rg, account in accounts: # pylint: disable=C0103
+                if adls_path.account == account:
+                    keys: List[StorageAccountKey] = storage_client.storage_accounts.list_keys(
+                        resource_group_name=rg,
+                        account_name=account).keys
+
+                    if set_env:
+                        os.environ.setdefault('AZURE_STORAGE_ACCOUNT_NAME', account)
+                        os.environ.setdefault('AZURE_STORAGE_ACCOUNT_KEY', keys[0].value)
+
+                        return None
+
+                    return {
+                        'AZURE_STORAGE_ACCOUNT_NAME': account,
+                        'AZURE_STORAGE_ACCOUNT_KEY': keys[0].value
+                    }
+
+            raise ValueError(f"Can't locate an account {path.account}")
+
+        return None
+
+    def get_credentials(self) -> DefaultAzureCredential:
+        return DefaultAzureCredential(exclude_shared_token_cache_credential=True)
diff --git a/proteus/security/clients/_base.py b/proteus/security/clients/_base.py
new file mode 100644
index 00000000..8991dfac
--- /dev/null
+++ b/proteus/security/clients/_base.py
@@ -0,0 +1,47 @@
+"""
+ Base client for all infrastructure providers.
+"""
+from abc import ABC, abstractmethod
+from typing import Optional, Dict
+
+from proteus.storage.models.base import DataPath
+
+
+class ProteusClient(ABC):
+    """
+     Base functionality all infrastructure providers must implement.
+    """
+
+    @abstractmethod
+    def get_credentials(self):
+        """
+         Authentication credentials getter.
+        :return:
+        """
+
+    @abstractmethod
+    def get_access_token(self, scope: Optional[str] = None) -> str:
+        """
+         If a provider uses OAuth2, it must implement this method to allow fetching access tokens on the fly.
+
+        :param scope: OAuth2 access scope.
+        :return: Access token (JWT).
+        """
+
+    @abstractmethod
+    def connect_storage(self, path: DataPath, set_env: bool = False) -> Optional[Dict]:
+        """
+         Optional method to create authenticated session for the provided path.
+
+        :param path: Data path to authenticate.
+        :param set_env: if set, saves credentials in provider-specific environment variables.
+        :return: Environment variables with credentials, if any.
+        """
+
+    @abstractmethod
+    def connect_account(self):
+        """
+         Connects infrastructure provider account, usually by setting specific environment variables.
+
+        :return:
+        """
diff --git a/proteus/security/clients/_local_client.py b/proteus/security/clients/_local_client.py
new file mode 100644
index 00000000..9c8c35ab
--- /dev/null
+++ b/proteus/security/clients/_local_client.py
@@ -0,0 +1,40 @@
+"""
+ Client representing Local infrastructure. Mainly used for unit tests.
+"""
+from typing import Optional, Dict
+
+from proteus.security.clients._base import ProteusClient
+from proteus.storage.models.base import DataPath
+
+
+class LocalClient(ProteusClient):
+    """
+     Local mode Proteus Client.
+    """
+
+    def get_credentials(self):
+        """
+         Not supported in local client.
+        :return:
+        """
+
+    def get_access_token(self, scope: Optional[str] = None) -> str:
+        """
+         Not supported in local client.
+        :param scope:
+        :return:
+        """
+
+    def connect_storage(self, path: DataPath, set_env: bool = False) -> Optional[Dict]:
+        """
+         Not supported in local client.
+        :param path:
+        :param set_env:
+        :return:
+        """
+
+    def connect_account(self):
+        """
+         Not supported in local client.
+        :return:
+        """
diff --git a/proteus/storage/__init__.py b/proteus/storage/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/proteus/storage/blob/__init__.py b/proteus/storage/blob/__init__.py
new file mode 100644
index 00000000..3f4a7855
--- /dev/null
+++ b/proteus/storage/blob/__init__.py
@@ -0,0 +1,4 @@
+"""
+ Import index.
+"""
+from proteus.storage.blob._functions import *
diff --git a/proteus/storage/blob/_functions.py b/proteus/storage/blob/_functions.py
new file mode 100644
index 00000000..a24661e4
--- /dev/null
+++ b/proteus/storage/blob/_functions.py
@@ -0,0 +1,28 @@
+"""
+ Utility functions for storage operations.
+"""
+import json
+from io import BytesIO
+from typing import Union
+
+import pandas
+
+
+def pandas_to_parquet(dataframe: pandas.DataFrame) -> bytes:
+    """
+     Converts pandas DataFrame to parquet bytestream.
+
+    :param dataframe: Pandas DataFrame.
+    :return: Byte array.
+    """
+    return dataframe.to_parquet(BytesIO())
+
+
+def json_to_bytes(data: Union[dict, list]) -> bytes:
+    """
+      Converts dict or list to json-serialized byte array.
+
+    :param data: Dictionary or list to serialize.
+    :return: Byte array.
+    """
+    return json.dumps(data).encode(encoding='utf-8')
diff --git a/proteus/storage/blob/azure_storage_client.py b/proteus/storage/blob/azure_storage_client.py
new file mode 100644
index 00000000..e9897567
--- /dev/null
+++ b/proteus/storage/blob/azure_storage_client.py
@@ -0,0 +1,53 @@
+"""
+ Storage Client implementation for Azure Cloud.
+"""
+from datetime import datetime, timedelta
+from typing import Union, Optional, Dict
+
+from azure.storage.blob import BlobServiceClient, BlobSasPermissions, BlobClient, generate_blob_sas
+
+from proteus.storage.blob.base import StorageClient
+from proteus.security.clients import AzureClient
+from proteus.storage.models.azure import AdlsGen2Path, WasbPath, cast_path
+from proteus.storage.models.base import DataPath
+
+
+class AzureStorageClient(StorageClient):
+    """
+     Azure Storage (Blob and ADLS) Client.
+    """
+    def __init__(self, *, base_client: AzureClient, path: Union[AdlsGen2Path, WasbPath]):
+        super().__init__(base_client=base_client)
+        self._storage_options = self._base_client.connect_storage(path)
+        connection_string = f"DefaultEndpointsProtocol=https;AccountName={self._storage_options['AZURE_STORAGE_ACCOUNT_NAME']};AccountKey={self._storage_options['AZURE_STORAGE_ACCOUNT_KEY']};BlobEndpoint=https://{self._storage_options['AZURE_STORAGE_ACCOUNT_NAME']}.blob.core.windows.net/;"
+
+        self._blob_service_client: BlobServiceClient = BlobServiceClient.from_connection_string(connection_string)
+
+    def _get_blob_client(self, blob_path: DataPath) -> BlobClient:
+        azure_path = cast_path(blob_path)
+
+        return self._blob_service_client.get_blob_client(
+            container=azure_path.container,
+            blob=azure_path.path,
+        )
+
+    def save_bytes_as_blob(self, data_bytes: bytes, blob_path: DataPath, metadata: Optional[Dict[str, str]] = None):
+        self._get_blob_client(blob_path).upload_blob(data_bytes, metadata=metadata)
+
+    def get_blob_uri(self, blob_path: DataPath, **kwargs) -> str:
+        blob_client = self._get_blob_client(blob_path)
+        azure_path = cast_path(blob_path)
+
+        kwargs['account_key'] = self._storage_options['AZURE_STORAGE_ACCOUNT_KEY']
+
+        sas_token = generate_blob_sas(
+            blob_name=azure_path.path,
+            container_name=azure_path.container,
+            account_name=azure_path.account,
+            permission=kwargs.get('permission', BlobSasPermissions(read=True)),
+            expiry=kwargs.get('expiry', datetime.utcnow() + timedelta(hours=1)),
+            **kwargs
+        )
+
+        sas_uri = f'{blob_client.url}?{sas_token}'
+        return sas_uri
diff --git a/proteus/storage/blob/base.py b/proteus/storage/blob/base.py
new file mode 100644
index 00000000..71ecf446
--- /dev/null
+++ b/proteus/storage/blob/base.py
@@ -0,0 +1,37 @@
+"""
+ Abstraction for storage operations.
+"""
+from abc import ABC, abstractmethod
+from typing import Optional, Dict
+
+from proteus.security.clients import ProteusClient
+from proteus.storage.models.base import DataPath
+
+
+class StorageClient(ABC):
+    """
+     Base storage operations for all backends.
+    """
+    def __init__(self, *, base_client: ProteusClient):
+        self._base_client = base_client
+
+    @abstractmethod
+    def save_bytes_as_blob(self, data_bytes: bytes, blob_path: DataPath, metadata: Optional[Dict[str, str]] = None):
+        """
+         Saves byte array to a blob.
+
+        :param data_bytes: Bytes to save.
+        :param blob_path: Blob path in DataPath notation.
+        :param metadata: Optional blob tags or metadata to attach.
+        :return:
+        """
+
+    @abstractmethod
+    def get_blob_uri(self, blob_path: DataPath, **kwargs):
+        """
+         Generates a URL which can be used to download this blob.
+
+        :param blob_path:
+        :param kwargs:
+        :return:
+        """
diff --git a/proteus/storage/delta_lake/README.md b/proteus/storage/delta_lake/README.md
new file mode 100644
index 00000000..9af6f992
--- /dev/null
+++ b/proteus/storage/delta_lake/README.md
@@ -0,0 +1,29 @@
+# Delta Lake Operations
+
+Supported API:
+- read delta table as `pandas.DataFrame`
+- read delta table in batches of a provided size, each batch being `pandas.DataFrame`
+- read a subset of columns from delta table
+- read and filter a delta table without loading all rows in memory
+
+## Example usage for Azure Datalake Gen2
+
+```python
+from proteus.security.clients import AzureClient
+from proteus.storage.models.azure import AdlsGen2Path
+from proteus.storage.delta_lake import load
+from pyarrow.dataset import field as pyarrow_field
+
+# prepare connection
+azure_client = AzureClient(subscription_id='6c5538ce-b24a-4e2a-877f-979ad71287ff')
+adls_path = AdlsGen2Path.from_hdfs_path('abfss://container@account.dfs.core.windows.net/path/to/my/table')
+
+# get Iterable[pandas.DataFrame]
+batches = load(azure_client, adls_path, batch_size=1000)
+	
+# create a filter and apply it
+filter = (pyarrow_field("my_column") == "some-value") # case sensitive!
+filtered = load(azure_client, adls_path, row_filter=filter, columns=["my_column", "my_other_column"])
+
+# filtered is of type pandas.DataFrame
+```
\ No newline at end of file
diff --git a/proteus/storage/delta_lake/__init__.py b/proteus/storage/delta_lake/__init__.py
new file mode 100644
index 00000000..60f669c7
--- /dev/null
+++ b/proteus/storage/delta_lake/__init__.py
@@ -0,0 +1,4 @@
+"""
+ Import index.
+"""
+from proteus.storage.delta_lake._functions import *
diff --git a/proteus/storage/delta_lake/_functions.py b/proteus/storage/delta_lake/_functions.py
new file mode 100644
index 00000000..00a0bff3
--- /dev/null
+++ b/proteus/storage/delta_lake/_functions.py
@@ -0,0 +1,86 @@
+"""
+ Operations on Delta Lake tables.
+"""
+from typing import Optional, Union, Iterator, List, Tuple, Any
+
+import pandas
+import pyarrow
+from deltalake import DeltaTable, RawDeltaTable
+from deltalake.fs import DeltaStorageHandler
+from pyarrow import RecordBatch, Table
+from pyarrow._compute import Expression  # pylint: disable=E0611
+from pyarrow._dataset import FileSystemDataset # pylint: disable=E0611
+from pyarrow._dataset_parquet import ParquetFileFormat, ParquetReadOptions # pylint: disable=E0611
+import pyarrow.fs as pa_fs
+
+from proteus.security.clients._base import ProteusClient
+from proteus.storage.models.base import DataPath
+
+
+def _to_pyarrow_dataset(
+        table: RawDeltaTable,
+        schema: pyarrow.Schema,
+        partitions: Optional[List[Tuple[str, str, Any]]] = None,
+        filesystem: Optional[Union[str, pa_fs.FileSystem]] = None,
+) -> pyarrow.dataset.Dataset:
+    """
+    Build a PyArrow Dataset using data from the DeltaTable.
+
+    :param partitions: A list of partition filters, see help(DeltaTable.files_by_partitions) for filter syntax
+    :param filesystem: A concrete implementation of the Pyarrow FileSystem or a fsspec-compatible interface. If None, the first file path will be used to determine the right FileSystem
+    :return: the PyArrow dataset in PyArrow
+    """
+    if not filesystem:
+        filesystem = pa_fs.PyFileSystem(
+            DeltaStorageHandler(table.table_uri())
+        )
+
+    read_format = ParquetFileFormat(read_options=ParquetReadOptions(coerce_int96_timestamp_unit='ms'))
+
+    fragments = [
+        read_format.make_fragment(
+            file,
+            filesystem=filesystem,
+            partition_expression=part_expression,
+        )
+        for file, part_expression in table.dataset_partitions(partitions)
+    ]
+
+    return FileSystemDataset(fragments, schema, read_format, filesystem)
+
+
+def load(proteus_client: ProteusClient,  # pylint: disable=R0913
+         path: DataPath,
+         version: Optional[int] = None,
+         row_filter: Optional[Expression] = None,
+         columns: Optional[List[str]] = None,
+         batch_size: Optional[int] = None
+         ) -> Union[DeltaTable, pandas.DataFrame, Iterator[pandas.DataFrame]]:
+    """
+     Loads Delta Lake table from Azure storage and converts it to a pandas dataframe.
+
+    :param proteus_client: ProteusClient for target storage.
+    :param path: Path to delta table, in HDFS format: abfss://container@account.dfs.core.windows.net/my/path
+    :param version: Optional version to read. Defaults to latest.
+    :param row_filter: Optional filter to apply, as pyarrow expression. Example:
+      from pyarrow.dataset import field as pyarrow_field
+
+      filter = (pyarrow_field("year") == "2021") & (pyarrow_field("value") > "4")
+
+    :param columns: Optional list of columns to select when reading. Defaults to all columns of not provided.
+    :param batch_size: Optional batch size when reading in batches. If not set, whole table will be loaded into memory.
+    :return: A DeltaTable wrapped Rust class, pandas Dataframe or an iterator of pandas Dataframes, for batched reads.
+    """
+    proteus_client.connect_storage(path, set_env=True)
+    delta_table = DeltaTable(path.to_delta_rs_path(), version=version)
+    pyarrow_ds = _to_pyarrow_dataset(delta_table._table, delta_table.pyarrow_schema())
+
+    if batch_size:
+        batches: Iterator[RecordBatch] = pyarrow_ds.to_batches(filter=row_filter, columns=columns,
+                                                               batch_size=batch_size)
+
+        return map(lambda batch: batch.to_pandas(timestamp_as_object=True), batches)
+
+    pyarrow_table: Table = pyarrow_ds.to_table(filter=row_filter, columns=columns)
+
+    return pyarrow_table.to_pandas(timestamp_as_object=True)
diff --git a/proteus/storage/models/__init__.py b/proteus/storage/models/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/proteus/storage/models/azure.py b/proteus/storage/models/azure.py
new file mode 100644
index 00000000..784624b3
--- /dev/null
+++ b/proteus/storage/models/azure.py
@@ -0,0 +1,84 @@
+"""
+ Models used by Azure Client when working with storage.
+"""
+from dataclasses import dataclass
+from typing import Union
+
+from proteus.storage.models.base import DataPath, DataProtocols
+
+
+@dataclass
+class AdlsGen2Path(DataPath):
+    """
+     Path wrapper for ADLS Gen2.
+    """
+    account: str
+    container: str
+    path: str
+    protocol: str = DataProtocols.ADLS2.value
+
+    @classmethod
+    def from_hdfs_path(cls, hdfs_path: str) -> "AdlsGen2Path":
+        assert '@' in hdfs_path and 'dfs.core.windows.net' in hdfs_path and hdfs_path.startswith(
+            'abfss://'), 'Invalid HDFS (ALDS2) path supplied. Please use the following format: abfss://<container>@<account>.dfs.core.windows.net/my/data'
+
+        return AdlsGen2Path(
+            account=hdfs_path.split('@')[1].split('.')[0],
+            container=hdfs_path.split('@')[0].split('//')[1],
+            path=hdfs_path.split('.dfs.core.windows.net')[1][1:]
+        )
+
+    def _check_path(self):
+        assert not self.path.startswith('/'), 'Path should not start with /'
+
+    def to_hdfs_path(self) -> str:
+        self._check_path()
+        return f"abfss://{self.container}@{self.account}.dfs.core.windows.net/{self.path}"
+
+    def to_delta_rs_path(self) -> str:
+        self._check_path()
+        return f"adls2://{self.account}/{self.container}/{self.path}"
+
+
+@dataclass
+class WasbPath(DataPath):
+    """
+     Path wrapper for ADLS Gen2.
+    """
+    account: str
+    container: str
+    path: str
+    protocol: str = DataProtocols.AZURE_BLOB.value
+
+    @classmethod
+    def from_hdfs_path(cls, hdfs_path: str) -> "WasbPath":
+        assert '@' in hdfs_path and 'blob.core.windows.net' in hdfs_path and hdfs_path.startswith(
+            'wasbs://'), 'Invalid HDFS (WASB) path supplied. Please use the following format: wasbs://<container>@<account>.blob.core.windows.net/my/data'
+
+        return WasbPath(
+            account=hdfs_path.split('@')[1].split('.')[0],
+            container=hdfs_path.split('@')[0].split('//')[1],
+            path=hdfs_path.split('.dfs.core.windows.net')[1][1:]
+        )
+
+    def _check_path(self):
+        assert not self.path.startswith('/'), 'Path should not start with /'
+
+    def to_hdfs_path(self) -> str:
+        self._check_path()
+        return f"wasbs://{self.container}@{self.account}.blob.core.windows.net/{self.path}"
+
+    def to_delta_rs_path(self) -> str:
+        raise NotImplementedError("WASB not supported by delta-rs yet")
+
+
+def cast_path(blob_path: DataPath) -> Union[AdlsGen2Path, WasbPath]:
+    """
+     Type cast from DataPath to one of Azure paths.
+
+    :param blob_path: DataPath
+    :return: AdlsGen2Path or WasbPath
+    """
+    assert isinstance(blob_path, (AdlsGen2Path, WasbPath)), 'Only Azure Data paths are supported by this client.'
+
+    return blob_path
diff --git a/proteus/storage/models/base.py b/proteus/storage/models/base.py
new file mode 100644
index 00000000..01706b26
--- /dev/null
+++ b/proteus/storage/models/base.py
@@ -0,0 +1,45 @@
+"""
+ Base class representing file system path.
+"""
+from abc import abstractmethod, ABC
+from enum import Enum
+
+
+class DataProtocols(Enum):
+    """
+     HDFS protocol aliases for data path implementations.
+    """
+    AZURE_BLOB = 'wasbs'
+    ADLS2 = 'abfss'
+    FILE = 'file'
+
+
+class DataPath(ABC):
+    """
+     Base path to any data entity.
+    """
+    path: str
+    protocol: str
+
+    @classmethod
+    @abstractmethod
+    def from_hdfs_path(cls, hdfs_path: str) -> "DataPath":
+        """
+           Converts HDFS FileSystem path notation to this class.
+         :param hdfs_path: abfss://...
+         :return:
+        """
+
+    @abstractmethod
+    def to_hdfs_path(self) -> str:
+        """
+         Returns valid HDFS path from this class.
+        :return:
+        """
+
+    @abstractmethod
+    def to_delta_rs_path(self) -> str:
+        """
+         Returns valid Delta-RS (https://github.com/delta-io/delta-rs) path from this class.
+        :return:
+        """
diff --git a/proteus/storage/models/local.py b/proteus/storage/models/local.py
new file mode 100644
index 00000000..ee93dc08
--- /dev/null
+++ b/proteus/storage/models/local.py
@@ -0,0 +1,29 @@
+"""
+ Models used by Local Client when working with storage.
+"""
+from dataclasses import dataclass
+
+from proteus.storage.models.base import DataPath, DataProtocols
+
+
+@dataclass
+class LocalPath(DataPath):
+    """
+     Local file system path.
+    """
+    path: str
+    protocol: str = DataProtocols.FILE.value
+
+    @classmethod
+    def from_hdfs_path(cls, hdfs_path: str) -> "LocalPath":
+        assert hdfs_path.startswith("file://"), "HDFS local path should start with file://"
+
+        return LocalPath(
+            path=hdfs_path.replace("file://", "")
+        )
+
+    def to_hdfs_path(self) -> str:
+        return f"file://{self.path}"
+
+    def to_delta_rs_path(self) -> str:
+        return self.path
diff --git a/pyproject.toml b/pyproject.toml
index 56bf2d76..3dff003d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,20 +10,21 @@ repository = 'https://github.com/SneaksAndData/proteus'
 
 [tool.poetry.dependencies]
 python = "^3.8"
-
-[tool.poetry.group.beast.dependencies]
 requests = "~2.27"
 cryptography = "~36.0"
 azure-identity = "~1.7"
+deltalake = "~0.5"
+azure-storage-blob = "~12.10.0"
+azure-mgmt-storage = "~19.1.0"
+pandas = "~1.4.1"
 
-[tool.poetry.group.crystal.dependencies]
-requests = "~2.27"
-
-[tool.poetry.group.dev.dependencies]
+[tool.poetry.dev-dependencies]
 pytest = "~6.2"
+pytest-cov = "~2.12"
 pylint = "~2.12"
 requests = "~2.27"
 cryptography = "~36.0"
+pandas = "~1.4.1"
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]


Test patch:
diff --git a/tests/delta_table/_delta_log/00000000000000000000.crc b/tests/delta_table/_delta_log/00000000000000000000.crc
new file mode 100644
index 00000000..88ee0449
--- /dev/null
+++ b/tests/delta_table/_delta_log/00000000000000000000.crc
@@ -0,0 +1,1 @@
+{"tableSizeBytes":11322,"numFiles":17,"numMetadata":1,"numProtocol":1,"numTransactions":0}
diff --git a/tests/delta_table/_delta_log/00000000000000000000.json b/tests/delta_table/_delta_log/00000000000000000000.json
new file mode 100644
index 00000000..acc060e0
--- /dev/null
+++ b/tests/delta_table/_delta_log/00000000000000000000.json
@@ -0,0 +1,20 @@
+{"protocol":{"minReaderVersion":1,"minWriterVersion":2}}
+{"metaData":{"id":"1c477f58-9814-4aed-9e51-b99c9fe22e33","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"A\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"B\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1647944224479}}
+{"add":{"path":"part-00000-7d712efc-8cc5-42ce-a5ac-46887e04ee94-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000000","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00001-e4bd0782-01a5-4ad7-945b-45985e006430-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000001","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00002-699e469a-a328-4a71-8ec8-c91ad28ec573-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000002","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00003-77b01f7a-a659-493c-97da-088809a39869-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000003","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00004-56d9dacf-5261-4c24-91d9-7e0ce1ca23be-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000004","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00005-e4450c20-5811-4b48-bc3a-88ad814c21f8-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000005","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00006-2da73dac-c07c-49ba-9402-8cc0c526b8b9-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000006","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00007-b096ad61-7a48-48d8-a782-f5dc4bc1a108-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000007","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00008-f6aef848-fe43-4081-bf66-d3af1b86b05f-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000008","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00009-0786b91a-0621-4c1f-b48f-bc3188cfdba5-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000009","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00010-cdd7ce86-3222-4b45-bee0-7e19423c24ce-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000010","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00011-10919495-4e38-4c46-b65b-b8e20b62caa1-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000011","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00012-7acc28f1-27d2-4e5e-94df-972f9615d809-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000012","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00013-1815f141-0c09-4999-94a6-cb650c4bffd4-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000013","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00014-dc777d3c-f4e1-484a-ad19-cddf236bb8a7-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944263000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000014","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00015-95bb2b86-3e5a-436c-bfe6-3d3ccae60296-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944266000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000015","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"add":{"path":"part-00016-3154693d-eff5-4680-ae56-dc8acbdffa0c-c000.snappy.parquet","partitionValues":{},"size":666,"modificationTime":1647944265000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"A\":\"a\",\"B\":\"1\"},\"maxValues\":{\"A\":\"a\",\"B\":\"1\"},\"nullCount\":{\"A\":0,\"B\":0}}","tags":{"INSERTION_TIME":"1647944263000016","OPTIMIZE_TARGET_SIZE":"268435456"}}}
+{"commitInfo":{"timestamp":1647944268720,"userId":"5615013122731566","userName":"gzu@ecco.com","operation":"CREATE TABLE AS SELECT","operationParameters":{"isManaged":"false","description":null,"partitionBy":"[]","properties":"{}"},"notebook":{"notebookId":"1738937046592170"},"clusterId":"0103-141810-3dj6cfva","isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"17","numOutputBytes":"11322","numOutputRows":"17"}}}
diff --git a/tests/delta_table/part-00000-7d712efc-8cc5-42ce-a5ac-46887e04ee94-c000.snappy.parquet b/tests/delta_table/part-00000-7d712efc-8cc5-42ce-a5ac-46887e04ee94-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00000-7d712efc-8cc5-42ce-a5ac-46887e04ee94-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00001-e4bd0782-01a5-4ad7-945b-45985e006430-c000.snappy.parquet b/tests/delta_table/part-00001-e4bd0782-01a5-4ad7-945b-45985e006430-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00001-e4bd0782-01a5-4ad7-945b-45985e006430-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00002-699e469a-a328-4a71-8ec8-c91ad28ec573-c000.snappy.parquet b/tests/delta_table/part-00002-699e469a-a328-4a71-8ec8-c91ad28ec573-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00002-699e469a-a328-4a71-8ec8-c91ad28ec573-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00003-77b01f7a-a659-493c-97da-088809a39869-c000.snappy.parquet b/tests/delta_table/part-00003-77b01f7a-a659-493c-97da-088809a39869-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00003-77b01f7a-a659-493c-97da-088809a39869-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00004-56d9dacf-5261-4c24-91d9-7e0ce1ca23be-c000.snappy.parquet b/tests/delta_table/part-00004-56d9dacf-5261-4c24-91d9-7e0ce1ca23be-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00004-56d9dacf-5261-4c24-91d9-7e0ce1ca23be-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00005-e4450c20-5811-4b48-bc3a-88ad814c21f8-c000.snappy.parquet b/tests/delta_table/part-00005-e4450c20-5811-4b48-bc3a-88ad814c21f8-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00005-e4450c20-5811-4b48-bc3a-88ad814c21f8-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00006-2da73dac-c07c-49ba-9402-8cc0c526b8b9-c000.snappy.parquet b/tests/delta_table/part-00006-2da73dac-c07c-49ba-9402-8cc0c526b8b9-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00006-2da73dac-c07c-49ba-9402-8cc0c526b8b9-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00007-b096ad61-7a48-48d8-a782-f5dc4bc1a108-c000.snappy.parquet b/tests/delta_table/part-00007-b096ad61-7a48-48d8-a782-f5dc4bc1a108-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00007-b096ad61-7a48-48d8-a782-f5dc4bc1a108-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00008-f6aef848-fe43-4081-bf66-d3af1b86b05f-c000.snappy.parquet b/tests/delta_table/part-00008-f6aef848-fe43-4081-bf66-d3af1b86b05f-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00008-f6aef848-fe43-4081-bf66-d3af1b86b05f-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00009-0786b91a-0621-4c1f-b48f-bc3188cfdba5-c000.snappy.parquet b/tests/delta_table/part-00009-0786b91a-0621-4c1f-b48f-bc3188cfdba5-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00009-0786b91a-0621-4c1f-b48f-bc3188cfdba5-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00010-cdd7ce86-3222-4b45-bee0-7e19423c24ce-c000.snappy.parquet b/tests/delta_table/part-00010-cdd7ce86-3222-4b45-bee0-7e19423c24ce-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00010-cdd7ce86-3222-4b45-bee0-7e19423c24ce-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00011-10919495-4e38-4c46-b65b-b8e20b62caa1-c000.snappy.parquet b/tests/delta_table/part-00011-10919495-4e38-4c46-b65b-b8e20b62caa1-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00011-10919495-4e38-4c46-b65b-b8e20b62caa1-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00012-7acc28f1-27d2-4e5e-94df-972f9615d809-c000.snappy.parquet b/tests/delta_table/part-00012-7acc28f1-27d2-4e5e-94df-972f9615d809-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00012-7acc28f1-27d2-4e5e-94df-972f9615d809-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00013-1815f141-0c09-4999-94a6-cb650c4bffd4-c000.snappy.parquet b/tests/delta_table/part-00013-1815f141-0c09-4999-94a6-cb650c4bffd4-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00013-1815f141-0c09-4999-94a6-cb650c4bffd4-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00014-dc777d3c-f4e1-484a-ad19-cddf236bb8a7-c000.snappy.parquet b/tests/delta_table/part-00014-dc777d3c-f4e1-484a-ad19-cddf236bb8a7-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00014-dc777d3c-f4e1-484a-ad19-cddf236bb8a7-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00015-95bb2b86-3e5a-436c-bfe6-3d3ccae60296-c000.snappy.parquet b/tests/delta_table/part-00015-95bb2b86-3e5a-436c-bfe6-3d3ccae60296-c000.snappy.parquet
new file mode 100644
index 00000000..c130e90b
Binary files /dev/null and b/tests/delta_table/part-00015-95bb2b86-3e5a-436c-bfe6-3d3ccae60296-c000.snappy.parquet differ
diff --git a/tests/delta_table/part-00016-3154693d-eff5-4680-ae56-dc8acbdffa0c-c000.snappy.parquet b/tests/delta_table/part-00016-3154693d-eff5-4680-ae56-dc8acbdffa0c-c000.snappy.parquet
new file mode 100644
index 00000000..3a613fcb
Binary files /dev/null and b/tests/delta_table/part-00016-3154693d-eff5-4680-ae56-dc8acbdffa0c-c000.snappy.parquet differ
diff --git a/tests/test_delta_load.py b/tests/test_delta_load.py
new file mode 100644
index 00000000..1d37ae5f
--- /dev/null
+++ b/tests/test_delta_load.py
@@ -0,0 +1,49 @@
+import pathlib
+
+import pandas
+import pytest
+
+from proteus.security.clients import LocalClient
+from proteus.storage.models.local import LocalPath
+from proteus.storage.delta_lake import load
+from pyarrow.dataset import field as pyarrow_field
+
+
+@pytest.fixture
+def get_client_and_path():
+    test_data_path = f"{pathlib.Path(__file__).parent.resolve()}/delta_table"
+
+    client = LocalClient()
+    data_path = LocalPath.from_hdfs_path(f'file:///{test_data_path}')
+
+    return client, data_path
+
+
+def test_delta_load(get_client_and_path):
+    client, data_path = get_client_and_path
+    table: pandas.DataFrame = load(client, data_path)
+
+    assert len(table) == 17
+
+
+# important note: batch loading doesn't seem to kick off beyond specific row count. So for our test table we always get row-per-dataframe
+# on real tables batching seems to work, thus we only test return data type here
+def test_delta_batch_load(get_client_and_path):
+    client, data_path = get_client_and_path
+    table = list(load(client, data_path, batch_size=10))
+
+    assert isinstance(table[0], pandas.DataFrame)
+
+
+def test_delta_filter(get_client_and_path):
+    client, data_path = get_client_and_path
+    table = load(client, data_path, row_filter=(pyarrow_field('A') == "b"))
+
+    assert len(table) == 0
+
+
+def test_column_project(get_client_and_path):
+    client, data_path = get_client_and_path
+    table = load(client, data_path, columns=["B"])
+
+    assert len(table.columns.to_list()) == 1