Repository: astronomer/astro-sdk

Issue:
Support users who do not have permissions of creating schemas
**Context**

Astro 0.5.1 assumes the users have permission to create schemas. In practice, many developers building ETL pipelines do not have this permission.

**Acceptance criteria**

- Users who do not have permission to create schemas should still be able to use all Astro operators.


Discussion:

Resolving patch:
diff --git a/src/astro/sql/operators/sql_decorator.py b/src/astro/sql/operators/sql_decorator.py
--- a/src/astro/sql/operators/sql_decorator.py
+++ b/src/astro/sql/operators/sql_decorator.py
@@ -14,6 +14,7 @@
 limitations under the License.
 """
 import inspect
+import os
 from builtins import NotImplementedError
 from typing import Callable, Dict, Iterable, Mapping, Optional, Union
 
@@ -29,7 +30,7 @@
 from astro.utils import postgres_transform, snowflake_transform
 from astro.utils.dependencies import BigQueryHook, PostgresHook, SnowflakeHook
 from astro.utils.load_dataframe import move_dataframe_to_sql
-from astro.utils.schema_util import get_schema, set_schema_query
+from astro.utils.schema_util import create_schema_query, get_schema, schema_exists
 
 
 class SqlDecoratoratedOperator(DecoratedOperator):
@@ -103,6 +104,8 @@ def execute(self, context: Dict):
         self.schema = self.schema or get_schema()
         self.user = conn.login
         self.run_id = context.get("run_id")
+        self._set_hook()
+
         self.convert_op_arg_dataframes()
         self.convert_op_kwarg_dataframes()
         self.read_sql()
@@ -116,7 +119,10 @@ def execute(self, context: Dict):
 
         if not self.raw_sql:
             # Create a table name for the temp table
-            self._set_schema_if_needed()
+            if not schema_exists(
+                hook=self.hook, schema=self.schema, conn_type=self.conn_type
+            ):
+                self._set_schema_if_needed()
 
             if not self.output_table:
                 output_table_name = create_table_name(context=context)
@@ -232,23 +238,28 @@ def _set_variables_from_first_table(self):
             self.warehouse = first_table.warehouse or self.warehouse
             self.role = first_table.role or self.role
 
-    def _set_schema_if_needed(self):
-        schema_statement = ""
+    def _set_hook(self):
         if self.conn_type == "postgres":
             self.hook = PostgresHook(
                 postgres_conn_id=self.conn_id, schema=self.database
             )
-            schema_statement = set_schema_query(
+
+        elif self.conn_type == "snowflake":
+            self.hook = self.get_snow_hook()
+
+    def _set_schema_if_needed(self):
+        schema_statement = ""
+        if self.conn_type == "postgres":
+            schema_statement = create_schema_query(
                 conn_type=self.conn_type,
                 hook=self.hook,
                 schema_id=self.schema,
                 user=self.user,
             )
         elif self.conn_type == "snowflake":
-            hook = self.get_snow_hook()
-            schema_statement = set_schema_query(
+            schema_statement = create_schema_query(
                 conn_type=self.conn_type,
-                hook=hook,
+                hook=self.hook,
                 schema_id=self.schema,
                 user=self.user,
             )
diff --git a/src/astro/utils/load_dataframe.py b/src/astro/utils/load_dataframe.py
--- a/src/astro/utils/load_dataframe.py
+++ b/src/astro/utils/load_dataframe.py
@@ -13,6 +13,7 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 """
+import os
 from typing import Optional, Union
 
 from pandas import DataFrame
@@ -20,7 +21,7 @@
 from snowflake.connector import pandas_tools
 
 from astro.utils.dependencies import BigQueryHook, PostgresHook, SnowflakeHook
-from astro.utils.schema_util import set_schema_query
+from astro.utils.schema_util import create_schema_query, schema_exists
 
 
 def move_dataframe_to_sql(
@@ -51,10 +52,11 @@ def move_dataframe_to_sql(
     if database:
         hook.database = database
 
-    schema_query = set_schema_query(
-        conn_type=conn_type, hook=hook, schema_id=schema, user=user
-    )
-    hook.run(schema_query)
+    if not schema_exists(hook=hook, schema=schema, conn_type=conn_type):
+        schema_query = create_schema_query(
+            conn_type=conn_type, hook=hook, schema_id=schema, user=user
+        )
+        hook.run(schema_query)
     if conn_type == "snowflake":
 
         db = SQLDatabase(engine=hook.get_sqlalchemy_engine())
diff --git a/src/astro/utils/schema_util.py b/src/astro/utils/schema_util.py
--- a/src/astro/utils/schema_util.py
+++ b/src/astro/utils/schema_util.py
@@ -5,10 +5,25 @@
 from psycopg2 import sql
 
 from astro.sql.table import Table
-from astro.utils.dependencies import PostgresHook
 
 
-def set_schema_query(conn_type, hook, schema_id, user):
+def schema_exists(hook, schema, conn_type):
+    if conn_type in ["postgresql", "postgres"]:
+        created_schemas = hook.run(
+            "SELECT schema_name FROM information_schema.schemata;",
+            handler=lambda x: [y[0] for y in x.fetchall()],
+        )
+        return schema in created_schemas
+    elif conn_type == "snowflake":
+        created_schemas = [
+            x["SCHEMA_NAME"]
+            for x in hook.run("SELECT SCHEMA_NAME from information_schema.schemata;")
+        ]
+        return schema in created_schemas
+    return False
+
+
+def create_schema_query(conn_type, hook, schema_id, user):
 
     if conn_type in ["postgresql", "postgres"]:
         return (


Test patch: