Skip to content

Commit 5b90b7b

Browse files
authored
Merge pull request #11 from browserbase/miguel/bb-928-python-sdk-ci
CI updates for linting, testing & publishing package
2 parents bd85a29 + 28a51bd commit 5b90b7b

20 files changed

+471
-156
lines changed

.bumpversion.cfg

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[bumpversion]
2+
current_version = 0.3.0
3+
commit = True
4+
tag = True
5+
6+
[bumpversion:file:setup.py]
7+
search = version="{current_version}"
8+
replace = version="{new_version}"
9+
10+
[bumpversion:file:stagehand/__init__.py]
11+
search = __version__ = "{current_version}"
12+
replace = __version__ = "{new_version}"

.github/README_PUBLISHING.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Publishing stagehand-python to PyPI
2+
3+
This repository is configured with a GitHub Actions workflow to automate the process of publishing new versions to PyPI.
4+
5+
## Prerequisites
6+
7+
Before using the publishing workflow, ensure you have:
8+
9+
1. Set up the following secrets in your GitHub repository settings:
10+
- `PYPI_USERNAME`: Your PyPI username
11+
- `PYPI_API_TOKEN`: Your PyPI API token (not your password)
12+
13+
## How to Publish a New Version
14+
15+
### Manual Trigger
16+
17+
1. Go to the "Actions" tab in your GitHub repository
18+
2. Select the "Publish to PyPI" workflow from the list
19+
3. Click "Run workflow" on the right side
20+
4. Configure the workflow:
21+
- Choose the release type:
22+
- `patch` (e.g., 0.3.0 → 0.3.1) for bug fixes
23+
- `minor` (e.g., 0.3.0 → 0.4.0) for backward-compatible features
24+
- `major` (e.g., 0.3.0 → 1.0.0) for breaking changes
25+
- Toggle "Create GitHub Release" if you want to create a GitHub release
26+
5. Click "Run workflow" to start the process
27+
28+
### What Happens During Publishing
29+
30+
The workflow will:
31+
32+
1. Checkout the repository
33+
2. Set up Python environment
34+
3. Install dependencies
35+
4. **Run Ruff linting checks**:
36+
- Checks for code style and quality issues
37+
- Verifies formatting according to project standards
38+
- Fails the workflow if issues are found
39+
5. Run tests to ensure everything works
40+
6. Update the version number using bumpversion
41+
7. Build the package
42+
8. Upload to PyPI
43+
9. Push the version bump commit and tag
44+
10. Create a GitHub release (if selected)
45+
46+
## Code Quality Standards
47+
48+
This project uses Ruff for linting and formatting. The workflow enforces these standards before publishing:
49+
50+
- Style checks following configured rules in `pyproject.toml`
51+
- Format verification without making changes
52+
- All linting issues must be fixed before a successful publish
53+
54+
To run the same checks locally:
55+
```bash
56+
# Install Ruff
57+
pip install ruff
58+
59+
# Run linting
60+
ruff check .
61+
62+
# Check formatting
63+
ruff format --check .
64+
65+
# Auto-fix issues where possible
66+
ruff check --fix .
67+
ruff format .
68+
69+
# Use Black to format the code
70+
black .
71+
```
72+
73+
## Troubleshooting
74+
75+
If the workflow fails, check the following:
76+
77+
1. **Linting errors**: Fix any issues reported by Ruff
78+
2. Ensure all secrets are properly set
79+
3. Verify that tests pass locally
80+
4. Check if you have proper permissions on the repository
81+
5. Make sure you have a PyPI account with publishing permissions

.github/workflows/publish.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
release_type:
7+
description: 'Release type (patch, minor, major)'
8+
required: true
9+
default: 'patch'
10+
type: choice
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
create_release:
16+
description: 'Create GitHub Release'
17+
required: true
18+
default: true
19+
type: boolean
20+
21+
jobs:
22+
build-and-publish:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Check out repository
26+
uses: actions/checkout@v3
27+
with:
28+
fetch-depth: 0
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@v4
32+
with:
33+
python-version: '3.10'
34+
35+
- name: Install dependencies
36+
run: |
37+
python -m pip install --upgrade pip
38+
pip install build twine wheel setuptools bumpversion ruff
39+
pip install -r requirements.txt
40+
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
41+
42+
- name: Run Ruff linting
43+
run: |
44+
# Run Ruff linter
45+
ruff check .
46+
47+
# Run Ruff formatter check (without modifying files)
48+
ruff format --check .
49+
50+
- name: Run tests
51+
run: |
52+
pytest
53+
54+
- name: Update version
55+
run: |
56+
git config --local user.email "[email protected]"
57+
git config --local user.name "GitHub Action"
58+
bumpversion ${{ github.event.inputs.release_type }}
59+
60+
- name: Build package
61+
run: |
62+
python -m build
63+
64+
- name: Upload to PyPI
65+
env:
66+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
67+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
68+
run: |
69+
twine upload dist/*
70+
71+
- name: Push version bump
72+
run: |
73+
git push
74+
git push --tags
75+
76+
- name: Create GitHub Release
77+
if: ${{ github.event.inputs.create_release == 'true' }}
78+
uses: softprops/action-gh-release@v1
79+
with:
80+
tag_name: v$(python setup.py --version)
81+
name: Release v$(python setup.py --version)
82+
generate_release_notes: true

evals/act/google_jobs.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import traceback
3-
from typing import Any, Dict, Optional
3+
from typing import Any, Optional, dict
44

55
from pydantic import BaseModel
66

@@ -10,23 +10,24 @@
1010

1111
class Qualifications(BaseModel):
1212
degree: Optional[str] = None
13-
yearsOfExperience: Optional[float] = None # Representing the number
13+
years_of_experience: Optional[float] = None # Representing the number
1414

1515

1616
class JobDetails(BaseModel):
17-
applicationDeadline: Optional[str] = None
18-
minimumQualifications: Qualifications
19-
preferredQualifications: Qualifications
17+
application_deadline: Optional[str] = None
18+
minimum_qualifications: Qualifications
19+
preferred_qualifications: Qualifications
2020

2121

22-
def is_job_details_valid(details: Dict[str, Any]) -> bool:
22+
def is_job_details_valid(details: dict[str, Any]) -> bool:
2323
"""
2424
Validates that each top-level field in the extracted job details is not None.
25-
For nested dictionary values, each sub-value must be non-null and a string or a number.
25+
For nested dictionary values, each sub-value must be non-null and a string
26+
or a number.
2627
"""
2728
if not details:
2829
return False
29-
for key, value in details.items():
30+
for _key, value in details.items():
3031
if value is None:
3132
return False
3233
if isinstance(value, dict):
@@ -53,9 +54,9 @@ async def google_jobs(model_name: str, logger, use_text_extract: bool) -> dict:
5354
4. Extracting job posting details using an AI-driven extraction schema.
5455
5556
The extraction schema requires:
56-
- applicationDeadline: The opening date until which applications are accepted.
57-
- minimumQualifications: An object with degree and yearsOfExperience.
58-
- preferredQualifications: An object with degree and yearsOfExperience.
57+
- application_deadline: The opening date until which applications are accepted.
58+
- minimum_qualifications: An object with degree and years_of_experience.
59+
- preferred_qualifications: An object with degree and years_of_experience.
5960
6061
Returns a dictionary containing:
6162
- _success (bool): Whether valid job details were extracted.
@@ -90,8 +91,9 @@ async def google_jobs(model_name: str, logger, use_text_extract: bool) -> dict:
9091
job_details = await stagehand.page.extract(
9192
ExtractOptions(
9293
instruction=(
93-
"Extract the following details from the job posting: application deadline, "
94-
"minimum qualifications (degree and years of experience), and preferred qualifications "
94+
"Extract the following details from the job posting: "
95+
"application deadline, minimum qualifications "
96+
"(degree and years of experience), and preferred qualifications "
9597
"(degree and years of experience)"
9698
),
9799
schemaDefinition=JobDetails.model_json_schema(),

evals/extract/extract_press_releases.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class PressReleases(BaseModel):
1919

2020
async def extract_press_releases(model_name: str, logger, use_text_extract: bool):
2121
"""
22-
Extract press releases from the dummy press releases page using the Stagehand client.
22+
Extract press releases from the dummy press releases page using the Stagehand
23+
client.
2324
2425
Args:
2526
model_name (str): Name of the AI model to use.
@@ -56,7 +57,10 @@ async def extract_press_releases(model_name: str, logger, use_text_extract: bool
5657
# TODO - FAILING - extract is likely timing out
5758
raw_result = await stagehand.page.extract(
5859
ExtractOptions(
59-
instruction="extract the title and corresponding publish date of EACH AND EVERY press releases on this page. DO NOT MISS ANY PRESS RELEASES.",
60+
instruction=(
61+
"extract the title and corresponding publish date of EACH AND EVERY "
62+
"press releases on this page. DO NOT MISS ANY PRESS RELEASES."
63+
),
6064
schemaDefinition=PressReleases.model_json_schema(),
6165
useTextExtract=use_text_extract,
6266
)

evals/init_stagehand.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
async def init_stagehand(model_name: str, logger, dom_settle_timeout_ms: int = 3000):
88
"""
9-
Initialize a Stagehand client with the given model name, logger, and DOM settle timeout.
9+
Initialize a Stagehand client with the given model name, logger, and DOM settle
10+
timeout.
1011
11-
This function creates a configuration from environment variables, initializes the Stagehand client,
12-
and returns a tuple of (stagehand, init_response). The init_response contains debug and session URLs.
12+
This function creates a configuration from environment variables, initializes
13+
the Stagehand client, and returns a tuple of (stagehand, init_response).
14+
The init_response contains debug and session URLs.
1315
1416
Args:
1517
model_name (str): The name of the AI model to use.
@@ -37,7 +39,8 @@ async def init_stagehand(model_name: str, logger, dom_settle_timeout_ms: int = 3
3739
model_client_options={"apiKey": os.getenv("MODEL_API_KEY")},
3840
)
3941

40-
# Create a Stagehand client with the configuration; server_url is taken from environment variables.
42+
# Create a Stagehand client with the configuration; server_url is taken from
43+
# environment variables.
4144
stagehand = Stagehand(
4245
config=config, server_url=os.getenv("STAGEHAND_SERVER_URL"), verbose=2
4346
)

examples/example.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ async def main():
8787
if len(observed) > 0:
8888
element = observed[0]
8989
console.print("✅ [success]Found element:[/] News button")
90+
console.print("\n▶️ [highlight] Performing action on observed element")
9091
await page.act(element)
92+
console.print("✅ [success]Performing Action:[/] Action completed successfully")
93+
9194
else:
9295
console.print("❌ [error]No element found[/]")
9396

examples/example_sync.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from rich.panel import Panel
77
from rich.theme import Theme
88

9-
from stagehand.sync import Stagehand
109
from stagehand.config import StagehandConfig
10+
from stagehand.sync import Stagehand
1111

1212
# Create a custom theme for consistent styling
1313
custom_theme = Theme(
@@ -60,6 +60,7 @@ def main():
6060
)
6161

6262
import time
63+
6364
time.sleep(2)
6465

6566
console.print("\n▶️ [highlight] Navigating[/] to Google")
@@ -112,4 +113,4 @@ def main():
112113
padding=(1, 10),
113114
),
114115
)
115-
main()
116+
main()

format.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
3+
# Define source directories (adjust as needed)
4+
SOURCE_DIRS="evals stagehand"
5+
6+
# Apply Black formatting only to source directories
7+
echo "Applying Black formatting..."
8+
black $SOURCE_DIRS
9+
10+
# Fix import sorting (addresses I001 errors)
11+
echo "Sorting imports..."
12+
isort $SOURCE_DIRS
13+
14+
# Apply Ruff with autofix for remaining issues
15+
echo "Applying Ruff autofixes..."
16+
ruff check --fix $SOURCE_DIRS
17+
18+
echo "Checking for remaining issues..."
19+
ruff check $SOURCE_DIRS
20+
21+
echo "Done! Code has been formatted and linted."

0 commit comments

Comments
 (0)