Skip to content
Open
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
72 changes: 69 additions & 3 deletions .github/workflows/manual_release_stable.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,83 @@ jobs:
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

version_docs:
name: Version docs
needs: [release_prepare, changelog_update, pypi_publish]
runs-on: ubuntu-latest
outputs:
version_docs_commitish: ${{ steps.commit_versioned_docs.outputs.commit_long_sha }}
permissions:
contents: write
env:
NODE_VERSION: 22
PYTHON_VERSION: 3.14

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
Comment on lines +120 to +121
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can explicitly checkout the needs.changelog_update.outputs.changelog_commitish, to prevent race conditions or other mishaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, checkout now explicitly uses ref: ${{ needs.changelog_update.outputs.changelog_commitish }}

ref: ${{ needs.changelog_update.outputs.changelog_commitish }}

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Set up uv package manager
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install Python dependencies
run: uv run poe install-dev

- name: Install website dependencies
run: |
cd website
corepack enable
yarn install

- name: Snapshot the current version
run: |
cd website
VERSION="$(python -c "import tomllib, pathlib; print(tomllib.loads(pathlib.Path('../pyproject.toml').read_text())['project']['version'])")"
MAJOR_MINOR="$(echo "$VERSION" | cut -d. -f1-2)"
export MAJOR_MINOR
# Remove existing version if present (patch releases override)
rm -rf "versioned_docs/version-${MAJOR_MINOR}"
rm -rf "versioned_sidebars/version-${MAJOR_MINOR}-sidebars.json"
jq 'map(select(. != env.MAJOR_MINOR))' versions.json > tmp.json && mv tmp.json versions.json
# Build API reference and create version snapshots
bash build_api_reference.sh
npx docusaurus docs:version "$MAJOR_MINOR"
npx docusaurus api:version "$MAJOR_MINOR"

- name: Commit and push versioned docs
id: commit_versioned_docs
uses: EndBug/add-and-commit@v9
with:
add: "website/versioned_docs website/versioned_sidebars website/versions.json"
message: "docs: version ${{ needs.release_prepare.outputs.version_number }} docs [skip ci]"
default_author: github_actions

doc_release:
name: Doc release
needs: [changelog_update, pypi_publish]
needs: [changelog_update, pypi_publish, version_docs]
permissions:
contents: write
pages: write
id-token: write
uses: ./.github/workflows/_release_docs.yaml
with:
# Use the ref from the changelog update to include the updated changelog.
ref: ${{ needs.changelog_update.outputs.changelog_commitish }}
# Use the version_docs commit to include both changelog and versioned docs.
ref: ${{ needs.version_docs.outputs.version_docs_commitish }}
secrets: inherit

trigger_docker_build:
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ allow-direct-references = true
[tool.ruff]
line-length = 120
include = ["src/**/*.py", "tests/**/*.py", "docs/**/*.py", "website/**/*.py"]
exclude = [
"website/versioned_docs/**",
]

[tool.ruff.lint]
select = ["ALL"]
Expand Down Expand Up @@ -201,6 +204,7 @@ python-version = "3.10"

[tool.ty.src]
include = ["src", "tests", "scripts", "docs", "website"]
exclude = ["website/versioned_docs"]

[[tool.ty.overrides]]
include = [
Expand Down
38 changes: 32 additions & 6 deletions website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { join, resolve } = require('node:path');
const { config } = require('@apify/docs-theme');

const { externalLinkProcessor } = require('./tools/utils/externalLink');
const versions = require('./versions.json');

const GROUP_ORDER = [
'Actor',
Expand All @@ -18,9 +19,14 @@ const GROUP_ORDER = [
];

const groupSort = (g1, g2) => {
if (GROUP_ORDER.includes(g1) && GROUP_ORDER.includes(g2)) {
return GROUP_ORDER.indexOf(g1) - GROUP_ORDER.indexOf(g2);
}
const i1 = GROUP_ORDER.indexOf(g1);
const i2 = GROUP_ORDER.indexOf(g2);
// Both known – sort by defined order
if (i1 !== -1 && i2 !== -1) return i1 - i2;
// Unknown groups go after known ones
if (i1 !== -1) return -1;
if (i2 !== -1) return 1;
// Both unknown – alphabetical
return g1.localeCompare(g2);
};

Expand Down Expand Up @@ -66,19 +72,21 @@ module.exports = {
title: 'SDK for Python',
items: [
{
to: 'docs/overview',
type: 'doc',
docId: 'introduction/introduction',
label: 'Docs',
position: 'left',
activeBaseRegex: '/docs(?!/changelog)',
},
{
to: '/reference',
type: 'custom-versioned-reference',
label: 'Reference',
position: 'left',
activeBaseRegex: '/reference',
},
{
to: 'docs/changelog',
type: 'doc',
docId: 'changelog',
label: 'Changelog',
position: 'left',
activeBaseRegex: '/docs/changelog',
Expand All @@ -88,6 +96,17 @@ module.exports = {
label: 'GitHub',
position: 'left',
},
{
type: 'docsVersionDropdown',
position: 'left',
className: 'navbar__item',
'data-api-links': JSON.stringify([
'reference/next',
...versions.map((version, i) => (i === 0 ? 'reference' : `reference/${version}`)),
]),
dropdownItemsBefore: [],
dropdownItemsAfter: [],
},
],
},
},
Expand Down Expand Up @@ -280,13 +299,20 @@ module.exports = {
includeGeneratedIndex: false,
includePages: true,
relativePaths: false,
excludeRoutes: [
'/sdk/python/reference/[0-9]*/**',
'/sdk/python/reference/[0-9]*',
'/sdk/python/reference/next/**',
'/sdk/python/reference/next',
],
},
},
],
...config.plugins,
],
themeConfig: {
...config.themeConfig,
versions,
tableOfContents: {
...config.themeConfig.tableOfContents,
maxHeadingLevel: 5,
Expand Down
7 changes: 7 additions & 0 deletions website/src/theme/NavbarItem/ComponentTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import OriginalComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
import VersionedReferenceNavbarItem from './VersionedReferenceNavbarItem';

export default {
...OriginalComponentTypes,
'custom-versioned-reference': VersionedReferenceNavbarItem,
};
91 changes: 91 additions & 0 deletions website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useMemo } from 'react';
import { useVersions, useActiveDocContext, useDocsVersionCandidates } from '@docusaurus/plugin-content-docs/client';
import { useDocsPreferredVersion } from '@docusaurus/theme-common';
import { translate } from '@docusaurus/Translate';
import { useLocation } from '@docusaurus/router';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';

const getVersionMainDoc = (version) => version.docs.find((doc) => doc.id === version.mainDocId);

/* eslint-disable react/prop-types */
export default function DocsVersionDropdownNavbarItem({
mobile,
docsPluginId,
dropdownActiveClassDisabled,
dropdownItemsBefore,
dropdownItemsAfter,
...props
}) {
const { search, hash, pathname } = useLocation();
const { siteConfig } = useDocusaurusContext();
const baseUrl = siteConfig.baseUrl.endsWith('/') ? siteConfig.baseUrl : `${siteConfig.baseUrl}/`;
const apiLinks = useMemo(() => {
if (!pathname.startsWith(`${baseUrl}reference`)) {
return [];
}
try {
return JSON.parse(props['data-api-links']);
} catch {
return [];
}
}, [props['data-api-links'], pathname, baseUrl]);

const activeDocContext = useActiveDocContext(docsPluginId);
const versions = useVersions(docsPluginId);
const { savePreferredVersionName } = useDocsPreferredVersion(docsPluginId);
const versionLinks = versions.map((version, idx) => {
// We try to link to the same doc, in another version
// When not possible, fallback to the "main doc" of the version
const versionDoc = activeDocContext.alternateDocVersions[version.name] ?? getVersionMainDoc(version);
return {
label: version.label,
// preserve ?search#hash suffix on version switches
to: `${apiLinks[idx] ?? versionDoc.path}${search}${hash}`,
isActive: () => version === activeDocContext.activeVersion,
onClick: () => savePreferredVersionName(version.name),
};
});
const items = [...dropdownItemsBefore, ...versionLinks, ...dropdownItemsAfter];
const dropdownVersion = useDocsVersionCandidates(docsPluginId)[0];
// Mobile dropdown is handled a bit differently
const dropdownLabel =
mobile && items.length > 1
? translate({
id: 'theme.navbar.mobileVersionsDropdown.label',
message: 'Versions',
description: 'The label for the navbar versions dropdown on mobile view',
})
: dropdownVersion.label;
let dropdownTo = mobile && items.length > 1 ? undefined : getVersionMainDoc(dropdownVersion).path;

if (dropdownTo && pathname.startsWith(`${baseUrl}reference`)) {
dropdownTo = versionLinks.find((v) => v.label === dropdownVersion.label)?.to;
}

// We don't want to render a version dropdown with 0 or 1 item. If we build
// the site with a single docs version (onlyIncludeVersions: ['1.0.0']),
// We'd rather render a button instead of a dropdown
if (items.length <= 1) {
return (
<DefaultNavbarItem
{...props}
mobile={mobile}
label={dropdownLabel}
to={dropdownTo}
isActive={dropdownActiveClassDisabled ? () => false : undefined}
/>
);
}
return (
<DropdownNavbarItem
{...props}
mobile={mobile}
label={dropdownLabel}
to={dropdownTo}
items={items}
isActive={dropdownActiveClassDisabled ? () => false : undefined}
/>
);
}
16 changes: 16 additions & 0 deletions website/src/theme/NavbarItem/VersionedReferenceNavbarItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { useDocsVersionCandidates } from '@docusaurus/plugin-content-docs/client';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';

/* eslint-disable react/prop-types */
export default function VersionedReferenceNavbarItem({ docsPluginId, ...props }) {
const [version] = useDocsVersionCandidates(docsPluginId);

// Latest version → /reference, "current" (next) → /reference/next, others → /reference/{name}
let to = '/reference';
if (!version.isLast) {
to = `/reference/${version.name === 'current' ? 'next' : version.name}`;
}

return <DefaultNavbarItem {...props} to={to} />;
}
1 change: 1 addition & 0 deletions website/versioned_docs/version-1.7/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
changelog.md
50 changes: 50 additions & 0 deletions website/versioned_docs/version-1.7/01-introduction/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
id: introduction
title: Overview
sidebar_label: Overview
slug: /overview
description: 'The official library for creating Apify Actors in Python, providing tools for web scraping, automation, and data storage integration.'
---

The Apify SDK for Python is the official library for creating [Apify Actors](https://docs.apify.com/platform/actors) in Python.

```python
from apify import Actor
from bs4 import BeautifulSoup
import requests

async def main():
async with Actor:
actor_input = await Actor.get_input()
response = requests.get(actor_input['url'])
soup = BeautifulSoup(response.content, 'html.parser')
await Actor.push_data({ 'url': actor_input['url'], 'title': soup.title.string })
```

## What are Actors?

Actors are serverless cloud programs that can do almost anything a human can do in a web browser. They can do anything from small tasks such as filling in forms or unsubscribing from online services, all the way up to scraping and processing vast numbers of web pages.

Actors can be run either locally, or on the [Apify platform](https://docs.apify.com/platform/), where you can run them at scale, monitor them, schedule them, and even publish and monetize them.

If you're new to Apify, learn [what is Apify](https://docs.apify.com/platform/about) in the Apify platform documentation.

## Quick start

To create and run Actors through Apify Console, see the [Console documentation](https://docs.apify.com/academy/getting-started/creating-actors#choose-your-template). For creating and running Python Actors locally, refer to the [quick start guide](./quick-start).

Explore the Guides section in the sidebar for a deeper understanding of the SDK's features and best practices.

## Installation

The Apify SDK for Python requires Python version 3.8 or above. It is typically installed when you create a new Actor project using the [Apify CLI](https://docs.apify.com/cli). To install it manually in an existing project, use:

```bash
pip install apify
```

:::note API client alternative

If you need to interact with the Apify API programmatically without creating Actors, use the [Apify API client for Python](https://docs.apify.com/api/client/python) instead.

:::
Loading