morpheus-bigquery

Import prefix morpheus_bigquery. Layer MorpheusBigQueryLayer. Used by enrichment-scraper, morpheus-hubspot, and phone-agent.

Source: libs/morpheus-bigquery/morpheus_bigquery/client.py. The smallest library in the repository — one function.

create_bigquery_client()

def create_bigquery_client(*, credentials_json: str) -> bigquery.Client:
    """Build a BigQuery client from a service account JSON string."""
    info = json.loads(credentials_json)
    credentials = service_account.Credentials.from_service_account_info(info)
    return bigquery.Client(credentials=credentials, project=credentials.project_id)

Credentials are always passed by the consumer — the library never reads an environment variable. The project is taken from the service account itself rather than configured separately, so a credential swap moves the client to the right project automatically.

Usage

The import is deferred into the handler body, not done at module scope, so the module stays importable in a test environment without the layer:

def handle(self, event: ScheduledEvent, context: Any) -> None:
    secrets = load_secrets()

    if not secrets.bigquery_credentials.strip():
        logger.warning("BigQuery credentials not configured — skipping …")
        return

    from morpheus_bigquery import create_bigquery_client          # deferred

    bq_client = create_bigquery_client(credentials_json=secrets.bigquery_credentials)
    rows = [dict(row) for row in bq_client.query(SOME_QUERY).result()]

Every warehouse-backed handler follows this shape: check for credentials, warn and return if absent, then build the client.

Credentials

BIGQUERY_CREDENTIALS in the shared secret — a Google service-account JSON document stored as a string. Each consuming function declares it as an optional field defaulting to "":

class HubspotSecrets(BaseModel):
    bigquery_credentials: str = ""

An empty value is a supported state, not an error. It disables every BigQuery-backed handler with a warning:

Function Handlers disabled by empty credentials

morpheus-hubspot

Hubspot.EmailClickReEngagement, Hubspot.SupplierAccountReEngagement, Hubspot.UnattendedMeetingReEngagement, Hubspot.LostDealReEngagement, Hubspot.BigQueryEngagementSync

enrichment-scraper

Scraper.BigQueryContactSync

phone-agent

Phone.CampaignTargetReEngagement

Clearing the key is therefore a single switch that turns off all warehouse-driven segmentation. See kill switches.

Query patterns

Two styles appear in the handlers.

Literal query, results as dicts
result = client.query(BQ_LOST_DEAL_REENGAGEMENT_QUERY).result()
rows = [dict(row) for row in result]

+ Used by all four re-engagement segments. The query is a constant in the function’s utils/constants.py, selecting from a named view with a LIMIT.

Parameterised query
from google.cloud import bigquery as bq

job_config = bq.QueryJobConfig(query_parameters=[
    bq.ScalarQueryParameter("watermark", "TIMESTAMP", watermark_to_dt(watermark)),
    bq.ScalarQueryParameter("row_limit", "INT64", limit),
])
result = client.query(BQ_CONTACT_SYNC_QUERY, job_config=job_config).result()

+ Used by the two watermark-driven syncs. Parameters, not interpolation — the watermark comes from the configs table.

Type coercion

BigQuery TIMESTAMP values arrive as datetime objects, but exports and casts can produce strings or epoch integers. Every handler carries a defensive coercion helper:

def _to_datetime(value: Any) -> datetime | None:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
    if isinstance(value, (int, float)):
        return datetime.fromtimestamp(value / 1000, tz=timezone.utc)   # epoch ms
    try:
        return datetime.fromisoformat(str(value))
    except (ValueError, TypeError):
        return None

Naive datetimes are assumed UTC. An unparseable value becomes None, which the calling suppression rules interpret as "no known last call" — deliberately the permissive direction, so a parse failure never silently suppresses a contact.

The helper is duplicated across handlers rather than shared. It is a candidate for this library if a fourth copy appears.

Warehouse layout

Dataset Contents

bigqueryexport-183608.hubspot4

Stitch export of the HubSpot portal — contacts, deals, engagements, v_engagements_with_owner

bigqueryexport-183608.outreach_app

Mirror of application tables plus the derived views Morpheus queries

Tests

cd libs/morpheus-bigquery && uv run pytest tests -v

tests/test_client.py covers the factory. The handler-level query and coercion logic is tested in the consuming functions' suites.