apify-client 3.3.0__tar.gz → 3.3.1b2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apify_client-3.3.0 → apify_client-3.3.1b2}/CHANGELOG.md +14 -0
- apify_client-3.3.1b2/CONTRIBUTING.md +166 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/PKG-INFO +4 -8
- {apify_client-3.3.0 → apify_client-3.3.1b2}/README.md +1 -6
- {apify_client-3.3.0 → apify_client-3.3.1b2}/pyproject.toml +60 -16
- {apify_client-3.3.0 → apify_client-3.3.1b2}/pyproject.toml.orig +25 -15
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/dataset.py +3 -3
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/key_value_store.py +3 -3
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/log.py +3 -3
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_streamed_log.py +105 -43
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/try_import.py +2 -2
- apify_client-3.3.0/CONTRIBUTING.md +0 -189
- {apify_client-3.3.0 → apify_client-3.3.1b2}/LICENSE +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/__init__.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_apify_client.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_client_registry.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_consts.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_docs.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_literals.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_logging.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_models.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_pagination.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/__init__.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/_resource_client.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_env_var.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_env_var_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_version.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_version_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/build.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/build_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/dataset_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/key_value_store_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/request_queue.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/request_queue_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/run.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/run_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/schedule.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/schedule_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/store_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/task.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/task_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/user.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook_dispatch.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook_dispatch_collection.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_statistics.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_status_message_watcher.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_typeddicts.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/__init__.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/crypto.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/encoding.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/errors.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/http.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/time.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_utils/wait_for_resources.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/errors.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_clients/__init__.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_clients/_base.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_clients/_httpx2.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_clients/_impit.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_clients/_streamed_body.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_compressors/__init__.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_compressors/_base.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_compressors/_brotli.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_compressors/_gzip.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/http_compressors/_resolve.py +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/py.typed +0 -0
- {apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/types.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
<!-- git-cliff-unreleased-start -->
|
|
6
|
+
## 3.3.1 - **not yet released**
|
|
7
|
+
|
|
8
|
+
### 🚀 Features
|
|
9
|
+
|
|
10
|
+
- Add support for Python 3.15 ([#1122](https://github.com/apify/apify-client-python/pull/1122)) ([ef8f088](https://github.com/apify/apify-client-python/commit/ef8f088c3d4fb5f6facfc2728343298464c5ecb6)) by [@vdusek](https://github.com/vdusek)
|
|
11
|
+
|
|
12
|
+
### 🐛 Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Redirect the run log when the first log stream comes back empty ([#1084](https://github.com/apify/apify-client-python/pull/1084)) ([a370e7a](https://github.com/apify/apify-client-python/commit/a370e7a9db75053c2f65ad626ba1bcb67c4cf398)) by [@vdusek](https://github.com/vdusek)
|
|
15
|
+
- **deps:** Allow impit v0.15 ([#1128](https://github.com/apify/apify-client-python/pull/1128)) ([eb9e188](https://github.com/apify/apify-client-python/commit/eb9e188dfb4cdbae620307c7696b0886ed8a6db3)) by [@vdusek](https://github.com/vdusek)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
<!-- git-cliff-unreleased-end -->
|
|
5
19
|
## [3.3.0](https://github.com/apify/apify-client-python/releases/tag/v3.3.0) (2026-10-08)
|
|
6
20
|
|
|
7
21
|
### 🚀 Features
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
Here you'll find a contributing guide to get started with development.
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
For local development, you need a Python version that satisfies `requires-python` in `pyproject.toml`.
|
|
8
|
+
|
|
9
|
+
We use [uv](https://docs.astral.sh/uv/) for project management. Install it and set up your IDE accordingly.
|
|
10
|
+
|
|
11
|
+
We use [Poe the Poet](https://poethepoet.natn.io/) as a task runner, similar to npm scripts in `package.json`. All tasks are defined in `pyproject.toml` under `[tool.poe.tasks]`. Run `uv run poe` to list them with their descriptions, and `uv run poe <task>` to run one.
|
|
12
|
+
|
|
13
|
+
## Dependencies
|
|
14
|
+
|
|
15
|
+
To install this package with its development dependencies and the pre-commit hooks, run:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
uv run poe install-dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Code checking
|
|
22
|
+
|
|
23
|
+
To run all code checks (lint, type check, async docstring check, and unit tests), run:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
uv run poe check-code
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
We use [ruff](https://docs.astral.sh/ruff/) for linting and formatting, [ty](https://docs.astral.sh/ty/) for type checking, and [pytest](https://docs.pytest.org/) for tests. All of them are configured in `pyproject.toml`, and each has its own task (`lint`, `format`, `type-check`, `unit-tests`).
|
|
30
|
+
|
|
31
|
+
## Tests
|
|
32
|
+
|
|
33
|
+
- **Unit tests** (`tests/unit`) are isolated from the Apify API. See [`tests/unit/README.md`](tests/unit/README.md) for the rules they follow. Run them with `uv run poe unit-tests`, or a single test with `uv run pytest tests/unit/<path>.py::<test_name>`.
|
|
34
|
+
- **Integration tests** (`tests/integration`) call the real Apify API. See [`tests/integration/README.md`](tests/integration/README.md) for the tokens they need. Run them with `uv run poe integration-tests`.
|
|
35
|
+
|
|
36
|
+
## Code style
|
|
37
|
+
|
|
38
|
+
### Readability
|
|
39
|
+
|
|
40
|
+
- Prefer a few simple statements over one dense line. Give intermediate values a name instead of nesting calls, comprehensions, and unpacking into a single long expression.
|
|
41
|
+
- When replacing a call with an equivalent one, change only the call and keep the surrounding statements as they were.
|
|
42
|
+
|
|
43
|
+
### Comments and docstrings
|
|
44
|
+
|
|
45
|
+
- Every module starts with `from __future__ import annotations`.
|
|
46
|
+
- Comments and docstrings describe what the code does now. The bug a change fixed, what a PR changed, and contrasts with removed behavior (`instead of ...`, `no longer ...`) belong in the commit message.
|
|
47
|
+
- Don't explain language features in comments. A comment earns its place with a domain reason, an invariant the types can't express, or a non-obvious consequence for callers.
|
|
48
|
+
- Use single backticks for code references, never RST-style double backticks.
|
|
49
|
+
- Wrap docstrings and prose comments to the full line length configured in `pyproject.toml`.
|
|
50
|
+
- Write only ASCII in source files: `->` instead of an arrow, `-` instead of a dash, straight quotes.
|
|
51
|
+
- Write `RQ` for the request queue. Log messages and public `.mdx` docs spell out the full term.
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
- Write flat `def test_*()` functions, never `class Test*`, and no section-header comments between tests.
|
|
56
|
+
- Almost every test gets a one-line docstring stating what it verifies, without a `Regression test:`-style prefix.
|
|
57
|
+
- In `@pytest.mark.parametrize`, wrap each case in `pytest.param(..., id='...')` with an id that describes the case.
|
|
58
|
+
- Use `Mock` and the `monkeypatch` fixture for test doubles, not hand-rolled stubs, counters, or flags.
|
|
59
|
+
- A test that changes process-wide or event-loop-wide state restores it. Prefer `monkeypatch`; for APIs it can't patch (`loop.set_exception_handler()`, `signal.signal()`, logging config, `os.chdir()`), capture the original value and restore it in a `finally`.
|
|
60
|
+
- On Windows with Python 3.12 or older, `datetime.now()` ticks in 1-15 ms steps, so two timestamps taken a few milliseconds apart can be equal. A test that asserts one timestamp is later than another injects a `Mock` clock instead of sleeping.
|
|
61
|
+
- For a TCP endpoint that must refuse connections, bind a socket to `('127.0.0.1', 0)` and hold it open without calling `listen()`. A hard-coded port can be taken by a server in a parallel xdist worker. Test servers use `127.0.0.1`, since on Windows `localhost` resolves to both IPv4 and IPv6.
|
|
62
|
+
|
|
63
|
+
### YAML
|
|
64
|
+
|
|
65
|
+
Use double quotes for quoted YAML values (`retries: "3"`). Keep single quotes when the value itself contains double quotes, and for string literals inside GitHub Actions expressions (`${{ ... }}`, `if:` conditions), where double quotes are invalid.
|
|
66
|
+
|
|
67
|
+
## Generated models
|
|
68
|
+
|
|
69
|
+
`src/apify_client/_models.py`, `_typeddicts.py`, and `_literals.py` are generated from the Apify OpenAPI specification by [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator) and post-processed by `scripts/postprocess_generated_models.py`. Never edit them by hand; make lasting fixes in the post-processing script. The generator is configured under `[tool.datamodel-codegen]` in `pyproject.toml`, with field aliases in `datamodel_codegen_aliases.json`.
|
|
70
|
+
|
|
71
|
+
- `uv run poe generate-models` regenerates them from the published specification. `scripts/openapi_spec.py` describes how the specification version is recorded. Don't commit a regeneration that only moves that version.
|
|
72
|
+
- `uv run poe generate-models-from-file <path>` generates them from a local specification, for trying out unpublished changes. Don't commit the result.
|
|
73
|
+
- The nightly `Regenerate models` workflow opens a `chore:` PR when the models change. If the diff is user-facing, retitle it to `fix:` or `feat:` so it lands in the changelog and triggers a release.
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
We adhere to the [Google docstring format](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) for documenting our codebase. Every user-facing class or method is documented. Documentation standards are enforced using [Ruff](https://docs.astral.sh/ruff/).
|
|
78
|
+
|
|
79
|
+
Resource client docstrings follow one pattern:
|
|
80
|
+
|
|
81
|
+
- The first line is `Sub-client for managing a specific <resource>.` or `Sub-client for the <resource> collection.`
|
|
82
|
+
- The second paragraph names two or three things the client does (not every method) and says to obtain an instance via an appropriate method on the parent client class, without naming that method.
|
|
83
|
+
|
|
84
|
+
Docstrings are written on the sync clients only. After editing one, run `uv run poe fix-docstrings` to copy it to the async client; `uv run poe check-docstrings` fails when the two differ.
|
|
85
|
+
|
|
86
|
+
Our API documentation is generated from these docstrings using [pydoc-markdown](https://pypi.org/project/pydoc-markdown/) with additional post-processing. Markdown files in the `docs/` folder complement the autogenerated content. Final documentation is rendered using [Docusaurus](https://docusaurus.io/) and published to GitHub Pages.
|
|
87
|
+
|
|
88
|
+
A user-facing feature ships with its documentation in the same PR: a page in `docs/02_concepts/` that explains the mechanism and, where it helps, a page in `docs/03_guides/` that shows a realistic use.
|
|
89
|
+
|
|
90
|
+
Code examples in `docs/` come in `_sync.py` / `_async.py` pairs and have their own lint settings in `docs/pyproject.toml`. Never edit `website/versioned_docs/`: those snapshots are produced by the release automation and frozen against the code that shipped. Fix `docs/`, and the next release snapshot carries the change.
|
|
91
|
+
|
|
92
|
+
To run the documentation locally (requires Node.js):
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
uv run poe run-docs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Linting the docs and website
|
|
99
|
+
|
|
100
|
+
Markdown content (this guide, `README.md`, and the `docs/` folder) is checked with
|
|
101
|
+
[markdownlint](https://github.com/DavidAnson/markdownlint). The Docusaurus website code is linted
|
|
102
|
+
with [oxlint](https://oxc.rs/) in type-aware mode (types come from `website/tsconfig.json`) and
|
|
103
|
+
formatted with [oxfmt](https://oxc.rs/). All of them run in CI.
|
|
104
|
+
To run them locally (requires Node.js 22.12 or newer and pnpm), from the `website/` directory:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
pnpm lint # lint Markdown and website code
|
|
108
|
+
pnpm lint:fix # auto-fix both
|
|
109
|
+
pnpm format # format the website code
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Doc images are committed as optimized `.webp`. To convert a new image, run
|
|
113
|
+
`pnpm opt:images <path-to-image>` from the `website/` directory.
|
|
114
|
+
|
|
115
|
+
## Commits
|
|
116
|
+
|
|
117
|
+
We use [Conventional Commits](https://www.conventionalcommits.org/) format for commit messages. The release process reads them to determine the version bump and build the changelog. Pick the type by what the commit changes, not by why:
|
|
118
|
+
|
|
119
|
+
| Type | Description |
|
|
120
|
+
| ---- | ----------- |
|
|
121
|
+
| `feat` | A new feature in the source code |
|
|
122
|
+
| `fix` | A bug fix in the source code |
|
|
123
|
+
| `perf` | A source code change that improves performance |
|
|
124
|
+
| `refactor` | A source code change that neither fixes a bug nor adds a feature |
|
|
125
|
+
| `style` | A source code change that doesn't affect its meaning (white-space, formatting) |
|
|
126
|
+
| `test` | Adding or changing tests |
|
|
127
|
+
| `docs` | Documentation changes |
|
|
128
|
+
| `ci` | CI workflow changes |
|
|
129
|
+
| `build` | Build system changes |
|
|
130
|
+
| `chore` | Dependency bumps, tooling, generated files, and other housekeeping |
|
|
131
|
+
| `revert` | Reverting a previous commit |
|
|
132
|
+
|
|
133
|
+
`feat`, `fix`, `perf`, `refactor`, and `style` trigger a release and appear in the changelog, so use them for source code changes only. `docs` triggers a docs release on master.
|
|
134
|
+
|
|
135
|
+
Logging changes are breaking: changing a log level, a logger name, or the wording of a log message, or removing one, breaks downstream log-based tooling. Keep the accurate type, add `!` after it, and list the changes in a `BREAKING CHANGE:` footer. PRs are squash-merged, so the `!` must also be in the PR title.
|
|
136
|
+
|
|
137
|
+
## Release process
|
|
138
|
+
|
|
139
|
+
Publishing new versions to [PyPI](https://pypi.org/project/apify_client) is automated through GitHub Actions.
|
|
140
|
+
|
|
141
|
+
- **Beta releases**: On each commit to the master branch, a new beta release is automatically published. The version number is determined based on the latest release and conventional commits. The beta version suffix is incremented by 1 from the last beta release on PyPI.
|
|
142
|
+
- **Stable releases**: A stable version release may be created by triggering the `release` GitHub Actions workflow. The version number is determined based on the latest release and conventional commits (`auto` release type), or it may be overridden using the `custom` release type.
|
|
143
|
+
|
|
144
|
+
### Publishing to PyPI manually
|
|
145
|
+
|
|
146
|
+
1. **Do not do this unless absolutely necessary.** In all conceivable scenarios, you should use the `release` workflow instead.
|
|
147
|
+
2. **Make sure you know what you're doing.**
|
|
148
|
+
3. Update the version number by modifying the `version` field under `project` in `pyproject.toml`:
|
|
149
|
+
|
|
150
|
+
```toml
|
|
151
|
+
[project]
|
|
152
|
+
name = "apify_client"
|
|
153
|
+
version = "x.z.y"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
4. Build the package:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
uv run poe build
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
5. Upload to PyPI:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
uv publish --token YOUR_API_TOKEN
|
|
166
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apify_client
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.1b2
|
|
4
4
|
Summary: Apify API client for Python
|
|
5
5
|
Keywords: apify,api,client,automation,crawling,scraping
|
|
6
6
|
Author: Apify Technologies s.r.o.
|
|
@@ -15,9 +15,10 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
18
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
20
|
Requires-Dist: colorama>=0.4.0
|
|
20
|
-
Requires-Dist: impit
|
|
21
|
+
Requires-Dist: impit>=0.14.0,<0.16.0
|
|
21
22
|
Requires-Dist: more-itertools>=10.0.0
|
|
22
23
|
Requires-Dist: pydantic[email]>=2.11.0
|
|
23
24
|
Requires-Dist: typing-extensions>=4.6.0
|
|
@@ -261,12 +262,7 @@ The full documentation lives at **[docs.apify.com/api/client/python](https://doc
|
|
|
261
262
|
|
|
262
263
|
## Contributing
|
|
263
264
|
|
|
264
|
-
Bug reports, fixes, and improvements are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, coding standards, testing, and the release process.
|
|
265
|
-
|
|
266
|
-
```bash
|
|
267
|
-
uv run poe install-dev # install dev deps and git hooks
|
|
268
|
-
uv run poe check-code # lint, type-check, unit tests, docstring check
|
|
269
|
-
```
|
|
265
|
+
Bug reports, fixes, and improvements are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, coding standards, testing, and the release process.
|
|
270
266
|
|
|
271
267
|
## License
|
|
272
268
|
|
|
@@ -224,12 +224,7 @@ The full documentation lives at **[docs.apify.com/api/client/python](https://doc
|
|
|
224
224
|
|
|
225
225
|
## Contributing
|
|
226
226
|
|
|
227
|
-
Bug reports, fixes, and improvements are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, coding standards, testing, and the release process.
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
uv run poe install-dev # install dev deps and git hooks
|
|
231
|
-
uv run poe check-code # lint, type-check, unit tests, docstring check
|
|
232
|
-
```
|
|
227
|
+
Bug reports, fixes, and improvements are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development setup, coding standards, testing, and the release process.
|
|
233
228
|
|
|
234
229
|
## License
|
|
235
230
|
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apify_client"
|
|
7
|
-
version = "3.3.
|
|
7
|
+
version = "3.3.1b2"
|
|
8
8
|
description = "Apify API client for Python"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.12",
|
|
20
20
|
"Programming Language :: Python :: 3.13",
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: 3.15",
|
|
22
23
|
"Topic :: Software Development :: Libraries",
|
|
23
24
|
]
|
|
24
25
|
keywords = [
|
|
@@ -31,7 +32,7 @@ keywords = [
|
|
|
31
32
|
]
|
|
32
33
|
dependencies = [
|
|
33
34
|
"colorama>=0.4.0",
|
|
34
|
-
"impit
|
|
35
|
+
"impit>=0.14.0,<0.16.0",
|
|
35
36
|
"more_itertools>=10.0.0",
|
|
36
37
|
"pydantic[email]>=2.11.0",
|
|
37
38
|
"typing-extensions>=4.6.0",
|
|
@@ -68,7 +69,7 @@ dev = [
|
|
|
68
69
|
"pytest-timeout<3.0.0",
|
|
69
70
|
"pytest-xdist<4.0.0",
|
|
70
71
|
"pytest<10.0.0",
|
|
71
|
-
"ruff~=0.
|
|
72
|
+
"ruff~=0.17.0",
|
|
72
73
|
"setuptools",
|
|
73
74
|
"types-colorama<0.5.0",
|
|
74
75
|
"ty~=0.0.0",
|
|
@@ -262,56 +263,99 @@ apify-client = false
|
|
|
262
263
|
apify-shared = false
|
|
263
264
|
apify_fingerprint_datapoints = false
|
|
264
265
|
crawlee = false
|
|
266
|
+
impit = false
|
|
265
267
|
|
|
266
|
-
[tool.poe.tasks]
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
268
|
+
[tool.poe.tasks.clean]
|
|
269
|
+
cmd = "rm -rf .coverage .pytest_cache .ruff_cache .ty_cache build dist htmlcov"
|
|
270
|
+
help = "Remove build artifacts and clean caches"
|
|
271
|
+
|
|
272
|
+
[tool.poe.tasks.install-sync]
|
|
273
|
+
cmd = "uv sync --all-extras"
|
|
274
|
+
help = "Sync dependencies with all extras"
|
|
275
|
+
|
|
276
|
+
[tool.poe.tasks.build]
|
|
277
|
+
cmd = "uv build"
|
|
278
|
+
help = "Build the package"
|
|
279
|
+
|
|
280
|
+
[tool.poe.tasks.publish-to-pypi]
|
|
281
|
+
cmd = "uv publish --token ${APIFY_PYPI_TOKEN_CRAWLEE}"
|
|
282
|
+
help = "Publish the package to PyPI"
|
|
283
|
+
|
|
284
|
+
[tool.poe.tasks.type-check]
|
|
285
|
+
cmd = "uv run ty check"
|
|
286
|
+
help = "Run the type checker"
|
|
287
|
+
|
|
288
|
+
[tool.poe.tasks.unit-tests]
|
|
289
|
+
cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/unit"
|
|
290
|
+
help = "Run unit tests"
|
|
291
|
+
|
|
292
|
+
[tool.poe.tasks.unit-tests-cov]
|
|
293
|
+
cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-unit.xml tests/unit"
|
|
294
|
+
help = "Run unit tests with an XML coverage report"
|
|
295
|
+
|
|
296
|
+
[tool.poe.tasks.integration-tests]
|
|
297
|
+
cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/integration"
|
|
298
|
+
help = "Run integration tests"
|
|
299
|
+
|
|
300
|
+
[tool.poe.tasks.integration-tests-cov]
|
|
301
|
+
cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-integration.xml tests/integration"
|
|
302
|
+
help = "Run integration tests with an XML coverage report"
|
|
303
|
+
|
|
304
|
+
[tool.poe.tasks.check-docstrings]
|
|
305
|
+
cmd = "uv run python -m scripts.check_docstrings"
|
|
306
|
+
help = "Check that async client docstrings match their sync counterparts"
|
|
307
|
+
|
|
308
|
+
[tool.poe.tasks.fix-docstrings]
|
|
309
|
+
cmd = "uv run python -m scripts.fix_docstrings"
|
|
310
|
+
help = "Copy sync client docstrings to their async counterparts"
|
|
311
|
+
|
|
312
|
+
[tool.poe.tasks.check-code]
|
|
313
|
+
sequence = [
|
|
279
314
|
"lint",
|
|
280
315
|
"type-check",
|
|
281
316
|
"check-docstrings",
|
|
282
317
|
"unit-tests",
|
|
283
318
|
]
|
|
319
|
+
help = "Run lint, type check, docstring check, and unit tests"
|
|
284
320
|
|
|
285
321
|
[tool.poe.tasks.install-dev]
|
|
286
322
|
shell = "uv sync --all-extras && uv run pre-commit install"
|
|
323
|
+
help = "Install development dependencies and pre-commit hooks"
|
|
287
324
|
|
|
288
325
|
[tool.poe.tasks.lint]
|
|
289
326
|
shell = "uv run ruff format --check && uv run ruff check"
|
|
327
|
+
help = "Run the linter and check formatting"
|
|
290
328
|
|
|
291
329
|
[tool.poe.tasks.format]
|
|
292
330
|
shell = "uv run ruff check --fix && uv run ruff format"
|
|
331
|
+
help = "Fix lint issues and format code"
|
|
293
332
|
|
|
294
333
|
[tool.poe.tasks.update-docs-theme]
|
|
295
334
|
shell = "pnpm update @apify/docs-theme"
|
|
296
335
|
cwd = "website"
|
|
336
|
+
help = "Update the documentation website theme"
|
|
297
337
|
|
|
298
338
|
[tool.poe.tasks.build-docs]
|
|
299
339
|
shell = "./build_api_reference.sh && pnpm install && uv run pnpm build"
|
|
300
340
|
cwd = "website"
|
|
341
|
+
help = "Build the documentation website"
|
|
301
342
|
|
|
302
343
|
[tool.poe.tasks.run-docs]
|
|
303
344
|
shell = "./build_api_reference.sh && pnpm install && uv run pnpm start"
|
|
304
345
|
cwd = "website"
|
|
346
|
+
help = "Run the documentation website locally"
|
|
305
347
|
|
|
306
348
|
[tool.poe.tasks.generate-models]
|
|
307
349
|
shell = """
|
|
308
350
|
uv run python -m scripts.openapi_spec fetch && uv run datamodel-codegen --input tmp/openapi.json --alias-generator to_camel && uv run datamodel-codegen --input tmp/openapi.json --output src/apify_client/_typeddicts.py --output-model-type typing.TypedDict --no-use-closed-typed-dict && python scripts/postprocess_generated_models.py && uv run python -m scripts.openapi_spec record-version
|
|
309
351
|
"""
|
|
352
|
+
help = "Regenerate models from the published OpenAPI specification"
|
|
310
353
|
|
|
311
354
|
[tool.poe.tasks.generate-models-from-file]
|
|
312
355
|
shell = """
|
|
313
356
|
uv run datamodel-codegen --input $input_file --alias-generator to_camel && uv run datamodel-codegen --input $input_file --output src/apify_client/_typeddicts.py --output-model-type typing.TypedDict --no-use-closed-typed-dict && python scripts/postprocess_generated_models.py && echo "Generated from $input_file - the version in [tool.apify.openapi-spec] still names the published specification, so do not commit these models."
|
|
314
357
|
"""
|
|
358
|
+
help = "Generate models from a local OpenAPI specification file (don't commit the result)"
|
|
315
359
|
|
|
316
360
|
[[tool.poe.tasks.generate-models-from-file.args]]
|
|
317
361
|
name = "input-file"
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apify_client"
|
|
7
|
-
version = "3.3.
|
|
7
|
+
version = "3.3.1b2"
|
|
8
8
|
description = "Apify API client for Python"
|
|
9
9
|
authors = [{ name = "Apify Technologies s.r.o.", email = "support@apify.com" }]
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -20,12 +20,13 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Programming Language :: Python :: 3.15",
|
|
23
24
|
"Topic :: Software Development :: Libraries",
|
|
24
25
|
]
|
|
25
26
|
keywords = ["apify", "api", "client", "automation", "crawling", "scraping"]
|
|
26
27
|
dependencies = [
|
|
27
28
|
"colorama>=0.4.0",
|
|
28
|
-
"impit
|
|
29
|
+
"impit>=0.14.0,<0.16.0",
|
|
29
30
|
"more_itertools>=10.0.0",
|
|
30
31
|
"pydantic[email]>=2.11.0",
|
|
31
32
|
# 4.6.0 is the first release whose runtime protocol checks look attributes up statically.
|
|
@@ -63,7 +64,7 @@ dev = [
|
|
|
63
64
|
"pytest-timeout<3.0.0",
|
|
64
65
|
"pytest-xdist<4.0.0",
|
|
65
66
|
"pytest<10.0.0",
|
|
66
|
-
"ruff~=0.
|
|
67
|
+
"ruff~=0.17.0",
|
|
67
68
|
"setuptools", # setuptools are used by pytest but not explicitly required
|
|
68
69
|
"types-colorama<0.5.0",
|
|
69
70
|
"ty~=0.0.0",
|
|
@@ -262,42 +263,49 @@ apify-client = false
|
|
|
262
263
|
apify-shared = false
|
|
263
264
|
apify_fingerprint_datapoints = false
|
|
264
265
|
crawlee = false
|
|
266
|
+
impit = false
|
|
265
267
|
|
|
266
268
|
# Run tasks with: uv run poe <task>
|
|
267
269
|
[tool.poe.tasks]
|
|
268
|
-
clean = "rm -rf .coverage .pytest_cache .ruff_cache .ty_cache build dist htmlcov"
|
|
269
|
-
install-sync = "uv sync --all-extras"
|
|
270
|
-
build = "uv build"
|
|
271
|
-
publish-to-pypi = "uv publish --token ${APIFY_PYPI_TOKEN_CRAWLEE}"
|
|
272
|
-
type-check = "uv run ty check"
|
|
273
|
-
unit-tests = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/unit"
|
|
274
|
-
unit-tests-cov = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-unit.xml tests/unit"
|
|
275
|
-
integration-tests = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/integration"
|
|
276
|
-
integration-tests-cov = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-integration.xml tests/integration"
|
|
277
|
-
check-docstrings = "uv run python -m scripts.check_docstrings"
|
|
278
|
-
fix-docstrings = "uv run python -m scripts.fix_docstrings"
|
|
279
|
-
check-code = ["lint", "type-check", "check-docstrings", "unit-tests"]
|
|
270
|
+
clean = { cmd = "rm -rf .coverage .pytest_cache .ruff_cache .ty_cache build dist htmlcov", help = "Remove build artifacts and clean caches" }
|
|
271
|
+
install-sync = { cmd = "uv sync --all-extras", help = "Sync dependencies with all extras" }
|
|
272
|
+
build = { cmd = "uv build", help = "Build the package" }
|
|
273
|
+
publish-to-pypi = { cmd = "uv publish --token ${APIFY_PYPI_TOKEN_CRAWLEE}", help = "Publish the package to PyPI" }
|
|
274
|
+
type-check = { cmd = "uv run ty check", help = "Run the type checker" }
|
|
275
|
+
unit-tests = { cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/unit", help = "Run unit tests" }
|
|
276
|
+
unit-tests-cov = { cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-unit.xml tests/unit", help = "Run unit tests with an XML coverage report" }
|
|
277
|
+
integration-tests = { cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} tests/integration", help = "Run integration tests" }
|
|
278
|
+
integration-tests-cov = { cmd = "uv run pytest --numprocesses=${TESTS_CONCURRENCY:-auto} --cov=src/apify_client --cov-report=xml:coverage-integration.xml tests/integration", help = "Run integration tests with an XML coverage report" }
|
|
279
|
+
check-docstrings = { cmd = "uv run python -m scripts.check_docstrings", help = "Check that async client docstrings match their sync counterparts" }
|
|
280
|
+
fix-docstrings = { cmd = "uv run python -m scripts.fix_docstrings", help = "Copy sync client docstrings to their async counterparts" }
|
|
281
|
+
check-code = { sequence = ["lint", "type-check", "check-docstrings", "unit-tests"], help = "Run lint, type check, docstring check, and unit tests" }
|
|
280
282
|
|
|
281
283
|
[tool.poe.tasks.install-dev]
|
|
282
284
|
shell = "uv sync --all-extras && uv run pre-commit install"
|
|
285
|
+
help = "Install development dependencies and pre-commit hooks"
|
|
283
286
|
|
|
284
287
|
[tool.poe.tasks.lint]
|
|
285
288
|
shell = "uv run ruff format --check && uv run ruff check"
|
|
289
|
+
help = "Run the linter and check formatting"
|
|
286
290
|
|
|
287
291
|
[tool.poe.tasks.format]
|
|
288
292
|
shell = "uv run ruff check --fix && uv run ruff format"
|
|
293
|
+
help = "Fix lint issues and format code"
|
|
289
294
|
|
|
290
295
|
[tool.poe.tasks.update-docs-theme]
|
|
291
296
|
shell = "pnpm update @apify/docs-theme"
|
|
292
297
|
cwd = "website"
|
|
298
|
+
help = "Update the documentation website theme"
|
|
293
299
|
|
|
294
300
|
[tool.poe.tasks.build-docs]
|
|
295
301
|
shell = "./build_api_reference.sh && pnpm install && uv run pnpm build"
|
|
296
302
|
cwd = "website"
|
|
303
|
+
help = "Build the documentation website"
|
|
297
304
|
|
|
298
305
|
[tool.poe.tasks.run-docs]
|
|
299
306
|
shell = "./build_api_reference.sh && pnpm install && uv run pnpm start"
|
|
300
307
|
cwd = "website"
|
|
308
|
+
help = "Run the documentation website locally"
|
|
301
309
|
|
|
302
310
|
# Both passes read one downloaded copy, so a specification redeployed mid-run can't leave the generated files
|
|
303
311
|
# describing two different inputs. The version is recorded last, once generation succeeded.
|
|
@@ -316,6 +324,7 @@ uv run python -m scripts.openapi_spec fetch \
|
|
|
316
324
|
&& python scripts/postprocess_generated_models.py \
|
|
317
325
|
&& uv run python -m scripts.openapi_spec record-version
|
|
318
326
|
"""
|
|
327
|
+
help = "Regenerate models from the published OpenAPI specification"
|
|
319
328
|
|
|
320
329
|
# For a candidate specification that isn't published yet. The recorded version deliberately stays put - it names
|
|
321
330
|
# the published specification - so models generated this way must not be committed, hence the warning.
|
|
@@ -331,3 +340,4 @@ uv run datamodel-codegen --input $input_file \
|
|
|
331
340
|
&& echo "Generated from $input_file - the version in [tool.apify.openapi-spec] still names the published specification, so do not commit these models."
|
|
332
341
|
"""
|
|
333
342
|
args = [{ name = "input-file", positional = true, required = true }]
|
|
343
|
+
help = "Generate models from a local OpenAPI specification file (don't commit the result)"
|
|
@@ -12,7 +12,7 @@ from apify_client._utils.crypto import create_storage_content_signature
|
|
|
12
12
|
from apify_client._utils.http import response_to_dict, response_to_list
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import AsyncIterator, Iterator
|
|
15
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterator
|
|
16
16
|
from datetime import timedelta
|
|
17
17
|
|
|
18
18
|
from apify_client._literals import GeneralAccess
|
|
@@ -405,7 +405,7 @@ class DatasetClient(ResourceClient):
|
|
|
405
405
|
flatten: list[str] | None = None,
|
|
406
406
|
signature: str | None = None,
|
|
407
407
|
timeout: Timeout = 'long',
|
|
408
|
-
) ->
|
|
408
|
+
) -> Generator[HttpResponse]:
|
|
409
409
|
"""Retrieve the items in the dataset as a stream.
|
|
410
410
|
|
|
411
411
|
https://docs.apify.com/api/v2#/reference/datasets/item-collection/get-items
|
|
@@ -970,7 +970,7 @@ class DatasetClientAsync(ResourceClientAsync):
|
|
|
970
970
|
flatten: list[str] | None = None,
|
|
971
971
|
signature: str | None = None,
|
|
972
972
|
timeout: Timeout = 'long',
|
|
973
|
-
) ->
|
|
973
|
+
) -> AsyncGenerator[HttpResponse]:
|
|
974
974
|
"""Retrieve the items in the dataset as a stream.
|
|
975
975
|
|
|
976
976
|
https://docs.apify.com/api/v2#/reference/datasets/item-collection/get-items
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/key_value_store.py
RENAMED
|
@@ -22,7 +22,7 @@ from apify_client._utils.http import response_to_dict, to_path_segment
|
|
|
22
22
|
from apify_client.errors import ApifyApiError, InvalidResponseBodyError
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from collections.abc import AsyncIterator, Iterator
|
|
25
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterator
|
|
26
26
|
from datetime import timedelta
|
|
27
27
|
|
|
28
28
|
from apify_client._literals import GeneralAccess
|
|
@@ -318,7 +318,7 @@ class KeyValueStoreClient(ResourceClient):
|
|
|
318
318
|
@contextmanager
|
|
319
319
|
def stream_record(
|
|
320
320
|
self, key: str, *, signature: str | None = None, timeout: Timeout = 'long'
|
|
321
|
-
) ->
|
|
321
|
+
) -> Generator[dict | None]:
|
|
322
322
|
"""Retrieve the given record from the key-value store, as a stream.
|
|
323
323
|
|
|
324
324
|
https://docs.apify.com/api/v2#/reference/key-value-stores/record/get-record
|
|
@@ -756,7 +756,7 @@ class KeyValueStoreClientAsync(ResourceClientAsync):
|
|
|
756
756
|
@asynccontextmanager
|
|
757
757
|
async def stream_record(
|
|
758
758
|
self, key: str, *, signature: str | None = None, timeout: Timeout = 'long'
|
|
759
|
-
) ->
|
|
759
|
+
) -> AsyncGenerator[dict | None]:
|
|
760
760
|
"""Retrieve the given record from the key-value store, as a stream.
|
|
761
761
|
|
|
762
762
|
https://docs.apify.com/api/v2#/reference/key-value-stores/record/get-record
|
|
@@ -9,7 +9,7 @@ from apify_client._utils.errors import catch_not_found_for_resource_or_throw
|
|
|
9
9
|
from apify_client.errors import ApifyApiError
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
-
from collections.abc import
|
|
12
|
+
from collections.abc import AsyncGenerator, Generator
|
|
13
13
|
|
|
14
14
|
from apify_client.http_clients import HttpResponse
|
|
15
15
|
from apify_client.types import Timeout
|
|
@@ -96,7 +96,7 @@ class LogClient(ResourceClient):
|
|
|
96
96
|
return None
|
|
97
97
|
|
|
98
98
|
@contextmanager
|
|
99
|
-
def stream(self, *, raw: bool = False, timeout: Timeout = 'long') ->
|
|
99
|
+
def stream(self, *, raw: bool = False, timeout: Timeout = 'long') -> Generator[HttpResponse | None]:
|
|
100
100
|
"""Retrieve the log as a stream.
|
|
101
101
|
|
|
102
102
|
https://docs.apify.com/api/v2#/reference/logs/log/get-log
|
|
@@ -211,7 +211,7 @@ class LogClientAsync(ResourceClientAsync):
|
|
|
211
211
|
return None
|
|
212
212
|
|
|
213
213
|
@asynccontextmanager
|
|
214
|
-
async def stream(self, *, raw: bool = False, timeout: Timeout = 'long') ->
|
|
214
|
+
async def stream(self, *, raw: bool = False, timeout: Timeout = 'long') -> AsyncGenerator[HttpResponse | None]:
|
|
215
215
|
"""Retrieve the log as a stream.
|
|
216
216
|
|
|
217
217
|
https://docs.apify.com/api/v2#/reference/logs/log/get-log
|
|
@@ -32,6 +32,17 @@ class StreamedLogBase:
|
|
|
32
32
|
duration of the run (Impit currently maps it to an effective 24-hour cap) and mirrors the JS client.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
+
_empty_stream_retry_s: ClassVar[float] = 0.5
|
|
36
|
+
"""Pause before the first reopen of a log stream that ended before the run logged anything.
|
|
37
|
+
|
|
38
|
+
The API serves the log of a run that has not logged anything yet as an empty stream that ends at once. Each further
|
|
39
|
+
reopen doubles the pause up to `_empty_stream_max_retry_s`, which bounds the request rate while a run waits long to
|
|
40
|
+
start, for example for free memory.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_empty_stream_max_retry_s: ClassVar[float] = 5
|
|
44
|
+
"""Upper bound on the pause between reopens of an empty log stream."""
|
|
45
|
+
|
|
35
46
|
def __init__(self, to_logger: logging.Logger, *, from_start: bool = True) -> None:
|
|
36
47
|
if self._force_propagate:
|
|
37
48
|
to_logger.propagate = True
|
|
@@ -39,8 +50,11 @@ class StreamedLogBase:
|
|
|
39
50
|
self._stream_buffer = list[bytes]()
|
|
40
51
|
self._split_marker = re.compile(rb'(?:\n|^)(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)')
|
|
41
52
|
self._relevancy_time_limit: datetime | None = None if from_start else datetime.now(tz=UTC)
|
|
53
|
+
self._received_data = False
|
|
42
54
|
|
|
43
55
|
def _process_new_data(self, data: bytes) -> None:
|
|
56
|
+
if data:
|
|
57
|
+
self._received_data = True
|
|
44
58
|
new_chunk = data
|
|
45
59
|
self._stream_buffer.append(new_chunk)
|
|
46
60
|
if re.findall(self._split_marker, new_chunk):
|
|
@@ -75,6 +89,12 @@ class StreamedLogBase:
|
|
|
75
89
|
message = decoded_marker + decoded_content
|
|
76
90
|
self._to_logger.log(level=self._guess_log_level_from_message(message), msg=message.strip())
|
|
77
91
|
|
|
92
|
+
def _process_whole_log(self, log: bytes | None) -> None:
|
|
93
|
+
"""Redirect a whole log read in one request, including its last part."""
|
|
94
|
+
if log:
|
|
95
|
+
self._process_new_data(log)
|
|
96
|
+
self._log_buffer_content(include_last_part=True)
|
|
97
|
+
|
|
78
98
|
@staticmethod
|
|
79
99
|
def _guess_log_level_from_message(message: str) -> int:
|
|
80
100
|
"""Guess the log level from the message."""
|
|
@@ -120,7 +140,7 @@ class StreamedLog(StreamedLogBase):
|
|
|
120
140
|
self._log_client = log_client
|
|
121
141
|
self._streaming_thread: Thread | None = None
|
|
122
142
|
self._log_stream: HttpResponse | None = None
|
|
123
|
-
self.
|
|
143
|
+
self._stop_event = threading.Event()
|
|
124
144
|
|
|
125
145
|
def start(self) -> Thread:
|
|
126
146
|
"""Start the streaming thread.
|
|
@@ -129,7 +149,8 @@ class StreamedLog(StreamedLogBase):
|
|
|
129
149
|
"""
|
|
130
150
|
if self._streaming_thread and self._streaming_thread.is_alive():
|
|
131
151
|
raise RuntimeError('Streaming thread already active')
|
|
132
|
-
self.
|
|
152
|
+
self._stop_event.clear()
|
|
153
|
+
self._received_data = False
|
|
133
154
|
# A daemon thread so a stream still blocked on a read can never hold up interpreter shutdown.
|
|
134
155
|
self._streaming_thread = threading.Thread(target=self._stream_log, daemon=True)
|
|
135
156
|
self._streaming_thread.start()
|
|
@@ -138,13 +159,14 @@ class StreamedLog(StreamedLogBase):
|
|
|
138
159
|
def stop(self) -> None:
|
|
139
160
|
"""Signal the streaming thread to stop logging and wait up to `_stop_timeout_s` for it to finish.
|
|
140
161
|
|
|
141
|
-
A thread that outlives the wait is a daemon with `
|
|
162
|
+
A thread that outlives the wait is a daemon with `_stop_event` set, so it exits after at most one more chunk,
|
|
142
163
|
and only then does its buffered tail reach the logger. Its handle is kept while it is alive, so `start` cannot
|
|
143
|
-
revive it beside a second thread on the same buffer.
|
|
164
|
+
revive it beside a second thread on the same buffer. If no stream has delivered anything yet, the thread reads
|
|
165
|
+
the whole log in one request before it ends.
|
|
144
166
|
"""
|
|
145
167
|
if not self._streaming_thread:
|
|
146
168
|
raise RuntimeError('Streaming thread is not active')
|
|
147
|
-
self.
|
|
169
|
+
self._stop_event.set()
|
|
148
170
|
# Read once; the streaming thread clears the attribute as soon as the stream ends.
|
|
149
171
|
log_stream = self._log_stream
|
|
150
172
|
if log_stream is not None:
|
|
@@ -173,39 +195,60 @@ class StreamedLog(StreamedLogBase):
|
|
|
173
195
|
|
|
174
196
|
def _stream_log(self) -> None:
|
|
175
197
|
try:
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
# An empty stream means the run has not logged anything yet, so reopen it until the first bytes arrive.
|
|
199
|
+
retry_s = self._empty_stream_retry_s
|
|
200
|
+
while not self._stop_event.is_set():
|
|
201
|
+
if not self._stream_log_once() or self._received_data:
|
|
178
202
|
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
try:
|
|
182
|
-
# `stop` may have run before the response existed for it to close.
|
|
183
|
-
if self._stop_logging:
|
|
184
|
-
return
|
|
185
|
-
for data in log_stream.iter_bytes():
|
|
186
|
-
self._process_new_data(data)
|
|
187
|
-
if self._stop_logging:
|
|
188
|
-
break
|
|
189
|
-
finally:
|
|
190
|
-
self._log_stream = None
|
|
191
|
-
try:
|
|
192
|
-
# Flush the last buffered part even if the read timed out or was stopped.
|
|
193
|
-
self._log_buffer_content(include_last_part=True)
|
|
194
|
-
except Exception:
|
|
195
|
-
# A truncated stream leaves an undecodable tail, which is worth a traceback even while a stop
|
|
196
|
-
# is in progress.
|
|
197
|
-
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
203
|
+
self._stop_event.wait(retry_s)
|
|
204
|
+
retry_s = min(retry_s * 2, self._empty_stream_max_retry_s)
|
|
198
205
|
except Exception as exc:
|
|
199
|
-
if self.
|
|
206
|
+
if self._stop_event.is_set():
|
|
200
207
|
# `stop` closed the stream out from under the read, so the failure is expected.
|
|
201
208
|
self._to_logger.debug('Log streaming stopped while `stop` was in progress: %r', exc)
|
|
202
|
-
|
|
203
|
-
if self._log_client._http_client.is_timeout_error(exc): # noqa: SLF001
|
|
209
|
+
elif self._log_client._http_client.is_timeout_error(exc): # noqa: SLF001
|
|
204
210
|
# The stream cannot continue, so warn and let the thread end instead of leaking a traceback.
|
|
205
211
|
self._to_logger.warning('Log streaming stopped: the log stream request timed out.')
|
|
212
|
+
return
|
|
206
213
|
else:
|
|
207
214
|
# Any other failure in log redirection must not escape the background thread; log it instead.
|
|
208
215
|
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
216
|
+
return
|
|
217
|
+
if self._received_data:
|
|
218
|
+
return
|
|
219
|
+
# Stopped before any stream delivered a byte, which a run that finishes quickly can cause.
|
|
220
|
+
try:
|
|
221
|
+
self._process_whole_log(self._log_client.get_as_bytes(raw=True))
|
|
222
|
+
except Exception:
|
|
223
|
+
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
224
|
+
|
|
225
|
+
def _stream_log_once(self) -> bool:
|
|
226
|
+
"""Redirect one log stream until it ends or `stop` is called. Return `False` when the log does not exist."""
|
|
227
|
+
with self._log_client.stream(raw=True, timeout=self._stream_timeout) as log_stream:
|
|
228
|
+
if not log_stream:
|
|
229
|
+
return False
|
|
230
|
+
# Published so `stop` can close the response.
|
|
231
|
+
self._log_stream = log_stream
|
|
232
|
+
try:
|
|
233
|
+
# `stop` may have run before the response existed for it to close. No earlier stream delivered
|
|
234
|
+
# anything, and one opened this late would end after its first chunk, so read the whole log in one
|
|
235
|
+
# request.
|
|
236
|
+
if self._stop_event.is_set():
|
|
237
|
+
return True
|
|
238
|
+
for data in log_stream.iter_bytes():
|
|
239
|
+
self._process_new_data(data)
|
|
240
|
+
if self._stop_event.is_set():
|
|
241
|
+
break
|
|
242
|
+
finally:
|
|
243
|
+
self._log_stream = None
|
|
244
|
+
try:
|
|
245
|
+
# Flush the last buffered part even if the read timed out or was stopped.
|
|
246
|
+
self._log_buffer_content(include_last_part=True)
|
|
247
|
+
except Exception:
|
|
248
|
+
# A truncated stream leaves an undecodable tail, which is worth a traceback even while a stop is
|
|
249
|
+
# in progress.
|
|
250
|
+
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
251
|
+
return True
|
|
209
252
|
|
|
210
253
|
|
|
211
254
|
@docs_group('Other')
|
|
@@ -240,14 +283,19 @@ class StreamedLogAsync(StreamedLogBase):
|
|
|
240
283
|
"""
|
|
241
284
|
if self._streaming_task and not self._streaming_task.done():
|
|
242
285
|
raise RuntimeError('Streaming task already active')
|
|
286
|
+
self._received_data = False
|
|
243
287
|
self._streaming_task = asyncio.create_task(self._stream_log())
|
|
244
288
|
return self._streaming_task
|
|
245
289
|
|
|
246
290
|
async def stop(self) -> None:
|
|
247
|
-
"""Stop the streaming task.
|
|
291
|
+
"""Stop the streaming task.
|
|
292
|
+
|
|
293
|
+
If no stream has delivered anything yet, read the whole log in one request instead.
|
|
294
|
+
"""
|
|
248
295
|
if not self._streaming_task:
|
|
249
296
|
raise RuntimeError('Streaming task is not active')
|
|
250
297
|
|
|
298
|
+
was_streaming = not self._streaming_task.done()
|
|
251
299
|
self._streaming_task.cancel()
|
|
252
300
|
try:
|
|
253
301
|
await self._streaming_task
|
|
@@ -255,6 +303,13 @@ class StreamedLogAsync(StreamedLogBase):
|
|
|
255
303
|
pass
|
|
256
304
|
finally:
|
|
257
305
|
self._streaming_task = None
|
|
306
|
+
if not was_streaming or self._received_data:
|
|
307
|
+
return
|
|
308
|
+
# Stopped before any stream delivered a byte, which a run that finishes quickly can cause.
|
|
309
|
+
try:
|
|
310
|
+
self._process_whole_log(await self._log_client.get_as_bytes(raw=True))
|
|
311
|
+
except Exception:
|
|
312
|
+
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
258
313
|
|
|
259
314
|
async def __aenter__(self) -> Self:
|
|
260
315
|
"""Start the streaming task within the context. Exiting the context will cancel the streaming task."""
|
|
@@ -269,20 +324,27 @@ class StreamedLogAsync(StreamedLogBase):
|
|
|
269
324
|
|
|
270
325
|
async def _stream_log(self) -> None:
|
|
271
326
|
try:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
finally:
|
|
327
|
+
# An empty stream means the run has not logged anything yet, so reopen it until the first bytes arrive.
|
|
328
|
+
retry_s = self._empty_stream_retry_s
|
|
329
|
+
while True:
|
|
330
|
+
async with self._log_client.stream(raw=True, timeout=self._stream_timeout) as log_stream:
|
|
331
|
+
if not log_stream:
|
|
332
|
+
return
|
|
279
333
|
try:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
334
|
+
async for data in log_stream.aiter_bytes():
|
|
335
|
+
self._process_new_data(data)
|
|
336
|
+
finally:
|
|
337
|
+
try:
|
|
338
|
+
# Flush the last buffered part even if the task is cancelled by `stop()`.
|
|
339
|
+
self._log_buffer_content(include_last_part=True)
|
|
340
|
+
except Exception:
|
|
341
|
+
# A truncated stream leaves an undecodable tail. Keeping the failure here also keeps the
|
|
342
|
+
# cancellation `stop` raised propagating, so the task ends up cancelled as asyncio expects.
|
|
343
|
+
self._to_logger.exception('Log redirection stopped due to unexpected error:')
|
|
344
|
+
if self._received_data:
|
|
345
|
+
return
|
|
346
|
+
await asyncio.sleep(retry_s)
|
|
347
|
+
retry_s = min(retry_s * 2, self._empty_stream_max_retry_s)
|
|
286
348
|
except Exception as exc:
|
|
287
349
|
if self._log_client._http_client.is_timeout_error(exc): # noqa: SLF001
|
|
288
350
|
# A timeout on the long-lived stream is an expected terminal condition, not an error.
|
|
@@ -7,7 +7,7 @@ from types import ModuleType
|
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import Generator
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ class ImportState:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@contextmanager
|
|
22
|
-
def try_import(module_name: str, *symbol_names: str, dependency_name: str, extra_name: str) ->
|
|
22
|
+
def try_import(module_name: str, *symbol_names: str, dependency_name: str, extra_name: str) -> Generator[ImportState]:
|
|
23
23
|
"""Context manager to attempt importing symbols into a module.
|
|
24
24
|
|
|
25
25
|
If the named optional dependency is missing, the symbols are replaced with `FailedImport` objects whose message
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
# Development
|
|
2
|
-
|
|
3
|
-
Here you'll find a contributing guide to get started with development.
|
|
4
|
-
|
|
5
|
-
## Environment
|
|
6
|
-
|
|
7
|
-
For local development, it is required to have Python 3.11 (or a later version) installed.
|
|
8
|
-
|
|
9
|
-
We use [uv](https://docs.astral.sh/uv/) for project management. Install it and set up your IDE accordingly.
|
|
10
|
-
|
|
11
|
-
We use [Poe the Poet](https://poethepoet.natn.io/) as a task runner, similar to npm scripts in `package.json`.
|
|
12
|
-
All tasks are defined in `pyproject.toml` under `[tool.poe.tasks]` and can be run with `uv run poe <task>`.
|
|
13
|
-
|
|
14
|
-
### Available tasks
|
|
15
|
-
|
|
16
|
-
| Task | Description |
|
|
17
|
-
| ---- | ----------- |
|
|
18
|
-
| `install-dev` | Install development dependencies |
|
|
19
|
-
| `check-code` | Run lint, type-check, unit-tests, and docstring check |
|
|
20
|
-
| `lint` | Run linter |
|
|
21
|
-
| `format` | Fix lint issues and format code |
|
|
22
|
-
| `type-check` | Run type checker |
|
|
23
|
-
| `unit-tests` | Run unit tests |
|
|
24
|
-
| `unit-tests-cov` | Run unit tests with coverage |
|
|
25
|
-
| `integration-tests` | Run integration tests |
|
|
26
|
-
| `integration-tests-cov` | Run integration tests with coverage |
|
|
27
|
-
| `check-docstrings` | Check async client docstrings |
|
|
28
|
-
| `fix-docstrings` | Fix async client docstrings |
|
|
29
|
-
| `build-docs` | Build documentation website |
|
|
30
|
-
| `run-docs` | Run documentation website locally |
|
|
31
|
-
| `build` | Build package |
|
|
32
|
-
| `clean` | Remove build artifacts and clean caches |
|
|
33
|
-
|
|
34
|
-
## Dependencies
|
|
35
|
-
|
|
36
|
-
To install this package and its development dependencies, run:
|
|
37
|
-
|
|
38
|
-
```sh
|
|
39
|
-
uv run poe install-dev
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Code checking
|
|
43
|
-
|
|
44
|
-
To execute all code checking tools together, run:
|
|
45
|
-
|
|
46
|
-
```sh
|
|
47
|
-
uv run poe check-code
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Linting
|
|
51
|
-
|
|
52
|
-
We utilize [ruff](https://docs.astral.sh/ruff/) for linting, which analyzes code for potential issues and enforces consistent style. Refer to `pyproject.toml` for configuration details.
|
|
53
|
-
|
|
54
|
-
To run linting:
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
uv run poe lint
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Formatting
|
|
61
|
-
|
|
62
|
-
Our automated code formatting also leverages [ruff](https://docs.astral.sh/ruff/), ensuring uniform style and addressing fixable linting issues. Configuration specifics are outlined in `pyproject.toml`.
|
|
63
|
-
|
|
64
|
-
To run formatting:
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
uv run poe format
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Type checking
|
|
71
|
-
|
|
72
|
-
Type checking is handled by [ty](https://docs.astral.sh/ty/), verifying code against type annotations. Configuration settings can be found in `pyproject.toml`.
|
|
73
|
-
|
|
74
|
-
To run type checking:
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
uv run poe type-check
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Unit tests
|
|
81
|
-
|
|
82
|
-
We use [pytest](https://docs.pytest.org/) as a testing framework with many plugins. Check `pyproject.toml` for configuration details and installed plugins.
|
|
83
|
-
|
|
84
|
-
To run unit tests:
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
uv run poe unit-tests
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
To run unit tests with XML coverage report:
|
|
91
|
-
|
|
92
|
-
```sh
|
|
93
|
-
uv run poe unit-tests-cov
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Integration tests
|
|
97
|
-
|
|
98
|
-
We have integration tests which interact with the Apify platform.
|
|
99
|
-
|
|
100
|
-
Prerequisites:
|
|
101
|
-
|
|
102
|
-
- Set `APIFY_TEST_USER_API_TOKEN` to your Apify API token
|
|
103
|
-
- Set `APIFY_TEST_USER_2_API_TOKEN` for storage access tests (requires two accounts)
|
|
104
|
-
- Optionally set `APIFY_INTEGRATION_TESTS_API_URL` to use a different Apify API environment
|
|
105
|
-
|
|
106
|
-
To run integration tests:
|
|
107
|
-
|
|
108
|
-
```sh
|
|
109
|
-
uv run poe integration-tests
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Documentation
|
|
113
|
-
|
|
114
|
-
We adhere to the [Google docstring format](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) for documenting our codebase. Every user-facing class or method is documented. Documentation standards are enforced using [Ruff](https://docs.astral.sh/ruff/).
|
|
115
|
-
|
|
116
|
-
Our API documentation is generated from these docstrings using [pydoc-markdown](https://pypi.org/project/pydoc-markdown/) with additional post-processing. Markdown files in the `docs/` folder complement the autogenerated content. Final documentation is rendered using [Docusaurus](https://docusaurus.io/) and published to GitHub Pages.
|
|
117
|
-
|
|
118
|
-
To run the documentation locally (requires Node.js):
|
|
119
|
-
|
|
120
|
-
```sh
|
|
121
|
-
uv run poe run-docs
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Linting the docs and website
|
|
125
|
-
|
|
126
|
-
Markdown content (this guide, `README.md`, and the `docs/` folder) is checked with
|
|
127
|
-
[markdownlint](https://github.com/DavidAnson/markdownlint). The Docusaurus website code is linted
|
|
128
|
-
with [oxlint](https://oxc.rs/) and formatted with [oxfmt](https://oxc.rs/). All of them run in CI.
|
|
129
|
-
To run them locally (requires Node.js 22.12 or newer and pnpm), from the `website/` directory:
|
|
130
|
-
|
|
131
|
-
```sh
|
|
132
|
-
pnpm lint # lint Markdown and website code
|
|
133
|
-
pnpm lint:fix # auto-fix both
|
|
134
|
-
pnpm format # format the website code
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Doc images are committed as optimized `.webp`. To convert a new image, run
|
|
138
|
-
`pnpm opt:images <path-to-image>` from the `website/` directory.
|
|
139
|
-
|
|
140
|
-
## Commits
|
|
141
|
-
|
|
142
|
-
We use [Conventional Commits](https://www.conventionalcommits.org/) format for commit messages. This convention is used to automatically determine version bumps during the release process.
|
|
143
|
-
|
|
144
|
-
### Available commit types
|
|
145
|
-
|
|
146
|
-
| Type | Description |
|
|
147
|
-
| ---- | ----------- |
|
|
148
|
-
| `feat` | A new feature |
|
|
149
|
-
| `fix` | A bug fix |
|
|
150
|
-
| `docs` | Documentation only changes |
|
|
151
|
-
| `style` | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) |
|
|
152
|
-
| `refactor` | A code change that neither fixes a bug nor adds a feature |
|
|
153
|
-
| `perf` | A code change that improves performance |
|
|
154
|
-
| `test` | Adding missing tests or correcting existing tests |
|
|
155
|
-
| `build` | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
|
|
156
|
-
| `ci` | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
|
|
157
|
-
| `chore` | Other changes that don't modify src or test files |
|
|
158
|
-
| `revert` | Reverts a previous commit |
|
|
159
|
-
|
|
160
|
-
## Release process
|
|
161
|
-
|
|
162
|
-
Publishing new versions to [PyPI](https://pypi.org/project/apify_client) is automated through GitHub Actions.
|
|
163
|
-
|
|
164
|
-
- **Beta releases**: On each commit to the master branch, a new beta release is automatically published. The version number is determined based on the latest release and conventional commits. The beta version suffix is incremented by 1 from the last beta release on PyPI.
|
|
165
|
-
- **Stable releases**: A stable version release may be created by triggering the `release` GitHub Actions workflow. The version number is determined based on the latest release and conventional commits (`auto` release type), or it may be overridden using the `custom` release type.
|
|
166
|
-
|
|
167
|
-
### Publishing to PyPI manually
|
|
168
|
-
|
|
169
|
-
1. **Do not do this unless absolutely necessary.** In all conceivable scenarios, you should use the `release` workflow instead.
|
|
170
|
-
2. **Make sure you know what you're doing.**
|
|
171
|
-
3. Update the version number by modifying the `version` field under `project` in `pyproject.toml`:
|
|
172
|
-
|
|
173
|
-
```toml
|
|
174
|
-
[project]
|
|
175
|
-
name = "apify_client"
|
|
176
|
-
version = "x.z.y"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
4. Build the package:
|
|
180
|
-
|
|
181
|
-
```sh
|
|
182
|
-
uv run poe build
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
5. Upload to PyPI:
|
|
186
|
-
|
|
187
|
-
```sh
|
|
188
|
-
uv publish --token YOUR_API_TOKEN
|
|
189
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/_resource_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_collection.py
RENAMED
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_env_var.py
RENAMED
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/actor_version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/build_collection.py
RENAMED
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/dataset_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/request_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/run_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/store_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/task_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook_collection.py
RENAMED
|
File without changes
|
{apify_client-3.3.0 → apify_client-3.3.1b2}/src/apify_client/_resource_clients/webhook_dispatch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|