Smart Scanning¶
The BaseScanner ABC (Abstract Base Class) provides a consistent interface and shared utilities for framework-specific adapters (e.g., django-apcore, flask-apcore).
BaseScanner Core Methods¶
| Method | Description |
|---|---|
scan(**kwargs) |
Abstract. Framework-specific implementation of the scan logic. |
get_source_name() |
Abstract. Returns the scanner's name (e.g., "django-ninja"). |
extract_docstring(func) |
Convenience wrapper around apcore.parse_docstring(). |
filter_modules(...) |
Apply regex-based include/exclude filters to module IDs. |
infer_annotations(...) |
Infer readonly, destructive, or idempotent from HTTP methods. |
deduplicate_ids(...) |
Automatically resolve duplicate module IDs by appending suffixes (_2, _3). |
Implementation Example¶
When implementing a custom scanner, you inherit from BaseScanner:
from apcore_toolkit import BaseScanner, ScannedModule
class MyScanner(BaseScanner):
def scan(self, **kwargs) -> list[ScannedModule]:
# 1. Discover endpoints from your framework
endpoints = self.get_my_endpoints()
# 2. Convert endpoints to ScannedModules
modules = []
for e in endpoints:
desc, docs, params = self.extract_docstring(e.view_func)
modules.append(ScannedModule(
module_id=e.name,
description=desc,
target=f"{e.module}:{e.func_name}",
annotations=self.infer_annotations_from_method(e.method),
# ... other metadata
))
# 3. Apply shared refining utilities
modules = self.filter_modules(modules, include=kwargs.get("include"))
modules = self.deduplicate_ids(modules)
return modules
def get_source_name(self) -> str:
return "my-framework-scanner"
import { BaseScanner, ScannedModule } from "apcore-toolkit";
class MyScanner extends BaseScanner {
scan(options?: { include?: RegExp }): ScannedModule[] {
// 1. Discover endpoints from your framework
const endpoints = this.getMyEndpoints();
// 2. Convert endpoints to ScannedModules
let modules = endpoints.map((e) =>
new ScannedModule({
moduleId: e.name,
description: this.extractDocstring(e.viewFunc).description,
target: `${e.module}:${e.funcName}`,
annotations: this.inferAnnotationsFromMethod(e.method),
// ... other metadata
})
);
// 3. Apply shared refining utilities
modules = this.filterModules(modules, { include: options?.include });
modules = this.deduplicateIds(modules);
return modules;
}
getSourceName(): string {
return "my-framework-scanner";
}
}
Module Deduplication¶
Scanners often encounter naming collisions (e.g., GET /users and POST /users both being called users). deduplicate_ids() handles this by:
1. Tracking encountered IDs.
2. Appending _2, _3 etc. to duplicates.
3. Injecting a warning into the ScannedModule.warnings list for human audit.
Behavioral Inference¶
infer_annotations_from_method() provides a sensible default for mapping HTTP verbs to apcore's ModuleAnnotations:
- GET $\rightarrow$ readonly=True
- DELETE $\rightarrow$ destructive=True
- PUT $\rightarrow$ idempotent=True
- Others $\rightarrow$ Default (all False)