diff --git a/.github/workflows/cli_test.yaml b/.github/workflows/cli_test.yaml index f6fb914..f3ca7bd 100644 --- a/.github/workflows/cli_test.yaml +++ b/.github/workflows/cli_test.yaml @@ -12,50 +12,54 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: "3.9" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true - name: Install dependencies - run: | - pip install poetry - poetry install + run: uv sync --no-dev --group test - name: Test CLI Commands run: | app_name="${{ matrix.app_type }}App" case "${{ matrix.app_type }}" in "Blank") - poetry run pynest generate application -n "$app_name" + uv run pynest generate application -n "$app_name" ;; "SyncORM") - poetry run pynest generate application -n "$app_name" -db sqlite + uv run pynest generate application -n "$app_name" -db sqlite ;; "AsyncORM") - poetry run pynest generate application -n "$app_name" -db sqlite --is-async + uv run pynest generate application -n "$app_name" -db sqlite --is-async ;; "MongoDB") - poetry run pynest generate application -n "$app_name" -db mongodb + uv run pynest generate application -n "$app_name" -db mongodb ;; "PostgresSync") - poetry run pynest generate application -n "$app_name" -db postgresql + uv run pynest generate application -n "$app_name" -db postgresql ;; "PostgresAsync") - poetry run pynest generate application -n "$app_name" -db postgresql --is-async + uv run pynest generate application -n "$app_name" -db postgresql --is-async ;; "MySQLSync") - poetry run pynest generate application -n "$app_name" -db mysql + uv run pynest generate application -n "$app_name" -db mysql ;; "MySQLAsync") - poetry run pynest generate application -n "$app_name" -db mysql --is-async + uv run pynest generate application -n "$app_name" -db mysql --is-async ;; esac - + cd "$app_name" - poetry run pynest generate resource -n user + uv run pynest generate resource -n user - name: Verify Boilerplate run: | @@ -66,7 +70,7 @@ jobs: echo "Directory $app_name does not exist." exit 1 fi - + if [ -d "$app_name/src/user" ]; then echo "Directory $app_name/src/user exists." else @@ -74,12 +78,10 @@ jobs: exit 1 fi - # List of expected files declare -a files=("main.py" "requirements.txt" "README.md") declare -a src_level_files=("app_module.py" "app_service.py" "app_controller.py") declare -a module_files=("user_controller.py" "user_service.py" "user_module.py" "user_model.py") - # Check each file in the list of files for file in "${files[@]}"; do if [ -f "$app_name/$file" ]; then echo "$file exists in $app_name." @@ -88,8 +90,7 @@ jobs: exit 1 fi done - - # Check each file in the list of files + for file in "${src_level_files[@]}"; do if [ -f "$app_name/src/$file" ]; then echo "$file exists in $app_name." @@ -99,7 +100,6 @@ jobs: fi done - # Check each file in the list of module_files for file in "${module_files[@]}"; do if [ -f "$app_name/src/user/$file" ]; then echo "$file exists in $app_name." @@ -109,4 +109,4 @@ jobs: fi done - echo "Boilerplate for ${{ matrix.app_type }} generated successfully." \ No newline at end of file + echo "Boilerplate for ${{ matrix.app_type }} generated successfully." diff --git a/.github/workflows/deploy_docs.yaml b/.github/workflows/deploy_docs.yaml index c179add..9c490a2 100644 --- a/.github/workflows/deploy_docs.yaml +++ b/.github/workflows/deploy_docs.yaml @@ -20,32 +20,36 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Copy License File run: cp LICENSE docs/license.md - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true + - name: Install dependencies - run: | - pip install poetry - poetry install --with docs + run: uv sync --no-dev --group docs - name: Build docs - run: poetry run mkdocs build --clean + run: uv run mkdocs build --clean - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: - path: 'site' + path: "site" - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 2f122cc..8196839 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,17 +12,21 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: "3.9" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true - name: Install dependencies - run: | - pip install poetry - poetry install + run: uv sync --no-dev --group test - name: Start Application run: | @@ -38,15 +42,14 @@ jobs: fi if [ "${{ matrix.app_type }}" == "Blank" ]; then - poetry run pynest generate application -n "$app_name" + uv run pynest generate application -n "$app_name" else - poetry run pynest generate application -n "$app_name" -db sqlite $is_async - poetry add aiosqlite + uv run pynest generate application -n "$app_name" -db sqlite $is_async fi cd "$app_name" - poetry run pynest generate resource -n user - poetry run uvicorn "src.app_module:http_server" --host "0.0.0.0" --port 8000 --reload & + uv run pynest generate resource -n user + uv run uvicorn "src.app_module:http_server" --host "0.0.0.0" --port 8000 --reload & - name: Wait for the server to start run: sleep 10 @@ -62,4 +65,4 @@ jobs: curl -f http://localhost:8000/user/ - name: Kill the server - run: kill $(jobs -p) || true \ No newline at end of file + run: kill $(jobs -p) || true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b86e0b2..3567ffc 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,14 +2,11 @@ name: Release Package on: workflow_dispatch: - permissions: - users: - - ItayTheDar inputs: increment_version: - description: 'Increment version by major, minor, or patch' + description: "Increment version by major, minor, or patch" required: true - default: 'patch' + default: "patch" type: choice options: - major @@ -17,7 +14,6 @@ on: - patch env: - VERSION_FILE_PATH: pyproject.toml CHANGELOG_FILE_PATH: CHANGELOG.md jobs: @@ -32,69 +28,84 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.RELEASE_GIT_TOKEN }} - name: Install Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - - name: Install Poetry - run: | - pip install poetry + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true - - name: Set version using Poetry + - name: Set version run: | - # Extract the current version - CURRENT_VERSION=$(poetry version -s) + CURRENT_VERSION=$(uv version --short) echo "Current version: $CURRENT_VERSION" - - # Increment version - case ${{ inputs.increment_version }} in - major) - NEW_VERSION=$(poetry version major | awk '{print $NF}') - ;; - minor) - NEW_VERSION=$(poetry version minor | awk '{print $NF}') - ;; - patch) - NEW_VERSION=$(poetry version patch | awk '{print $NF}') - ;; - *) - echo "Invalid input for increment_version" - exit 1 - ;; - esac + + uv version --bump "${{ inputs.increment_version }}" --frozen + NEW_VERSION=$(uv version --short) echo "New version: $NEW_VERSION" - echo "RELEASE_VERSION=$NEW_VERSION" >> $GITHUB_ENV + echo "RELEASE_VERSION=$NEW_VERSION" >> "$GITHUB_ENV" + + NEW_VERSION="$NEW_VERSION" python - <<'PY' + import os + import re + from pathlib import Path + + version = os.environ["NEW_VERSION"] + path = Path("nest/__init__.py") + text = path.read_text() + updated = re.sub( + r'^__version__ = "[^"]+"$', + f'__version__ = "{version}"', + text, + flags=re.MULTILINE, + ) + if updated == text: + raise SystemExit("Could not update nest/__init__.py version") + path.write_text(updated) + PY - name: Install build dependencies - run: | - poetry install --with build - + run: uv sync --no-dev --group build + - name: Update CHANGELOG.md - run: poetry run git-changelog . -o $CHANGELOG_FILE_PATH + run: uv run git-changelog . -o "$CHANGELOG_FILE_PATH" - - name: Build package with Poetry - run: | - poetry build + - name: Build package + run: uv build --sdist --wheel + + - name: Check package metadata + run: uv run twine check dist/* + + - name: Smoke test wheel + run: uv run --isolated --no-project --with dist/*.whl python -c "import nest; from nest.cli.cli import nest_cli; print(nest.__version__)" + + - name: Smoke test source distribution + run: uv run --isolated --no-project --with dist/*.tar.gz python -c "import nest; from nest.cli.cli import nest_cli; print(nest.__version__)" - name: Commit and push changes run: | git config --global user.name "github-actions" git config --global user.email "github@actions.com" - git add $CHANGELOG_FILE_PATH pyproject.toml - git commit -m "Increment version to $NEW_VERSION" + git add "$CHANGELOG_FILE_PATH" pyproject.toml nest/__init__.py + git commit -m "Increment version to $RELEASE_VERSION" git push - name: Publish package to PyPI - run: | - poetry publish --username ${{ secrets.PYPI_API_USER }} --password ${{ secrets.PYPI_API_TOKEN }} + env: + UV_PUBLISH_USERNAME: ${{ secrets.PYPI_API_USER }} + UV_PUBLISH_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: uv publish - name: Create GitHub release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: v${{ env.RELEASE_VERSION }} tag_name: v${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/release_beta.yaml b/.github/workflows/release_beta.yaml index 74c5332..7bbc545 100644 --- a/.github/workflows/release_beta.yaml +++ b/.github/workflows/release_beta.yaml @@ -2,23 +2,18 @@ name: Release Beta on: workflow_dispatch: - permissions: - users: - - ItayTheDar inputs: increment_version: - description: 'Increment version by major, minor, or patch' + description: "Increment version by major, minor, or patch" required: true - default: 'patch' + default: "patch" type: choice options: - major - minor - patch - env: - VERSION_FILE_PATH: nest/__init__.py CHANGELOG_FILE_PATH: CHANGELOG.md jobs: @@ -29,73 +24,81 @@ jobs: runs-on: ubuntu-latest needs: run-tests permissions: - contents: write + contents: read steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v5 with: - token: ${{ secrets.RELEASE_GIT_TOKEN }} + python-version: "3.10" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true - name: Set version run: | - VERSION_REGEX='^(__version__ = \")(([[:digit:]]+\.)*[[:digit:]]+)((a|b|rc)[[:digit:]]+)?(\.post[[:digit:]]+)?(.dev[[:digit:]]+)?(\")$' - - # get current version from version file - CURRENT_VERSION=`sed -n -E "s/$VERSION_REGEX/\2/p" $VERSION_FILE_PATH` + CURRENT_VERSION=$(uv version --short) echo "Current version: $CURRENT_VERSION" - # switch case for incrementing_version based on input - case ${{ inputs.increment_version }} in - major) - NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$1=$1+1; $2=0; $3=0; print $0}' OFS=".") - ;; - minor) - NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$2=$2+1; $3=0; print $0}' OFS=".") - ;; - patch) - NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$3=$3+1; print $0}' OFS=".") - ;; - *) - echo "Invalid input for increment_version" - exit 1 - ;; - esac + uv version --bump "${{ inputs.increment_version }}" --frozen + NEW_VERSION=$(uv version --short) echo "New version: $NEW_VERSION" - echo "RELEASE_VERSION=$NEW_VERSION" >> $GITHUB_ENV - - # update version file - sed -i -E "s/$VERSION_REGEX/\1$NEW_VERSION\8/" $VERSION_FILE_PATH - + echo "RELEASE_VERSION=$NEW_VERSION" >> "$GITHUB_ENV" + + NEW_VERSION="$NEW_VERSION" python - <<'PY' + import os + import re + from pathlib import Path + + version = os.environ["NEW_VERSION"] + path = Path("nest/__init__.py") + text = path.read_text() + updated = re.sub( + r'^__version__ = "[^"]+"$', + f'__version__ = "{version}"', + text, + flags=re.MULTILINE, + ) + if updated == text: + raise SystemExit("Could not update nest/__init__.py version") + path.write_text(updated) + PY + + - name: Install build dependencies + run: uv sync --no-dev --group build - - name: Install python - uses: actions/setup-python@v4 - with: - python-version: "3.10" + - name: Update CHANGELOG.md + run: uv run git-changelog . -o "$CHANGELOG_FILE_PATH" - - name: Install python dependencies - run: | - pip install --upgrade pip - pip install -r requirements-release.txt + - name: Build package + run: uv build --sdist --wheel - - name: Update CHANGELOG.md - run: git-changelog . -o $CHANGELOG_FILE_PATH + - name: Check package metadata + run: uv run twine check dist/* + - name: Smoke test wheel + run: uv run --isolated --no-project --with dist/*.whl python -c "import nest; from nest.cli.cli import nest_cli; print(nest.__version__)" - - name: Build package - run: python -m build + - name: Smoke test source distribution + run: uv run --isolated --no-project --with dist/*.tar.gz python -c "import nest; from nest.cli.cli import nest_cli; print(nest.__version__)" - name: Publish package to TestPyPI - run: | - python -m twine upload dist/* -r testpypi -u ${{ secrets.TEST_PYPI_API_USER }} -p ${{ secrets.TEST_PYPI_API_TOKEN }} + env: + UV_PUBLISH_USERNAME: ${{ secrets.TEST_PYPI_API_USER }} + UV_PUBLISH_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} + run: uv publish --publish-url https://test.pypi.org/legacy/ --check-url https://test.pypi.org/simple/ - name: Install test package run: | - pip install --index-url https://test.pypi.org/simple/ --no-deps pynest-api - - # import to check if package is installed correctly - python -c "import nest" + uv venv .testpypi-venv + uv pip install --python .testpypi-venv/bin/python --index-url https://test.pypi.org/simple/ --no-deps "pynest-api==$RELEASE_VERSION" + .testpypi-venv/bin/python -c "import nest; print(nest.__version__)" - name: Copy pip url - run: | - echo "pip install --index-url https://test.pypi.org/simple/ --no-deps pynest-api" \ No newline at end of file + run: echo "pip install --index-url https://test.pypi.org/simple/ --no-deps pynest-api==$RELEASE_VERSION" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2ac7ca9..7e1eeaf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,21 +7,24 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.7" + enable-cache: true + - name: Install dependencies - run: | - pip install poetry - poetry install --with test + run: uv sync --no-dev --group test - name: Run tests - run: | - poetry run pytest tests \ No newline at end of file + run: uv run pytest tests diff --git a/.gitignore b/.gitignore index 3376826..3c8f313 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,12 @@ __pycache__/ *.pyd # Dependency directories +.venv/ venv/ env/ envs/ .env -poetry.lock +uv.lock requirements.txt # Compiled source @@ -34,6 +35,7 @@ requirements.txt # Compiled files dist/ build/ +site/ *.egg-info/ # Testing diff --git a/README.md b/README.md index c396b82..4bb2c92 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,31 @@ or open a new one if you have an idea for a new feature or improvement. This would bring a huge impact to the project and the community. +### Development + +This repository uses [uv](https://docs.astral.sh/uv/) for local development, testing, building, and release automation. +As a library project, `uv.lock` is intentionally ignored and not committed. + +```bash +uv sync --no-dev --group test +uv run pytest tests +``` + +Build and validate the package before publishing: + +```bash +uv sync --no-dev --group build +uv build --sdist --wheel +uv run twine check dist/* +``` + +Docs can be built with: + +```bash +uv sync --no-dev --group docs +uv run mkdocs build --clean +``` + ## License PyNest is [MIT licensed](LICENSE). diff --git a/nest/__init__.py b/nest/__init__.py index 260c070..6a9beea 100644 --- a/nest/__init__.py +++ b/nest/__init__.py @@ -1 +1 @@ -__version__ = "0.3.1" +__version__ = "0.4.0" diff --git a/pyproject.toml b/pyproject.toml index 4faa078..83c86b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,20 @@ [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling>=1.26"] +build-backend = "hatchling.build" - -[tool.poetry] +[project] name = "pynest-api" version = "0.4.0" description = "PyNest is a FastAPI Abstraction for building microservices, influenced by NestJS." -authors = ["itay.dar "] -readme = "README.md" -homepage = "https://github.com/PythonNest/PyNest" -documentation = "https://pythonnest.github.io/PyNest/" -packages = [ - { include = "nest" } +authors = [ + { name = "itay.dar", email = "itay2803@gmail.com" }, ] +readme = "README.md" +requires-python = ">=3.9" +license = { file = "LICENSE" } classifiers = [ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -25,59 +24,71 @@ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", ] +dependencies = [ + "click>=8.1.7,<9.0.0", + "injector>=0.22.0,<0.23.0", + "astor>=0.8.1,<0.9.0", + "pyyaml>=6.0.2,<7.0.0", + "fastapi>=0.115.4,<0.116.0", + "pydantic>=2.9.2,<3.0.0", + "uvicorn>=0.32.0,<0.33.0", + "black>=24.10.0,<25.0.0", +] +[project.optional-dependencies] +postgres = [ + "sqlalchemy>=2.0.36,<3.0.0", + "asyncpg>=0.30.0,<0.31.0", + "psycopg2>=2.9.3,<3.0.0", + "alembic>=1.13.3,<2.0.0", + "greenlet>=3.1.1,<4.0.0", + "python-dotenv>=1.0.1,<2.0.0", +] +mongo = [ + "beanie>=1.27.0,<2.0.0", + "python-dotenv>=1.0.1,<2.0.0", +] +test = [ + "pytest>=7.0.1,<8.0.0", +] +[dependency-groups] +test = [ + "pytest>=7.0.1,<8.0.0", + "sqlalchemy>=2.0.36,<3.0.0", + "motor>=3.2.0,<4.0.0", + "beanie>=1.27.0,<2.0.0", + "python-dotenv>=1.0.1,<2.0.0", + "aiosqlite>=0.19.0,<1.0.0", +] +docs = [ + "mkdocs-material>=9.5.43,<10.0.0", + "mkdocstrings-python>=1.12.2,<2.0.0", +] +build = [ + "build>=1.2.2.post1,<2.0.0", + "twine>=6.2.0,<7.0.0", + "git-changelog>=2.5.2,<3.0.0", +] -[tool.poetry.dependencies] -python = "^3.9" -# Core dependencies -click = "^8.1.7" -injector = "^0.22.0" -astor = "^0.8.1" -pyyaml = "^6.0.2" -fastapi = "^0.115.4" -pydantic = "^2.9.2" -uvicorn = "^0.32.0" - - -# Optional dependencies -sqlalchemy = { version = "^2.0.36", optional = true } -asyncpg = { version = "^0.30.0", optional = true } -psycopg2 = { version = "^2.9.3", optional = true } -alembic = { version = "^1.13.3", optional = true } -beanie = { version = "^1.27.0", optional = true } -python-dotenv = { version = "^1.0.1", optional = true } -greenlet = { version = "^3.1.1", optional = true } -black = "^24.10.0" - - - -[tool.poetry.extras] -postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"] -mongo = ["beanie", "python-dotenv"] -test = ["pytest"] - -[tool.poetry.group.build.dependencies] -setuptools = "^75.3.0" -wheel = "^0.44.0" -build = "^1.2.2.post1" -twine = "^5.1.1" -git-changelog = "^2.5.2" +[project.urls] +Homepage = "https://github.com/PythonNest/PyNest" +Documentation = "https://pythonnest.github.io/PyNest/" -[tool.poetry.group.test.dependencies] -pytest = "^7.0.1" -fastapi = "^0.115.4" -sqlalchemy = "^2.0.36" -motor = "^3.2.0" -beanie = "^1.27.0" -pydantic = "^2.9.2" -python-dotenv = "^1.0.1" -uvicorn = "^0.32.0" +[project.scripts] +pynest = "nest.cli.cli:nest_cli" -[tool.poetry.group.docs.dependencies] -mkdocs-material = "^9.5.43" -mkdocstrings-python = "^1.12.2" +[tool.hatch.build.targets.wheel] +packages = ["nest"] +[tool.hatch.build.targets.sdist] +include = [ + "nest", + "tests", + "README.md", + "LICENSE", + "pyproject.toml", +] [tool.black] force-exclude = ''' @@ -100,12 +111,3 @@ exclude = [ "/*venv*" ] ignore_missing_imports = true - -[tool.poetry.urls] -Homepage = "https://github.com/PythonNest/PyNest" -Documentation = "https://pythonnest.github.io/PyNest/" - -[tool.poetry.scripts] -pynest = "nest.cli.cli:nest_cli" - -