Skip to content

Commit

Permalink
Merge pull request #299 from Snowflake-Labs/release/v4.0.0
Browse files Browse the repository at this point in the history
Release/v4.0.0
  • Loading branch information
sfc-gh-tmathew authored Oct 29, 2024
2 parents 9be9e01 + e141821 commit 53f893a
Show file tree
Hide file tree
Showing 15 changed files with 59 additions and 41 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.

*The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).*

## [4.0.0] - TBD
### Added
- use of structlog for standard log outputs

### Changed
- Refactored the main cli.py to be more modular to aide in future development and testing.


## [3.7.0] - 2024-07-22
### Added
- Improved unit test coverage
Expand Down
2 changes: 2 additions & 0 deletions demo/basics_demo/V1.0.2__StoredProc.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use database {{ database_name }};
use schema {{ schema_name }};
-- This block of code executes in Visual Studio Code but fails in Schemachange.
-- Use the $$ ... $$ to mark the block and execute the code block successfully.
-- The comment from a community user help find the root cause.
Expand Down
3 changes: 3 additions & 0 deletions demo/provision/setup_schemachange_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ CREATE DATABASE ROLE IF NOT EXISTS DB_R;
CREATE DATABASE ROLE IF NOT EXISTS DB_W;
CREATE DATABASE ROLE IF NOT EXISTS DB_C;

GRANT CREATE SCHEMA ON DATABASE IDENTIFIER($TARGET_DB_NAME) TO DATABASE ROLE DB_C;

GRANT DATABASE ROLE DB_C TO ROLE IDENTIFIER($DEPLOY_ROLE);

CREATE DATABASE ROLE IF NOT EXISTS IDENTIFIER($SC_M);
Expand All @@ -38,6 +40,7 @@ GRANT DATABASE ROLE IDENTIFIER($SC_W) TO DATABASE ROLE IDENTIFIER($SC_C);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($TARGET_SCHEMA_NAME) WITH MANAGED ACCESS;
-- USE SCHEMA INFORMATION_SCHEMA;
-- DROP SCHEMA IF EXISTS PUBLIC;
GRANT OWNERSHIP ON SCHEMA IDENTIFIER($TARGET_SCHEMA_NAME) TO ROLE IDENTIFIER($DEPLOY_ROLE);

USE SCHEMA IDENTIFIER($SCHEMACHANGE_NAMESPACE);
-- SCHEMA
Expand Down
1 change: 1 addition & 0 deletions demo/setup/basics_demo/A__setup_basics_demo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GRANT DATABASE ROLE IDENTIFIER($SC_W) TO DATABASE ROLE IDENTIFIER($SC_C);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($TARGET_SCHEMA_NAME) WITH MANAGED ACCESS;
-- USE SCHEMA INFORMATION_SCHEMA;
-- DROP SCHEMA IF EXISTS PUBLIC;
GRANT OWNERSHIP ON SCHEMA IDENTIFIER($TARGET_SCHEMA_NAME) TO ROLE IDENTIFIER($DEPLOY_ROLE);

USE SCHEMA IDENTIFIER($SCHEMACHANGE_NAMESPACE);
-- SCHEMA
Expand Down
1 change: 1 addition & 0 deletions demo/setup/citibike_demo/A__setup_citibike_demo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GRANT DATABASE ROLE IDENTIFIER($SC_W) TO DATABASE ROLE IDENTIFIER($SC_C);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($TARGET_SCHEMA_NAME) WITH MANAGED ACCESS;
-- USE SCHEMA INFORMATION_SCHEMA;
-- DROP SCHEMA IF EXISTS PUBLIC;
GRANT OWNERSHIP ON SCHEMA IDENTIFIER($TARGET_SCHEMA_NAME) TO ROLE IDENTIFIER($DEPLOY_ROLE);

USE SCHEMA IDENTIFIER($SCHEMACHANGE_NAMESPACE);
-- SCHEMA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GRANT DATABASE ROLE IDENTIFIER($SC_W) TO DATABASE ROLE IDENTIFIER($SC_C);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($TARGET_SCHEMA_NAME) WITH MANAGED ACCESS;
-- USE SCHEMA INFORMATION_SCHEMA;
-- DROP SCHEMA IF EXISTS PUBLIC;
GRANT OWNERSHIP ON SCHEMA IDENTIFIER($TARGET_SCHEMA_NAME) TO ROLE IDENTIFIER($DEPLOY_ROLE);

USE SCHEMA IDENTIFIER($SCHEMACHANGE_NAMESPACE);
-- SCHEMA
Expand Down
2 changes: 1 addition & 1 deletion schemachange/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# region Global Variables
# metadata
SCHEMACHANGE_VERSION = "3.7.0"
SCHEMACHANGE_VERSION = "4.0.0"
SNOWFLAKE_APPLICATION_NAME = "schemachange"
module_logger = structlog.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion schemachange/config/BaseConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
T = TypeVar("T", bound="BaseConfig")


@dataclasses.dataclass(frozen=True, kw_only=True)
@dataclasses.dataclass(frozen=True)
class BaseConfig(ABC):
default_config_file_name: ClassVar[str] = "schemachange-config.yml"

Expand Down
2 changes: 1 addition & 1 deletion schemachange/config/DeployConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from schemachange.config.utils import get_snowflake_identifier_string


@dataclasses.dataclass(frozen=True, kw_only=True)
@dataclasses.dataclass(frozen=True)
class DeployConfig(BaseConfig):
subcommand: Literal["deploy"] = "deploy"
snowflake_account: str | None = None
Expand Down
10 changes: 8 additions & 2 deletions schemachange/config/RenderConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from schemachange.config.utils import validate_file_path


@dataclasses.dataclass(frozen=True, kw_only=True)
@dataclasses.dataclass(frozen=True)
class RenderConfig(BaseConfig):
script_path: Path | None = None
subcommand: Literal["render"] = "render"
script_path: Path

@classmethod
def factory(
Expand All @@ -31,3 +31,9 @@ def factory(
script_path=validate_file_path(file_path=script_path),
**kwargs,
)

def __post_init__(self):
if self.script_path is None:
raise TypeError(
"RenderConfig is missing 1 required argument: 'script_path'"
)
8 changes: 5 additions & 3 deletions schemachange/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def alphanum_convert(text: str):
# Each number is converted to and integer and string parts are left as strings
# This will enable correct sorting in python when the lists are compared
# e.g. get_alphanum_key('1.2.2') results in ['', 1, '.', 2, '.', 2, '']
def get_alphanum_key(key):
def get_alphanum_key(key: str | int | None) -> list:
if key == "" or key is None:
return []
alphanum_key = [alphanum_convert(c) for c in re.split("([0-9]+)", key)]
return alphanum_key

Expand Down Expand Up @@ -100,7 +102,7 @@ def deploy(config: DeployConfig, session: SnowflakeSession):
script_metadata = versioned_scripts.get(script.name)

if (
max_published_version != ""
max_published_version is not None
and get_alphanum_key(script.version) <= max_published_version
):
if script_metadata is None:
Expand All @@ -113,7 +115,7 @@ def deploy(config: DeployConfig, session: SnowflakeSession):
else:
script_log.debug(
"Script has already been applied",
max_published_version=str(max_published_version),
max_published_version=max_published_version,
)
if script_metadata["checksum"] != checksum_current:
script_log.info("Script checksum has drifted since application")
Expand Down
34 changes: 14 additions & 20 deletions schemachange/session/Credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import dataclasses
import os
from abc import ABC
from typing import Literal, Union

import structlog
Expand All @@ -14,37 +13,32 @@
)


@dataclasses.dataclass(kw_only=True, frozen=True)
class Credential(ABC):
authenticator: str


@dataclasses.dataclass(kw_only=True, frozen=True)
class OauthCredential(Credential):
authenticator: Literal["oauth"] = "oauth"
@dataclasses.dataclass(frozen=True)
class OauthCredential:
token: str
authenticator: Literal["oauth"] = "oauth"


@dataclasses.dataclass(kw_only=True, frozen=True)
class PasswordCredential(Credential):
authenticator: Literal["snowflake"] = "snowflake"
@dataclasses.dataclass(frozen=True)
class PasswordCredential:
password: str
authenticator: Literal["snowflake"] = "snowflake"


@dataclasses.dataclass(kw_only=True, frozen=True)
class PrivateKeyCredential(Credential):
authenticator: Literal["snowflake"] = "snowflake"
@dataclasses.dataclass(frozen=True)
class PrivateKeyCredential:
private_key: bytes
authenticator: Literal["snowflake"] = "snowflake"


@dataclasses.dataclass(kw_only=True, frozen=True)
class ExternalBrowserCredential(Credential):
authenticator: Literal["externalbrowser"] = "externalbrowser"
@dataclasses.dataclass(frozen=True)
class ExternalBrowserCredential:
password: str | None = None
authenticator: Literal["externalbrowser"] = "externalbrowser"


@dataclasses.dataclass(kw_only=True, frozen=True)
class OktaCredential(Credential):
@dataclasses.dataclass(frozen=True)
class OktaCredential:
authenticator: str
password: str

Expand Down
8 changes: 4 additions & 4 deletions schemachange/session/Script.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
T = TypeVar("T", bound="Script")


@dataclasses.dataclass(kw_only=True, frozen=True)
@dataclasses.dataclass(frozen=True)
class Script(ABC):
pattern: ClassVar[Pattern[str]]
type: ClassVar[Literal["V", "R", "A"]]
Expand Down Expand Up @@ -47,7 +47,7 @@ def from_path(cls, file_path: Path, **kwargs) -> T:
)


@dataclasses.dataclass(kw_only=True, frozen=True)
@dataclasses.dataclass(frozen=True)
class VersionedScript(Script):
pattern: ClassVar[re.Pattern[str]] = re.compile(
r"^(V)(?P<version>.+?)?__(?P<description>.+?)\.", re.IGNORECASE
Expand All @@ -64,15 +64,15 @@ def from_path(cls: T, file_path: Path, **kwargs) -> T:
)


@dataclasses.dataclass(kw_only=True, frozen=True)
@dataclasses.dataclass(frozen=True)
class RepeatableScript(Script):
pattern: ClassVar[re.Pattern[str]] = re.compile(
r"^(R)__(?P<description>.+?)\.", re.IGNORECASE
)
type: ClassVar[Literal["R"]] = "R"


@dataclasses.dataclass(kw_only=True, frozen=True)
@dataclasses.dataclass(frozen=True)
class AlwaysScript(Script):
pattern: ClassVar[re.Pattern[str]] = re.compile(
r"^(A)__(?P<description>.+?)\.", re.IGNORECASE
Expand Down
10 changes: 3 additions & 7 deletions schemachange/session/SnowflakeSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,7 @@ def get_script_metadata(

self.logger.info(
"Max applied change script version %(max_published_version)s"
% {
"max_published_version": max_published_version
if max_published_version != ""
else "None"
}
% {"max_published_version": max_published_version}
)
return change_history, r_scripts_checksum, max_published_version

Expand Down Expand Up @@ -251,10 +247,10 @@ def fetch_versioned_scripts(

# Collect all the results into a list
versioned_scripts: dict[str, dict[str, str | int]] = defaultdict(dict)
versions: list[str | int] = []
versions: list[str | int | None] = []
for cursor in results:
for version, script, checksum in cursor:
versions.append(version)
versions.append(version if version != "" else None)
versioned_scripts[script] = {
"version": version,
"script": script,
Expand Down
8 changes: 6 additions & 2 deletions tests/test_cli_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


def test_cli_given__schemachange_version_change_updated_in_setup_config_file():
assert SCHEMACHANGE_VERSION == "3.7.0"
assert SCHEMACHANGE_VERSION == "4.0.0"


def test_cli_given__constants_exist():
Expand All @@ -30,7 +30,11 @@ def test_alphanum_convert_given__lowercase():


def test_get_alphanum_key_given__empty_string():
assert get_alphanum_key("") == [""]
assert get_alphanum_key("") == []


def test_get_alphanum_key_given__none():
assert get_alphanum_key(None) == []


def test_get_alphanum_key_given__numbers_only():
Expand Down

0 comments on commit 53f893a

Please sign in to comment.