From 4f36efad9dc8fc7dd32c2fc6cc271842ec79ad11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leynier=20Guti=C3=A9rrez=20Gonz=C3=A1lez?= Date: Wed, 29 Dec 2021 15:34:25 +0000 Subject: [PATCH] fix: update gotrue version and modify client options class Now client options class does not make a deep copy in the replace method because local storage is an abstract class and not dict like before --- poetry.lock | 99 +++++++++++++++++----------------- pyproject.toml | 4 +- supabase/client.py | 22 ++++---- supabase/lib/auth_client.py | 31 +++++++---- supabase/lib/client_options.py | 36 ++++++------- supabase/lib/constants.py | 3 -- tests/test_client.py | 16 +++--- tests/test_client_options.py | 20 ++++--- 8 files changed, 120 insertions(+), 111 deletions(-) delete mode 100644 supabase/lib/constants.py diff --git a/poetry.lock b/poetry.lock index 802d3051..afa4d35b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -223,21 +223,15 @@ pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "gotrue" -version = "0.2.0" +version = "0.3.0" description = "Python Client Library for GoTrue" category = "main" optional = false -python-versions = "^3.7" -develop = false +python-versions = ">=3.7,<4.0" [package.dependencies] -requests = "^2.26.0" - -[package.source] -type = "git" -url = "https://github.com/supabase-community/gotrue-py.git" -reference = "9ba3192dbdccd2f02a4819b52dd6cf51095af7e7" -resolved_reference = "9ba3192dbdccd2f02a4819b52dd6cf51095af7e7" +httpx = ">=0.20,<0.22" +pydantic = ">=1.8.2,<2.0.0" [[package]] name = "h11" @@ -489,6 +483,21 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyflakes" version = "2.4.0" @@ -591,24 +600,6 @@ dataclasses = ">=0.6,<0.7" python-dateutil = ">=2.8.1,<3.0.0" websockets = ">=9.1,<10.0" -[[package]] -name = "requests" -version = "2.26.0" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] - [[package]] name = "rfc3986" version = "1.5.0" @@ -687,19 +678,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "urllib3" -version = "1.26.7" -description = "HTTP library with thread-safe connection pooling, file post, and more." -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)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - [[package]] name = "virtualenv" version = "20.11.2" @@ -750,7 +728,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "ade2d73af908787d4b578473d8ece025da1e036166392eaaf16c295c388deedd" +content-hash = "3ff9a078913ddff65770db733917d723446ce71e8cb386eae3c604148bce8a05" [metadata.files] anyio = [ @@ -870,7 +848,10 @@ flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] -gotrue = [] +gotrue = [ + {file = "gotrue-0.3.0-py3-none-any.whl", hash = "sha256:e1f89e6a7852d597bad454981fdfaa4ff02bd722c960ff639167a5eae8eaf1da"}, + {file = "gotrue-0.3.0.tar.gz", hash = "sha256:a5037f6d3b0117613a3ea94b85795333080b7126e4e73e91b8178260559882cd"}, +] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, @@ -1026,6 +1007,30 @@ pycodestyle = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] pyflakes = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, @@ -1089,10 +1094,6 @@ realtime = [ {file = "realtime-0.0.3-py3-none-any.whl", hash = "sha256:3b977acd5c7c507804d6113cbc921d778451bd2a6112af40e74329338088a410"}, {file = "realtime-0.0.3.tar.gz", hash = "sha256:97c347e14330f77238b856383f70bb9a64688345dfda3ed27558cbfc5ac4d5bb"}, ] -requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, -] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -1145,10 +1146,6 @@ typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] -urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, -] virtualenv = [ {file = "virtualenv-20.11.2-py2.py3-none-any.whl", hash = "sha256:efd556cec612fd826dc7ef8ce26a6e4ba2395f494244919acd135fb5ceffa809"}, {file = "virtualenv-20.11.2.tar.gz", hash = "sha256:7f9e9c2e878d92a434e760058780b8d67a7c5ec016a66784fe4b0d5e50a4eb5c"}, diff --git a/pyproject.toml b/pyproject.toml index 202574e4..022ddda1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ python = "^3.7" postgrest-py = "^0.6.0" realtime = "^0.0.3" -gotrue = "^0.2.0" +gotrue = "^0.3.0" httpx = ">=0.19,<0.22" [tool.poetry.dev-dependencies] @@ -32,7 +32,7 @@ commitizen = "^2.20.3" [tool.commitizen] name = "cz_conventional_commits" -version = "0.0.3" +version = "0.1.0" version_files = [ "supabase/__init__.py", "pyproject.toml:version" diff --git a/supabase/client.py b/supabase/client.py index 6cf42e35..2d05988a 100644 --- a/supabase/client.py +++ b/supabase/client.py @@ -5,7 +5,6 @@ from supabase.lib.auth_client import SupabaseAuthClient from supabase.lib.client_options import ClientOptions -from supabase.lib.constants import DEFAULT_OPTIONS from supabase.lib.realtime_client import SupabaseRealtimeClient from supabase.lib.storage_client import SupabaseStorageClient @@ -17,7 +16,7 @@ def __init__( self, supabase_url: str, supabase_key: str, - **options, + options: ClientOptions = ClientOptions(), ): """Instantiate the client. @@ -38,20 +37,18 @@ def __init__( raise Exception("supabase_key is required") self.supabase_url = supabase_url self.supabase_key = supabase_key - - settings = DEFAULT_OPTIONS.replace(**options) - settings.headers.update(self._get_auth_headers()) + options.headers.update(self._get_auth_headers()) self.rest_url: str = f"{supabase_url}/rest/v1" self.realtime_url: str = f"{supabase_url}/realtime/v1".replace("http", "ws") self.auth_url: str = f"{supabase_url}/auth/v1" self.storage_url = f"{supabase_url}/storage/v1" - self.schema: str = settings.schema + self.schema: str = options.schema # Instantiate clients. self.auth = self._init_supabase_auth_client( auth_url=self.auth_url, supabase_key=self.supabase_key, - client_options=settings, + client_options=options, ) # TODO(fedden): Bring up to parity with JS client. # self.realtime: SupabaseRealtimeClient = self._init_realtime_client( @@ -62,7 +59,7 @@ def __init__( self.postgrest = self._init_postgrest_client( rest_url=self.rest_url, supabase_key=self.supabase_key, - headers=settings.headers, + headers=options.headers, ) def storage(self) -> SupabaseStorageClient: @@ -149,7 +146,6 @@ def _init_supabase_auth_client( return SupabaseAuthClient( url=auth_url, auto_refresh_token=client_options.auto_refresh_token, - detect_session_in_url=client_options.detect_session_in_url, persist_session=client_options.persist_session, local_storage=client_options.local_storage, headers=client_options.headers, @@ -175,7 +171,11 @@ def _get_auth_headers(self) -> Dict[str, str]: } -def create_client(supabase_url: str, supabase_key: str, **options) -> Client: +def create_client( + supabase_url: str, + supabase_key: str, + options: ClientOptions = ClientOptions(), +) -> Client: """Create client function to instantiate supabase client like JS runtime. Parameters @@ -202,4 +202,4 @@ def create_client(supabase_url: str, supabase_key: str, **options) -> Client: ------- Client """ - return Client(supabase_url=supabase_url, supabase_key=supabase_key, **options) + return Client(supabase_url=supabase_url, supabase_key=supabase_key, options=options) diff --git a/supabase/lib/auth_client.py b/supabase/lib/auth_client.py index e91ced80..ab3dbe3f 100644 --- a/supabase/lib/auth_client.py +++ b/supabase/lib/auth_client.py @@ -1,26 +1,39 @@ -from typing import Any, Dict +from typing import Dict, Optional -import gotrue +from gotrue import ( + CookieOptions, + SyncGoTrueAPI, + SyncGoTrueClient, + SyncMemoryStorage, + SyncSupportedStorage, +) +from gotrue.constants import COOKIE_OPTIONS -class SupabaseAuthClient(gotrue.Client): +class SupabaseAuthClient(SyncGoTrueClient): """SupabaseAuthClient""" def __init__( self, + *, url: str, - detect_session_in_url: bool = False, - auto_refresh_token: bool = False, - persist_session: bool = False, - local_storage: Dict[str, Any] = {}, headers: Dict[str, str] = {}, + auto_refresh_token: bool = True, + persist_session: bool = True, + local_storage: SyncSupportedStorage = SyncMemoryStorage(), + cookie_options: CookieOptions = CookieOptions.parse_obj(COOKIE_OPTIONS), + api: Optional[SyncGoTrueAPI] = None, + replace_default_headers: bool = False, ): """Instanciate SupabaseAuthClient instance.""" - super().__init__( + SyncGoTrueClient.__init__( + self, url=url, headers=headers, - detect_session_in_url=detect_session_in_url, auto_refresh_token=auto_refresh_token, persist_session=persist_session, local_storage=local_storage, + cookie_options=cookie_options, + api=api, + replace_default_headers=replace_default_headers, ) diff --git a/supabase/lib/client_options.py b/supabase/lib/client_options.py index 2f9c577f..f3cbffdd 100644 --- a/supabase/lib/client_options.py +++ b/supabase/lib/client_options.py @@ -1,13 +1,14 @@ -import copy -import dataclasses +from dataclasses import dataclass, field from typing import Any, Callable, Dict, Optional +from gotrue import SyncMemoryStorage, SyncSupportedStorage + from supabase import __version__ DEFAULT_HEADERS = {"X-Client-Info": f"supabase-py/{__version__}"} -@dataclasses.dataclass +@dataclass class ClientOptions: schema: str = "public" """ @@ -15,7 +16,7 @@ class ClientOptions: Must be on the list of exposed schemas in Supabase. Defaults to 'public'. """ - headers: Dict[str, str] = dataclasses.field(default_factory=DEFAULT_HEADERS.copy) + headers: Dict[str, str] = field(default_factory=DEFAULT_HEADERS.copy) """Optional headers for initializing the client.""" auto_refresh_token: bool = True @@ -24,14 +25,11 @@ class ClientOptions: persist_session: bool = True """Whether to persist a logged in session to storage.""" - detect_session_in_url: bool = True - """Detect a session from the URL. Used for OAuth login callbacks.""" - - local_storage: Dict[str, Any] = dataclasses.field(default_factory=lambda: {}) + local_storage: SyncSupportedStorage = field(default_factory=SyncMemoryStorage) """A storage provider. Used to store the logged in session.""" - """Options passed to the realtime-py instance""" realtime: Optional[Dict[str, Any]] = None + """Options passed to the realtime-py instance""" fetch: Optional[Callable] = None """A custom `fetch` implementation.""" @@ -42,17 +40,19 @@ def replace( headers: Optional[Dict[str, str]] = None, auto_refresh_token: Optional[bool] = None, persist_session: Optional[bool] = None, - detect_session_in_url: Optional[bool] = None, - local_storage: Optional[Dict[str, Any]] = None, + local_storage: Optional[SyncSupportedStorage] = None, realtime: Optional[Dict[str, Any]] = None, fetch: Optional[Callable] = None, ) -> "ClientOptions": """Create a new SupabaseClientOptions with changes""" - changes = { - key: value - for key, value in locals().items() - if key != "self" and value is not None - } - client_options = dataclasses.replace(self, **changes) - client_options = copy.deepcopy(client_options) + client_options = ClientOptions() + client_options.schema = schema or self.schema + client_options.headers = headers or self.headers + client_options.auto_refresh_token = ( + auto_refresh_token or self.auto_refresh_token + ) + client_options.persist_session = persist_session or self.persist_session + client_options.local_storage = local_storage or self.local_storage + client_options.realtime = realtime or self.realtime + client_options.fetch = fetch or self.fetch return client_options diff --git a/supabase/lib/constants.py b/supabase/lib/constants.py deleted file mode 100644 index 49d99a0a..00000000 --- a/supabase/lib/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -from supabase.lib.client_options import ClientOptions - -DEFAULT_OPTIONS: ClientOptions = ClientOptions() diff --git a/tests/test_client.py b/tests/test_client.py index 168a8484..2d0fa5d4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,9 +2,10 @@ import random import string -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Union import pytest +from gotrue import Session, User if TYPE_CHECKING: from supabase import Client @@ -15,15 +16,12 @@ def _random_string(length: int = 10) -> str: return "".join(random.choices(string.ascii_uppercase + string.digits, k=length)) -def _assert_authenticated_user(data: Dict[str, Any]) -> None: +def _assert_authenticated_user(data: Union[Session, User, str, None]) -> None: """Raise assertion error if user is not logged in correctly.""" - assert "access_token" in data - assert "refresh_token" in data - assert data.get("status_code") == 200 - user = data.get("user") - assert user is not None - assert user.get("id") is not None - assert user.get("aud") == "authenticated" + assert data is not None + assert isinstance(data, Session) + assert data.user is not None + assert data.user.aud == "authenticated" @pytest.mark.xfail( diff --git a/tests/test_client_options.py b/tests/test_client_options.py index c386f26e..46273ec0 100644 --- a/tests/test_client_options.py +++ b/tests/test_client_options.py @@ -1,14 +1,17 @@ +from gotrue import SyncMemoryStorage + from supabase.lib.client_options import ClientOptions def test__client_options__replace__returns_updated_options(): + local_storage = SyncMemoryStorage() + local_storage.set_item("key", "value") options = ClientOptions( schema="schema", headers={"key": "value"}, auto_refresh_token=False, persist_session=False, - detect_session_in_url=False, - local_storage={"key": "value"}, + local_storage=local_storage, realtime={"key": "value"}, ) @@ -18,8 +21,7 @@ def test__client_options__replace__returns_updated_options(): headers={"key": "value"}, auto_refresh_token=False, persist_session=False, - detect_session_in_url=False, - local_storage={"key": "value"}, + local_storage=local_storage, realtime={"key": "value"}, ) @@ -28,12 +30,14 @@ def test__client_options__replace__returns_updated_options(): def test__client_options__replace__updates_only_new_options(): # Arrange - options = ClientOptions(local_storage={"key": "value"}) + local_storage = SyncMemoryStorage() + local_storage.set_item("key", "value") + options = ClientOptions(local_storage=local_storage) new_options = options.replace() # Act - new_options.local_storage["key"] = "new_value" + new_options.local_storage.set_item("key", "new_value") # Assert - assert options.local_storage["key"] == "value" - assert new_options.local_storage["key"] == "new_value" + assert options.local_storage.get_item("key") == "new_value" + assert new_options.local_storage.get_item("key") == "new_value"