tundri 1.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. tundri-1.3.1/.github/pull_request_template.md +16 -0
  2. tundri-1.3.1/.github/workflows/manual-release.yml +81 -0
  3. tundri-1.3.1/.github/workflows/on-release-merge.yml +84 -0
  4. tundri-1.3.1/.github/workflows/pr-tests.yml +53 -0
  5. tundri-1.3.1/.github/workflows/publish-on-release.yml +35 -0
  6. tundri-1.3.1/.github/workflows/publish-to-testpypi.yml +54 -0
  7. tundri-1.3.1/.gitignore +17 -0
  8. tundri-1.3.1/.python-version +1 -0
  9. tundri-1.3.1/PKG-INFO +119 -0
  10. tundri-1.3.1/README.md +92 -0
  11. tundri-1.3.1/docs/RELEASE_WORKFLOW.md +195 -0
  12. tundri-1.3.1/docs/images/logo.jpg +0 -0
  13. tundri-1.3.1/docs/images/run_example.png +0 -0
  14. tundri-1.3.1/examples/permifrost.yml +364 -0
  15. tundri-1.3.1/examples/permifrost_with_bob.yml +393 -0
  16. tundri-1.3.1/examples/permifrost_without_bob.yml +364 -0
  17. tundri-1.3.1/pyproject.toml +62 -0
  18. tundri-1.3.1/pytest.ini +3 -0
  19. tundri-1.3.1/tests/conftest.py +33 -0
  20. tundri-1.3.1/tests/data/correct_required_params_spec.yml +21 -0
  21. tundri-1.3.1/tests/data/incorrect_required_params_spec.yml +21 -0
  22. tundri-1.3.1/tests/data/uppercase_meta_params_spec.yml +22 -0
  23. tundri-1.3.1/tests/integration_tests/test_connection.py +34 -0
  24. tundri-1.3.1/tests/integration_tests/test_ddl.py +44 -0
  25. tundri-1.3.1/tests/test_core.py +46 -0
  26. tundri-1.3.1/tests/test_parser.py +84 -0
  27. tundri-1.3.1/tests/test_utils.py +47 -0
  28. tundri-1.3.1/tundri/__init__.py +3 -0
  29. tundri-1.3.1/tundri/cli.py +98 -0
  30. tundri-1.3.1/tundri/constants.py +47 -0
  31. tundri-1.3.1/tundri/core.py +278 -0
  32. tundri-1.3.1/tundri/inspector.py +97 -0
  33. tundri-1.3.1/tundri/objects.py +74 -0
  34. tundri-1.3.1/tundri/parser.py +112 -0
  35. tundri-1.3.1/tundri/utils.py +208 -0
  36. tundri-1.3.1/uv.lock +1785 -0
@@ -0,0 +1,16 @@
1
+ ## Description
2
+
3
+ <!--- In this section, concisely describe what your Pull Request does, and why it is important (both in a technical and business value sense) -->
4
+
5
+ ## Changes
6
+
7
+ <!--- Please document here, if relevant, what has changed, and in which specific models -->
8
+
9
+ ## Checklist
10
+
11
+ <!--- Please make sure all of the below are checked off before submitting your PR ;) -->
12
+
13
+ - [ ] My pull request represents one logical piece of work
14
+ - [ ] My commits are related to the pull request and look clean
15
+ - [ ] I tested my changes locally with `pytest -vv`
16
+ - [ ] I formatted my code with `black .`
@@ -0,0 +1,81 @@
1
+ name: Manual Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version_type:
7
+ description: "Type of version bump (major, minor, patch)"
8
+ required: true
9
+ default: "patch"
10
+ type: choice
11
+ options:
12
+ - major
13
+ - minor
14
+ - patch
15
+
16
+ jobs:
17
+ release:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - name: Checkout code
22
+ uses: actions/checkout@v3
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v4
26
+ with:
27
+ python-version: '3.12'
28
+
29
+ - name: Bump version
30
+ id: bump_version
31
+ run: |
32
+ CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed -E "s/version = \"([0-9]+\.[0-9]+\.[0-9]+)\"/\1/")
33
+ echo "Current version: $CURRENT_VERSION"
34
+
35
+ # Determine the version type to bump (major, minor, patch)
36
+ VERSION_TYPE=${{ github.event.inputs.version_type }}
37
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
38
+
39
+ if [ "$VERSION_TYPE" == "major" ]; then
40
+ MAJOR=$((MAJOR + 1))
41
+ MINOR=0
42
+ PATCH=0
43
+ elif [ "$VERSION_TYPE" == "minor" ]; then
44
+ MINOR=$((MINOR + 1))
45
+ PATCH=0
46
+ elif [ "$VERSION_TYPE" == "patch" ]; then
47
+ PATCH=$((PATCH + 1))
48
+ else
49
+ echo "Invalid version type: $VERSION_TYPE"
50
+ exit 1
51
+ fi
52
+
53
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
54
+ echo "New version: $NEW_VERSION"
55
+
56
+ # Update pyproject.toml with the new version
57
+ sed -i "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" pyproject.toml
58
+
59
+ echo "new_version=$NEW_VERSION" >> $GITHUB_ENV
60
+
61
+ - name: Create release branch
62
+ id: create_branch
63
+ run: |
64
+ NEW_VERSION=${{ env.new_version }}
65
+ BRANCH_NAME="release/v$NEW_VERSION"
66
+ git checkout -b "$BRANCH_NAME"
67
+ git push origin "$BRANCH_NAME"
68
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
69
+
70
+ - name: Create pull request
71
+ uses: peter-evans/create-pull-request@v7
72
+ with:
73
+ token: ${{ secrets.GITHUB_TOKEN }}
74
+ branch: ${{ env.branch_name }}
75
+ base: main
76
+ title: "Release ${{ env.new_version }}"
77
+ body: |
78
+ This PR bumps the version to ${{ env.new_version }} and prepares the release.
79
+
80
+ # Tag and release creation will be handled by on-release-merge.yml workflow
81
+ # when this PR is merged
@@ -0,0 +1,84 @@
1
+ name: On Release Merge
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+ branches:
7
+ - main
8
+ paths:
9
+ - 'pyproject.toml'
10
+
11
+ jobs:
12
+ create-tag-and-release:
13
+ if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v3
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v4
24
+ with:
25
+ python-version: '3.12'
26
+
27
+ - name: Get version
28
+ id: get_version
29
+ run: |
30
+ VERSION=$(grep '^version = ' pyproject.toml | sed -E "s/version = \"([0-9]+\.[0-9]+\.[0-9]+)\"/\1/")
31
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
32
+
33
+ - name: Create tag
34
+ run: |
35
+ VERSION=${{ steps.get_version.outputs.version }}
36
+ TAG_NAME="v$VERSION"
37
+ git config user.name "github-actions"
38
+ git config user.email "github-actions@github.com"
39
+ git tag "$TAG_NAME"
40
+ git push origin "$TAG_NAME"
41
+
42
+ - name: Create release
43
+ uses: actions/create-release@v1
44
+ env:
45
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46
+ with:
47
+ tag_name: v${{ steps.get_version.outputs.version }}
48
+ release_name: v${{ steps.get_version.outputs.version }}
49
+ body: |
50
+ This release includes the following changes:
51
+ - Version bump to ${{ steps.get_version.outputs.version }}
52
+ draft: false
53
+ prerelease: false
54
+
55
+ publish-to-pypi:
56
+ if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
57
+ runs-on: ubuntu-latest
58
+ needs: create-tag-and-release
59
+
60
+ steps:
61
+ - name: Checkout code
62
+ uses: actions/checkout@v3
63
+
64
+ - name: Set up Python
65
+ uses: actions/setup-python@v4
66
+ with:
67
+ python-version: '3.12'
68
+
69
+ - name: Install build dependencies
70
+ run: |
71
+ python -m pip install --upgrade pip
72
+ pip install build twine
73
+
74
+ - name: Build package
75
+ run: python -m build
76
+
77
+ - name: Check package
78
+ run: twine check dist/*
79
+
80
+ - name: Publish to PyPI
81
+ env:
82
+ TWINE_USERNAME: __token__
83
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
84
+ run: twine upload dist/*
@@ -0,0 +1,53 @@
1
+ name: PR Unit Tests
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+
12
+ env:
13
+ PERMISSION_BOT_USER: ${{ secrets.SNOWFLAKE_USER }}
14
+ PERMISSION_BOT_KEY_PATH: "./.ssh/private_key.p8"
15
+ PERMISSION_BOT_KEY_PASSPHRASE: ${{ secrets.SNOWFLAKE_KEY_PASSPHRASE }}
16
+ PERMISSION_BOT_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
17
+ PERMISSION_BOT_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
18
+ PERMISSION_BOT_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
19
+ PERMISSION_BOT_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
20
+
21
+ steps:
22
+ - name: Checkout code
23
+ uses: actions/checkout@v3
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v4
27
+ with:
28
+ python-version: '3.12'
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v1
32
+ with:
33
+ version: "latest"
34
+
35
+ # snowflake-manager currently only supports connecting to Snowflake by providing
36
+ # the path to the user's private key, so we need to dump the key somewhere in
37
+ # the Ubuntu environment. While connecting via password is also possible, we are
38
+ # opting for key-pair to be future-proof
39
+ #
40
+ # TODO: extend snowflake-manager's get_snowflake_cursor() function to allow
41
+ # passing raw keys, so we can avoid exposing the private key
42
+ - name: Create folder for private key
43
+ run: mkdir -p ./.ssh
44
+
45
+ - name: Write private key to file
46
+ run: |
47
+ echo "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > $PERMISSION_BOT_KEY_PATH
48
+
49
+ - name: Install dependencies
50
+ run: uv sync
51
+
52
+ - name: Run tests
53
+ run: uv run pytest -vv
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI on Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish-to-pypi:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout code
13
+ uses: actions/checkout@v3
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v4
17
+ with:
18
+ python-version: '3.12'
19
+
20
+ - name: Install build dependencies
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install build twine
24
+
25
+ - name: Build package
26
+ run: python -m build
27
+
28
+ - name: Check package
29
+ run: twine check dist/*
30
+
31
+ - name: Publish to PyPI
32
+ env:
33
+ TWINE_USERNAME: __token__
34
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
35
+ run: twine upload dist/*
@@ -0,0 +1,54 @@
1
+ name: Publish to TestPyPI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'pyproject.toml'
9
+ - 'tundri/**'
10
+
11
+ jobs:
12
+ publish-to-testpypi:
13
+ runs-on: ubuntu-latest
14
+ if: github.event.pull_request.merged == false
15
+
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v3
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v4
22
+ with:
23
+ python-version: '3.12'
24
+
25
+ - name: Install build dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install build twine
29
+
30
+ - name: Build package
31
+ run: python -m build
32
+
33
+ - name: Check package
34
+ run: twine check dist/*
35
+
36
+ - name: Publish to TestPyPI
37
+ env:
38
+ TWINE_USERNAME: __token__
39
+ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
40
+ run: twine upload --repository testpypi dist/*
41
+
42
+ - name: Comment PR with TestPyPI link
43
+ uses: actions/github-script@v6
44
+ with:
45
+ script: |
46
+ const version = require('fs').readFileSync('pyproject.toml', 'utf8')
47
+ .match(/version = "([^"]+)"/)[1];
48
+ const testpypiUrl = `https://test.pypi.org/project/tundri/${version}/`;
49
+ github.rest.issues.createComment({
50
+ issue_number: context.issue.number,
51
+ owner: context.repo.owner,
52
+ repo: context.repo.repo,
53
+ body: `🚀 Package published to TestPyPI!\n\nYou can test the installation with:\n\`\`\`bash\npip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ tundri==${version}\n\`\`\`\n\nView package: ${testpypiUrl}`
54
+ });
@@ -0,0 +1,17 @@
1
+ # Developer-specific notes
2
+ TODO.md
3
+
4
+ # Environment variables
5
+ .env
6
+
7
+ # Python-generated files
8
+ __pycache__/
9
+ *.py[oc]
10
+ build/
11
+ dist/
12
+ wheels/
13
+ *.egg-info
14
+
15
+ # Virtual environments
16
+ .venv
17
+
@@ -0,0 +1 @@
1
+ 3.12
tundri-1.3.1/PKG-INFO ADDED
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: tundri
3
+ Version: 1.3.1
4
+ Summary: Drop, create and alter Snowflake objects and set permissions with Permifrost
5
+ Project-URL: Homepage, https://github.com/Gemma-Analytics/tundri
6
+ Project-URL: Repository, https://github.com/Gemma-Analytics/tundri
7
+ Project-URL: Issues, https://github.com/Gemma-Analytics/tundri/issues
8
+ Author-email: Gemma Analytics <bijan.soltani@gemmaanalytics.com>
9
+ License: MIT
10
+ Keywords: database,ddl,permifrost,snowflake
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Requires-Dist: gemma-permifrost
22
+ Requires-Dist: python-dotenv
23
+ Requires-Dist: pyyaml
24
+ Requires-Dist: rich
25
+ Requires-Dist: snowflake-connector-python
26
+ Description-Content-Type: text/markdown
27
+
28
+ <div align="center">
29
+ <img src="docs/images/logo.jpg" alt="Snowflake Manager Logo" width="200">
30
+ </div>
31
+
32
+ **tundri** is a Python package to declaratively create, drop, and alter Snowflake objects and manage their permissions with [Permifrost](https://gitlab.com/gitlab-data/permifrost).
33
+
34
+ ## Motivation
35
+
36
+ Permifrost is great at managing permissions, but it doesn't create or alter objects. As [GitLab's data team handbook](https://handbook.gitlab.com/handbook/enterprise-data/platform/permifrost/) states:
37
+ > Object creation and deletion is not managed by permifrost
38
+
39
+ With only Permifrost, one would have to manually create the objects and then run Permifrost to set the permissions. This is error prone and time consuming. That is where tundri comes in.
40
+
41
+ ### In a nutshell
42
+ **tundri** reads the [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) and compares with the current state of the Snowflake account. It then creates, drops, and alters the objects to match. It leverages Permifrost's YAML `meta` tags to set attributes like `default_role` for users and `warehouse_size` for warehouses. Once the objects are created, tundri runs Permifrost to set the permissions.
43
+
44
+ ## Getting started
45
+
46
+ ### Prerequisites
47
+
48
+ - Credentials to a Snowflake account with the `securityadmin` role
49
+ - A Permifrost spec file
50
+
51
+ ### Install
52
+
53
+ ```bash
54
+ pip install tundri
55
+ ```
56
+
57
+ ### Configure
58
+
59
+ #### Permifrost
60
+ Add a valid [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) to your repository. You can use the files in the `examples` folder as reference.
61
+
62
+ #### Snowflake
63
+ Set up your Snowflake connection details in the environment variables listed below.
64
+
65
+ > [!TIP]
66
+ > You can use a `.env` file to store your credentials. Place it in the same folder as the Permifrost spec file.
67
+
68
+ ```bash
69
+ PERMISSION_BOT_ACCOUNT=abc134.west-europe.azure # Your account identifier
70
+ PERMISSION_BOT_USER=PERMIFROST
71
+ PERMISSION_BOT_PASSWORD=...
72
+ PERMISSION_BOT_ROLE=SECURITYADMIN # Permifrost requires it to be `SECURITYADMIN`
73
+ PERMISSION_BOT_DATABASE=PERMIFROST
74
+ PERMISSION_BOT_WAREHOUSE=ADMIN
75
+ ```
76
+
77
+ ### Usage
78
+ The `run` subcommand is going to drop/create objects and run Permifrost.
79
+
80
+ #### Dry run
81
+ ```bash
82
+ tundri run --permifrost_spec_path examples/permifrost.yml --dry
83
+ ```
84
+
85
+ #### Normal run
86
+ ```bash
87
+ tundri run --permifrost_spec_path examples/permifrost.yml
88
+ ```
89
+
90
+ #### Getting help
91
+ ```bash
92
+ tundri --help
93
+ ```
94
+
95
+ ## Development
96
+ ### Local setup
97
+ Install the development dependencies
98
+
99
+ ```bash
100
+ uv sync
101
+ ```
102
+
103
+ ### Run tests
104
+ Run the tests
105
+ ```bash
106
+ uv run pytest -v
107
+ ```
108
+
109
+ ### Formatting
110
+ Run the command below to format the code
111
+ ```bash
112
+ uv run black .
113
+ ```
114
+
115
+ ### Testing locally
116
+ Dry run with the example spec file
117
+ ```bash
118
+ uv run tundri run --dry -p examples/permifrost.yml
119
+ ```
tundri-1.3.1/README.md ADDED
@@ -0,0 +1,92 @@
1
+ <div align="center">
2
+ <img src="docs/images/logo.jpg" alt="Snowflake Manager Logo" width="200">
3
+ </div>
4
+
5
+ **tundri** is a Python package to declaratively create, drop, and alter Snowflake objects and manage their permissions with [Permifrost](https://gitlab.com/gitlab-data/permifrost).
6
+
7
+ ## Motivation
8
+
9
+ Permifrost is great at managing permissions, but it doesn't create or alter objects. As [GitLab's data team handbook](https://handbook.gitlab.com/handbook/enterprise-data/platform/permifrost/) states:
10
+ > Object creation and deletion is not managed by permifrost
11
+
12
+ With only Permifrost, one would have to manually create the objects and then run Permifrost to set the permissions. This is error prone and time consuming. That is where tundri comes in.
13
+
14
+ ### In a nutshell
15
+ **tundri** reads the [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) and compares with the current state of the Snowflake account. It then creates, drops, and alters the objects to match. It leverages Permifrost's YAML `meta` tags to set attributes like `default_role` for users and `warehouse_size` for warehouses. Once the objects are created, tundri runs Permifrost to set the permissions.
16
+
17
+ ## Getting started
18
+
19
+ ### Prerequisites
20
+
21
+ - Credentials to a Snowflake account with the `securityadmin` role
22
+ - A Permifrost spec file
23
+
24
+ ### Install
25
+
26
+ ```bash
27
+ pip install tundri
28
+ ```
29
+
30
+ ### Configure
31
+
32
+ #### Permifrost
33
+ Add a valid [Permifrost spec file](https://gitlab.com/gitlab-data/permifrost#spec_file) to your repository. You can use the files in the `examples` folder as reference.
34
+
35
+ #### Snowflake
36
+ Set up your Snowflake connection details in the environment variables listed below.
37
+
38
+ > [!TIP]
39
+ > You can use a `.env` file to store your credentials. Place it in the same folder as the Permifrost spec file.
40
+
41
+ ```bash
42
+ PERMISSION_BOT_ACCOUNT=abc134.west-europe.azure # Your account identifier
43
+ PERMISSION_BOT_USER=PERMIFROST
44
+ PERMISSION_BOT_PASSWORD=...
45
+ PERMISSION_BOT_ROLE=SECURITYADMIN # Permifrost requires it to be `SECURITYADMIN`
46
+ PERMISSION_BOT_DATABASE=PERMIFROST
47
+ PERMISSION_BOT_WAREHOUSE=ADMIN
48
+ ```
49
+
50
+ ### Usage
51
+ The `run` subcommand is going to drop/create objects and run Permifrost.
52
+
53
+ #### Dry run
54
+ ```bash
55
+ tundri run --permifrost_spec_path examples/permifrost.yml --dry
56
+ ```
57
+
58
+ #### Normal run
59
+ ```bash
60
+ tundri run --permifrost_spec_path examples/permifrost.yml
61
+ ```
62
+
63
+ #### Getting help
64
+ ```bash
65
+ tundri --help
66
+ ```
67
+
68
+ ## Development
69
+ ### Local setup
70
+ Install the development dependencies
71
+
72
+ ```bash
73
+ uv sync
74
+ ```
75
+
76
+ ### Run tests
77
+ Run the tests
78
+ ```bash
79
+ uv run pytest -v
80
+ ```
81
+
82
+ ### Formatting
83
+ Run the command below to format the code
84
+ ```bash
85
+ uv run black .
86
+ ```
87
+
88
+ ### Testing locally
89
+ Dry run with the example spec file
90
+ ```bash
91
+ uv run tundri run --dry -p examples/permifrost.yml
92
+ ```