Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Default owners for everything in the repo
* @miquel-palet
* @getlatedev
116 changes: 116 additions & 0 deletions .github/workflows/release-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Preview release info on PRs to main
name: Release Preview

on:
pull_request:
branches: [main]

jobs:
preview:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python
run: uv python install 3.11

- name: Get version from pyproject.toml
id: version
run: |
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Check if tag exists
id: check_tag
run: |
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi

- name: Check PyPI for existing version
id: check_pypi
run: |
VERSION="${{ steps.version.outputs.version }}"
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/late-sdk/$VERSION/json")
if [ "$HTTP_STATUS" = "200" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi

- name: Create PR Comment
uses: actions/github-script@v7
with:
script: |
const version = '${{ steps.version.outputs.version }}';
const tagExists = '${{ steps.check_tag.outputs.exists }}' === 'true';
const pypiExists = '${{ steps.check_pypi.outputs.exists }}' === 'true';

let body = '## 📦 Release Preview\n\n';
body += '| Item | Value |\n';
body += '|------|-------|\n';
body += `| Version | \`${version}\` |\n`;
body += `| Git tag exists | ${tagExists ? '✅ Yes' : '❌ No'} |\n`;
body += `| PyPI version exists | ${pypiExists ? '✅ Yes' : '❌ No'} |\n\n`;

if (tagExists || pypiExists) {
body += '### ⏭️ No Release\n\n';
if (tagExists && pypiExists) {
body += `Version \`${version}\` already exists on both GitHub and PyPI.\n\n`;
} else if (tagExists) {
body += `Git tag \`v${version}\` already exists.\n\n`;
} else {
body += `Version \`${version}\` already exists on PyPI.\n\n`;
}
body += '> 💡 **To create a new release**, update `version` in `pyproject.toml`\n';
} else {
body += '### 🚀 New Release\n\n';
body += 'When this PR is merged, the following will happen:\n\n';
body += `1. ✅ Create GitHub Release \`v${version}\`\n`;
body += `2. ✅ Publish to PyPI as \`late-sdk==${version}\`\n\n`;
body += '```bash\n';
body += `pip install late-sdk==${version}\n`;
body += '```\n';
}

// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('📦 Release Preview')
);

if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}
56 changes: 53 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install uv
uses: astral-sh/setup-uv@v4
Expand All @@ -30,25 +31,60 @@ jobs:
- name: Run tests
run: uv run pytest tests -v --tb=short

- name: Get version
- name: Get version from pyproject.toml
id: version
run: |
VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
echo "::notice::📦 Version in pyproject.toml: $VERSION"

- name: Check if tag exists
id: check_tag
run: |
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "::notice::🔄 Tag v${{ steps.version.outputs.version }} already exists - skipping release"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "::notice::🚀 New version detected! Will create release v${{ steps.version.outputs.version }}"
fi

- name: Check PyPI for existing version
id: check_pypi
if: steps.check_tag.outputs.exists == 'false'
run: |
VERSION="${{ steps.version.outputs.version }}"
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/late-sdk/$VERSION/json")
if [ "$HTTP_STATUS" = "200" ]; then
echo "::error::❌ Version $VERSION already exists on PyPI! Bump the version in pyproject.toml"
exit 1
else
echo "::notice::✅ Version $VERSION not found on PyPI - ready to publish"
fi

- name: Release Summary
run: |
echo "## 📋 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${{ steps.version.outputs.version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag exists | ${{ steps.check_tag.outputs.exists }} |" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.check_tag.outputs.exists }}" = "true" ]; then
echo "| Action | ⏭️ **Skipped** (version already released) |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "> 💡 To release a new version, update \`version\` in \`pyproject.toml\`" >> $GITHUB_STEP_SUMMARY
else
echo "| Action | 🚀 **New Release** |" >> $GITHUB_STEP_SUMMARY
echo "| GitHub Release | v${{ steps.version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
echo "| PyPI | late-sdk==${{ steps.version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
fi

- name: Build package
if: steps.check_tag.outputs.exists == 'false'
run: uv build
run: |
uv build
echo "::notice::📦 Built: $(ls dist/)"

- name: Create GitHub Release
if: steps.check_tag.outputs.exists == 'false'
Expand All @@ -64,3 +100,17 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}

- name: Post-release Summary
if: steps.check_tag.outputs.exists == 'false'
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## ✅ Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- 🏷️ GitHub: [v${{ steps.version.outputs.version }}](https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }})" >> $GITHUB_STEP_SUMMARY
echo "- 📦 PyPI: [late-sdk ${{ steps.version.outputs.version }}](https://pypi.org/project/late-sdk/${{ steps.version.outputs.version }}/)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Install with:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "pip install late-sdk==${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
8 changes: 8 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ on:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install uv
uses: astral-sh/setup-uv@v4
Expand All @@ -39,9 +43,13 @@ jobs:
build:
runs-on: ubuntu-latest
needs: test
permissions:
contents: read

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install uv
uses: astral-sh/setup-uv@v4
Expand Down
20 changes: 15 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "late-sdk"
version = "1.0.0"
version = "1.0.1"
description = "Python SDK for Late API - Social Media Scheduling"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -71,8 +71,8 @@ late-mcp = "late.mcp.server:mcp.run"
[project.urls]
Homepage = "https://getlate.dev"
Documentation = "https://docs.getlate.dev"
Repository = "https://github.com/getlate/late-python-starter"
Issues = "https://github.com/getlate/late-python-starter/issues"
Repository = "https://github.com/getlatedev/late-python-sdk"
Issues = "https://github.com/getlatedev/late-python-sdk/issues"

[build-system]
requires = ["hatchling"]
Expand Down Expand Up @@ -123,11 +123,21 @@ indent-style = "space"
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_ignores = true
warn_return_any = false
warn_unused_ignores = false
disallow_untyped_defs = true
plugins = ["pydantic.mypy"]

[[tool.mypy.overrides]]
module = [
"late.models._generated.*",
"late.ai.*",
"late.mcp.*",
"late.resources.*",
"late.pipelines.*",
]
ignore_errors = true

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
Expand Down
2 changes: 1 addition & 1 deletion src/late/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Schedule social media posts across multiple platforms.
"""

from .client.late_client import Late
from .client.exceptions import (
LateAPIError,
LateAuthenticationError,
Expand All @@ -16,6 +15,7 @@
LateTimeoutError,
LateValidationError,
)
from .client.late_client import Late

__version__ = "1.0.0"

Expand Down
11 changes: 8 additions & 3 deletions src/late/ai/content_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any, AsyncIterator
from typing import TYPE_CHECKING, Any

from .protocols import AIProvider, GenerateRequest, GenerateResponse, StreamingAIProvider
from .protocols import (
AIProvider,
GenerateRequest,
GenerateResponse,
StreamingAIProvider,
)

if TYPE_CHECKING:
pass
from collections.abc import AsyncIterator


class ContentGenerator:
Expand Down
5 changes: 4 additions & 1 deletion src/late/ai/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

from abc import abstractmethod
from dataclasses import dataclass, field
from typing import Any, AsyncIterator, Protocol, runtime_checkable
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

if TYPE_CHECKING:
from collections.abc import AsyncIterator


@dataclass
Expand Down
4 changes: 2 additions & 2 deletions src/late/ai/providers/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, AsyncIterator
from typing import TYPE_CHECKING, Any

from ..protocols import GenerateRequest, GenerateResponse

if TYPE_CHECKING:
pass
from collections.abc import AsyncIterator


class OpenAIProvider:
Expand Down
2 changes: 1 addition & 1 deletion src/late/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
LateTimeoutError,
LateValidationError,
)
from .rate_limiter import RateLimitInfo, RateLimiter
from .rate_limiter import RateLimiter, RateLimitInfo

__all__ = [
"BaseClient",
Expand Down
Loading