Platform¶
Platform abstraction: auto-detects Fabric, Databricks, or vanilla Spark and exposes a single shared interface for config reading, Delta writes, and filesystem operations regardless of which platform is actually running.
- class scamander.processor.platform.Platform(*args, **kwargs)¶
Bases:
ABCAbstract base class that defines the shared interface for all supported Spark platforms.
On instantiation, the platform is auto-detected from environment variables and a concrete subclass (
Fabric,Databricks, orSpark) is returned transparently via__new__. Shared logic — config reading, Delta writes, filename parsing, and datatype mapping — lives here so platform-specific subclasses stay lean.- Subclasses must implement:
validate_compatibility– assert that the environment meets platform requirements.check_file_exists– use the platform’s native filesystem API.resolve_filemask– expand a glob/wildcard path into concrete file paths._list_dir_names– list base names of entries directly inside a folder, used byresolve_filemask_recursive.move_file– move/rename a file via the platform’s filesystem API.get_notebook_name– return the name of the currently running notebook.get_notebook_url– return a browser-accessible URL to the notebook.create_empty_table_from_metadata– DDL-create a Delta table from a metadata dict.
- Parameters:
logging_catalog (str) – Unity Catalog / catalog name that contains the logging schema. Defaults to
"main".logging_schema (str) – Schema name for the logging Delta table. Defaults to
"mdif".logging_table (str) – Table name used to persist machine-readable log records. Defaults to
"logging".retention_days (int) – Number of days of machine log history to retain. Older rows are deleted on every
Platforminitialisation. Defaults to90.log_level (str) – Minimum level a record must meet to be written to the machine log table (e.g.
"ERROR"). Independent of what’s printed to the console. Defaults to"INFO".log_timezone (str) – IANA timezone name used to stamp machine log timestamps. Defaults to
"Europe/Amsterdam".log_flush_interval_seconds (int) – How often the machine log’s background timer auto-flushes buffered rows. Set to
0to disable the timer and rely only on error-triggered/explicit/atexitflushes. Defaults to300(5 minutes).
Example:
from scamander import Platform # Auto-detects Fabric / Databricks / vanilla Spark from env vars. platform = Platform() platform.log.info("Pipeline started")
- abstractmethod check_file_exists(file_name: str, raise_exceptions=False) bool¶
Check whether a file or directory exists on the platform’s filesystem.
- Parameters:
file_name – Absolute path to the file or directory.
raise_exceptions – When
True, raiseFileNotFoundErrorinstead of returningFalseif the path is missing. Defaults toFalse.
- Returns:
Trueif the path exists,Falseotherwise.- Return type:
bool
- Raises:
FileNotFoundError – Only when
raise_exceptions=Trueand the path does not exist.
- check_file_set_exists(config: list[dict], file_key: str, raise_exceptions: bool = False) bool¶
Check that every file path listed in a config exists on the platform filesystem.
Iterates each record in
config, reads the file path fromrecord[file_key], and callscheck_file_exists()for each. All records are checked before raising so the caller gets a complete picture of what is missing.- Parameters:
config – Parsed configuration — a list of dicts, as returned by
read_config()for CSV, JSON array, or YAML sequence sources.file_key – The dict key whose value holds the file path to check.
raise_exceptions – When
True, raisesFileNotFoundErrorafter checking all records if any files are missing. Defaults toFalse.
- Returns:
Trueif every file exists,Falseif one or more are missing.- Return type:
bool
- Raises:
FileNotFoundError – Only when
raise_exceptions=Trueand at least one file is missing. The message lists all missing paths.KeyError – If a record in
configdoes not containfile_key.
- abstractmethod create_empty_table_from_metadata(metadata: dict)¶
DDL-create an empty Delta table from a metadata definition.
Column definitions are generated via
map_datatype(). Whenmetadata["track_history"]isTrue, the five SCD Type-2 columns (HashSleutel,HashWaarde,GeldigVan,GeldigTot,IsActief) are appended automatically. Platforms that support Delta constraints (e.g. Databricks Unity Catalog) may also emit PRIMARY KEY / FOREIGN KEY clauses from the metadata.- Parameters:
metadata – Metadata dictionary with
"table","columns", and"track_history"keys — seemap_datatype()for the shape of each entry in"columns".
- static extract_datetime_from_filename(filename: str) datetime¶
Extracts a
datetimefrom a filename by searching for a 12-digit timestamp in the formatYYYYMMDDHHMI(year, month, day, hour, minute).This is useful for sorting a list of files resolved by
resolve_filemaskin chronological order so that the most-recent file can be selected or processed last.- Parameters:
filename (str) – The filename (or full path) to inspect.
- Returns:
- The extracted datetime if a valid 12-digit sequence is found;
datetime.maxotherwise, so the file naturally sorts to the end of any ordered list.
- Return type:
datetime
Example:
>>> Platform.extract_datetime_from_filename("export_202503151430_v1.csv") datetime(2025, 3, 15, 14, 30)
- flush_logs() None¶
Force any buffered machine-log rows to be written to the Delta logging table.
MachineLoggerbuffers low-severity log rows in memory for performance and only flushes automatically on error, at process exit, or via the installedsys.excepthook. Call this explicitly (e.g. in a notebook’s final cell) to guarantee the full log trail is persisted before the run ends.
- abstractmethod get_notebook_name() str¶
Return the name of the currently running notebook.
Used to label human/machine log records with their origin. Returns a platform-appropriate placeholder (e.g.
"local") when no notebook context is available.- Returns:
The notebook’s display name.
- Return type:
str
- abstractmethod get_notebook_url() str¶
Return a browser-accessible URL to the currently running notebook.
Used to link machine log records back to their source notebook. Returns a platform-appropriate placeholder (e.g.
"local") when no notebook context is available.- Returns:
The notebook’s URL.
- Return type:
str
- get_secret(secret_name: str, vault: str | None = None) str¶
Retrieve a secret via the configured
SecretProvider.Delegates to the
SecretProviderpassed during construction. When no provider was supplied, the call raisesNotImplementedErrorwith instructions on how to configure one.- Parameters:
secret_name – Name or path of the secret.
vault – Provider-specific vault / scope / mount identifier. See the concrete
SecretProviderfor details.
- Returns:
The secret value as a plain string.
- Raises:
NotImplementedError – When no
SecretProviderwas configured.
- static map_datatype(col: dict) str¶
Converts a metadata column definition into a Delta Lake column DDL fragment.
Accepts a wide range of SQL Server, PostgreSQL, and generic SQL type names and normalises them to their Delta / Spark SQL equivalents. The mapping is intentionally broad so that metadata config files can use source-system type names directly.
- Parameters:
col (dict) –
A column metadata dictionary with at minimum: -
"target"(str): The column name in the Delta table. -"type"(str): The source data type string. -"nullable"(bool, optional): Whether the column allows NULLs.Defaults to
True."precision"(int, optional): Decimal precision (DECIMAL only)."scale"(int, optional): Decimal scale (DECIMAL only).
- Returns:
A SQL DDL column fragment, e.g.
"amount DECIMAL(18,2) NOT NULL".- Return type:
str
- abstractmethod move_file(source: str, destination: str, overwrite=True, create_missing_dirs: bool = False) bool¶
Move/rename a file via the platform’s native filesystem API.
- Parameters:
source – Absolute path to the source file.
destination – Absolute destination path.
overwrite – When
True(default), replace an existing file atdestination.create_missing_dirs – When
True, create the destination directory if it does not exist. Defaults toFalse.
- Returns:
Trueon success,Falseon any failure.- Return type:
bool
- read_config(config_path: str)¶
Reads a configuration file from the platform’s filesystem and returns its contents.
- Supported formats:
.json– read directly via the Hadoop filesystem and parsed withjson.loads; returned as adictorlist..csv– parsed via Spark with a header row; returned as a list of dicts (one dict per row, via pandasto_dict(orient="records"))..yaml/.yml– read directly via the Hadoop filesystem and parsed withyaml.safe_load; returned as adictorlist.
- Parameters:
config_path (str) – Absolute path (including filename and extension) to the configuration file on the platform filesystem (e.g. DBFS, OneLake).
- Returns:
Parsed configuration content.
- Return type:
dict | list
- Raises:
FileNotFoundError – If
check_file_existsreports the file is missing.ValueError – If the file extension is not one of the supported formats.
- abstractmethod resolve_filemask(mask_path: str, case_sensitive: bool = False, full_path: bool = False)¶
Expand a wildcard path into the names of matching files.
- Parameters:
mask_path – Absolute path containing a
*wildcard, e.g."/data/exports/sales_*.csv".case_sensitive – When
True, match file names exactly as cased. Defaults toFalse(case-insensitive matching, consistent across platforms/OSes).full_path – When
True, return each match prefixed with the folder part ofmask_path. Defaults toFalse(base names only).
- Returns:
Sorted list of matching file base names, or full paths when
full_path=True. Empty if nothing matches or the parent directory is missing.- Return type:
list[str]
- resolve_filemask_recursive(mask_path: str, case_sensitive: bool = False) list[str]¶
Expand a wildcard path into full paths of matching files, walking wildcarded folders too.
Unlike
resolve_filemask(), which only expands a wildcard in the final path segment, this method also expands*wildcards in intermediate folder segments — e.g."/path/to/*csv*/test_*.csv"first resolves*csv*against actual subfolder names, then matchestest_*.csvinside each matching subfolder. Each/-separated segment from the first one containing a wildcard onward is resolved in turn via_list_dir_names(), narrowing the set of candidate paths at every step.Because the same file name can legitimately exist under multiple matched subfolders, this returns full paths rather than base names.
- Parameters:
mask_path – Absolute path containing one or more
*wildcards, which may appear in any path segment, e.g."/data/exports/*csv*/sales_*.csv".case_sensitive – When
True, match names exactly as cased. Defaults toFalse(case-insensitive matching, consistent across platforms/OSes).
- Returns:
Sorted list of full matching file paths. Empty if no candidate matches at any level.
- Return type:
list[str]
- Raises:
ValueError – If
mask_pathcontains no*wildcard.
Example:
>>> platform.resolve_filemask_recursive( ... "/data/exports/*csv*/sales_*.csv" ... ) ['/data/exports/2024_csv_final/sales_202403150900.csv']
- rows_to_table(rows: list[dict], table_name: str, mode: str = 'overwrite', merge_keys: list[str] | None = None) bool¶
Convert a list of flat dicts to a DataFrame and write to a Delta table.
All values are coerced to
str(orNone) so the resulting schema is uniformlyStringType. An_ingestion_timestampaudit column is added automatically before writing.- Parameters:
rows – Flat dicts (values must be scalars or
None).table_name – Fully qualified Delta table name (
schema.table).mode –
"overwrite","append", or"upsert". When"upsert"is passed,merge_keysmust also be provided.merge_keys – Key columns for the upsert merge condition. Ignored unless
mode="upsert".
- Returns:
True on success, False on failure.
- property secret_provider: SecretProvider¶
The active
SecretProviderinstance.
- upsert_table(df: DataFrame, full_table_name: str, merge_keys: list) bool¶
Upsert a DataFrame into a Delta table using a simple match-all merge.
Matched rows (by
merge_keys) are updated column-by-column; unmatched rows are inserted. The source DataFrame doesn’t need to carry every column the target table has — this matters for sources like Microsoft Graph delta queries, which only return the properties that actually changed. Columns the target has but the source lacks are left untouched on update, and filled withNULLon insert. If the table does not yet exist it is created via a plain overwrite.This is a simple upsert, not SCD Type-2. Use
scamander.transformations.scd.apply_scd2_mergewhen history tracking is required.- Parameters:
df – Source DataFrame containing the latest state.
full_table_name – Fully qualified Delta table name (
schema.table).merge_keys – Column names used to match source rows to target rows.
- Returns:
True on success, False on failure.
- Raises:
ImportError – When
delta-sparkis not available.
- abstractmethod validate_compatibility()¶
Assert that the current environment satisfies this platform’s requirements.
Called once, at the start of
__init__(), before any logging or secret-provider setup happens. Concrete subclasses should raise if a required runtime dependency (e.g.dbutils,notebookutils) is missing so failures surface immediately at construction time rather than on first use.- Raises:
RuntimeError – If the environment does not meet this platform’s requirements. The exact condition is subclass-specific.
- validate_dataframe(df: DataFrame, suite_config: QualitySuiteConfig) QualityResult¶
Validate a DataFrame against a quality suite and log the result.
Delegates to
QualityCheckerusing this platform’s SparkSession and logger.- Parameters:
df – DataFrame to validate.
suite_config – Quality suite definition.
- Returns:
QualityResultwith per-expectation outcomes. Whensuite_config.raise_on_failureisTrueaDataQualityErroris raised instead of returning on failure.
- write_to_table(df: DataFrame, full_table_name: str, mode: str = 'overwrite', partition_by: list[str] | None = None, merge_schema: bool = False) bool¶
Writes a Spark DataFrame to a Delta table, creating the schema if it does not yet exist.
The table name must be fully qualified as
schema.table(two-part name). If the schema / database does not exist it is created automatically withCREATE DATABASE IF NOT EXISTSbefore the write is attempted.- Parameters:
df – Spark DataFrame to write.
full_table_name – Full table name in the form
schema.table.mode – Write mode:
"overwrite","append","error", or"ignore". Defaults to"overwrite".partition_by – Column names to physically partition the Delta table by. Defaults to
None(no partitioning).merge_schema – When
True, sets the DeltamergeSchemawrite option so new or changed columns indfare merged into the target table’s schema instead of failing the write. Defaults toFalse.
- Returns:
Trueif successful,Falseotherwise.- Return type:
bool
Example:
platform.write_to_table(df, "silver.orders", mode="append") platform.write_to_table( df, "gold.revenue_by_region", mode="append", partition_by=["year", "month"], merge_schema=True, )
- class scamander.processor.platform.SupportedPlatforms(*values)¶
Bases:
EnumEnumeration of the Spark platforms
Platformcan auto-detect.- unknown¶
No Fabric or Databricks environment variables were found;
Platformfalls back to the vanilla-Spark implementation.
- fabric¶
Running inside a Microsoft Fabric notebook/pipeline.
- databricks¶
Running inside an Azure/AWS Databricks cluster.