Skip to content

MongoDB Sync

Sync PropertyMe data to MongoDB for analytics, AI/ML pipelines, and system integrations.

Installation

uv add pypropertyme[mongodb]

Configuration

Set these environment variables (in .env or your shell):

Variable Description
MONGODB_URI MongoDB connection string (e.g., mongodb://localhost:27017)
MONGODB_DATABASE Database name

Quick Start

# 1. Set environment variables
export MONGODB_URI="mongodb://localhost:27017"
export MONGODB_DATABASE="pypropertyme"

# 2. Sync data
pypropertyme-mongodb sync

# 3. Check document counts
pypropertyme-mongodb status

CLI Commands

pypropertyme-mongodb sync

Syncs all records from PropertyMe to MongoDB.

pypropertyme-mongodb sync [--dry-run] [--collection <COLLECTION>]
Option Description
--dry-run Preview sync without writing to MongoDB
--collection Sync specific collection only (e.g., contacts, properties)

pypropertyme-mongodb status

Shows collection statistics and document counts.

pypropertyme-mongodb status

pypropertyme-mongodb test-connection

Tests connections to both PropertyMe and MongoDB.

pypropertyme-mongodb test-connection

Collections

The sync creates 8 collections in MongoDB:

Collection Description
contacts Owners, tenants, suppliers
members Agency staff
properties Managed properties
tenancies Lease agreements (dates, rent, bond, notes)
tenancy_balances Financial data (rent, arrears, bond)
jobs Maintenance jobs
inspections Property inspections
tasks Tasks and reminders

Note

tenancies and tenancy_balances are two views of the same PropertyMe entity (same IDs): /tenancies returns active tenancies with lease agreement fields, /tenancies/balances returns all tenancies (active and closed) with added financial fields. Both are mirrored for endpoint fidelity.

How It Works

Snapshot-First Fetch

Every run starts by fetching all PropertyMe bulk endpoints into one in-memory snapshot, before anything is written to MongoDB. If any fetch fails, the run aborts with the database untouched and the CLI exits non-zero.

Document Structure

Documents are stored as complete Pydantic model JSON with no transformations:

{
  "_id": ObjectId("..."),
  "id": "abea0092-b4f4-c250-9bc4-eba5ca44a35b",
  "name_text": "Gary Jarrel",
  "email": "[email protected]",
  "is_owner": true,
  "roles": ["owner"]
}

Upsert Logic

Uses MongoDB's bulk_write with ReplaceOne operations keyed by the id field. Documents are created if new, replaced if existing.

Stale Record Refresh

After syncing, documents in MongoDB that weren't included in the PropertyMe sync response are refreshed:

  • Stale properties are replaced from the snapshot's archived properties, and stale tenancies are flagged closed using the snapshot's balances as the close signal
  • Other collections fetch each stale document's current state from PropertyMe by ID; documents that return 404 (deleted from PropertyMe) are logged and skipped
  • Documents that have become archived/closed are updated with their true state

Indexing

A unique index on the id field is created for each collection to ensure efficient upserts and prevent duplicates.

Module Structure

src/pypropertyme/sync/mongodb/
├── __init__.py  # Module exports
├── cli.py       # CLI commands (pypropertyme-mongodb)
└── sync.py      # Core sync logic

Programmatic Usage

from pypropertyme.client import Client
from pypropertyme.sync.mongodb import MongoDBSync

# Create PropertyMe client first
pme_client = Client.get_client(token)

# Create sync instance
sync = MongoDBSync(pme_client, "mongodb://localhost:27017", "pypropertyme")

# Run sync
await sync.sync_all()

# Sync specific collection
await sync.sync_collection("contacts")

# Get status
status = await sync.get_status()
print(status)

Querying Data

Once synced, you can query the data using MongoDB's query language:

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017")
db = client["pypropertyme"]

# Find all owners
owners = list(db.contacts.find({"is_owner": True}))

# Find properties with rent > 500
high_rent = list(db.properties.find({"rent_amount": {"$gt": 500}}))

# Find open jobs
open_jobs = list(db.jobs.find({"status": {"$in": ["Assigned", "Quoted"]}}))

# Find overdue tenancies
import datetime
overdue = list(db.tenancy_balances.find({"arrears_days": {"$gt": 14}}))

Use Cases

Analytics Dashboard

# Aggregate rent by property type
pipeline = [
    {"$group": {
        "_id": "$property_type",
        "total_rent": {"$sum": "$rent_amount"},
        "count": {"$sum": 1}
    }}
]
results = list(db.properties.aggregate(pipeline))

AI/ML Integration

import pandas as pd

# Export to DataFrame for ML
contacts_df = pd.DataFrame(list(db.contacts.find()))
properties_df = pd.DataFrame(list(db.properties.find()))

# Feature engineering
features = properties_df[["rent_amount", "bedrooms", "bathrooms"]]
# Create text index
db.contacts.create_index([("name_text", "text"), ("email", "text")])

# Search
results = db.contacts.find({"$text": {"$search": "smith"}})