diff --git a/.github/pgslie-example.gif b/.github/pgslie-example.gif new file mode 100644 index 0000000..87cce27 Binary files /dev/null and b/.github/pgslie-example.gif differ diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index dbc1821..b01901e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -29,6 +29,20 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Get latest tag for version tagging + id: get_tag + run: | + # If triggered from a tag, use that tag + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + TAG="${{ github.ref_name }}" + else + # Otherwise, fetch and use the latest tag + git fetch --tags + TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + fi + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Using tag: $TAG" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -41,10 +55,8 @@ jobs: with: images: edraobdu/pgslice tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + type=semver,pattern={{version}},value=${{ steps.get_tag.outputs.tag }} + type=raw,value=latest - name: Log in to DockerHub uses: docker/login-action@v3 diff --git a/.github/workflows/release-finalize.yml b/.github/workflows/release-finalize.yml index 1e5b133..cbab390 100644 --- a/.github/workflows/release-finalize.yml +++ b/.github/workflows/release-finalize.yml @@ -89,6 +89,14 @@ jobs: echo "Triggering publish workflow for tag v${{ steps.get_version.outputs.version }}" gh workflow run publish.yml --ref "v${{ steps.get_version.outputs.version }}" + - name: Trigger Docker publish workflow + if: steps.check_release.outputs.is_release == 'true' && steps.check_tag.outputs.tag_exists == 'false' + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "Triggering docker-publish workflow for tag v${{ steps.get_version.outputs.version }}" + gh workflow run docker-publish.yml --ref "v${{ steps.get_version.outputs.version }}" + - name: Delete release branch if: steps.check_release.outputs.is_release == 'true' continue-on-error: true diff --git a/README.md b/README.md index bcbf5c1..96a03f2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Python CLI tool for extracting PostgreSQL records with all related data via foreign key relationships. +![PgSlice Example](.github/pgslie-example.gif) + ## Overview `pgslice` extracts a specific database record and **ALL** its related records by following foreign key relationships bidirectionally. Perfect for: @@ -57,6 +59,11 @@ pip install pgslice # Or with uv uv tool install pgslice + +# check instalation +pgslice --version +# or +uv run pgslice --version ``` ### From Docker Hub @@ -65,12 +72,12 @@ uv tool install pgslice # Pull the image docker pull edraobdu/pgslice:latest -# Run pgslice +# Check instalation docker run --rm -it \ -v $(pwd)/dumps:/home/pgslice/.pgslice/dumps \ -e PGPASSWORD=your_password \ edraobdu/pgslice:latest \ - pgslice --host your.db.host --port 5432 --user your_user --database your_db + pgslice --version # Pin to specific version docker pull edraobdu/pgslice:0.1.1 @@ -128,16 +135,6 @@ docker run --rm -it \ pgslice --host your.db.host --database your_db --dump users --pks 42 ``` -**For remote servers:** -```bash -# Run dump on remote server -ssh user@remote-server "docker run --rm -v /tmp/dumps:/home/pgslice/.pgslice/dumps \ - edraobdu/pgslice:latest pgslice --dump users --pks 42" - -# Copy file locally -scp user@remote-server:/tmp/dumps/users_42_*.sql ./ -``` - ### From Source (Development) See [DEVELOPMENT.md](DEVELOPMENT.md) for detailed development setup instructions. @@ -192,87 +189,17 @@ pgslice --host localhost --database mydb --tables pgslice --host localhost --database mydb --describe users ``` -### SSH Remote Execution - -Run pgslice on a remote server and capture output locally: - -```bash -# Execute on remote server, save output locally -ssh remote.server.com "PGPASSWORD=xxx pgslice --host db.internal --database mydb \ - --dump users --pks 1 --create-schema" > local_dump.sql - -# With SSH tunnel for database access -ssh -f -N -L 5433:db.internal:5432 bastion.example.com -PGPASSWORD=xxx pgslice --host localhost --port 5433 --database mydb \ - --dump users --pks 42 > user.sql -``` - ### Interactive REPL ```bash # Start interactive REPL -pgslice --host localhost --database mydb +PGPASSWORD=mypassword pgslice --host localhost --database mydb --user myuser --port 5432 -pgslice> dump "film" 1 --output film_1.sql +pgslice> dump film 1 --output film_1.sql pgslice> tables -pgslice> describe "film" -``` - -## CLI vs REPL: Output Behavior - -Understanding the difference between CLI and REPL modes: - -### CLI Mode (files with progress) -The CLI writes to files and shows progress bars (helpful for large datasets): - -```bash -# Writes to ~/.pgslice/dumps/public_users_42_TIMESTAMP.sql -pgslice --dump users --pks 42 - -# Specify output file -pgslice --dump users --pks 42 --output user_42.sql +pgslice> describe film ``` -### REPL Mode (same behavior) -The REPL also writes to **`~/.pgslice/dumps/`** by default: - -```bash -# Writes to ~/.pgslice/dumps/public_users_42_TIMESTAMP.sql -pgslice> dump "users" 42 - -# Specify custom output path -pgslice> dump "users" 42 --output /path/to/user.sql -``` - -Both modes now behave identically - always writing to files with visible progress. - -### Same Operations, Different Modes - -| Operation | CLI | REPL | -|-----------|-----|------| -| **List tables** | `pgslice --tables` | `pgslice> tables` | -| **Describe table** | `pgslice --describe users` | `pgslice> describe "users"` | -| **Dump (auto-named)** | `pgslice --dump users --pks 42` | `pgslice> dump "users" 42` | -| **Dump to file** | `pgslice --dump users --pks 42 --output user.sql` | `pgslice> dump "users" 42 --output user.sql` | -| **Dump (default path)** | `~/.pgslice/dumps/public_users_42_TIMESTAMP.sql` | `~/.pgslice/dumps/public_users_42_TIMESTAMP.sql` | -| **Multiple PKs** | `pgslice --dump users --pks 1,2,3` | `pgslice> dump "users" 1,2,3` | -| **Truncate filter** | `pgslice --dump users --pks 42 --truncate "orders:2024-01-01:2024-12-31"` | `pgslice> dump "users" 42 --truncate "orders:2024-01-01:2024-12-31"` | -| **Wide mode** | `pgslice --dump users --pks 42 --wide` | `pgslice> dump "users" 42 --wide` | - -### When to Use Each Mode - -**Use CLI mode when:** -- Piping output to other commands -- Scripting and automation -- Remote execution via SSH -- One-off dumps - -**Use REPL mode when:** -- Exploring database schema interactively -- Running multiple dumps in a session -- You prefer persistent file output -- Testing different dump configurations - ## Configuration Key environment variables (see `.env.example` for full reference): diff --git a/pyproject.toml b/pyproject.toml index 690f147..080b8d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ dependencies = [ "psycopg[binary]>=3.2.2", "prompt-toolkit>=3.0.0", - "printy==3.0.0", + "printy==3.0.1", "tabulate>=0.9.0", "python-dotenv>=1.0.0", "tqdm>=4.66.0", diff --git a/uv.lock b/uv.lock index a393558..6f848f0 100644 --- a/uv.lock +++ b/uv.lock @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "pgslice" -version = "0.1.1" +version = "0.2.1" source = { editable = "." } dependencies = [ { name = "printy" }, @@ -402,7 +402,7 @@ requires-dist = [ { name = "freezegun", marker = "extra == 'dev'", specifier = ">=1.2.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0.0" }, - { name = "printy", specifier = "==3.0.0" }, + { name = "printy", specifier = "==3.0.1" }, { name = "prompt-toolkit", specifier = ">=3.0.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, @@ -453,11 +453,11 @@ wheels = [ [[package]] name = "printy" -version = "3.0.0" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/85/e394b48e5c93f6eeabd9a160507cc95c0998297f4962d1aa598692a345a5/printy-3.0.0.tar.gz", hash = "sha256:2760dac665f02466e619ac3e337881533c5a8ee1dc0cc447062497865cbe0d05", size = 22202, upload-time = "2025-12-27T01:51:45.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/a0/d76ce4a38893610c5c8f21b118ddab0cd55e28e32d7adcb44c2cb79d39bc/printy-3.0.1.tar.gz", hash = "sha256:4a8b29bff6994542852346980c2c52ad3c4301bb58119100dead7b6a00f63358", size = 23034, upload-time = "2025-12-28T17:16:15.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/79/5f0fb8a5e6a35c0bb5221e0fb912e210b64a26684a0be7cf1af3dc45e5db/printy-3.0.0-py3-none-any.whl", hash = "sha256:d5773dd5fac991c726edc0e5d83b9ad7d33aa1adcc6f1849be489ea6223c997f", size = 14894, upload-time = "2025-12-27T01:51:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e6/00f4ff4a2698fcba52537f0d14b09e5af9354ba6689770476a2b5fee9cfe/printy-3.0.1-py3-none-any.whl", hash = "sha256:7992610f6c7c2c1da99ebb082ab8897bf8eff890c07f8730d9c5cae876963f24", size = 15253, upload-time = "2025-12-28T17:16:13.854Z" }, ] [[package]]