ArchFactory: Sheet-to-Repo Synchronizer#

class arch.tools.mod.sync.ArchFactory(*, root, db=None, local=False, push=False, sheet_id='', sheet=<my.apis.GoogleSheet.GoogleSheet object>, worksheets={}, tiers=('', '', '', ''), names={}, old_arch=ARCH, new_arch=None, delta={}, arch_files={}, inst_files={})#

An effectively-static class that builds and/or maintains the local architectonic.

The primary role of this class at the moment is to sync the local architectonic with the ground truth values in the arch’s Google Sheet, changing folder and file names as needed. It could easily expand beyond that role if similar use cases come up, however.

The default direction is pull (sheet -> local): the sheet is treated as ground truth and the repository (plus database) is renamed to match. The --push flag reverses that (local -> remote): the local architectonic is treated as ground truth and only the sheet cells that disagree are rewritten, leaving every other sheet cell untouched.

There are currently (at least) three places where each Architectonic project is instantiated:

  1. The Sheet: This is the ground truth source, detailing all the elements & moments of the architectonic in a series of 5+ worksheets.

  2. The Repository: This is where the actual code lives, organized in a specific way that matches the structure of the architectonic.

  3. The Database: This is where the architectonic is stored in a more structured format, implemented as a Postgres database with SQLAlchemy models.

model_config = {'arbitrary_types_allowed': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod validate_root(root)#

Validate the given root directory, ensuring it is a git repository.

assert_clean_tree()#

Assert the root’s git tree is clean, apart from allowlisted architectonic files.

Only required for the pull direction, which mutates the repository; pushing local values to the sheet leaves the filesystem untouched, so a dirty tree is fine there.

async connect()#

Connect to DB & sheet, then load the relevant worksheet(s) into memory.

connect_db()#

Construct the DB URL from environment variables, then connect via SQLAlchemy.

async disconnect()#

Gracefully dispose of the DB connection.

worksheet(idx)#

Get the worksheet that contains the full data regarding the passed index.

db_session()#

Yield an async SQLAlchemy session, ensuring proper cleanup after use.

division(idx)#

Get the division of the given index.

This is determined by the first word of the corresponding cell in the worksheet, which is guaranteed to be consistent across all elements of the same division.

For abstract elements, the division is determined by the first word of the concrete sibling’s cell.

moments(idx)#

Get the three moments of the given index.

rename_singular(idx, old, new)#

Rename the singular form of the given index, changing file names and content.

rename_plural(idx, old, new)#

Rename the plural form of the given index, changing directory names and content.

rename_moment(idx, old, new)#

Rename the given moment of the given index, changing file contents.

rename_division(idx, old, new)#

Rename the division of the given index, changing file contents.

replace(repl, files=None)#

Apply the given replacements to the given files, counting the number of changes made.

static clean_worksheet(df)#

Clean the given worksheet, preparing it for use in constructing the architectonic.

all_files(idx)#

Get all files relevant to the given index.

static col_name(num)#

Convert a 1-based column number to its A1 column name (1 -> A, 27 -> AA).

classmethod locate_cell(values, row_key, col_header)#

Locate a cell in raw worksheet values by row key and column header.

Parameters:
  • values – The raw list-of-lists values of the worksheet, as returned by the API.

  • row_key – The value of the row’s idx column (e.g. 3000).

  • col_header – The header text of the column (e.g. division).

Returns:

The 1-based (row, column) coordinates of the cell.

static rewrite_moment(cell_text, new_moment)#

Rewrite a sheet moment cell with a new moment, preserving its local style.

Moment cells may carry annotations (Draft: sketch the claim) or casing conventions (GET:); the read path extracts only the first word, so the write path replaces only that first word, matching its casing and leaving the rest of the cell untouched.

static first_leaf(idx, depth)#

Compute the first leaf-row key of the given index’s subtree.

In the elements worksheet, each element’s plural name is written exactly once, in the first leaf row of its subtree (e.g. 300a appears in row 3000, 300b in 3002).

get_worksheets()#

Load the relevant worksheets from the given google sheet via the Google Sheets API.

parse_tiers()#

Parse the tiers of the architectonic from the ‘elements’ worksheet.

There are guaranteed to be in columns 1, 3, 5, and 7, and the names must correspond to actual worksheets.

collect_element_names()#

Collect the singular names of all elements in the architectonic a worksheet.

construct_architectonic()#

Construct a new architectonic based on the collected names and other relevant data.

This is purely a data structure – it does not imply updates to the filesystem OR google sheet(yet).

change_contents(idxs)#

Determine then apply all necessary file content changes for the given indices.

flag_conflicts(idxs)#

Prevent race conditions between swapped names by replacing conflicts w/ UUIDS.

These UUIDS are to be processed at the end in order to prevent race conditions.

Parameters:

idxs – The list of all the indices that are being changed.

Returns:

A list of all indices that had conflicts.

resolve_conflicts(conflicts)#

Apply all the queued changes that were deferred due to naming conflicts.

async change_filesystem(idxs)#

Determine & apply all necessary filesystem changes for the given indices.

async change_database(idxs, flush)#

Apply all necessary database changes for the given indices.

Parameters:
  • idxs – The list of all the indices that are being changed.

  • flush – Whether to flush the entire architectonic to the database, rather than just the deltas.

plan_push(raw)#

Plan the sheet cell writes needed to push the local architectonic to the sheet.

For every cell of the delta (computed by sync(), where old is local and new is the sheet), this locates the exact sheet cell the sheet-side value was read from and targets it with the local value. Abstract division and moment deltas are skipped on purpose: they are derived reads (from a concrete child/sibling row), so they resolve once the concrete cell is fixed – and if they don’t, no sheet cell can represent them.

Parameters:

raw – Raw list-of-lists values for every worksheet the delta touches, keyed by worksheet name, as returned by the Google Sheets API.

Returns:

The planned cell updates, sorted by worksheet, row, and column.

async push_to_sheet(dry=False)#

Push the local architectonic to the Google Sheet (local -> remote).

Rewrites exactly the sheet cells whose values disagree with the local architectonic, leaving every other cell untouched, then re-fetches the sheet and recomputes the delta to verify the sync. Local files and the database are never modified in this direction.

Parameters:

dry – If True, plan and print the updates without applying anything.

Returns:

The planned cell updates (empty if the push was aborted).

async sync()#

Sync the local architectonic with the ground truth values in the Google Sheet.

survey(root=None, idx=None)#

Recursively walk through the filesystem, recording arch structure.

Parameters:
  • root – The directory to survey.

  • idx – The current index in the architectonic.

async change(flush, local)#

Determine & apply all necessary changes to the local architectonic.

Parameters:
  • flush – Whether to flush the entire architectonic to the database, rather than just the deltas.

  • local – Whether to only make local filesystem changes, without updating the database.

model_post_init(context, /)#

This function is meant to behave like a BaseModel method to initialize private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Parameters:
  • self – The BaseModel instance.

  • context – The context.