Skip to content

Commit f0704d3

Browse files
authored
fix: Improve Python project detection and entrypoint resolution (#1010)
### Summary - Simplified Python project detection to discover packages purely by looking for directories with `__init__.py` files. - Derived default Actor name from the Python package name. - Added near-miss detection with actionable fix suggestions for broken Python packages. - Subpackage detection. - Added 29 unit tests covering the full detection matrix. ### Python project detection - Detect Python projects purely via package structure: directories with valid Python identifier names containing `__init__.py`. - Skip hidden directories (`.venv`) and underscore-prefixed directories (`__pycache__`, `_internal`) as they shouldn't be main entrypoints. - When `src/` is itself a package (has `__init__.py`), treat nested directories as subpackages, not separate top-level packages. ### Actor name derivation - For Python projects, the default Actor name is derived from the package name. - Entrypoint src.my_package → extracts my_package → sanitizes to my-package. - Entrypoint my_package → sanitizes to my-package. ### Runtime precedence - Node.js (`package.json`) takes precedence over Python indicators when both exist. ### Error handling - Near-miss: invalid name + `__init__.py` — suggests renaming the directory (e.g., my-package/ → my_package/) - Near-miss: valid name + .py files but no `__init__.py` — suggests adding `__init__.py` - Near-miss: invalid name + .py files but no `__init__.py` — suggests both renaming and adding `__init__.py` - Multiple packages — lists all found packages and guides user to ensure only one top-level package exists - No package and no Python files — returns Unknown (not a Python project) ### Test plan - 29 tests covering the full parametrized matrix: {CWD with dashes | underscores} × {flat | src container} × {valid | invalid pkg name} × {`__init__.py` + .py | .py only | no .py} = 24 cases, plus 5 individual cases: - JavaScript and Python coexistence (JS takes precedence) - Multiple flat packages error - Multiple packages in `src/` error - Loose .py files without package structure - No Python project at all (Unknown) - All existing local tests pass --- Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
1 parent 02ca16d commit f0704d3

5 files changed

Lines changed: 832 additions & 42 deletions

File tree

src/commands/init.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useYesNoConfirm } from '../lib/hooks/user-confirmations/useYesNoConfirm
1212
import { createPrefilledInputFileFromInputSchema } from '../lib/input_schema.js';
1313
import { error, info, success, warning } from '../lib/outputs.js';
1414
import { wrapScrapyProject } from '../lib/projects/scrapy/wrapScrapyProject.js';
15-
import { setLocalConfig, setLocalEnv, validateActorName } from '../lib/utils.js';
15+
import { sanitizeActorName, setLocalConfig, setLocalEnv, validateActorName } from '../lib/utils.js';
1616

1717
export class InitCommand extends ApifyCommand<typeof InitCommand> {
1818
static override name = 'init' as const;
@@ -57,6 +57,20 @@ export class InitCommand extends ApifyCommand<typeof InitCommand> {
5757

5858
const project = projectResult.unwrap();
5959

60+
if (project.warnings?.length) {
61+
for (const w of project.warnings) {
62+
warning({ message: w });
63+
}
64+
}
65+
66+
let defaultActorName = basename(cwd);
67+
if (project.type === ProjectLanguage.Python && project.entrypoint?.path) {
68+
const entryPath = project.entrypoint.path;
69+
// Extract the actual package name (last segment of dotted path)
70+
const packageName = entryPath.includes('.') ? entryPath.split('.').pop()! : entryPath;
71+
defaultActorName = sanitizeActorName(packageName);
72+
}
73+
6074
if (project.type === ProjectLanguage.Scrapy) {
6175
info({ message: 'The current directory looks like a Scrapy project. Using automatic project wrapping.' });
6276
this.telemetryData.actorWrapper = 'scrapy';
@@ -100,7 +114,7 @@ export class InitCommand extends ApifyCommand<typeof InitCommand> {
100114
try {
101115
const answer = await useUserInput({
102116
message: 'Actor name:',
103-
default: basename(cwd),
117+
default: defaultActorName,
104118
});
105119

106120
validateActorName(answer);

src/commands/run.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ export class RunCommand extends ApifyCommand<typeof RunCommand> {
130130
const project = projectRuntimeResult.unwrap();
131131
const { type, entrypoint: cwdEntrypoint, runtime } = project;
132132

133+
if (project.warnings?.length) {
134+
for (const w of project.warnings) {
135+
warning({ message: w });
136+
}
137+
}
138+
133139
if (type === ProjectLanguage.Unknown) {
134140
throw new Error(
135141
'Actor is of an unknown format.' +

0 commit comments

Comments
 (0)