Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Actor.charge() #346

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7529857
Add signatures of new methods
janbuchar Dec 23, 2024
7f786f8
Add ACTOR_MAX_TOTAL_CHARGE_USD configuration option
janbuchar Dec 23, 2024
32982bb
Fix type error
janbuchar Dec 23, 2024
1897d08
Update method signatures to be more in line with Actor whitepaper
janbuchar Dec 23, 2024
6461f01
Update apify-client
janbuchar Dec 23, 2024
0d558b6
Partially implement ChargingManager
janbuchar Dec 23, 2024
044ae6c
Use ChargingManager in Actor
janbuchar Dec 23, 2024
5d2a678
Read pricing info and use it
janbuchar Jan 6, 2025
38ff1b6
Dataset set up
janbuchar Jan 8, 2025
582bb0d
Load more charging information on platform
janbuchar Jan 9, 2025
fe38c87
Make sure that we stay within the budget when charging
janbuchar Jan 9, 2025
b3dafeb
Reorder stuff
janbuchar Jan 9, 2025
c58aae6
Fill in docblocks
janbuchar Jan 9, 2025
8dc3422
Reorder operations to prevent race conditions
janbuchar Jan 9, 2025
02f1953
Update apify-client
janbuchar Jan 10, 2025
35a378f
Update e2e test directory structure
janbuchar Jan 10, 2025
e9482fc
WIP: Add e2e sdk test setup
janbuchar Jan 10, 2025
9925108
Finalize sdk e2e testing environment
janbuchar Jan 14, 2025
973b19f
Initial e2e test of Actor.charge
janbuchar Jan 14, 2025
72ff36a
Improve test runner
janbuchar Jan 15, 2025
b5ebe79
Make Actor.charge test fail
janbuchar Jan 15, 2025
1bb2dc3
More tests
janbuchar Jan 16, 2025
7d4263c
Simplify control flow
janbuchar Jan 16, 2025
6ef64a6
Ensure Actor init in PPE methods
janbuchar Jan 16, 2025
c312681
Improve logging
janbuchar Jan 16, 2025
da8daf3
Add remaining charges to ChargeResult
janbuchar Jan 16, 2025
dbd8be6
Merge branch 'master' into actor-charge
B4nan Jan 20, 2025
99d13f2
Do not charge for items not yet added to the dataset
janbuchar Jan 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ apify_storage
crawlee_storage
storage
.turbo
*.tgz
mise.toml
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
"build": "turbo run build",
"ci:build": "turbo run build --cache-dir=\".turbo\"",
"test": "vitest run --silent",
"test:e2e": "node test/e2e/run.mjs",
"test:e2e": "npm run test:e2e:scrapers && npm run test:e2e:sdk",
"test:e2e:scrapers": "node test/e2e/runScraperTests.mjs",
"test:e2e:sdk": "npm run test:e2e:sdk:tarball && node test/e2e/runSdkTests.mjs",
"test:e2e:sdk:tarball": "cd packages/apify; mv $(npm pack | tail -n 1) ../../test/e2e/apify.tgz",
"coverage": "vitest --coverage",
"release": "npm run build && lerna publish from-package --contents dist",
"publish:next": "lerna publish --canary --preid beta --dist-tag next",
Expand Down
2 changes: 1 addition & 1 deletion packages/apify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@crawlee/core": "^3.9.0",
"@crawlee/types": "^3.9.0",
"@crawlee/utils": "^3.9.0",
"apify-client": "^2.10.0",
"apify-client": "^2.11.1",
"fs-extra": "^11.2.0",
"ow": "^0.28.2",
"semver": "^7.5.4",
Expand Down
87 changes: 83 additions & 4 deletions packages/apify/src/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
import ow from 'ow';

import { Configuration } from './configuration';
import { ChargingManager } from './internals/charging';
import type { ChargeOptions, ChargeResult } from './internals/charging';
import { KeyValueStore } from './key_value_store';
import { PlatformEventManager } from './platform_event_manager';
import type { ProxyConfigurationOptions } from './proxy_configuration';
Expand Down Expand Up @@ -86,11 +88,14 @@ export class Actor<Data extends Dictionary = Dictionary> {
*/
private isRebooting = false;

private chargingManager: ChargingManager;

constructor(options: ConfigurationOptions = {}) {
// use default configuration object if nothing overridden (it fallbacks to env vars)
this.config = Object.keys(options).length === 0 ? Configuration.getGlobalConfig() : new Configuration(options);
this.apifyClient = this.newClient();
this.eventManager = new PlatformEventManager(this.config);
this.chargingManager = new ChargingManager(this.config, this.apifyClient);
}

/**
Expand Down Expand Up @@ -222,6 +227,9 @@ export class Actor<Data extends Dictionary = Dictionary> {
log.debug(`Default storages purged`);

Configuration.storage.enterWith(this.config);

await this.chargingManager.init();
log.debug(`ChargingManager initialized`);
}

/**
Expand Down Expand Up @@ -609,13 +617,29 @@ export class Actor<Data extends Dictionary = Dictionary> {
*
* @param item Object or array of objects containing data to be stored in the default dataset.
* The objects must be serializable to JSON and the JSON representation of each object must be smaller than 9MB.
* @param eventName If provided, the method will attempt to charge for the event for each pushed item.
* @ignore
*/
async pushData(item: Data | Data[]): Promise<void> {
async pushData(item: Data | Data[]): Promise<void>;
async pushData(item: Data | Data[], eventName: string): Promise<ChargeResult>;
async pushData(item: Data | Data[], eventName?: string | undefined): Promise<ChargeResult | void> {
this._ensureActorInit('pushData');

const dataset = await this.openDataset();
return dataset.pushData(item);

const maxChargedCount = eventName !== undefined ? this.chargingManager.calculateMaxEventChargeCountWithinLimit(eventName) : Infinity;
const toCharge = Array.isArray(item) ? item.length : 1;

if (toCharge > maxChargedCount) {
const items = Array.isArray(item) ? item : [item];
await dataset.pushData(items.slice(0, toCharge));
} else {
await dataset.pushData(item);
}

if (eventName) {
return await this.chargingManager.charge({ eventName, count: Math.min(toCharge, maxChargedCount) });
}
}

/**
Expand Down Expand Up @@ -896,6 +920,32 @@ export class Actor<Data extends Dictionary = Dictionary> {
return undefined;
}

/**
* Charge for a specified number of events - sub-operations of the Actor.
*
* @param options The name of the event to charge for and the number of events to be charged.
*/
async charge(options: ChargeOptions): Promise<ChargeResult> {
this._ensureActorInit('charge');
return this.chargingManager.charge(options);
janbuchar marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get the maximum amount of money that the Actor is allowed to charge.
*/
getMaxTotalChargeUsd(): number {
this._ensureActorInit('getMaxTotalChargeUsd');
return this.chargingManager.getMaxTotalChargeUsd();
}

/**
* Get the number of events with given name that the Actor has charged for so far.
*/
getChargedEventCount(eventName: string): number {
this._ensureActorInit('getChargedEventCount');
return this.chargingManager.getChargedEventCount(eventName);
}

/**
* Modifies Actor env vars so parsing respects the structure of {@apilink ApifyEnv} interface.
*/
Expand Down Expand Up @@ -1304,9 +1354,15 @@ export class Actor<Data extends Dictionary = Dictionary> {
*
* @param item Object or array of objects containing data to be stored in the default dataset.
* The objects must be serializable to JSON and the JSON representation of each object must be smaller than 9MB.
* @param eventName If provided, the method will attempt to charge for the event for each pushed item.
*/
static async pushData<Data extends Dictionary = Dictionary>(item: Data | Data[]): Promise<void> {
return Actor.getDefaultInstance().pushData(item);
static async pushData<Data extends Dictionary = Dictionary>(item: Data | Data[]): Promise<void>;
static async pushData<Data extends Dictionary = Dictionary>(item: Data | Data[], eventName: string): Promise<ChargeResult>;
static async pushData<Data extends Dictionary = Dictionary>(item: Data | Data[], eventName?: string): Promise<ChargeResult | void> {
if (eventName === undefined) {
return await Actor.getDefaultInstance().pushData(item);
}
return await Actor.getDefaultInstance().pushData(item, eventName);
}

/**
Expand Down Expand Up @@ -1512,6 +1568,29 @@ export class Actor<Data extends Dictionary = Dictionary> {
return Actor.getDefaultInstance().createProxyConfiguration(proxyConfigurationOptions);
}

/**
* Charge for a specified number of events - sub-operations of the Actor.
*
* @param options The name of the event to charge for and the number of events to be charged.
*/
static async charge(options: ChargeOptions): Promise<ChargeResult> {
return Actor.getDefaultInstance().charge(options);
}

/**
* Get the maximum amount of money that the Actor is allowed to charge.
*/
static getMaxTotalChargeUsd(): number {
return Actor.getDefaultInstance().getMaxTotalChargeUsd();
}

/**
* Get the number of events with given name that the Actor has charged for so far.
*/
static getChargedEventCount(eventName: string): number {
return Actor.getDefaultInstance().getChargedEventCount(eventName);
}

/**
* Returns a new {@apilink ApifyEnv} object which contains information parsed from all the Apify environment variables.
*
Expand Down
2 changes: 2 additions & 0 deletions packages/apify/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface ConfigurationOptions extends CoreConfigurationOptions {
userId?: string;
inputSecretsPrivateKeyPassphrase?: string;
inputSecretsPrivateKeyFile?: string;
maxTotalChargeUsd?: number;
metaOrigin?: typeof META_ORIGINS[keyof typeof META_ORIGINS];
}

Expand Down Expand Up @@ -164,6 +165,7 @@ export class Configuration extends CoreConfiguration {
ACTOR_TASK_ID: 'actorTaskId',
ACTOR_WEB_SERVER_PORT: 'containerPort',
ACTOR_WEB_SERVER_URL: 'containerUrl',
ACTOR_MAX_TOTAL_CHARGE_USD: 'maxTotalChargeUsd',
};

protected static override INTEGER_VARS = [...super.INTEGER_VARS, 'proxyPort', 'containerPort', 'metamorphAfterSleepMillis'];
Expand Down
Loading
Loading