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
75 changes: 75 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Release

# Triggers on git tag push `vX.Y.Z`. Builds the wheel + sdist and publishes
# to PyPI via TRUSTED PUBLISHING (OIDC) — no API token to manage.
#
# Setup once on the PyPI side:
# PyPI → Manage socialapis → Publishing → Add trusted publisher:
# Owner: SocialAPIsHub
# Repository: socialapis-python
# Workflow: release.yml
# Environment: pypi
#
# Then to ship:
# 1. Bump socialapis/_version.py
# 2. Add CHANGELOG.md entry
# 3. Commit + git tag v0.X.Y
# 4. git push --tags
# 5. This workflow auto-publishes; PyPI page updates in ~30s

on:
push:
tags:
- "v*.*.*"

# Single in-flight release at a time (no race between two tag pushes)
concurrency:
group: release
cancel-in-progress: false

jobs:
build:
name: Build wheel + sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install build
run: pip install --upgrade build
- name: Build distributions
run: python -m build
- name: Verify version matches tag
# Belt-and-suspenders — fail loudly if the tag and the package
# version disagree, instead of shipping a confusing release.
run: |
tag="${GITHUB_REF_NAME#v}"
file_version=$(ls dist/socialapis-*.tar.gz | sed -E 's|.*socialapis-([^-]+)\.tar\.gz|\1|')
if [ "$tag" != "$file_version" ]; then
echo "::error::Tag ${tag} does not match package version ${file_version}"
exit 1
fi
- name: Upload built artifacts
uses: actions/upload-artifact@v5
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/socialapis/
permissions:
id-token: write # OIDC token for PyPI Trusted Publishing
steps:
- name: Download built distributions
uses: actions/download-artifact@v5
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
63 changes: 63 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Test

# Runs on every PR + every push to main. Three jobs:
# 1. lint — ruff (format + lint, one tool replaces black/isort/flake8)
# 2. types — mypy --strict (catches Any leaks, missing annotations)
# 3. test — pytest on a matrix of supported Python versions
#
# All three must pass before a PR can be merged (configure as required
# checks in the repo Settings → Branches → main branch protection).

on:
push:
branches: [main]
pull_request:
branches: [main]

# Cancel in-progress runs when a new commit lands on the same PR — saves CI minutes
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install ruff
run: pip install "ruff>=0.6"
- name: Lint
run: ruff check .
- name: Format check
run: ruff format --check .

types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install package + dev deps
run: pip install -e ".[dev]"
- name: Mypy
run: mypy socialapis tests

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install package + dev deps
run: pip install -e ".[dev]"
- name: Run pytest
run: pytest
87 changes: 87 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Changelog

All notable changes to this project will be documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] — Unreleased

First public release. Full coverage of the SocialAPIs.io public REST surface
in one shot — no v0.2/v0.3 follow-ups required for core endpoints.

### Added — Facebook namespace (`Facebook` / `AsyncFacebook`)

**Pages**: `get_page_id`, `get_page_info`, `get_page_posts`, `get_page_reels`,
`get_page_videos`

**Groups**: `get_group_id`, `get_group_details`, `get_group_metadata`,
`get_group_posts`, `get_group_videos`

**Posts**: `get_post_id`, `get_post_details`, `get_post_details_extended`,
`get_post_comments`, `get_comment_replies`, `get_post_attachments`,
`get_video_post_details`

**Search**: `search_pages`, `search_people`, `search_locations`,
`search_posts`, `search_videos`

**Meta Ads Library**: `get_ads_countries`, `search_ads`,
`get_ads_page_details`, `get_ad_archive_details`, `search_ads_by_keywords`

**Marketplace**: `search_marketplace`, `get_listing_details`,
`get_seller_details`, `get_marketplace_categories`, `get_city_coordinates`,
`search_vehicles`, `search_rentals`

**Media**: `download_media`

### Added — Instagram namespace (`Instagram` / `AsyncInstagram`)

**Profiles**: `get_user_id`, `get_profile_details`, `get_profile_posts`,
`get_profile_reels`, `get_profile_highlights`, `get_highlight_details`

**Posts**: `get_post_id`, `get_post_details`

**Reels**: `get_reels_feed`, `get_reels_by_audio`

**Search + Locations**: `search`, `get_location_posts`,
`get_nearby_locations`

### Added — Account namespace (`Account` / `AsyncAccount`)

`get_usage`, `get_top_ups`, `get_limits`. All free (don't consume credits).

### Added — Infrastructure

- Typed exception hierarchy (`SocialAPIsError`, `APIError`,
`AuthenticationError`, `InsufficientCreditsError`, `RateLimitError`,
`BadRequestError`, `APIServerError`, `APIConnectionError`)
- Pydantic v2 response models for headline endpoints (`PageInfo`,
`GroupInfo`, `ProfileInfo`). Niche endpoints return `dict[str, Any]`
with full data preserved.
- Sync + async context-manager support (`with` / `async with`)
- Identifier normalisation — pass either a slug or a full URL; the SDK
coerces to whatever shape the API expects
- `**kwargs` pass-through on every method — forward-compatible when the
API adds new filters; no client release needed to use them
- No `limit=N` parameters anywhere — the API decides page size; pagination
is cursor-driven via response body + kwargs

### Added — Migration aliases (graveyard capture)

- `FacebookScraper` / `AsyncFacebookScraper` — exact aliases of
`Facebook` / `AsyncFacebook`. Lets users of the abandoned
`kevinzg/facebook-scraper` library migrate by changing only the import.
- `InstagramScraper` / `AsyncInstagramScraper` — same for users of
`arc298/instagram-scraper`.
- `test_aliases.py` asserts the identity contract so accidental
decoupling fails CI.

### Added — Tooling

- `pyproject.toml` with hatchling, modern Python (3.10+), no `setup.py`
- Test suite using `respx` for HTTP mocking (no live API calls in CI)
- CI: lint (ruff), type check (mypy --strict), tests on Python 3.10–3.13
- Release workflow: publishes to PyPI via Trusted Publishing on
`vX.Y.Z` tag (no API token to rotate)
- PEP 561 `py.typed` marker — distributed type hints
- Coverage gate at 85% in CI
Loading