flexicon.sync package

Submodules

flexicon.sync.conflict_resolvers module

Conflict Resolvers - Strategies for resolving sync conflicts

This module provides conflict resolution policies for sync operations.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.conflict_resolvers.ConflictResolver[source]

Bases: ABC

Base class for conflict resolution strategies.

When an object exists in both source and target but differs, a conflict resolver determines which version to keep.

Custom resolvers can be implemented by subclassing and implementing resolve().

abstract resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Resolve conflict between source and target objects.

Parameters:
  • source_obj – Source version of object

  • target_obj – Target version of object

  • source_project – Source FLExProject instance

  • target_project – Target FLExProject instance

Returns:

The object to keep (either source_obj or target_obj)

class flexicon.sync.conflict_resolvers.SourceWinsResolver[source]

Bases: ConflictResolver

Resolve conflicts by always choosing the source version.

This is the most common strategy for one-way sync operations where the source is considered authoritative.

Use this when: - Source is the “master” or “stable” project - Syncing updates from development to production - You want to overwrite target with source data

Example

>>> resolver = SourceWinsResolver()
>>> sync.sync(
...     object_type="Allomorph",
...     conflict_resolver=resolver
... )
resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Always choose source version.

Parameters:
  • source_obj – Source object (will be chosen)

  • target_obj – Target object (will be overwritten)

  • source_project – Source project

  • target_project – Target project

Returns:

source_obj

class flexicon.sync.conflict_resolvers.TargetWinsResolver[source]

Bases: ConflictResolver

Resolve conflicts by always keeping the target version.

This strategy preserves the target project’s data when conflicts occur. Useful for selective merging or when target has priority.

Use this when: - Target contains more recent data - You want to preserve local changes - Syncing metadata but keeping target content

Example

>>> resolver = TargetWinsResolver()
>>> sync.sync(
...     object_type="Allomorph",
...     conflict_resolver=resolver
... )
resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Always keep target version.

Parameters:
  • source_obj – Source object (will be ignored)

  • target_obj – Target object (will be kept)

  • source_project – Source project

  • target_project – Target project

Returns:

target_obj

class flexicon.sync.conflict_resolvers.NewestWinsResolver[source]

Bases: ConflictResolver

Resolve conflicts by choosing the most recently modified version.

This strategy compares modification timestamps and chooses the newer version. Requires objects to have DateModified or similar timestamp properties.

Use this when: - Both source and target are actively maintained - You want automatic conflict resolution based on recency - Bidirectional sync scenarios

Example

>>> resolver = NewestWinsResolver()
>>> sync.sync(
...     object_type="LexEntry",
...     conflict_resolver=resolver
... )

Note

Full implementation in Phase 2. Phase 1 placeholder.

resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Choose version with most recent modification date.

Parameters:
  • source_obj – Source object

  • target_obj – Target object

  • source_project – Source project

  • target_project – Target project

Returns:

Newer object (source or target)

class flexicon.sync.conflict_resolvers.ManualResolver[source]

Bases: ConflictResolver

Resolve conflicts by prompting the user.

This strategy asks the user to manually choose which version to keep for each conflict. Useful for interactive sync operations.

Use this when: - Conflicts are rare and important - You want human review of changes - Data is critical and needs careful merging

Example

>>> resolver = ManualResolver()
>>> sync.sync(
...     object_type="LexEntry",
...     conflict_resolver=resolver
... )

Note

Full implementation in Phase 2. Phase 1 placeholder.

resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Prompt user to choose version.

Parameters:
  • source_obj – Source object

  • target_obj – Target object

  • source_project – Source project

  • target_project – Target project

Returns:

User-chosen object

Raises:

NotImplementedError – Phase 1 - full implementation in Phase 2

class flexicon.sync.conflict_resolvers.FieldMergeResolver(source_fields: List[str], target_fields: List[str])[source]

Bases: ConflictResolver

Resolve conflicts by merging specific fields.

This strategy allows fine-grained control, merging some fields from source and keeping others from target.

Example

>>> # Keep target's gloss, but update form from source
>>> resolver = FieldMergeResolver(
...     source_fields=["form", "morph_type"],
...     target_fields=["gloss", "definition"]
... )

Note

Full implementation in Phase 4. Phase 1 placeholder.

resolve(source_obj: Any, target_obj: Any, source_project: Any, target_project: Any) Any[source]

Merge fields from both versions.

Note

Phase 1 placeholder - full implementation in Phase 4.

flexicon.sync.dependency_graph module

Dependency Graph - Phase 3.1

Models object dependencies as a directed acyclic graph (DAG) and provides topological sorting for correct import order.

Author: FlexTools Development Team Date: 2025-11-27

class flexicon.sync.dependency_graph.DependencyType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: Enum

Types of dependencies between objects.

OWNERSHIP = 'ownership'
REFERENCE = 'reference'
CROSS_REFERENCE = 'cross_ref'
class flexicon.sync.dependency_graph.DependencyNode(guid: str, object_type: str, obj: ~typing.Any = None, dependencies: ~typing.Set[str] = <factory>, dependents: ~typing.Set[str] = <factory>, dependency_types: ~typing.Dict[str, ~flexicon.sync.dependency_graph.DependencyType] = <factory>, visited: bool = False, in_progress: bool = False)[source]

Bases: object

Node in dependency graph representing a FLEx object.

guid: str
object_type: str
obj: Any = None
dependencies: Set[str]
dependents: Set[str]
dependency_types: Dict[str, DependencyType]
visited: bool = False
in_progress: bool = False
class flexicon.sync.dependency_graph.DependencyGraph[source]

Bases: object

Directed acyclic graph (DAG) of object dependencies.

Manages dependencies between FLEx objects and provides topological sorting to determine correct import order.

add_object(guid: str, object_type: str, obj: Any = None)[source]

Add object to graph.

Parameters:
  • guid – Object GUID

  • object_type – Type of object (e.g., “LexEntry”, “LexSense”)

  • obj – Actual FLEx object (optional)

add_dependency(from_guid: str, to_guid: str, dep_type: DependencyType = DependencyType.REFERENCE)[source]

Add dependency: from_guid depends on to_guid.

This means to_guid must be imported before from_guid.

Parameters:
  • from_guid – Object that depends on another

  • to_guid – Object that is depended upon

  • dep_type – Type of dependency

remove_dependency(from_guid: str, to_guid: str)[source]

Remove dependency between objects.

Used for breaking cycles or handling optional dependencies.

Parameters:
  • from_guid – Object that depends on another

  • to_guid – Object that is depended upon

get_import_order() List[Tuple[str, str]][source]

Get topologically sorted import order.

Returns list of (guid, object_type) tuples in order they should be imported (dependencies first).

Returns:

List of (guid, object_type) tuples

Raises:

CircularDependencyError – If circular dependency detected

detect_cycles() List[List[str]][source]

Detect circular dependencies using depth-first search.

Returns:

List of cycles found, each cycle is a list of GUIDs

get_roots() List[str][source]

Get root nodes (objects with no dependencies).

Returns:

List of GUIDs with no dependencies

get_leaves() List[str][source]

Get leaf nodes (objects that nothing depends on).

Returns:

List of GUIDs with no dependents

get_dependencies(guid: str, recursive: bool = False) List[str][source]

Get all dependencies for an object.

Parameters:
  • guid – Object GUID

  • recursive – If True, get transitive dependencies

Returns:

List of GUIDs this object depends on

get_dependents(guid: str, recursive: bool = False) List[str][source]

Get all objects that depend on this object.

Parameters:
  • guid – Object GUID

  • recursive – If True, get transitive dependents

Returns:

List of GUIDs that depend on this object

get_subgraph(guids: List[str]) DependencyGraph[source]

Extract subgraph containing only specified objects and their dependencies.

Parameters:

guids – List of GUIDs to include

Returns:

New DependencyGraph with only specified objects

summary() str[source]

Get summary of dependency graph.

Returns:

Human-readable summary string

exception flexicon.sync.dependency_graph.CircularDependencyError(cycle: List[str])[source]

Bases: Exception

Raised when circular dependency is detected.

flexicon.sync.dependency_resolver module

Dependency Resolver - Phase 3.2

Discovers and resolves object dependencies by analyzing FLEx object relationships.

Author: FlexTools Development Team Date: 2025-11-27

class flexicon.sync.dependency_resolver.DependencyConfig(include_owned: bool = True, resolve_references: bool = True, include_referring: bool = False, max_owned_depth: int = 10, max_reference_depth: int = 2, owned_types: List[str] | None = None, reference_types: List[str] | None = None, skip_existing: bool = True, create_stub_parents: bool = False, validate_all: bool = True, allow_cycles: bool = False)[source]

Bases: object

Configuration for dependency resolution.

include_owned: bool = True
resolve_references: bool = True
include_referring: bool = False
max_owned_depth: int = 10
max_reference_depth: int = 2
owned_types: List[str] | None = None
reference_types: List[str] | None = None
skip_existing: bool = True
create_stub_parents: bool = False
validate_all: bool = True
allow_cycles: bool = False
class flexicon.sync.dependency_resolver.DependencyResolver(source_project: Any, target_project: Any)[source]

Bases: object

Analyzes FLEx objects and discovers all dependencies.

Builds dependency graph by examining object ownership, references, and cross-references.

resolve_dependencies(obj: Any, object_type: str, config: DependencyConfig | None = None) DependencyGraph[source]

Build dependency graph for object and its dependencies.

Parameters:
  • obj – FLEx object to analyze

  • object_type – Type of object

  • config – Dependency resolution configuration

Returns:

DependencyGraph with all dependencies

get_owned_objects(obj: Any, object_type: str) List[Tuple[Any, str]][source]

Get all objects owned by this object.

Parameters:
  • obj – FLEx object

  • object_type – Type of object

Returns:

List of (owned_object, object_type) tuples

get_referenced_objects(obj: Any, object_type: str) List[Tuple[Any, str]][source]

Get all objects referenced by this object.

Parameters:
  • obj – FLEx object

  • object_type – Type of object

Returns:

List of (referenced_object, object_type) tuples

clear_cache()[source]

Clear existence cache.

flexicon.sync.diff module

DiffEngine - Object comparison and diff generation

This module provides diff functionality for comparing FLEx objects between projects.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.diff.ChangeType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: Enum

Type of change detected

NEW = 'new'
MODIFIED = 'modified'
DELETED = 'deleted'
CONFLICT = 'conflict'
UNCHANGED = 'unchanged'
class flexicon.sync.diff.Change(change_type: ~flexicon.sync.diff.ChangeType, source_guid: str | None, target_guid: str | None, object_type: str, description: str, details: ~typing.Dict[str, ~typing.Any] = <factory>)[source]

Bases: object

Represents a single detected change.

Variables:
  • change_type (flexicon.sync.diff.ChangeType) – Type of change

  • source_guid (str | None) – GUID of source object (None if deleted)

  • target_guid (str | None) – GUID of target object (None if new)

  • object_type (str) – Type of object

  • description (str) – Human-readable description

  • details (Dict[str, Any]) – Additional details about the change

change_type: ChangeType
source_guid: str | None
target_guid: str | None
object_type: str
description: str
details: Dict[str, Any]
property guid: str

Primary GUID for this change.

class flexicon.sync.diff.DiffResult(object_type: str)[source]

Bases: object

Results from a diff operation.

Contains all changes detected, organized by type, with methods for analysis and reporting.

Usage:
>>> diff = sync.compare(object_type="Allomorph")
>>>
>>> print(f"Total changes: {diff.total}")
>>> print(f"New: {diff.num_new}")
>>> print(f"Modified: {diff.num_modified}")
>>>
>>> for change in diff.new_changes:
...     print(f"  {change.description}")
>>>
>>> print(diff.summary())
add_change(change: Change) None[source]

Add a change to the result.

property new_changes: List[Change]

All NEW changes.

property modified_changes: List[Change]

All MODIFIED changes.

property deleted_changes: List[Change]

All DELETED changes.

property conflict_changes: List[Change]

All CONFLICT changes.

property unchanged_changes: List[Change]

All UNCHANGED changes.

property num_new: int

Count of NEW changes.

property num_modified: int

Count of MODIFIED changes.

property num_deleted: int

Count of DELETED changes.

property num_conflicts: int

Count of CONFLICT changes.

property num_unchanged: int

Count of UNCHANGED changes.

property total: int

Total number of changes (excluding unchanged).

property has_changes: bool

Whether any changes were detected.

property has_conflicts: bool

Whether any conflicts were detected.

summary() str[source]

Generate a text summary of changes.

Returns:

Human-readable summary string

Example

>>> print(diff.summary())
DIFF SUMMARY: Allomorphs
----------------------------------------
15 NEW (in source, not in target)
3 MODIFIED (different in source)
2 DELETED (in target, not in source)
0 CONFLICTS
to_report(format: str = 'console', verbose: bool = False) str[source]

Generate a detailed report.

Parameters:
  • format – Report format (“console”, “markdown”, “html”)

  • verbose – Include unchanged items

Returns:

Formatted report string

Example

>>> print(diff.to_report(format="console", verbose=False))
export(filename: str) None[source]

Export diff report to file.

Parameters:

filename – Output filename (.txt, .md, .html, .csv)

Example

>>> diff.export("allomorph_diff.md")
>>> diff.export("allomorph_diff.html")
class flexicon.sync.diff.DiffEngine[source]

Bases: object

Engine for comparing FLEx objects between projects.

The DiffEngine compares objects using a match strategy and detects all changes (new, modified, deleted, conflicts).

Usage:
>>> from flexlibs2.sync import DiffEngine, GuidMatchStrategy
>>>
>>> engine = DiffEngine()
>>> result = engine.compare(
...     source_objects=source_project.Allomorph,
...     target_objects=target_project.Allomorph,
...     source_project=source_project,
...     target_project=target_project,
...     match_strategy=GuidMatchStrategy()
... )
compare(source_objects: Any, target_objects: Any, source_project: Any, target_project: Any, match_strategy: MatchStrategy, filter_fn: Callable | None = None, progress_callback: Callable[[str], None] | None = None) DiffResult[source]

Compare objects between source and target.

Parameters:
  • source_objects – Source operations class instance

  • target_objects – Target operations class instance

  • source_project – Source FLExProject

  • target_project – Target FLExProject

  • match_strategy – Strategy for matching objects

  • filter_fn – Optional filter function

  • progress_callback – Optional progress callback

Returns:

DiffResult with all changes

flexicon.sync.engine module

SyncEngine - Core orchestrator for multi-database synchronization

This module provides the main SyncEngine class that coordinates all sync operations.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.engine.SyncMode(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: Enum

Sync operation mode

READONLY = 'readonly'
WRITE = 'write'
class flexicon.sync.engine.SyncEngine(source_project: Any, target_project: Any, mode: SyncMode | None = None)[source]

Bases: object

Main orchestrator for database synchronization operations.

The SyncEngine coordinates comparison and synchronization of FLEx objects between two projects. It respects the writeEnabled flag from FlexTools to determine whether to show diffs (readonly) or execute syncs (write).

Usage:
>>> from flexlibs2.sync import SyncEngine, GuidMatchStrategy
>>>
>>> # Create engine
>>> sync = SyncEngine(
...     source_project=source_project,
...     target_project=target_project
... )
>>>
>>> # Readonly mode - show diff only
>>> diff = sync.compare(
...     object_type="Allomorph",
...     match_strategy=GuidMatchStrategy()
... )
>>> print(diff.summary())
>>>
>>> # Write mode - execute sync
>>> result = sync.sync(
...     object_type="Allomorph",
...     match_strategy=GuidMatchStrategy(),
...     conflict_resolver="source_wins"
... )
register_strategy(name: str, strategy: MatchStrategy) None[source]

Register a custom match strategy.

Parameters:
  • name – Strategy name for later reference

  • strategy – MatchStrategy instance

Example

>>> class CustomMatcher(MatchStrategy):
...     def match(self, source_obj, target_candidates, source_project, target_project):
...         # Custom matching logic
...         pass
>>>
>>> sync.register_strategy("custom", CustomMatcher())
>>> diff = sync.compare(object_type="Entry", match_strategy="custom")
register_resolver(name: str, resolver: ConflictResolver) None[source]

Register a custom conflict resolver.

Parameters:
  • name – Resolver name for later reference

  • resolver – ConflictResolver instance

compare(object_type: str, match_strategy: str | MatchStrategy | None = None, filter_fn: Callable | None = None, progress_callback: Callable[[str], None] | None = None) DiffResult[source]

Compare objects between source and target projects (readonly).

This method performs a diff-only operation, showing what would change if a sync were executed. It never modifies the target database.

Parameters:
  • object_type – Type of objects to compare (e.g., “Allomorph”, “LexEntry”)

  • match_strategy – Strategy for matching objects (default: GUID-based)

  • filter_fn – Optional filter function to include only specific objects

  • progress_callback – Optional callback for progress updates

Returns:

DiffResult containing all changes detected

Example

>>> diff = sync.compare(
...     object_type="Allomorph",
...     match_strategy=GuidMatchStrategy()
... )
>>>
>>> print(f"New: {diff.num_new}")
>>> print(f"Modified: {diff.num_modified}")
>>> print(f"Deleted: {diff.num_deleted}")
sync(object_type: str, match_strategy: str | MatchStrategy | None = None, conflict_resolver: str | ConflictResolver = 'source_wins', include_dependencies: bool = False, filter_fn: Callable | None = None, progress_callback: Callable[[str], None] | None = None, dry_run: bool = False) SyncResult[source]

Synchronize objects from source to target project (write mode).

This method executes sync operations, creating/updating/deleting objects in the target project. Requires target project opened with writeEnabled=True.

Parameters:
  • object_type – Type of objects to sync

  • match_strategy – Strategy for matching objects (default: GUID-based)

  • conflict_resolver – How to resolve conflicts (default: “source_wins”)

  • include_dependencies – Auto-create missing dependencies (default: False)

  • filter_fn – Optional filter function

  • progress_callback – Optional callback for progress updates

  • dry_run – If True, show what would happen without making changes

Returns:

SyncResult with statistics and details

Raises:

RuntimeError – If in readonly mode or target not writable

Example

>>> result = sync.sync(
...     object_type="Allomorph",
...     match_strategy=GuidMatchStrategy(),
...     conflict_resolver="source_wins"
... )
>>>
>>> print(f"Created: {result.num_created}")
>>> print(f"Updated: {result.num_updated}")
>>> print(f"Deleted: {result.num_deleted}")
compare_possibility_list(list_type: str, match_strategy: str | MatchStrategy | None = None) DiffResult[source]

Compare possibility lists (POS, MorphTypes, etc.) between projects.

Parameters:
  • list_type – Type of possibility list (e.g., “PartsOfSpeech”)

  • match_strategy – Strategy for matching (default: GUID-based)

Returns:

DiffResult for the possibility list

Note

This is a specialized comparison for hierarchical possibility lists. Full implementation in Phase 4.

validate_dependencies(object_type: str, auto_create_missing: bool = False) DependencyValidation[source]

Validate that all dependencies exist in target project.

Parameters:
  • object_type – Type of objects to validate

  • auto_create_missing – If True, auto-create missing dependencies

Returns:

DependencyValidation result

Note

Full implementation in Phase 3 (Dependency Safety).

create_snapshot(project: Any) Snapshot[source]

Create a snapshot for rollback.

Parameters:

project – Project to snapshot

Returns:

Snapshot instance

Note

Full implementation in Phase 4 (Undo/Rollback).

class flexicon.sync.engine.SyncResult(object_type: str)[source]

Bases: object

Results from a sync operation.

Tracks all changes made during a sync, including creates, updates, deletes, and any errors encountered.

Usage:
>>> result = sync.sync(object_type="Allomorph")
>>> print(f"Created: {result.num_created}")
>>> print(f"Updated: {result.num_updated}")
>>> result.export_log("sync_log.txt")
property total: int

Total number of operations performed.

property success: bool

Whether sync completed without errors.

add_change(change: SyncChange) None[source]

Record a change made during sync.

add_error(error: SyncError) None[source]

Record an error during sync.

skip() None[source]

Increment skipped counter.

summary() str[source]

Generate text summary of sync results.

export_log(filename: str) None[source]

Export sync log to file.

Parameters:

filename – Output filename

Example

>>> result.export_log("allomorph_sync.log")
flexicon.sync.engine.import_datetime()[source]

Helper to get current datetime.

class flexicon.sync.engine.DependencyValidation[source]

Bases: object

Results from dependency validation.

Phase 3 implementation placeholder.

class flexicon.sync.engine.Snapshot[source]

Bases: object

Database snapshot for rollback.

Phase 4 implementation placeholder.

flexicon.sync.export module

Report Exporter - Generate diff reports in various formats

This module provides export functionality for diff results.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.export.ReportExporter[source]

Bases: object

Export diff reports in various formats.

Phase 1: Console and Markdown Phase 4: HTML and CSV

Usage:
>>> exporter = ReportExporter()
>>> exporter.export_console(diff, verbose=True)
>>> exporter.export_markdown(diff, filename="report.md")
export_console(diff: DiffResult, verbose: bool = False) str[source]

Generate console-formatted report.

Parameters:
  • diff – DiffResult to export

  • verbose – Include unchanged items

Returns:

Formatted console text

export_markdown(diff: DiffResult, filename: str | None = None, verbose: bool = False) str[source]

Generate markdown-formatted report.

Parameters:
  • diff – DiffResult to export

  • filename – Optional file to write to

  • verbose – Include unchanged items

Returns:

Formatted markdown text

export_html(diff: DiffResult, filename: str, verbose: bool = False) None[source]

Generate HTML report with interactive features.

Note

Full implementation in Phase 4.

Parameters:
  • diff – DiffResult to export

  • filename – Output HTML file

  • verbose – Include unchanged items

Raises:

NotImplementedError – Phase 1 - implemented in Phase 4

export_csv(diff: DiffResult, filename: str, verbose: bool = False) None[source]

Generate CSV report for spreadsheet analysis.

Note

Full implementation in Phase 4.

Parameters:
  • diff – DiffResult to export

  • filename – Output CSV file

  • verbose – Include unchanged items

Raises:

NotImplementedError – Phase 1 - implemented in Phase 4

flexicon.sync.hierarchical_importer module

Hierarchical Importer - Phase 3.3

Import FLEx objects with full dependency resolution and hierarchical cascade.

Author: FlexTools Development Team Date: 2025-11-27

class flexicon.sync.hierarchical_importer.HierarchicalImporter(source_project: Any, target_project: Any)[source]

Bases: object

Import FLEx objects with dependency resolution.

Automatically discovers and imports all required dependencies, including owned objects (senses, examples) and referenced objects (POS, semantic domains).

import_with_dependencies(object_type: str, guids: List[str], config: DependencyConfig | None = None, validate_references: bool = True, progress_callback: Callable[[str], None] | None = None, dry_run: bool = False) SyncResult[source]

Import objects with full dependency trees.

Discovers all dependencies (owned objects, referenced objects) and imports them in correct order.

Parameters:
  • object_type – Type of root objects to import

  • guids – List of GUIDs to import

  • config – Dependency resolution configuration

  • validate_references – Validate all objects before import

  • progress_callback – Optional callback for progress updates

  • dry_run – If True, preview without actually importing

Returns:

SyncResult with import details

Raises:

Import object and all objects that refer to it.

Useful for importing a semantic domain with all entries that use it, or a POS with all senses that reference it.

Parameters:
  • object_type – Type of object to import

  • guid – GUID of object

  • include_referring_objects – List of object types to import if they refer to this

  • validate_references – Validate all objects

  • progress_callback – Optional callback for progress updates

  • dry_run – If True, preview without importing

Returns:

SyncResult with import details

flexicon.sync.match_strategies module

Match Strategies - Pluggable object matching strategies

This module provides strategies for matching objects between source and target projects.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.match_strategies.MatchStrategy[source]

Bases: ABC

Base class for object matching strategies.

Match strategies determine how to match objects between source and target projects. Common strategies include GUID-based (for backups/forks) and field-based (for cross-project merging).

Custom strategies can be implemented by sub classing and implementing match().

abstract match(source_obj: Any, target_candidates: List[Any], source_project: Any, target_project: Any) Any | None[source]

Find matching target object for source object.

Parameters:
  • source_obj – Source object to match

  • target_candidates – List of all target objects to search

  • source_project – Source FLExProject instance

  • target_project – Target FLExProject instance

Returns:

Matching target object, or None if no match found

class flexicon.sync.match_strategies.GuidMatchStrategy[source]

Bases: MatchStrategy

Match objects by GUID (Globally Unique Identifier).

This is the safest and most reliable matching strategy for projects that share a common history (backups, forks, branches). GUIDs are persistent across projects and never change.

Use this strategy when: - Syncing between backup and main project - Syncing between test branch and stable branch - Source and target share common ancestry

Example

>>> strategy = GuidMatchStrategy()
>>> match = strategy.match(source_obj, target_objects, source_proj, target_proj)
match(source_obj: Any, target_candidates: List[Any], source_project: Any, target_project: Any) Any | None[source]

Match by GUID.

Parameters:
  • source_obj – Source object with .Guid property

  • target_candidates – Target objects to search

  • source_project – Source project (unused)

  • target_project – Target project (unused)

Returns:

Target object with matching GUID, or None

class flexicon.sync.match_strategies.FieldMatchStrategy(key_fields: List[str], get_field_fn: callable | None = None, case_sensitive: bool = True, writing_system: str | None = None)[source]

Bases: MatchStrategy

Match objects by field values.

This strategy matches objects based on specified field values (e.g., headword, form, name). Useful for merging independent projects that don’t share GUIDs.

Use this strategy when: - Merging data from different projects - Source and target have no common history - Matching by semantic equivalence rather than identity

Example

>>> # Match allomorphs by form
>>> strategy = FieldMatchStrategy(
...     key_fields=["form"],
...     get_field_fn=lambda obj, ops: ops.GetForm(obj)
... )
>>>
>>> # Match entries by headword
>>> strategy = FieldMatchStrategy(
...     key_fields=["headword"],
...     get_field_fn=lambda obj, ops: ops.GetHeadword(obj)
... )
match(source_obj: Any, target_candidates: List[Any], source_project: Any, target_project: Any) Any | None[source]

Match by field values.

Parameters:
  • source_obj – Source object

  • target_candidates – Target objects to search

  • source_project – Source project (for operations access)

  • target_project – Target project (for operations access)

Returns:

First target object with matching field values, or None

class flexicon.sync.match_strategies.HybridMatchStrategy(fallback_fields: List[str], case_sensitive: bool = True)[source]

Bases: MatchStrategy

Hybrid matching: Try GUID first, fall back to field-based.

This strategy combines GUID and field-based matching. It first attempts to match by GUID (fastest, most reliable), and if that fails, falls back to field-based matching.

Use this strategy when: - Source and target may partially overlap - Some objects share GUIDs, others don’t - You want reliability of GUID with flexibility of field matching

Example

>>> strategy = HybridMatchStrategy(
...     fallback_fields=["headword"],
...     case_sensitive=False
... )
match(source_obj: Any, target_candidates: List[Any], source_project: Any, target_project: Any) Any | None[source]

Try GUID match first, then field match.

Parameters:
  • source_obj – Source object

  • target_candidates – Target objects

  • source_project – Source project

  • target_project – Target project

Returns:

Matched target object, or None

flexicon.sync.merge_ops module

MergeOperations - Safe execution of sync operations

This module provides safe Create/Update/Delete operations for syncing FLEx objects between projects.

Author: FlexTools Development Team Date: 2025-11-26

class flexicon.sync.merge_ops.MergeOperations(target_project: Any)[source]

Bases: object

Execute sync operations safely.

This class handles the actual creation, updating, and deletion of FLEx objects during sync operations. All operations include validation and error handling.

Usage:
>>> merger = MergeOperations(target_project)
>>> merger.create_object(
...     target_ops=target_project.Allomorph,
...     source_obj=source_allomorph,
...     source_ops=source_project.Allomorph,
...     parent_obj=target_entry
... )
create_object(target_ops: Any, source_obj: Any, source_ops: Any, parent_obj: Any | None = None, **create_kwargs) Any[source]

Create a new object in target project.

This method creates a new object by extracting properties from the source object and calling the appropriate Operations.Create() method.

Parameters:
  • target_ops – Target operations instance (e.g., target_project.Allomorph)

  • source_obj – Source object to copy from

  • source_ops – Source operations instance

  • parent_obj – Optional parent object (for owned objects)

  • **create_kwargs – Additional arguments for Create() method

Returns:

Newly created object in target

Raises:
  • RuntimeError – If creation fails

  • AttributeError – If Create method not available

Example

>>> # Create allomorph in target
>>> new_allomorph = merger.create_object(
...     target_ops=target_project.Allomorph,
...     source_obj=source_allomorph,
...     source_ops=source_project.Allomorph,
...     parent_obj=target_entry
... )
update_object(target_obj: Any, source_obj: Any, source_ops: Any, target_ops: Any, fields: List[str] | None = None) bool[source]

Update an existing object in target project.

Copies properties from source object to target object. If fields is specified, only those fields are updated. Otherwise, all common properties are updated.

This method will attempt to use the new CompareTo() method if available on the operations class to efficiently detect differences. Falls back to traditional property-by-property copying if not available.

Parameters:
  • target_obj – Target object to update

  • source_obj – Source object to copy from

  • source_ops – Source operations instance

  • target_ops – Target operations instance

  • fields – Optional list of specific fields to update

Returns:

True if any changes were made

Raises:

RuntimeError – If update fails

Example

>>> # Update allomorph form only
>>> changed = merger.update_object(
...     target_obj=target_allomorph,
...     source_obj=source_allomorph,
...     source_ops=source_project.Allomorph,
...     target_ops=target_project.Allomorph,
...     fields=["form"]
... )
delete_object(target_ops: Any, target_obj: Any, validate_safe: bool = True) bool[source]

Delete an object from target project.

Parameters:
  • target_ops – Target operations instance

  • target_obj – Object to delete

  • validate_safe – If True, perform safety checks before deleting

Returns:

True if deleted successfully

Raises:

RuntimeError – If deletion fails or is unsafe

Example

>>> # Delete allomorph
>>> deleted = merger.delete_object(
...     target_ops=target_project.Allomorph,
...     target_obj=target_allomorph
... )
copy_properties(source_obj: Any, target_obj: Any, source_ops: Any, target_ops: Any) bool[source]

Copy properties from source to target object.

This method first attempts to use the new GetSyncableProperties() method from the sync framework if available. This provides complete, accurate property coverage for all object types.

If GetSyncableProperties() is not available, falls back to traditional pattern matching of Get*/Set* methods for backwards compatibility.

Parameters:
  • source_obj – Source object to copy from

  • target_obj – Target object to copy to

  • source_ops – Source operations instance

  • target_ops – Target operations instance

Returns:

True if any properties were changed

Example

>>> # Copy all properties
>>> changed = merger.copy_properties(
...     source_allomorph,
...     target_allomorph,
...     source_project.Allomorph,
...     target_project.Allomorph
... )
class flexicon.sync.merge_ops.SyncChange(operation: str, object_type: str, object_guid: str, details: Dict | None = None)[source]

Bases: object

Record of a single change made during sync.

Used for undo/audit tracking.

class flexicon.sync.merge_ops.SyncError(operation: str, object_guid: str | None, error_message: str)[source]

Bases: object

Record of an error during sync.

flexicon.sync.selective_import module

Selective Import Operations

One-way import of specific objects from source to target project. Matches the actual linguistic workflow: “import new allomorphs to stable project”.

Author: FlexTools Development Team Date: 2025-11-27

class flexicon.sync.selective_import.SelectiveImport(source_project: Any, target_project: Any)[source]

Bases: object

One-way selective import from source to target project.

This matches the linguist’s actual workflow: 1. Copy project for testing 2. Make changes in test project 3. Import ONLY new/modified items back to stable project 4. NEVER overwrite stable project’s existing data

Usage:
>>> importer = SelectiveImport(source_project, target_project)
>>> result = importer.import_new_objects(
...     object_type="Allomorph",
...     created_after=backup_timestamp,
...     validate_references=True
... )
>>> print(result.summary())

This is SAFER than bidirectional sync for linguistic data.

import_new_objects(object_type: str, created_after: datetime | None = None, modified_after: datetime | None = None, validate_references: bool = True, include_owned: bool = False, progress_callback: Callable[[str], None] | None = None, dry_run: bool = False) SyncResult[source]

Import objects created/modified after specified date.

This is the PRIMARY method for linguistic workflows: - Imports NEW objects only (not in target) - Filters by creation/modification date - NEVER overwrites existing target data - One-way operation (source → target only)

Parameters:
  • object_type – Type to import (e.g., “Allomorph”, “LexEntry”)

  • created_after – Only import objects created after this time

  • modified_after – Only import objects modified after this time

  • validate_references – Check for missing references before import

  • include_owned – Also import owned objects (WARNING: complex)

  • progress_callback – Optional progress updates

  • dry_run – Preview without making changes

Returns:

SyncResult with import statistics

Example

>>> # User workflow: backup → test → import new
>>> backup_time = datetime(2025, 11, 1)
>>> # ... work in test project ...
>>> result = importer.import_new_objects(
...     object_type="Allomorph",
...     created_after=backup_time,
...     validate_references=True
... )
>>> print(f"Imported {result.num_created} new allomorphs")
import_by_filter(object_type: str, filter_fn: Callable[[Any], bool], validate_references: bool = True, progress_callback: Callable[[str], None] | None = None, dry_run: bool = False) SyncResult[source]

Import objects matching custom filter function.

Parameters:
  • object_type – Type to import

  • filter_fn – Function that returns True for objects to import

  • validate_references – Validate before import

  • progress_callback – Progress updates

  • dry_run – Preview mode

Returns:

SyncResult with statistics

Example

>>> # Import only verified allomorphs
>>> def is_verified(obj):
...     return hasattr(obj, 'Status') and obj.Status == 'Verified'
>>> result = importer.import_by_filter(
...     object_type="Allomorph",
...     filter_fn=is_verified
... )

flexicon.sync.validation module

Linguistic Validation Framework

Validates linguistic data integrity before sync operations. Prevents orphaned references, broken hierarchies, and data corruption.

Author: FlexTools Development Team Date: 2025-11-27

class flexicon.sync.validation.ValidationSeverity(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: Enum

Severity levels for validation issues.

CRITICAL = 'critical'
WARNING = 'warning'
INFO = 'info'
class flexicon.sync.validation.ValidationIssue(severity: ~flexicon.sync.validation.ValidationSeverity, category: str, object_type: str, object_guid: str, message: str, details: ~typing.Dict[str, ~typing.Any] = <factory>)[source]

Bases: object

A single validation issue found during pre-sync checks.

Variables:
  • severity (flexicon.sync.validation.ValidationSeverity) – How serious the issue is

  • category (str) – Type of issue (reference, hierarchy, etc.)

  • object_type (str) – Type of object with issue

  • object_guid (str) – GUID of problematic object

  • message (str) – Human-readable description

  • details (Dict[str, Any]) – Additional context

severity: ValidationSeverity
category: str
object_type: str
object_guid: str
message: str
details: Dict[str, Any]
class flexicon.sync.validation.ValidationResult(issues: ~typing.List[~flexicon.sync.validation.ValidationIssue] = <factory>)[source]

Bases: object

Results from validation checks.

Tracks all issues found and provides summary.

issues: List[ValidationIssue]
property has_critical: bool

Check if any critical issues found.

property has_warnings: bool

Check if any warnings found.

property num_critical: int

Count critical issues.

property num_warnings: int

Count warnings.

property num_info: int

Count info messages.

add_issue(issue: ValidationIssue)[source]

Add a validation issue.

summary() str[source]

Generate summary report.

detailed_report() str[source]

Generate detailed report with all issues.

class flexicon.sync.validation.LinguisticValidator(target_project: Any)[source]

Bases: object

Validates linguistic data integrity before sync operations.

Checks for: - Missing reference targets (POS, semantic domains, etc.) - Orphaned owned objects (phonological environments, etc.) - Broken hierarchical relationships - Invalid linguistic constraints

Usage:
>>> validator = LinguisticValidator(target_project)
>>> result = validator.validate_before_create(source_obj, source_project)
>>> if result.has_critical:
...     raise ValidationError(result.summary())
validate_before_create(source_obj: Any, source_project: Any, object_type: str) ValidationResult[source]

Validate object before creating in target.

Parameters:
  • source_obj – Object to be created

  • source_project – Source project

  • object_type – Type of object

Returns:

ValidationResult with any issues found

exception flexicon.sync.validation.ValidationError(validation_result: ValidationResult)[source]

Bases: Exception

Raised when validation fails with critical issues.

Module contents

Multi-Database Sync/Merge Framework for flexlibs

This package provides tools for synchronizing and merging FLEx database objects between projects, with support for FlexTools integration, conflict resolution, and dependency management.

Author: FlexTools Development Team Date: 2025-11-26