Context
The SharePoint-related code in box2 is currently spread across three packages:
box2.sharepoint — API clients (session, list, docs, webhooks) and schema generation
box2.pipeline.mappers — generic to_sharepoint_fields() / from_sharepoint_fields() serialisation
box2.triage.models — SharePoint list schema models (SharepointInvitation, SharepointPQs, etc.)
This makes box2.sharepoint difficult to use standalone and creates awkward cross-package dependencies — for example, pipeline.mappers imports private helpers (_unwrap_optional, _contains_url_type) from sharepoint.graph_api_schema.
Consolidating everything into box2.sharepoint would make it a self-contained SharePoint client library that could potentially be extracted into its own repo in future.
What should move to box2.sharepoint
Serialisation functions (from pipeline/mappers.py)
| Symbol |
Reason |
to_sharepoint_fields() |
Generic serialiser — no pipeline/triage dependency, only uses Pydantic + sharepoint helpers |
from_sharepoint_fields() |
Generic deserialiser — same |
_split_list_field() |
SharePoint string format helper |
_extract_urls_from_html() |
SharePoint HTML format helper |
_HREF_PATTERN |
Regex constant |
_SP_INTERNAL_NAME_MAX |
SharePoint platform constant |
These could live in a new box2/sharepoint/serialisation.py (or fields.py) alongside graph_api_schema.py, since they are the inverse of generate_graph_schema.
SharePoint list schema models (from triage/models/)
| Model |
File |
Dependencies |
SharepointInvitation + EventType |
invitation_sharepoint.py |
Pydantic only |
SharepointInvitationQA |
invitation_qa_sharepoint.py |
Extends SharepointInvitation |
SharepointSubmission |
submission_sharepoint.py |
Pydantic only |
SharepointAction |
actions_sharepoint.py |
Pydantic only |
SharepointPQs |
parli_question_sharepoint.py |
Pydantic only |
None of these import anything from box2.triage. They are standalone Pydantic schema definitions that describe SharePoint list columns.
They could move to box2/sharepoint/models/ (the directory exists but is currently empty).
Private helpers to promote
_unwrap_optional() and _contains_url_type() in graph_api_schema.py are currently imported as private names by pipeline.mappers. After the move, the serialisation code lives in the same package as these helpers, so the cross-package private import disappears.
What stays in pipeline/mappers.py
The domain-specific mapper functions that bridge triage/pipeline models to SharePoint models:
| Function |
Why it stays |
to_sharepoint_invitation_qa() |
Maps TriagedInvitation (pipeline) → SharepointInvitationQA |
to_sharepoint_invitation() |
Maps SharepointInvitationQA → SharepointInvitation |
to_sharepoint_submission() |
Maps Submission (triage) → SharepointSubmission |
to_sharepoint_action() |
Maps Action + ActionReviewResult → SharepointAction |
These genuinely need both packages and are orchestration logic.
What stays in triage/models/
The domain models that describe triage concepts (not SharePoint schemas):
Invitation, NotInvitation, Submission, NotSubmission, Action, TriagedDecision, SafeDocument, RawDocument, MinisterPersona, CalendarEvent, etc.
Backwards compatibility
Re-export moved symbols from their original locations so existing imports continue to work:
box2.pipeline.mappers re-exports to_sharepoint_fields, from_sharepoint_fields
box2.pipeline.__init__ re-exports same
box2.triage.models.__init__ re-exports all Sharepoint* models
After the move: ListClient read-with-model methods
Once the serialisation functions live in box2.sharepoint, ListClient can offer typed read methods without cross-package imports:
model = client.get_item_as(item_id, SharepointInvitation)
models = client.get_items_as(SharepointInvitation)
These would use from_sharepoint_fields internally.
Import chain after the move
box2.sharepoint (self-contained)
├── session, list_client, docs_client, webhook_client
├── serialisation (to/from_sharepoint_fields)
├── graph_api_schema (generate_graph_schema)
└── models (SharepointInvitation, SharepointPQs, etc.)
box2.pipeline.mappers (domain bridging only)
→ box2.sharepoint.serialisation
→ box2.sharepoint.models
→ box2.triage.models (domain models: Action, Submission, etc.)
box2.receiver
→ box2.pipeline
→ box2.sharepoint
No more private cross-package imports. box2.sharepoint has zero dependencies on pipeline or triage.
Context
The SharePoint-related code in box2 is currently spread across three packages:
box2.sharepoint— API clients (session, list, docs, webhooks) and schema generationbox2.pipeline.mappers— genericto_sharepoint_fields()/from_sharepoint_fields()serialisationbox2.triage.models— SharePoint list schema models (SharepointInvitation,SharepointPQs, etc.)This makes
box2.sharepointdifficult to use standalone and creates awkward cross-package dependencies — for example,pipeline.mappersimports private helpers (_unwrap_optional,_contains_url_type) fromsharepoint.graph_api_schema.Consolidating everything into
box2.sharepointwould make it a self-contained SharePoint client library that could potentially be extracted into its own repo in future.What should move to
box2.sharepointSerialisation functions (from
pipeline/mappers.py)to_sharepoint_fields()from_sharepoint_fields()_split_list_field()_extract_urls_from_html()_HREF_PATTERN_SP_INTERNAL_NAME_MAXThese could live in a new
box2/sharepoint/serialisation.py(orfields.py) alongsidegraph_api_schema.py, since they are the inverse ofgenerate_graph_schema.SharePoint list schema models (from
triage/models/)SharepointInvitation+EventTypeinvitation_sharepoint.pySharepointInvitationQAinvitation_qa_sharepoint.pySharepointInvitationSharepointSubmissionsubmission_sharepoint.pySharepointActionactions_sharepoint.pySharepointPQsparli_question_sharepoint.pyNone of these import anything from
box2.triage. They are standalone Pydantic schema definitions that describe SharePoint list columns.They could move to
box2/sharepoint/models/(the directory exists but is currently empty).Private helpers to promote
_unwrap_optional()and_contains_url_type()ingraph_api_schema.pyare currently imported as private names bypipeline.mappers. After the move, the serialisation code lives in the same package as these helpers, so the cross-package private import disappears.What stays in
pipeline/mappers.pyThe domain-specific mapper functions that bridge triage/pipeline models to SharePoint models:
to_sharepoint_invitation_qa()TriagedInvitation(pipeline) →SharepointInvitationQAto_sharepoint_invitation()SharepointInvitationQA→SharepointInvitationto_sharepoint_submission()Submission(triage) →SharepointSubmissionto_sharepoint_action()Action+ActionReviewResult→SharepointActionThese genuinely need both packages and are orchestration logic.
What stays in
triage/models/The domain models that describe triage concepts (not SharePoint schemas):
Invitation,NotInvitation,Submission,NotSubmission,Action,TriagedDecision,SafeDocument,RawDocument,MinisterPersona,CalendarEvent, etc.Backwards compatibility
Re-export moved symbols from their original locations so existing imports continue to work:
box2.pipeline.mappersre-exportsto_sharepoint_fields,from_sharepoint_fieldsbox2.pipeline.__init__re-exports samebox2.triage.models.__init__re-exports allSharepoint*modelsAfter the move:
ListClientread-with-model methodsOnce the serialisation functions live in
box2.sharepoint,ListClientcan offer typed read methods without cross-package imports:These would use
from_sharepoint_fieldsinternally.Import chain after the move
No more private cross-package imports.
box2.sharepointhas zero dependencies onpipelineortriage.