-
Notifications
You must be signed in to change notification settings - Fork 24
Add GHA workflow to publish server to PyPI #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| name: Agent Memory Server CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| tags: | ||
| - 'server/v*.*.*' | ||
| pull_request: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build package | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: '3.12' | ||
|
|
||
| - name: Install build tools | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install build | ||
|
|
||
| - name: Build package | ||
| run: python -m build | ||
|
|
||
| - name: Upload dist artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: dist | ||
| path: dist/* | ||
|
|
||
| publish-testpypi: | ||
| name: Publish to TestPyPI | ||
| needs: build | ||
| if: startsWith(github.ref, 'refs/tags/server/') && contains(github.ref, '-test') | ||
| runs-on: ubuntu-latest | ||
| environment: testpypi | ||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
| steps: | ||
| - name: Download dist artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: dist | ||
| path: dist | ||
|
|
||
| - name: Publish package to TestPyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org/legacy/ | ||
| packages-dir: dist/ | ||
|
|
||
| publish-pypi: | ||
| name: Publish to PyPI | ||
| needs: build | ||
| if: startsWith(github.ref, 'refs/tags/server/') && !contains(github.ref, '-test') | ||
| runs-on: ubuntu-latest | ||
| environment: pypi | ||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
| steps: | ||
| - name: Download dist artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: dist | ||
| path: dist | ||
|
|
||
| - name: Publish package to PyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| packages-dir: dist/ | ||
|
|
||
| # Tag Format Guide: | ||
| # - For TestPyPI (testing): server/v1.0.0-test | ||
| # - For PyPI (production): server/v1.0.0 | ||
| # | ||
| # Use the script: python scripts/tag_and_push_server.py --test (for TestPyPI) | ||
| # python scripts/tag_and_push_server.py (for PyPI) | ||
| # | ||
| # This workflow uses PyPI Trusted Publishing (OIDC). Ensure the project is configured | ||
| # on PyPI to trust this GitHub repository before releasing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| """Redis Agent Memory Server - A memory system for conversational AI.""" | ||
|
|
||
| __version__ = "0.12.3" | ||
| __version__ = "0.12.4" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Script to tag and push agent-memory-server releases. | ||
|
|
||
| This script: | ||
| 1. Reads the current version from agent_memory_server/__init__.py | ||
| 2. Creates a git tag in the format: server/v{version} | ||
| 3. Pushes the tag to origin | ||
|
|
||
| Usage: | ||
| python scripts/tag_and_push_server.py [--dry-run] [--force] [--test] | ||
| """ | ||
|
|
||
| import argparse | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def get_server_version() -> str: | ||
| """Read the version from agent_memory_server/__init__.py""" | ||
| init_file = Path("agent_memory_server/__init__.py") | ||
|
|
||
| if not init_file.exists(): | ||
| raise FileNotFoundError(f"Could not find {init_file}") | ||
|
|
||
| content = init_file.read_text() | ||
|
|
||
| # Look for __version__ = "x.y.z" | ||
| version_match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content) | ||
|
|
||
| if not version_match: | ||
| raise ValueError(f"Could not find __version__ in {init_file}") | ||
|
|
||
| return version_match.group(1) | ||
|
|
||
|
|
||
| def run_command(cmd: list[str], dry_run: bool = False) -> subprocess.CompletedProcess: | ||
| """Run a command, optionally in dry-run mode.""" | ||
| print(f"Running: {' '.join(cmd)}") | ||
|
|
||
| if dry_run: | ||
| print(" (dry-run mode - command not executed)") | ||
| return subprocess.CompletedProcess(cmd, 0, "", "") | ||
|
|
||
| try: | ||
| result = subprocess.run(cmd, capture_output=True, text=True, check=True) | ||
| if result.stdout: | ||
| print(f" Output: {result.stdout.strip()}") | ||
| return result | ||
| except subprocess.CalledProcessError as e: | ||
| print(f" Error: {e.stderr.strip()}") | ||
| raise | ||
|
|
||
|
|
||
| def check_git_status(): | ||
| """Check if git working directory is clean.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["git", "status", "--porcelain"], capture_output=True, text=True, check=True | ||
| ) | ||
| if result.stdout.strip(): | ||
| print("Warning: Git working directory is not clean:") | ||
| print(result.stdout) | ||
| response = input("Continue anyway? (y/N): ") | ||
| if response.lower() != "y": | ||
| sys.exit(1) | ||
| except subprocess.CalledProcessError: | ||
| print("Error: Could not check git status") | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| def tag_exists(tag_name: str) -> bool: | ||
| """Check if a tag already exists.""" | ||
| try: | ||
| subprocess.run( | ||
| ["git", "rev-parse", f"refs/tags/{tag_name}"], | ||
| stdout=subprocess.DEVNULL, | ||
| stderr=subprocess.DEVNULL, | ||
| check=True, | ||
| ) | ||
| return True | ||
| except subprocess.CalledProcessError: | ||
| return False | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser( | ||
| description="Tag and push agent-memory-server release" | ||
| ) | ||
| parser.add_argument( | ||
| "--dry-run", | ||
| action="store_true", | ||
| help="Show what would be done without actually doing it", | ||
| ) | ||
| parser.add_argument( | ||
| "--force", | ||
| action="store_true", | ||
| help="Force tag creation even if tag already exists", | ||
| ) | ||
| parser.add_argument( | ||
| "--test", | ||
| action="store_true", | ||
| help="Add '-test' suffix to tag for TestPyPI deployment", | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Change to project root directory | ||
| script_dir = Path(__file__).parent | ||
| project_root = script_dir.parent | ||
|
|
||
| try: | ||
| original_cwd = Path.cwd() | ||
| if project_root.resolve() != original_cwd.resolve(): | ||
| print(f"Changing to project root: {project_root}") | ||
| import os | ||
|
|
||
| os.chdir(project_root) | ||
| except Exception as e: | ||
| print(f"Warning: Could not change to project root: {e}") | ||
|
|
||
| try: | ||
| # Get the current version | ||
| version = get_server_version() | ||
| tag_suffix = "-test" if args.test else "" | ||
| tag_name = f"server/v{version}{tag_suffix}" | ||
|
|
||
| print(f"Current server version: {version}") | ||
| print(f"Tag to create: {tag_name}") | ||
| print(f"Deployment target: {'TestPyPI' if args.test else 'PyPI (Production)'}") | ||
|
|
||
| if not args.dry_run: | ||
| # Check git status | ||
| check_git_status() | ||
|
|
||
| # Check if tag already exists | ||
| if tag_exists(tag_name): | ||
| if args.force: | ||
| print(f"Tag {tag_name} already exists, but --force specified") | ||
| run_command(["git", "tag", "-d", tag_name], args.dry_run) | ||
| else: | ||
| print( | ||
| f"Error: Tag {tag_name} already exists. Use --force to overwrite." | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| # Create the tag | ||
| run_command(["git", "tag", tag_name], args.dry_run) | ||
|
|
||
| # Push the tag | ||
| push_cmd = ["git", "push", "origin", tag_name] | ||
| if args.force: | ||
| push_cmd.insert(2, "--force") | ||
|
|
||
| run_command(push_cmd, args.dry_run) | ||
|
|
||
| print(f"\n✅ Successfully tagged and pushed {tag_name}") | ||
|
|
||
| if not args.dry_run: | ||
| print("\nThis should trigger the GitHub Actions workflow for:") | ||
| if args.test: | ||
| print(" - TestPyPI publication (testing)") | ||
| else: | ||
| print(" - PyPI publication (production)") | ||
|
|
||
| except Exception as e: | ||
| print(f"Error: {e}") | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
import osstatement should be moved to the top of the file with other imports (around line 14-18). Importing modules inside functions or conditional blocks is generally discouraged as it can make code harder to understand and debug. Move it to the module-level imports section.