Skip to content

API Reference

Auto-generated API documentation from source code docstrings.

Client

The main entry point for accessing the PropertyMe API.

Client(token, client_id, client_secret, token_saver_callback=None)

Base client for PropertyMe API endpoints.

The Client class provides a unified interface to all PropertyMe API endpoints. Each entity type (contacts, properties, etc.) has its own client subclass that inherits from this base class.

Entity clients are automatically registered as properties on the main Client instance, allowing access like client.contacts, client.properties, etc.

Example
from pypropertyme.client import Client

client = Client.get_client(token)
contacts = await client.contacts.all()
property = await client.properties.get("property-id")

Attributes:

Name Type Description
model type[ModelT]

The Pydantic model class for list operations.

detail_model type[DetailModelT] | None

Optional model class for get-by-id operations (returns richer data).

endpoint_path str

The API endpoint path (e.g., "contacts", "lots.sales").

use_iterator_by_default bool

Whether to use pagination for all() method.

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

Entity Clients

Each entity type has its own client class with all() and get() methods.

Contacts

Contacts(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Contact, ContactDetail]

Client for PropertyMe contacts (owners, tenants, suppliers).

Example
contacts = await client.contacts.all()
contact = await client.contacts.get("contact-id")
print(contact.contact.name_text)

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Properties

Properties(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Property, PropertyDetail], PropertyDetailMixin

Client for PropertyMe properties (internally called 'lots').

Note

The main /lots endpoint does NOT support pagination. Use filtered clients (rental_properties, sales_properties, etc.) for pagination.

Example
properties = await client.properties.all()
property = await client.properties.get("property-id")
print(property.address)

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

details(id) async

Get property details by ID.

Property details are always fetched from /lots/{id}/detail, not from the filtered endpoint.

Source code in src/pypropertyme/client.py
343
344
345
346
347
348
349
350
351
async def details(self, id: str) -> PropertyDetail:
    """Get property details by ID.

    Property details are always fetched from /lots/{id}/detail,
    not from the filtered endpoint.
    """
    builder = self._get_request_builder("lots")  # pyright: ignore[reportAttributeAccessIssue]
    response = await builder.by_id(id).detail.get()
    return PropertyDetail.model_validate_api(response, self)  # pyright: ignore[reportArgumentType]

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Tenancies

Tenancies(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Tenancy, Tenancy]

Client for PropertyMe tenancies (rental agreements).

Note

Does not support get-by-id (API limitation).

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

detail_model = None class-attribute instance-attribute

The model class for get-by-id operations. If set, get() uses this instead of model.

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Members

Members(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Member, Member]

Client for PropertyMe team members (agency staff).

Note

Does not support get-by-id (API limitation).

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

detail_model = None class-attribute instance-attribute

The model class for get-by-id operations. If set, get() uses this instead of model.

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Tasks

Tasks(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Task, Task]

Client for PropertyMe tasks and reminders.

Example
tasks = await client.tasks.all()
task = await client.tasks.get("task-id")

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

detail_model = None class-attribute instance-attribute

The model class for get-by-id operations. If set, get() uses this instead of model.

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Inspections

Inspections(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Inspection, InspectionDetail]

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

get(id) async

Fetches a single inspection by ID.

Note: PropertyME API requires the Id as both a path parameter AND a query parameter. This override ensures both are set correctly.

Source code in src/pypropertyme/client.py
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
async def get(self, id: str) -> InspectionDetail:
    """Fetches a single inspection by ID.

    Note: PropertyME API requires the Id as both a path parameter AND a query parameter.
    This override ensures both are set correctly.
    """
    from pypropertyme.api.inspections.item.item_request_builder import ItemRequestBuilder

    builder = self._get_request_builder(self.endpoint_path)
    target_model = self.detail_model or self.model

    # PropertyME requires Id as both path param and query param
    query_params = ItemRequestBuilder.ItemRequestBuilderGetQueryParameters()
    query_params.id = id
    config = RequestConfiguration(query_parameters=query_params)

    response = await builder.by_id(id).get(config)
    # PME returns 200 with null inspection field for non-existent IDs
    if response is None or response.inspection is None:
        raise EntityNotFoundError(target_model.__name__, id)
    return cast(InspectionDetail, target_model.model_validate_api(response, self))

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

all() async

Fetches all entities of this type from the PropertyMe API.

Basically it does as a GET request to the endpoint path and returns the list of entities that are returned by PropertyMe.

The method naming all can be somewhat misleading. PropertyMe tends to return different results from different end points. For example lots and tenancies will return all the entities. inspections however will return only a subset of the inspections in PropertyMe, where the changed timestap is greater than some value. PropertyMe API does not specifically mention what that value is.

TODO(garyj): modify the method to accept perhaps Timestamp parameter and set it to a really early default to get all the entities.

Returns:

Type Description
list[ModelT]

list[ModelT]: A list of model instances.

Source code in src/pypropertyme/client.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def all(self) -> list[ModelT]:
    """Fetches all entities of this type from the PropertyMe API.

    Basically it does as a ``GET`` request to the endpoint path and returns the list of entities that are returned
    by PropertyMe.

    The method naming ``all`` can be somewhat misleading. PropertyMe tends to return different results from
    different end points. For example ``lots`` and ``tenancies`` will return all the entities. ``inspections``
    however will return only a subset of the inspections in PropertyMe, where the ``changed`` timestap is greater
    than some value. PropertyMe API does not specifically mention what that value is.

    TODO(garyj): modify the method to accept perhaps ``Timestamp`` parameter and set it to a really early default
    to get all the entities.

    Returns:
        list[ModelT]: A list of model instances.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)
    if self.use_iterator_by_default:
        return [item async for item in self.get_iter(builder)]
    else:
        result = await builder.get()  # pyright: ignore[reportAttributeAccessIssue]
        return [self.model.model_validate_api(item, self) for item in result]

Jobs

Jobs(token, client_id, client_secret, token_saver_callback=None)

Bases: Client[Job, Job]

Initialize the PropertyMe API Client

Source code in src/pypropertyme/client.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> None:
    """Initialize the PropertyMe API Client"""

    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    auth_provider = PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback)
    request_adapter = HttpxRequestAdapter(auth_provider)
    self.pme_kiota = ApiClient(request_adapter)
    self.lock = RLock()

detail_model = None class-attribute instance-attribute

The model class for get-by-id operations. If set, get() uses this instead of model.

use_iterator_by_default = False class-attribute instance-attribute

Whether to use pagination by default in all().

Only some endpoints support offset/limit parameters. For example, /lots does not support pagination, but /lots/sales and /lots/rentals do.

Warning

Do not enable this for endpoints that don't support pagination.

request_builder property

Returns the request builder for this client's endpoint path

all(*, timestamp=0) async

Fetches all jobs from PropertyMe API.

By default, returns ALL jobs by setting timestamp=0. The PropertyMe API filters jobs by changed timestamp, so omitting this parameter returns only recently-modified jobs.

In an ideal world we would get all the jobs so that we can feed them to an LLM so that when a maintenance request comes in we can check if this work has been done before. However I can not seem to find a way to get all the jobs including closed ones.

Parameters:

Name Type Description Default
timestamp int

Return jobs changed after this timestamp. Default 0 returns all jobs.

0

Returns:

Type Description
list[Job]

list[Job]: A list of all jobs.

Source code in src/pypropertyme/client.py
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
async def all(self, *, timestamp: int = 0) -> list[Job]:
    """Fetches all jobs from PropertyMe API.

    By default, returns ALL jobs by setting timestamp=0. The PropertyMe API
    filters jobs by changed timestamp, so omitting this parameter returns
    only recently-modified jobs.

    In an ideal world we would get all the jobs so that we can feed them to an LLM so that when a maintenance
    request comes in we can check if this work has been done before. However I can not seem to find a way to get
    all the jobs including closed ones.

    Args:
        timestamp: Return jobs changed after this timestamp.
                  Default 0 returns all jobs.

    Returns:
        list[Job]: A list of all jobs.
    """
    from pypropertyme.api.jobtasks.jobtasks_request_builder import JobtasksRequestBuilder

    builder = self._get_request_builder(self.endpoint_path)
    query_params = JobtasksRequestBuilder.JobtasksRequestBuilderGetQueryParameters()
    query_params.timestamp = timestamp
    config = RequestConfiguration(query_parameters=query_params)

    result = await builder.get(config)
    return [self.model.model_validate_api(item, self) for item in result or []]

get_client(token, client_id=None, client_secret=None, token_saver_callback=None) classmethod

Create a client using environment variables if client_id or client_secret are not provided.

If the client_id, client_secret, are not provided, it will try to use the environment variables PROPERTYME_CLIENT_ID, PROPERTYME_CLIENT_SECRET respectively.

Source code in src/pypropertyme/client.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@classmethod
def get_client(
    cls,
    token: dict[str, Any],
    client_id: str | None = None,
    client_secret: str | None = None,
    token_saver_callback: TokenSaverCallbackT | None = None,
) -> Self:
    """Create a client using environment variables if ``client_id`` or ``client_secret`` are not provided.

    If the ``client_id``, ``client_secret``, are not provided, it will try to use the environment variables
    ``PROPERTYME_CLIENT_ID``, ``PROPERTYME_CLIENT_SECRET`` respectively.
    """
    client_id = client_id or os.environ.get("PROPERTYME_CLIENT_ID")
    client_secret = client_secret or os.environ.get("PROPERTYME_CLIENT_SECRET")

    if not client_id or not client_secret:
        raise ValueError("Client ID and Client Secret are required.")

    return cls(token, client_id, client_secret, token_saver_callback)

__init_subclass__(**kwargs)

A hook which will automatically adds a property to this client for every inheriting subclass

Source code in src/pypropertyme/client.py
179
180
181
182
183
def __init_subclass__(cls, **kwargs) -> None:
    """A hook which will automatically adds a property to this client for every inheriting subclass"""
    super().__init_subclass__(**kwargs)
    snake_case = camel_to_snake(cls.__name__)
    setattr(Client, snake_case, property(lambda self: self.get_instance_of(cls)))

get_instance_of(klass)

Returns an instance of the client API.

Uses the same authentication credentials as API object was created with.

The created instance is cached, so that subsequent requests will get an already existing instance.

Parameters:

Name Type Description Default
klass type[T]

A class for which the instance needs to be created, e.g. Properties.

required

Returns:

Type Description
T

An instance of the provided class.

Source code in src/pypropertyme/client.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def get_instance_of[T: Client](self, klass: type[T]) -> T:
    """Returns an instance of the client API.

    Uses the same authentication credentials as ``API`` object was created with.

    The created instance is cached, so that subsequent requests will get an already existing instance.

    Args:
        klass: A class for which the instance needs to be created, e.g. `Properties`.

    Returns:
        An instance of the provided class.
    """
    with self.lock:
        value = self.__dict__.get(klass.__name__, None)
        if value is None:
            value = klass(
                token=self.token,
                client_id=self.client_id,
                client_secret=self.client_secret,
                token_saver_callback=self.token_saver_callback,
            )
            self.__dict__[klass.__name__] = value
        return value

get_endpoint_path()

Returns the endpoint path for this client, buy default will return self.endpoint_path

Source code in src/pypropertyme/client.py
234
235
236
def get_endpoint_path(self):
    """Returns the endpoint path for this client, buy default will return ``self.endpoint_path``"""
    return self.endpoint_path

get_iter(request_builder=None, *, offset=0, limit=100) async

Returns an iterator over all entities of this type.

Parameters:

Name Type Description Default
request_builder BaseRequestBuilder | None

The optional request builder to use for fetching entities.

None
offset int

The starting offset for the entities. Defaults to 0.

0
limit int

The maximum number of entities to fetch in a single request. Defaults to 100.

100

Yields:

Name Type Description
ModelT AsyncGenerator[ModelT]

The entities of this type.

Source code in src/pypropertyme/client.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
async def get_iter(
    self, request_builder: BaseRequestBuilder | None = None, *, offset: int = 0, limit: int = 100
) -> AsyncGenerator[ModelT]:
    """Returns an iterator over all entities of this type.

    Args:
        request_builder (BaseRequestBuilder | None): The optional request builder to use for fetching entities.
        offset (int, optional): The starting offset for the entities. Defaults to 0.
        limit (int, optional): The maximum number of entities to fetch in a single request. Defaults to 100.

    Yields:
        ModelT: The entities of this type.
    """
    params = OffsetLimitParameters(limit=limit, offset=offset)
    builder = self._get_request_builder(self.endpoint_path) if not request_builder else request_builder
    while True:
        result = await builder.get(RequestConfiguration(query_parameters=params))  # pyright: ignore[reportAttributeAccessIssue]
        if not result:
            break

        for item in result:
            yield self.model.model_validate_api(item, self)

        params.offset += limit

get(id) async

Fetches a single entity of this type from the PropertyMe API.

Uses detail_model if available, otherwise falls back to model.

Parameters:

Name Type Description Default
id str

The ID of the entity to fetch.

required

Returns:

Type Description
ModelT | DetailModelT

DetailModelT | None: The entity, or None if not found.

Source code in src/pypropertyme/client.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get(self, id: str) -> ModelT | DetailModelT:
    """Fetches a single entity of this type from the PropertyMe API.

    Uses detail_model if available, otherwise falls back to model.

    Args:
        id (str): The ID of the entity to fetch.

    Returns:
        DetailModelT | None: The entity, or None if not found.
    """
    if not self.endpoint_path:
        raise ValueError("endpoint_path is not set")

    builder = self._get_request_builder(self.endpoint_path)

    # Use detail_model for by-id fetches if available
    target_model = self.detail_model or self.model

    try:
        response = await builder.by_id(id).get()  # pyright: ignore[reportAttributeAccessIssue]
        # Under some circumstances (tasks and jobs) we get a None response. Hence we raise an APIError here for
        # consistency so that if AI consumes this it knows when an entity is not found.
        if not response:
            raise EntityNotFoundError(target_model.__name__, id)
    except EntityNotFoundError:
        raise
    except APIError as e:
        if e.response_status_code == 404:
            raise EntityNotFoundError(target_model.__name__, id) from e

        raise

    return target_model.model_validate_api(response, self)

Models

Pydantic models for PropertyMe entities with full field definitions from the PropertyMe OpenAPI specification.

models

Contact pydantic-model

Bases: Contact

Contact model for list operations.

Represents a person in PropertyMe (owner, tenant, or supplier). Used when fetching contacts via client.contacts.all().

Config:

  • populate_by_name: True

Fields:

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

special_type = None pydantic-field

Marks a contact PropertyMe treats as a system party rather than an ordinary client; usually 'None'.

roles = None pydantic-field

Which roles a contact plays for the agency, as lower-case strings (owner, tenant, supplier, seller).

archived_on = None pydantic-field

Date the property or contact was archived (removed from active management); null while active, non-null exactly when IsArchived is true.

account_details = None pydantic-field

[unverified] Bank/disbursement accounts attached to a folio; bank numbers are always masked by the API.

reference = None pydantic-field

PropertyMe's human-readable display label for the record: free text, agency-editable, capped around 50 characters.

website = None pydantic-field

Free-text website for the contact, unnormalised and not guaranteed to be a valid URL; in practice only seen on suppliers.

abn = None pydantic-field

Australian Business Number of a business contact or supplier; eleven digits, entered by staff and not normalised.

person_migrated = None pydantic-field

[unverified] Internal PropertyMe housekeeping flag, likely marking a one-off conversion of the contact's person records; not a business state.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

notes = None pydantic-field

Record-level free-text notes kept by staff; an internal, unstructured scratchpad.

name_text = None pydantic-field

The contact's display name, taken from the primary contact person's full name; for an organisation it holds the company name.

postal_address_text = None pydantic-field

The contact's mailing address flattened for display, normally two lines separated by a newline.

physical_address_text = None pydantic-field

The contact's street/residential address flattened for display, normally two lines separated by a newline.

has_tenant_invoice_account = None pydantic-field

[unverified] Accounting flag indicating whether the contact has a tenant-invoice chart account configured, likely for on-charging supplier bills to tenants; meaning unconfirmed.

home_phone = None pydantic-field

Home/after-hours number of the person the row describes, as free-text staff entry; empty string when none.

work_phone = None pydantic-field

Business/daytime number of the person the row describes, as free-text staff entry; empty or null when none.

cell_phone = None pydantic-field

Mobile phone of the person the row describes, as free-text staff entry; empty string when none recorded.

trade_name = None pydantic-field

[unverified] Presumed supplier-facing text field of unconfirmed meaning: either a registered trading name or a trade category; do not rely on it.

email = None pydantic-field

Email of the person or contact the row describes; empty when none, and never unique enough to use as an identity key.

supplier_chart_account_id = None pydantic-field

GUID on supplier contacts, most likely the default chart-of-accounts account that bills from the supplier are coded to.

is_archived = None pydantic-field

True when the record has been archived (soft-retired) in PropertyMe.

is_supplier = None pydantic-field

True when the contact is a payee the agency raises bills to, such as trades, councils, water authorities, insurers, and system parties.

is_tenant = None pydantic-field

True when the contact holds the tenant role, meaning a renter the agency has on a tenancy; the boolean form of the 'tenant' role.

is_owner = None pydantic-field

True when the contact is a landlord/owner client of the agency; the boolean form of the 'owner' entry in Roles.

is_seller = None pydantic-field

True when the contact is a vendor on a sales listing, the party selling a property rather than renting it out.

phone_text = None pydantic-field

Ready-to-display phone summary of the primary contact person's mobile and work numbers; display only, not for parsing.

work_phone_text = None pydantic-field

One ready-to-display business-hours number for the contact, tagged with its source; despite the name it is often a mobile.

contact_phone = None pydantic-field

Ready-to-display summary of a contact's phone numbers, including the home number; empty string when none.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

contact_persons = None pydantic-field

[unverified] The individual people behind a contact record, ordered by SortOrder with exactly one flagged IsPrimary.

primary_contact_person = None pydantic-field

The single person to deal with for this contact: the ContactPersons entry whose IsPrimary is true, always populated.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

ContactDetail pydantic-model

Bases: GetContactResponse

Full contact details including metadata.

Returned by client.contacts.get(id). Contains more data than Contact, including nested contact information and folio details.

Config:

  • populate_by_name: True

Fields:

contact = None pydantic-field

The full contact record: roles, labels, notes and pre-rendered name/address/phone strings for the requested party.

contact_persons = None pydantic-field

[unverified] The individual people behind a contact record, ordered by SortOrder with exactly one flagged IsPrimary.

code = None pydantic-field

Short human-readable ledger code: a type prefix plus the zero-padded folio number (e.g. TEN##### for a tenancy, OWN##### for an ownership).

folio_id = None pydantic-field

GUID of the trust-account folio (money ledger) the record settles through. All-zero GUID means no folio of its own; treat as null.

payment_priority = None pydantic-field

[unverified] Supplier-only integer rank that groups suppliers for the creditor payment run; only meaningful when the contact is a supplier.

auto_approve_bill = None pydantic-field

Supplier-only flag: whether bills entered against this supplier are approved for payment automatically instead of awaiting staff approval.

tenant_invoice_chart_account_id = None pydantic-field

[unverified] Supplier-only GUID of the chart-of-accounts entry used when a charge is on-invoiced to a tenant; all-zeros GUID or null means unset.

reminder_count = None pydantic-field

[unverified] Count of reminder-like items associated with this contact; usually 0, meaning uncertain, so not reliable as an outstanding-reminders signal.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Property pydantic-model

Bases: MobLotGridData

Property model for list operations.

Represents a managed property (internally called 'Lot' in PropertyMe). Used when fetching properties via client.properties.all().

Config:

  • populate_by_name: True

Fields:

owner_contact_reference = None pydantic-field

Display name of the owner identified by OwnerContactId on the same property grid row, copied from that contact's Reference. Null when no owner.

tenant_contact_reference = None pydantic-field

Display name of the tenant party identified by TenantContactId on the same property grid row, copied from that contact's Reference.

rent_amount = None pydantic-field

Rent in AUD for one RentPeriod (the sibling field giving the unit, so never assume weekly).

rent_period = None pydantic-field

Period the accompanying rent amount covers: weekly, fortnightly or monthly (lower case); null when there is no rent, e.g. sales-only properties.

tenancy_start = None pydantic-field

Date the tenant's occupancy began; stays fixed across lease renewals while AgreementStart moves forward.

tenancy_end = None pydantic-field

Date the tenant vacates; null while running with no end date. Setting it flags the property as vacating, even while still active.

agreement_start = None pydantic-field

Start date of the tenancy's current lease agreement term; pairs with AgreementEnd.

agreement_end = None pydantic-field

Last day of the fixed-term lease agreement; null for periodic tenancies and often in the past once a tenancy rolls on.

owner_contact_id = None pydantic-field

GUID of the contact record for the property's owner (landlord, or vendor on a sale); matches Ids from /contacts and /contacts/ownerships.

tenant_contact_id = None pydantic-field

GUID of the contact record for the tenancy's tenant party; matches Ids from /contacts and /contacts/tenants. Null when vacant.

vacant = None pydantic-field

True when the property has no active tenancy or its tenancy has a set vacate date; read it as 'vacant or vacating'.

manager_name = None pydantic-field

Full name of the staff member responsible for this record, built untrimmed from the member's name parts.

effective_paid_to = None pydantic-field

Date the tenant's rent is effectively paid up to, including credit for any part payment.

ownership_updated_on = None pydantic-field

UTC timestamp of the last change to the property's ownership record (the landlord's management agreement), not to the property itself.

tenancy_updated_on = None pydantic-field

UTC timestamp of the last change to the property's active tenancy record, not to the property itself.

sale_agreement_updated_on = None pydantic-field

UTC timestamp of the last change to the property's sale agency agreement (the vendor's authority to sell), keyed by ActiveSaleAgreementId.

strata_manager_contact_name = None pydantic-field

Display name of the strata or owners corporation manager contact identified by StrataManagerContactId, saving a lookup against /contacts.

has_access_details = None pydantic-field

[unverified] Boolean flag that most likely indicates whether entry/access information has been recorded for the property, though its meaning is unconfirmed.

timestamp = None pydantic-field

Change token (.NET tick count) for incremental sync, not a wall-clock value; pass it back to fetch records changed since that point.

property_type = None pydantic-field

The kind of premises, a duplicate of PropertySubtype carrying the same value; the coarse Residential/Commercial split is PrimaryType instead.

commercial_category = None pydantic-field

Commercial use class of the premises, meaningful only when PrimaryType is 'Commercial'.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

reference = None pydantic-field

PropertyMe's human-readable display label for the record: free text, agency-editable, capped around 50 characters.

address = None pydantic-field

Structured street address of the property (the lot), as an AddressDetail object.

address_text = None pydantic-field

The property's full street address rendered as one comma-separated line.

primary_type = None pydantic-field

Top-level class of the property: 'Residential' or 'Commercial'.

property_subtype = None pydantic-field

The kind of premises (e.g. House, Apartment, Retail), one level below the sibling PrimaryType.

bedrooms = None pydantic-field

Number of bedrooms recorded against the property; 0 may mean none or simply never entered.

bathrooms = None pydantic-field

Number of bathrooms recorded against the property; 0 may mean none or simply never entered.

car_spaces = None pydantic-field

Total car spaces recorded against the property, counting all parking types together.

area = None pydantic-field

Building or floor area of the property, in the unit given by AreaUnit; often unrecorded.

area_unit = None pydantic-field

Unit of measure for the Area value; 'SquareMetres' is the only value seen.

land_area = None pydantic-field

Size of the land/block for the property, in the unit given by LandAreaUnit.

land_area_unit = None pydantic-field

Unit of measure that LandArea is expressed in (e.g. SquareMetres).

description = None pydantic-field

Long-form free-text body of the record; the counterpart to the one-line Summary.

notes = None pydantic-field

Record-level free-text notes kept by staff; an internal, unstructured scratchpad.

next_inspection_on = None pydantic-field

Next inspection date; on property records a routine-inspection plan that can be stale, on ListingInfo a UTC timestamp for the next open-for-inspection.

key_number = None pydantic-field

The agency's key-tag identifier for the property's keys, as written on the tag.

archived_on = None pydantic-field

Date the property or contact was archived (removed from active management); null while active, non-null exactly when IsArchived is true.

ad_rent_amount = None pydantic-field

Advertised (asking) rent held on the property record, in AUD for one AdRentPeriod (weekly or monthly).

ad_rent_period = None pydantic-field

Period the property's advertised asking rent covers (weekly/fortnightly/monthly); usually null, as advertised rent typically lives on the listing rather than the property record.

active_ownership_id = None pydantic-field

GUID of the ownership (the landlord's management agreement over this property) currently in force. Null when there is no current ownership.

active_sale_agreement_id = None pydantic-field

GUID of the current sale agency agreement (the vendor's authority to sell); the sales-side counterpart of ActiveOwnershipId.

active_tenancy_id = None pydantic-field

GUID of the tenancy currently in place at this property; matches Id in /tenancies and /tenancies/balances. Null when vacant.

longitude = None pydantic-field

WGS84 longitude in decimal degrees; uses the same -999 'never geocoded' sentinel as Latitude.

latitude = None pydantic-field

WGS84 latitude in decimal degrees (negative in Australia); -999 is the 'never geocoded' sentinel.

inspection_frequency = None pydantic-field

Numeric half of how often routine inspections are scheduled; the unit comes from InspectionFrequencyType.

inspection_frequency_type = None pydantic-field

Unit of time for the InspectionFrequency number: 'monthly' or 'weekly', lower-case.

initial_inspection_frequency = None pydantic-field

[unverified] Numeric half of the property's 'initial' inspection interval; the unit comes from InitialInspectionFrequencyType.

initial_inspection_frequency_type = None pydantic-field

Unit of time for the InitialInspectionFrequency number: 'monthly' or 'weekly', lower-case.

main_photo_document_id = None pydantic-field

GUID of the image document used as this row's main photo or thumbnail, resolvable through the matching .../images endpoint. Null when none uploaded.

active_manager_member_id = None pydantic-field

GUID of the staff member currently managing this property; resolves against Id in /members. Null when unassigned.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

strata_manager_contact_id = None pydantic-field

GUID of the contact for the strata / owners-corporation manager of the property. Null means not recorded, not 'not a strata property'.

rural_category = None pydantic-field

[unverified] Nominally the rural land-use category, but returns a constant default and should not be relied on.

display_address = None pydantic-field

Flag controlling whether the property's full street address may be shown publicly when it is advertised.

is_rental = None pydantic-field

Whether the property is a rental on the rent roll (not the opposite of 'for sale').

is_archived = None pydantic-field

True when the record has been archived (soft-retired) in PropertyMe.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

external_listing_id = None pydantic-field

[unverified] On property rows, holds the property's identifier in a listing system outside PropertyMe. Meaning unconfirmed; commonly null.

active_rental_listing_id = None pydantic-field

GUID of the property's current rental (lease) advertisement, not the lease itself. Null unless actively advertised for lease.

active_sale_listing_id = None pydantic-field

GUID of the property's current for-sale advertisement; the sale-side counterpart of ActiveRentalListingId. Null unless actively advertised for sale.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

PropertyDetail pydantic-model

Bases: LotDetailApiResponse

Full property details including ownership and tenancy.

Returned by client.properties.get(id). Contains comprehensive property information including ownership, tenancy, listings, and financial data.

Config:

  • populate_by_name: True

Fields:

ownership = None pydantic-field

The management agreement (landlord side) currently in force, embedded in full; null typically indicates an archived property.

active_ownership_id = None pydantic-field

GUID of the ownership (the landlord's management agreement over this property) currently in force. Null when there is no current ownership.

active_sale_agreement_id = None pydantic-field

GUID of the current sale agency agreement (the vendor's authority to sell); the sales-side counterpart of ActiveOwnershipId.

active_tenancy_id = None pydantic-field

GUID of the tenancy currently in place at this property; matches Id in /tenancies and /tenancies/balances. Null when vacant.

active_rental_listing_id = None pydantic-field

GUID of the property's current rental (lease) advertisement, not the lease itself. Null unless actively advertised for lease.

active_sale_listing_id = None pydantic-field

GUID of the property's current for-sale advertisement; the sale-side counterpart of ActiveRentalListingId. Null unless actively advertised for sale.

address = None pydantic-field

Structured street address of the property (the lot), as an AddressDetail object.

address_text = None pydantic-field

The property's full street address rendered as one comma-separated line.

archived_on = None pydantic-field

Date the property or contact was archived (removed from active management); null while active, non-null exactly when IsArchived is true.

bathrooms = None pydantic-field

Number of bathrooms recorded against the property; 0 may mean none or simply never entered.

bedrooms = None pydantic-field

Number of bedrooms recorded against the property; 0 may mean none or simply never entered.

car_spaces = None pydantic-field

Total car spaces recorded against the property, counting all parking types together.

area = None pydantic-field

Building or floor area of the property, in the unit given by AreaUnit; often unrecorded.

area_unit = None pydantic-field

Unit of measure for the Area value; 'SquareMetres' is the only value seen.

land_area = None pydantic-field

Size of the land/block for the property, in the unit given by LandAreaUnit.

land_area_unit = None pydantic-field

Unit of measure that LandArea is expressed in (e.g. SquareMetres).

description = None pydantic-field

Long-form free-text body of the record; the counterpart to the one-line Summary.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

key_number = None pydantic-field

The agency's key-tag identifier for the property's keys, as written on the tag.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

next_inspection_on = None pydantic-field

Next inspection date; on property records a routine-inspection plan that can be stale, on ListingInfo a UTC timestamp for the next open-for-inspection.

notes = None pydantic-field

Record-level free-text notes kept by staff; an internal, unstructured scratchpad.

property_manager = None pydantic-field

Display name of the agency staff member who manages the property; empty string (not null) when there is no active manager.

primary_type = None pydantic-field

Top-level class of the property: 'Residential' or 'Commercial'.

property_subtype = None pydantic-field

The kind of premises (e.g. House, Apartment, Retail), one level below the sibling PrimaryType.

reference = None pydantic-field

PropertyMe's human-readable display label for the record: free text, agency-editable, capped around 50 characters.

longitude = None pydantic-field

WGS84 longitude in decimal degrees; uses the same -999 'never geocoded' sentinel as Latitude.

latitude = None pydantic-field

WGS84 latitude in decimal degrees (negative in Australia); -999 is the 'never geocoded' sentinel.

task_reminders_count = None pydantic-field

[unverified] A count of reminders outstanding against the property, shown as a badge; not a count of open tasks and must not be used as one.

tenancy = None pydantic-field

The tenancy currently in place at the property, embedded as the same object a /tenancies/balances row returns; null when vacant or archived.

sale_listing = None pydantic-field

[unverified] Intended for-sale counterpart of RentalListing (same ListingInfo object with sale attributes), but never observed populated; do not rely on it to detect a sale.

rental_listing = None pydantic-field

The property's current rental advertisement, embedded in full; null when there is no active rental listing (ActiveRentalListingId unset).

has_listing_provider_import_in_progress = None pydantic-field

Transient flag: true while a listing-provider import is running for the property, so on-screen listing data may be mid-update; false means none is running now.

rent_owing_pre_vacate = None pydantic-field

Rent in AUD still owed for the period up to the vacate date, to be cleared before the tenancy is finalised; null unless vacating.

rent_overpaid = None pydantic-field

Rent in AUD the outgoing tenant has paid beyond their vacate date, to be refunded when the tenancy is finalised; null unless vacating.

pro_rata_status = None pydantic-field

[unverified] A pre-rendered balance line for the property's pro rata (part period) rent position (Label, Amount, Text); null when no pro rata period is running.

strata_manager_contact_id = None pydantic-field

GUID of the contact for the strata / owners-corporation manager of the property. Null means not recorded, not 'not a strata property'.

strata_manager_contact_name = None pydantic-field

Display name of the strata or owners corporation manager recorded against the property, the readable form of StrataManagerContactId; null when none is recorded.

has_active_invoice_template = None pydantic-field

Whether the property has an active invoice template: a saved recurring charge PropertyMe raises on a schedule (water usage, cleaning, parking) not a one-off.

has_access_details = None pydantic-field

Flags whether access details (keys, lock box location, alarm or gate codes, entry instructions) are recorded against the property; the underlying text is not exposed.

response_status = None pydantic-field

Error envelope on wrapped single-record responses; null when the call succeeded.

is_successful = None pydantic-field

Envelope flag on wrapped single-record responses; true when the payload is filled, false when the call failed.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Tenancy pydantic-model

Bases: TenancyApiData

Tenancy model representing a rental agreement.

Links a property to a tenant with lease dates and status.

Config:

  • populate_by_name: True

Fields:

contact_reference = None pydantic-field

Display name of the contact identified by ContactId on the same row, copied from that contact's Reference. Null when ContactId is null.

lot_address = None pydantic-field

Full single-line street address of the property this tenancy is on; string-identical to the property's AddressText on /lots.

lot_reference = None pydantic-field

Display label of the property identified by LotId, copied from its Reference. Free text, not a full address; null on rows with no property.

folio_number = None pydantic-field

Numeric identifier of this tenancy's money ledger (folio); the numeric part of Code, handy for sorting and range queries.

contact_email = None pydantic-field

Email of the contact linked via ContactId (supplier on a job, tenant party on a tenancy); empty when none.

contact_normalised_mobile_phone = None pydantic-field

E.164 form of ContactCellPhone for the ContactId contact; null when it is not machine-parseable.

contact_cell_phone = None pydantic-field

Mobile of the contact linked via ContactId (supplier on a job, tenant party on a tenancy), raw free-text form.

contact_work_phone = None pydantic-field

Business/daytime number of the contact linked via ContactId; empty when none, null when no contact is linked.

contact_home_phone = None pydantic-field

Home/after-hours number of the contact linked via ContactId; empty when none, null when no contact is linked.

contact_phone = None pydantic-field

Ready-to-display summary of a contact's phone numbers, including the home number; empty string when none.

is_active = None pydantic-field

True when this is the tenancy PropertyMe currently treats as the property's active tenancy; use it, not dates, to pick the current tenant.

is_closed = None pydantic-field

True when the tenancy's folio has been finalised (closed off); a plain /tenancies call returns only open tenancies by default.

active_ownership_id = None pydantic-field

GUID of the ownership (the landlord's management agreement over this property) currently in force. Null when there is no current ownership.

code = None pydantic-field

Short human-readable ledger code: a type prefix plus the zero-padded folio number (e.g. TEN##### for a tenancy, OWN##### for an ownership).

is_client_access_disabled = None pydantic-field

Likely true when the tenant's PropertyMe client-portal access for this folio has been switched off; false means this flag is not blocking access.

label = None pydantic-field

Ready-made display caption for the record, exactly as PropertyMe renders it in its UI.

search_text = None pydantic-field

Denormalised search blob backing the search box; space-joined text for substring matching only.

name = None pydantic-field

[unverified] Human-readable label for the record; what it names depends on the schema.

has_been_receipted = None pydantic-field

[unverified] Purpose unconfirmed and observed always false, so do not rely on it to tell whether a tenant has paid.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

lot_id = None pydantic-field

GUID of the property (PropertyMe calls a property a 'lot') the row belongs to; matches Id in /lots. Null where not tied to a property.

contact_id = None pydantic-field

GUID of the counterparty contact this row links to (never the row's own key, which is Id); which party depends on the containing schema.

tenancy_start = None pydantic-field

Date the tenant's occupancy began; stays fixed across lease renewals while AgreementStart moves forward.

agreement_start = None pydantic-field

Start date of the tenancy's current lease agreement term; pairs with AgreementEnd.

agreement_end = None pydantic-field

Last day of the fixed-term lease agreement; null for periodic tenancies and often in the past once a tenancy rolls on.

periodic = None pydantic-field

True when the tenancy is running periodically (rolling, no fixed end date) rather than on a fixed term.

tenancy_end = None pydantic-field

Date the tenant vacates; null while running with no end date. Setting it flags the property as vacating, even while still active.

termination = None pydantic-field

[unverified] Date recorded when a tenancy is ended other than by simply running its term out; null unless closed.

break_lease = None pydantic-field

[unverified] Date associated with a tenant breaking a fixed-term lease early; null unless the lease was broken.

notes = None pydantic-field

Record-level free-text notes kept by staff; an internal, unstructured scratchpad.

rent_amount = None pydantic-field

Rent in AUD for one RentPeriod (the sibling field giving the unit, so never assume weekly).

rent_period = None pydantic-field

Period the accompanying rent amount covers: weekly, fortnightly or monthly (lower case); null when there is no rent, e.g. sales-only properties.

bond_amount = None pydantic-field

Bond required for the tenancy, in AUD, as a one-off lump sum (not a per-period amount).

open_bond_received = None pydantic-field

[unverified] Bond money in AUD recorded as received against the tenancy's BondAmount.

bond_reference = None pydantic-field

Reference recorded for the tenancy's rental bond once lodged with the state bond authority (in Victoria, the RTBA lodgement number). Free text.

bond_in_trust = None pydantic-field

[unverified] Bond money in AUD attributed to the tenancy under a 'bond in trust' heading; typically 0.00.

bank_reference = None pydantic-field

Deposit reference a tenant quotes when paying rent so the payment auto-allocates to that tenancy's ledger. Text, not numeric.

tax_on_rent = None pydantic-field

Whether GST applies to the rent on this tenancy; true for commercial leases, false for GST-free residential rent.

generate_rent_invoice = None pydantic-field

Whether PropertyMe automatically raises a rent invoice each period instead of just receipting rent as it arrives.

rent_invoice_days_in_advance = None pydantic-field

Whole days ahead of the rent due date that PropertyMe raises the automatic rent invoice; days, not money.

receipt_warning = None pydantic-field

Free-text warning shown to staff before money is receipted against this tenancy.

next_increase_amount = None pydantic-field

The new full rent amount in AUD that takes effect on NextIncreaseDate, for the same RentPeriod as RentAmount.

next_increase_date = None pydantic-field

Calendar date a scheduled rent increase takes effect, raising rent to NextIncreaseAmount; both null together mean no increase is queued.

rent_sequence = None pydantic-field

[unverified] A small whole-number counter attached to a tenancy's rent; not money and not periods paid in advance.

paid_to = None pydantic-field

Date the tenant's rent is paid up to, counting whole rent periods only and ignoring any part payment.

effective_paid_to = None pydantic-field

Date the tenant's rent is effectively paid up to, including credit for any part payment.

part_paid = None pydantic-field

Rent received in AUD that is not enough to cover the next whole rent period, held as a part payment beyond PaidTo.

prorata_to = None pydantic-field

Date up to which a pro-rata (part-period) rent charge has been raised on the tenancy.

review_frequency = None pydantic-field

How often the rent on this tenancy is reviewed, expressed in months.

next_review_date = None pydantic-field

Date the next rent review is due; often in the past when overdue. Computed from LastReviewedOn plus ReviewFrequency, or falls back to AgreementEnd.

last_reviewed_on = None pydantic-field

UTC timestamp of the last recorded rent review; null if never reviewed. Anchors NextReviewDate (this plus ReviewFrequency months).

direct_debit = None pydantic-field

Whether automatic direct debit collection of rent is switched on for this tenancy.

direct_debit_fixed_amount = None pydantic-field

Fixed amount in AUD to pull on each direct debit run instead of the rent due at the time.

direct_debit_frequency = None pydantic-field

[unverified] Schedule selector for direct debit runs on this tenancy; not a money amount and not the rent frequency.

next_direct_debit_date = None pydantic-field

Date the next tenant direct debit is scheduled; null when DirectDebit is false (its usual state), populated only when direct debit is enabled.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

exclude_arrears_automation = None pydantic-field

When true, the tenancy is deliberately excluded from PropertyMe's automated arrears follow-up.

is_water_usage_charged = None pydantic-field

True when the tenancy is set up to on-charge the usage portion of the water bill to the tenant.

bond_due_date = None pydantic-field

Date the rental bond is due; usually equals TenancyStart. Read with BondAmount, BondReceipted and BondArrears to tell if the bond is outstanding.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

TenancyBalance pydantic-model

Bases: TenancyBalanceData

Financial balance view of a tenancy (rent, arrears, bond).

Same entity and IDs as Tenancy, and includes closed tenancies, but is NOT a field superset: it lacks some tenancy fields and names the tenant phone fields differently (see specs/restore-tenancies-table.md).

Config:

  • populate_by_name: True

Fields:

first_name = None pydantic-field

Given name of the person the row is about; blank for company-style and placeholder records.

last_name = None pydantic-field

Family name of the person the row is about; on placeholder records it may hold a label, not a surname.

salutation = None pydantic-field

How a person is addressed in correspondence (the text after 'Dear'); free text, often the first name.

lot_address = None pydantic-field

Full single-line street address of the tenancy's property, formatted by PropertyMe; the field to show a person.

lot_reference = None pydantic-field

Display label of the property identified by LotId, copied from its Reference. Free text, not a full address; null on rows with no property.

contact_reference = None pydantic-field

Display name of the contact identified by ContactId on the same row, copied from that contact's Reference. Null when ContactId is null.

contact_email = None pydantic-field

Email of the contact linked via ContactId (supplier on a job, tenant party on a tenancy); empty when none.

home_phone = None pydantic-field

Home/after-hours number of the person the row describes, as free-text staff entry; empty string when none.

work_phone = None pydantic-field

Business/daytime number of the person the row describes, as free-text staff entry; empty or null when none.

cell_phone = None pydantic-field

Mobile phone of the person the row describes, as free-text staff entry; empty string when none recorded.

normalised_mobile_phone = None pydantic-field

E.164 international form of CellPhone; null when the raw entry is not machine-readable.

folio_number = None pydantic-field

Sequential trust folio number, assigned in creation order and unique per tenancy; Code is this zero-padded as 'TENnnnnn'.

code = None pydantic-field

Short human-readable ledger code: a type prefix plus the zero-padded folio number (e.g. TEN##### for a tenancy, OWN##### for an ownership).

is_active = None pydantic-field

True when this is the property's current tenancy; the flag to filter on for currently managed tenants.

bond_receipted = None pydantic-field

[unverified] Bond in AUD receipted against the tenancy; reduces BondArrears, and usually holds the full BondAmount.

deposited = None pydantic-field

Deposit money in AUD held on the folio, separate from rent (PartPaid) and bond (BondReceipted/OpenBondReceived).

direct_deposited = None pydantic-field

[unverified] Deposit figure in AUD; observed 0.00 throughout, meaning undetermined, so do not read 0.00 as 'no bank-transfer deposit'.

arrears_days = None pydantic-field

Whole days rent is behind; positive means behind, negative means paid in advance, 0 means paid exactly to date.

invoice_days_in_arrears = None pydantic-field

Days the tenancy's unpaid invoices have been overdue; 0 unless InvoiceArrears is non-zero.

invoice_arrears = None pydantic-field

Unpaid balance in AUD of invoices raised against the tenancy, separate from paid-to rent; adds into TotalArrears.

closed_on = None pydantic-field

UTC timestamp when the record reached its terminal state (job Completed, inspection Closed, task closed); null while open, usually equal to UpdatedOn.

prorata_rent_due = None pydantic-field

[unverified] Money side of a pro rata (part-period) rent charge, matching the ProrataTo date; observed 0 throughout.

most_rent_due = None pydantic-field

[unverified] Rent-owing amount in AUD; byte-identical to RentArrears on every row, so read RentArrears and never sum the two.

rent_due_by_period = None pydantic-field

Signed whole-period rent balance in AUD; positive means owed, negative means paid ahead, the only rent field carrying a credit.

total_rent_paid = None pydantic-field

[unverified] Observed 0 throughout, including decades-old tenancies, so not a usable total of rent received; use PaidTo/PartPaid instead.

uncleared_balance = None pydantic-field

[unverified] Balance in AUD; observed 0.00 throughout, meaning undetermined, so do not rely on it in agent logic.

pending_payments = None pydantic-field

Payments in AUD initiated against the tenancy but not yet settled; not an amount owing (that is TotalArrears).

pending_rent_payments = None pydantic-field

Rent slice of PendingPayments in AUD: tenant rent payments initiated but not yet settled.

pending_invoice_payments = None pydantic-field

Invoice slice of PendingPayments in AUD: payments against tenant invoices initiated but not yet settled.

pending_deposit_payments = None pydantic-field

Deposit slice of PendingPayments in AUD: deposit money paid but not yet settled, matching the Deposited bucket.

pending_bond_payments = None pydantic-field

Bond slice of PendingPayments in AUD: bond paid but not yet settled, so not yet in BondReceipted/OpenBondReceived.

mepay_status = None pydantic-field

Whether MePay (PropertyMe's tenant-initiated online payment facility) is enabled for the tenancy; a string status.

active_ownership_id = None pydantic-field

GUID of the ownership (the landlord's management agreement over this property) currently in force. Null when there is no current ownership.

has_active_owner = None pydantic-field

True when the property behind this tenancy still has a current ownership; tracks presence of ActiveOwnershipId.

contact_person_count = None pydantic-field

[unverified] Observed 0 throughout even when the tenant contact has people attached; not a usable count, use ContactPersons via ContactId.

days_in_arrears = None pydantic-field

Whole days rent is behind; identical to ArrearsDays, same sign convention (positive behind, negative ahead).

rent_arrears = None pydantic-field

Rent owed today in AUD, accrued pro rata by day from EffectivePaidTo; floors at 0, so credit is invisible here.

rent_arrears_by_period = None pydantic-field

Rent arrears in AUD counted in whole rent periods, not days; equals max(0, RentDueByPeriod), never negative.

folio_id = None pydantic-field

GUID of the trust-account folio (money ledger) the record settles through. All-zero GUID means no folio of its own; treat as null.

bond_arrears = None pydantic-field

Bond still to be collected in AUD; equals max(0, BondAmount less BondReceipted less OpenBondReceived), never negative.

total_arrears = None pydantic-field

Everything the tenant owes in AUD on the pro rata basis; equals RentArrears + BondArrears + InvoiceArrears, never negative.

total_arrears_by_period = None pydantic-field

Total owing in AUD on the whole-period basis; equals RentArrearsByPeriod + BondArrears + InvoiceArrears, never negative.

is_closed = None pydantic-field

True when the tenancy's folio has been closed (ledger finalised); tracks the presence of a ClosedOn timestamp.

label = None pydantic-field

Ready-made display caption for the record, exactly as PropertyMe renders it in its UI.

search_text = None pydantic-field

Denormalised search blob backing the search box; space-joined text for substring matching only.

name = None pydantic-field

[unverified] Human-readable label for the record; what it names depends on the schema.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

lot_id = None pydantic-field

GUID of the property (PropertyMe calls a property a 'lot') the row belongs to; matches Id in /lots. Null where not tied to a property.

contact_id = None pydantic-field

GUID of the counterparty contact this row links to (never the row's own key, which is Id); which party depends on the containing schema.

tenancy_start = None pydantic-field

Date the tenant's occupancy began; stays fixed across lease renewals while AgreementStart moves forward.

agreement_start = None pydantic-field

Start date of the tenancy's current lease agreement term; pairs with AgreementEnd.

agreement_end = None pydantic-field

Last day of the fixed-term lease agreement; null for periodic tenancies and often in the past once a tenancy rolls on.

periodic = None pydantic-field

True when the tenancy is running periodically (rolling, no fixed end date) rather than on a fixed term.

tenancy_end = None pydantic-field

Date the tenant vacates; null while running with no end date. Setting it flags the property as vacating, even while still active.

termination = None pydantic-field

[unverified] Date recorded when a tenancy is ended other than by simply running its term out; null unless closed.

break_lease = None pydantic-field

[unverified] Date associated with a tenant breaking a fixed-term lease early; null unless the lease was broken.

notes = None pydantic-field

Record-level free-text notes kept by staff; an internal, unstructured scratchpad.

rent_amount = None pydantic-field

Rent in AUD for one RentPeriod (the sibling field giving the unit, so never assume weekly).

rent_period = None pydantic-field

Period the accompanying rent amount covers: weekly, fortnightly or monthly (lower case); null when there is no rent, e.g. sales-only properties.

bond_amount = None pydantic-field

Bond required for the tenancy, in AUD, as a one-off lump sum (not a per-period amount).

open_bond_received = None pydantic-field

[unverified] Bond money in AUD recorded as received against the tenancy's BondAmount.

bond_reference = None pydantic-field

Reference recorded for the tenancy's rental bond once lodged with the state bond authority (in Victoria, the RTBA lodgement number). Free text.

bond_in_trust = None pydantic-field

[unverified] Bond money in AUD attributed to the tenancy under a 'bond in trust' heading; typically 0.00.

bank_reference = None pydantic-field

Deposit reference a tenant quotes when paying rent so the payment auto-allocates to that tenancy's ledger. Text, not numeric.

tax_on_rent = None pydantic-field

Whether GST applies to the rent on this tenancy; true for commercial leases, false for GST-free residential rent.

generate_rent_invoice = None pydantic-field

Whether PropertyMe automatically raises a rent invoice each period instead of just receipting rent as it arrives.

rent_invoice_days_in_advance = None pydantic-field

Whole days ahead of the rent due date that PropertyMe raises the automatic rent invoice; days, not money.

receipt_warning = None pydantic-field

Free-text warning shown to staff before money is receipted against this tenancy.

next_increase_amount = None pydantic-field

The new full rent amount in AUD that takes effect on NextIncreaseDate, for the same RentPeriod as RentAmount.

next_increase_date = None pydantic-field

Calendar date a scheduled rent increase takes effect, raising rent to NextIncreaseAmount; both null together mean no increase is queued.

rent_sequence = None pydantic-field

[unverified] A small whole-number counter attached to a tenancy's rent; not money and not periods paid in advance.

paid_to = None pydantic-field

Date the tenant's rent is paid up to, counting whole rent periods only and ignoring any part payment.

effective_paid_to = None pydantic-field

Date the tenant's rent is effectively paid up to, including credit for any part payment.

part_paid = None pydantic-field

Rent received in AUD that is not enough to cover the next whole rent period, held as a part payment beyond PaidTo.

prorata_to = None pydantic-field

Date up to which a pro-rata (part-period) rent charge has been raised on the tenancy.

allow_mepay_payments = None pydantic-field

Tenancy-level permission for the tenant to pay via MePay; boolean, tracks MepayStatus being enabled.

review_frequency = None pydantic-field

How often the rent on this tenancy is reviewed, expressed in months.

next_review_date = None pydantic-field

Date the next rent review is due; often in the past when overdue. Computed from LastReviewedOn plus ReviewFrequency, or falls back to AgreementEnd.

last_reviewed_on = None pydantic-field

UTC timestamp of the last recorded rent review; null if never reviewed. Anchors NextReviewDate (this plus ReviewFrequency months).

direct_debit = None pydantic-field

Whether automatic direct debit collection of rent is switched on for this tenancy.

direct_debit_fixed_amount = None pydantic-field

Fixed amount in AUD to pull on each direct debit run instead of the rent due at the time.

direct_debit_frequency = None pydantic-field

[unverified] Schedule selector for direct debit runs on this tenancy; not a money amount and not the rent frequency.

next_direct_debit_date = None pydantic-field

Date the next tenant direct debit is scheduled; null when DirectDebit is false (its usual state), populated only when direct debit is enabled.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

exclude_arrears_automation = None pydantic-field

When true, the tenancy is deliberately excluded from PropertyMe's automated arrears follow-up.

is_water_usage_charged = None pydantic-field

True when the tenancy is set up to on-charge the usage portion of the water bill to the tenant.

bond_due_date = None pydantic-field

Date the rental bond is due; usually equals TenancyStart. Read with BondAmount, BondReceipted and BondArrears to tell if the bond is outstanding.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Member pydantic-model

Bases: CustomerMemberData

Agency team member model.

Represents a staff member at the property management agency. Used when fetching members via client.members.all().

Config:

  • populate_by_name: True

Fields:

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

user_id = None pydantic-field

Numeric account number for the person, separate from the GUID Id; opaque - join on Id, not this.

role = None pydantic-field

The member's access level; observed values are 'Admin' and 'Standard'. The only reliably populated access signal.

expire_on = None pydantic-field

Datetime when the member's access to the account is set to lapse; null means no expiry (an ongoing seat).

first_name = None pydantic-field

Given name of the person the row is about; blank for company-style and placeholder records.

last_name = None pydantic-field

Family name of the person the row is about; on placeholder records it may hold a label, not a surname.

company_name = None pydantic-field

Business or organisation name recorded against the person; empty or null when not recorded.

registered_email = None pydantic-field

Email address the member's PropertyMe login is registered under; identifies a login, not a published contact address.

registered_on = None pydantic-field

UTC timestamp of when the member's PropertyMe account was registered; not a last-login or activity time.

work_phone = None pydantic-field

Business/daytime number of the person the row describes, as free-text staff entry; empty or null when none.

mobile_phone = None pydantic-field

The member's mobile number as typed by the agency, free text and never normalised; null when none is recorded.

is_activated = None pydantic-field

Whether the member's PropertyMe login has been activated rather than sitting as an unaccepted invite.

agree_conditions_on = None pydantic-field

UTC timestamp of when the member accepted PropertyMe's terms and conditions.

region_code = None pydantic-field

[unverified] Per-member region code; 'ALL' means unrestricted. Could scope which region's properties they work on, or the legislative jurisdiction.

permissions = None pydantic-field

[unverified] Intended fine-grained permission set beyond Role, but commonly null; do not use for permission checks - rely on Role.

current_member_access_id = None pydantic-field

[unverified] A secondary GUID that appears to identify the member's current access grant; not a join key - use Id instead.

job_title = None pydantic-field

Free-text job title the agency types onto the member's profile for display; often null.

teams = None pydantic-field

[unverified] Intended PropertyMe team assignment (a member's team or a property's managing team); unverified.

is_support = None pydantic-field

[unverified] A flag of unconfirmed meaning; may mark a PropertyMe support account with access, or an agency-side support-staff designation.

is_billing_recipient = None pydantic-field

Marks the member who receives PropertyMe's own subscription invoices, not owner, tenant or supplier money.

is_two_factor_authentication_enabled = None pydantic-field

Whether the member has two-factor authentication enabled on their PropertyMe login.

name = None pydantic-field

[unverified] Human-readable label for the record; what it names depends on the schema.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Task pydantic-model

Bases: MobTaskQueryData

Task model for workflow tasks and reminders.

Used when fetching tasks via client.tasks.all() or client.tasks.get(id).

Config:

  • populate_by_name: True

Fields:

lot_reference = None pydantic-field

Display label of the property identified by LotId, copied from its Reference. Free text, not a full address; null on rows with no property.

tenant_reference = None pydantic-field

Display name of the tenant party identified by TenantContactId, copied verbatim from that contact's Reference. Null when there is no tenant.

owner_reference = None pydantic-field

Display name of the owner identified by OwnerContactId, copied verbatim from that contact's Reference, including any suffix staff typed.

contact_reference = None pydantic-field

Display name of the contact identified by ContactId on the same row, copied from that contact's Reference. Null when ContactId is null.

manager_name = None pydantic-field

Full name of the staff member responsible for this record, built untrimmed from the member's name parts.

active_property_manager_id = None pydantic-field

[unverified] Intended as the property's current managing staff member, but returns the all-zero GUID sentinel meaning 'no value' on this endpoint.

task_checklists = None pydantic-field

[unverified] List of checklist sub-steps under the task; an empty list (or null on the detail endpoint) means no checklist items.

task_type = None pydantic-field

Discriminator for the shared task/job model: 'Job' (from /jobtasks) or 'Task' (from /tasks).

timestamp = None pydantic-field

Change token (.NET tick count) for incremental sync, not a wall-clock value; pass it back to fetch records changed since that point.

type = None pydantic-field

What kind of thing the record is, from a different vocabulary per schema; never compare across schemas.

priority = None pydantic-field

[unverified] Importance/urgency ranking; the scale and declared type differ by entity, so never compare across types.

task_status = None pydantic-field

Workflow state of an office task, and the field to read for it; 'ToDo' is the open state.

created_by = None pydantic-field

Member Id (GUID) of the staff member who created the record; resolve it against /members.

closed_by = None pydantic-field

[unverified] Member GUID of the staff member who closed the task; null while the task is open, and pairs with ClosedOn.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

due_date = None pydantic-field

Calendar date the inspection, job or task is due; always set and equal to StartTime's date for inspections, optional and often null otherwise.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

closed_on = None pydantic-field

UTC timestamp when the record reached its terminal state (job Completed, inspection Closed, task closed); null while open, usually equal to UpdatedOn.

summary = None pydantic-field

One-line title of the record - what it is about; the long body lives in Description.

description = None pydantic-field

Long-form free-text body of the record; the counterpart to the one-line Summary.

lot_id = None pydantic-field

GUID of the property (PropertyMe calls a property a 'lot') the row belongs to; matches Id in /lots. Null where not tied to a property.

tenant_contact_id = None pydantic-field

GUID of the contact record for the tenancy's tenant party; matches Ids from /contacts and /contacts/tenants. Null when vacant.

owner_contact_id = None pydantic-field

GUID of the contact record for the property's owner (landlord, or vendor on a sale); matches Ids from /contacts and /contacts/ownerships.

contact_id = None pydantic-field

GUID of the counterparty contact this row links to (never the row's own key, which is Id); which party depends on the containing schema.

manager_member_id = None pydantic-field

GUID of the staff member responsible for this job, task or inspection; resolves against Id in /members. Not the property's manager.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Inspection pydantic-model

Bases: ChangedInspectionData

Inspection model for list operations (maps to ChangedInspectionData).

Config:

  • populate_by_name: True

Fields:

lot_reference = None pydantic-field

Display label of the property identified by LotId, copied from its Reference. Free text, not a full address; null on rows with no property.

address_text = None pydantic-field

The property's full street address rendered as one comma-separated line.

key_number = None pydantic-field

The agency's key-tag identifier for the property's keys, as written on the tag.

longitude = None pydantic-field

WGS84 longitude in decimal degrees; uses the same -999 'never geocoded' sentinel as Latitude.

latitude = None pydantic-field

WGS84 latitude in decimal degrees (negative in Australia); -999 is the 'never geocoded' sentinel.

tenant_reference = None pydantic-field

Display name of the tenant party identified by TenantContactId, copied verbatim from that contact's Reference. Null when there is no tenant.

owner_reference = None pydantic-field

Display name of the owner identified by OwnerContactId, copied verbatim from that contact's Reference, including any suffix staff typed.

publish_on = None pydantic-field

[unverified] Relates to publishing the inspection report to owner and tenant; whether it records the publish moment or a scheduled publish date is unconfirmed.

lot_main_photo_document_id = None pydantic-field

GUID of the property's main photo (the lot's MainPhotoDocumentId), not a photo taken at the inspection; null when the property has none.

manager_name = None pydantic-field

Full name of the staff member responsible for this record, built untrimmed from the member's name parts.

timestamp = None pydantic-field

Change token (.NET tick count) for incremental sync, not a wall-clock value; pass it back to fetch records changed since that point.

inspection_report = None pydantic-field

The condition report attached to an inspection; null when no report has been started.

previous_exit_report = None pydantic-field

[unverified] A second InspectionReport, by name the prior exit condition report, carried alongside the inspection's own report for comparison; only on the /inspections list.

current_rent_amount = None pydantic-field

The property's current rent in AUD for one CurrentRentPeriod, copied live from the lot record, not the rent at inspection time.

current_rent_period = None pydantic-field

The rent period that CurrentRentAmount covers (weekly, fortnightly, or monthly), copied from the property's RentPeriod.

status_text = None pydantic-field

Display rendering of Status for the UI; character-for-character identical to Status where present.

start_time_text = None pydantic-field

The inspection's StartTime rendered as a local time of day for display (e.g. '9:00 am'); use StartTime for anything computational.

is_published = None pydantic-field

Whether the inspection's report has been published/released to the tenant and owner.

start_time = None pydantic-field

Scheduled start of the inspection appointment in local wall-clock time with a misleading 'Z' suffix; do not timezone-convert. Its date equals DueDate.

duration = None pydantic-field

Scheduled length of the inspection appointment in minutes, measured from StartTime.

type = None pydantic-field

What kind of thing the record is, from a different vocabulary per schema; never compare across schemas.

status = None pydantic-field

Workflow state of the record, from a different vocabulary per entity type; never compare across types.

listing_id = None pydantic-field

GUID of the listing an inspection belongs to; populated on open-for-inspection (Type 'Open') rows, effectively marking a public open home.

tenant_return_date = None pydantic-field

Date the tenant returned their completed condition report; null or the 0001-01-01 sentinel means not returned. The deadline is TenantReturnDueDate.

assigned_to_tenant_date = None pydantic-field

Date the inspection was handed to the tenant to complete their condition report; null or the 0001-01-01 sentinel means not assigned.

inspection_snapshot_id = None pydantic-field

[unverified] On inspection rows, by name a pointer to a saved snapshot of the inspection. Meaning unconfirmed; commonly null.

status_prior_to_closing = None pydantic-field

For a closed inspection, the workflow status it held immediately before closing.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

due_date = None pydantic-field

Calendar date the inspection, job or task is due; always set and equal to StartTime's date for inspections, optional and often null otherwise.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

closed_on = None pydantic-field

UTC timestamp when the record reached its terminal state (job Completed, inspection Closed, task closed); null while open, usually equal to UpdatedOn.

summary = None pydantic-field

One-line title of the record - what it is about; the long body lives in Description.

description = None pydantic-field

Long-form free-text body of the record; the counterpart to the one-line Summary.

lot_id = None pydantic-field

GUID of the property (PropertyMe calls a property a 'lot') the row belongs to; matches Id in /lots. Null where not tied to a property.

tenant_contact_id = None pydantic-field

GUID of the contact record for the tenancy's tenant party; matches Ids from /contacts and /contacts/tenants. Null when vacant.

owner_contact_id = None pydantic-field

GUID of the contact record for the property's owner (landlord, or vendor on a sale); matches Ids from /contacts and /contacts/ownerships.

contact_id = None pydantic-field

GUID of the counterparty contact this row links to (never the row's own key, which is Id); which party depends on the containing schema.

manager_member_id = None pydantic-field

GUID of the staff member responsible for this job, task or inspection; resolves against Id in /members. Not the property's manager.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

InspectionDetail pydantic-model

Bases: GetInspectionResponse

Full inspection details from GetInspectionResponse wrapper.

Returned by client.inspections.get(id). Contains inspection task data, report, property, owner/tenant contacts, and rental information.

Config:

  • populate_by_name: True

Fields:

inspection = None pydantic-field

The inspection task itself (InspectionTaskData): scheduling, property/tenant references and Summary/Description.

property = None pydantic-field

The property (Lot) record the inspection is against, resolved from the inspection's LotId; the single-lot /lots/{Id} object, a subset of the /lots grid row.

tenant = None pydantic-field

Contact record of the tenant party the inspection is against, resolved from the inspection's TenantContactId; null when the inspection has no tenant.

owner = None pydantic-field

The landlord's Contact record for the inspected property, resolved from the inspection's OwnerContactId; the reliable 'who owns this' handle on an inspection.

inspection_report = None pydantic-field

The condition report attached to an inspection; null when no report has been started.

current_rent_amount = None pydantic-field

Lease rent charged on the inspected property for one CurrentRentPeriod, in AUD; the agreed tenancy rent, not the advertised asking rent.

current_rent_period = None pydantic-field

Rent period that CurrentRentAmount covers, lower-case: monthly, weekly, or fortnightly; copied from the property's lot-level RentPeriod.

owner_folio_id = None pydantic-field

GUID of the ownership folio (the trust ledger the owner is paid from) for the inspected property; use it to join to folio/money data.

listing_info = None pydantic-field

Display-only one-line summary of the listing this inspection belongs to, formatted ' -

'; empty when the inspection has no listing.

inspection_tenant_status = None pydantic-field

Block (TenantStatus, TenantReturnedDate) reporting the tenant's side of the condition-report review workflow; null members mean the report was never sent to the tenant.

tenant_has_reviewed = None pydantic-field

Whether the tenant has completed reviewing this inspection's condition report; read false as 'no tenant review recorded', not 'tenant rejected the report'.

subscription_allows_tenant_to_review_ecr = None pydantic-field

Account-level flag: whether the PropertyMe subscription includes the feature letting a tenant review the entry condition report (ECR) online.

requires_tenant_reviews_ecr_feature = None pydantic-field

Per-inspection boolean for the tenant entry-condition-report (ECR) review flow; true when this inspection's report goes to the tenant for review, false otherwise.

response_status = None pydantic-field

Error envelope on wrapped single-record responses; null when the call succeeded.

is_successful = None pydantic-field

Envelope flag on wrapped single-record responses; true when the payload is filled, false when the call failed.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Job pydantic-model

Bases: MobJobTaskQueryData

Maintenance job model.

Represents a maintenance work order in PropertyMe. Used when fetching jobs via client.jobs.all() or client.jobs.get(id).

Config:

  • populate_by_name: True

Fields:

lot_reference = None pydantic-field

Display label of the property identified by LotId, copied from its Reference. Free text, not a full address; null on rows with no property.

tenant_reference = None pydantic-field

Display name of the tenant party identified by TenantContactId, copied verbatim from that contact's Reference. Null when there is no tenant.

owner_reference = None pydantic-field

Display name of the owner identified by OwnerContactId, copied verbatim from that contact's Reference, including any suffix staff typed.

contact_reference = None pydantic-field

Display name of the contact identified by ContactId on the same row, copied from that contact's Reference. Null when ContactId is null.

manager_name = None pydantic-field

Full name of the staff member responsible for this record, built untrimmed from the member's name parts.

is_letter_statement = None pydantic-field

[unverified] Boolean flag on a task or maintenance job, apparently marking it as tied to an owner statement letter rather than ordinary work.

statement_id = None pydantic-field

[unverified] On task and job rows, by name a link to an owner statement. All-zero GUID means not linked; treat as null. Real semantics untested.

task_type = None pydantic-field

Discriminator for the shared task/job model: 'Job' (from /jobtasks) or 'Task' (from /tasks).

timestamp = None pydantic-field

Change token (.NET tick count) for incremental sync, not a wall-clock value; pass it back to fetch records changed since that point.

owner_attending = None pydantic-field

Flag on a maintenance job that the property owner is expected to attend the supplier's visit; typically false.

tenant_attending = None pydantic-field

Flag on a maintenance job that the tenant is expected to attend the supplier's visit; typically false.

supplier_reference = None pydantic-field

Display name of the supplier (the trade) assigned to a job, copied from that supplier contact's Reference. Null while no supplier is assigned.

display_number = None pydantic-field

Human-facing job/task number: the numeric Number field zero-padded to five characters.

number = None pydantic-field

[unverified] Means different things per schema (int32 job number, string street number, int32 area sequence), and the declared type moves with the meaning.

status = None pydantic-field

Workflow state of the record, from a different vocabulary per entity type; never compare across types.

reported_contact_type = None pydantic-field

Which party reported the maintenance job: one of 'Owner', 'Agent' or 'Tenant'; commonly 'Tenant'.

access = None pydantic-field

For a maintenance job, who the tradesperson obtains property access from (e.g. Tenant, Agent, Owner).

main_photo_document_id = None pydantic-field

GUID of the image document used as this row's main photo or thumbnail, resolvable through the matching .../images endpoint. Null when none uploaded.

[unverified] Purpose unverified; the name suggests display text for a document linked to the job, but it is null in every observation. Use /jobtasks/{Id}/documents instead.

quote_id = None pydantic-field

GUID of the quotation record attached to a job, retrievable through /jobtasks/{Id}/quotations. Set only once a quote exists on the job.

supplier_instructions = None pydantic-field

Free-text instructions written for the supplier/tradesperson attending the job; null when none entered.

id = None pydantic-field

The record's own primary key (a GUID) and the join target for *Id fields on other records. All-zero GUID means no persisted identity.

customer_id = None pydantic-field

GUID of the PropertyMe customer account (the agency) that owns the record. Constant within a token's data, so useless as a filter or join key.

due_date = None pydantic-field

Calendar date the inspection, job or task is due; always set and equal to StartTime's date for inspections, optional and often null otherwise.

created_on = None pydantic-field

UTC timestamp when the record was first created in PropertyMe; migrated records carry the migration date, not the real-world start of the relationship.

closed_on = None pydantic-field

UTC timestamp when the record reached its terminal state (job Completed, inspection Closed, task closed); null while open, usually equal to UpdatedOn.

summary = None pydantic-field

One-line title of the record - what it is about; the long body lives in Description.

description = None pydantic-field

Long-form free-text body of the record; the counterpart to the one-line Summary.

lot_id = None pydantic-field

GUID of the property (PropertyMe calls a property a 'lot') the row belongs to; matches Id in /lots. Null where not tied to a property.

tenant_contact_id = None pydantic-field

GUID of the contact record for the tenancy's tenant party; matches Ids from /contacts and /contacts/tenants. Null when vacant.

owner_contact_id = None pydantic-field

GUID of the contact record for the property's owner (landlord, or vendor on a sale); matches Ids from /contacts and /contacts/ownerships.

contact_id = None pydantic-field

GUID of the counterparty contact this row links to (never the row's own key, which is Id); which party depends on the containing schema.

manager_member_id = None pydantic-field

GUID of the staff member responsible for this job, task or inspection; resolves against Id in /members. Not the property's manager.

labels = None pydantic-field

The record's free-form tags, serialised as each tag wrapped in pipes and concatenated.

updated_on = None pydantic-field

UTC timestamp of the last change to this record itself; equals CreatedOn until edited. Related-entity changes bump Timestamp, not this field.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

AddressDetail pydantic-model

Bases: AddressDetail

Config:

  • populate_by_name: True

Fields:

unit = None pydantic-field

Unit, flat, apartment or suite identifier within the street number; free text, not always numeric, empty string (never null) when the dwelling is not subdivided.

number = None pydantic-field

[unverified] Means different things per schema (int32 job number, string street number, int32 area sequence), and the declared type moves with the meaning.

street = None pydantic-field

Street name, normally including the street type but not the number; free text, un-normalised, empty string when unset.

suburb = None pydantic-field

Suburb or town of the address; free text, not case-normalised, empty string when unset, so always casefold before comparing or grouping.

locality = None pydantic-field

[unverified] Additional locality line of the address; not observed populated (empty string) and never rendered into Text, so treat as unused.

postal_code = None pydantic-field

Postcode of the address, returned as a string not an integer; empty string when unset, mostly Australian four-digit codes but overseas formats appear.

state = None pydantic-field

[unverified] Two meanings by context: the Australian state/territory of an address, or a listing's advertising lifecycle state.

country = None pydantic-field

Country as a free-text name, not an ISO code; empty string (meaning 'not entered', not 'no country') on most addresses, Australia being the safe default.

building_name = None pydantic-field

[unverified] Free-text label prefixed to the street line when the address is rendered into Text; empty string otherwise.

mailbox_name = None pydantic-field

Postal delivery box line (PO Box, Locked Bag) for non-street addresses; seen only on contact addresses, empty string otherwise.

latitude = None pydantic-field

WGS84 latitude in decimal degrees (negative in Australia); -999 is the 'never geocoded' sentinel.

longitude = None pydantic-field

WGS84 longitude in decimal degrees; uses the same -999 'never geocoded' sentinel as Latitude.

street_long_name = None pydantic-field

[unverified] Purpose unconfirmed; not observed populated (empty string) and never rendered into Text, so use Street or Text instead.

text = None pydantic-field

The whole address preformatted for display; use this for showing or matching an address rather than reassembling the individual components.

reference = None pydantic-field

PropertyMe's human-readable display label for the record: free text, agency-editable, capped around 50 characters.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)

Authentication

OAuth 2.0 authentication for PropertyMe API.

PropertyMeAuthenticator(client_id, client_secret, redirect_url, scopes)

Initialize the authenticator with all the required keys and paths that it needs to do the OAuth2 flow.

Source code in src/pypropertyme/auth.py
21
22
23
24
25
26
def __init__(self, client_id: str, client_secret: str, redirect_url: str, scopes: list[str]):
    """Initialize the authenticator with all the required keys and paths that it needs to do the OAuth2 flow."""
    self.client_id = client_id
    self.client_secret = client_secret
    self.redirect_url = redirect_url
    self.scopes = scopes

CallbackHandler(redirect_url, *args, **kwargs)

Bases: BaseHTTPRequestHandler

HTTP handler for OAuth callback.

Source code in src/pypropertyme/auth.py
66
67
68
def __init__(self, redirect_url: str, *args, **kwargs) -> None:
    self.redirect_url = redirect_url
    super().__init__(*args, **kwargs)

do_GET()

Handle GET request to the callback URL.

Source code in src/pypropertyme/auth.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def do_GET(self):
    """Handle GET request to the callback URL."""
    # Parse the path from URL
    parsed_path = urlparse(self.path)
    path = parsed_path.path
    expected_path = urlparse(self.redirect_url).path

    # Check if this is our callback path
    if not path.startswith(expected_path):
        self.send_response(404)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(b"<html><body><h1>404 Not Found</h1></body></html>")
        return

    # Send success response
    self.send_response(200)
    self.send_header("Content-type", "text/html")
    self.end_headers()

    # Parse query parameters
    query = parse_qs(parsed_path.query)
    print(f"Received callback with query params: {list(query.keys())}", file=sys.stderr)

    # Verify state parameter to prevent CSRF
    if "state" not in query or query["state"][0] != self.server.state:
        self.wfile.write(b"<html><body><h1>Authentication Failed</h1>")
        self.wfile.write(b"<p>Invalid state parameter. Possible CSRF attack.</p></body></html>")
        print(
            f"State mismatch: expected {self.server.state}, got {query.get('state', ['None'])[0]}",
            file=sys.stderr,
        )
        return

    # Extract authorization code
    if "code" in query:
        self.server.auth_code = query["code"][0]
        print(f"Authorization code received: {self.server.auth_code[:10]}...", file=sys.stderr)
        self.wfile.write(b"<html><body><h1>Authentication Successful</h1>")
        self.wfile.write(b"<p>You can close this window and return to the CLI.</p></body></html>")
    else:
        print("No authorization code found in callback URL", file=sys.stderr)
        self.wfile.write(b"<html><body><h1>Authentication Failed</h1>")
        self.wfile.write(b"<p>No authorization code received.</p></body></html>")

log_message(format, *args)

Override to prevent server logs from cluttering output.

Source code in src/pypropertyme/auth.py
115
116
117
def log_message(self, format, *args):
    """Override to prevent server logs from cluttering output."""
    return

get_tokens_from_auth_code(auth_code) async

Exchange authorization code for tokens using Authlib.

Source code in src/pypropertyme/auth.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
async def get_tokens_from_auth_code(self, auth_code: str) -> dict[str, Any]:
    """Exchange authorization code for tokens using Authlib."""
    print(f"Exchanging auth code for tokens: {auth_code[:10]}...", file=sys.stderr)

    try:
        async with AsyncOAuth2Client(
            client_id=self.client_id, client_secret=self.client_secret, redirect_uri=self.redirect_url
        ) as client:
            # Exchange the code for tokens
            token = await client.fetch_token(TOKEN_URL, code=auth_code, grant_type="authorization_code")
            print(f"Token exchange successful: {list(token.keys())}", file=sys.stderr)
            return token
    except Exception as e:
        raise ValueError(f"Failed to exchange authorization code for tokens: {str(e)}") from e

construct_auth_url(random_state)

Construct the authorization URL

Source code in src/pypropertyme/auth.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def construct_auth_url(self, random_state: str) -> str:
    """Construct the authorization URL"""
    oauth_params = {
        "client_id": self.client_id,
        "redirect_uri": self.redirect_url,
        "scope": " ".join(self.scopes),
        "response_type": "code",
        "state": random_state,
        # "access_type": "offline",  # Request a refresh token
    }

    query = "&".join([f"{k}={v}" for k, v in oauth_params.items()])
    auth_url = f"{AUTH_URL}?{query}"
    return auth_url

get_auth_code()

Start a local server and open browser for OAuth authentication.

Method uses a blocking HTTP server.

Source code in src/pypropertyme/auth.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def get_auth_code(self) -> str | None:
    """Start a local server and open browser for OAuth authentication.

    Method uses a blocking HTTP server.
    """
    # Parse the redirect URI to extract host and port default
    redirect_url = urlparse(self.redirect_url)
    host = redirect_url.hostname or "localhost"
    port = redirect_url.port or 65385

    # Create server
    handler = partial(self.CallbackHandler, self.redirect_url)
    server = HTTPServer((host, port), handler)
    server.auth_code = None

    # Generate secure random state for CSRF protection
    state = secrets.token_urlsafe(16)
    server.state = state

    auth_url = self.construct_auth_url(state)

    print(f"Opening authorization URL: {auth_url}", file=sys.stderr)
    webbrowser.open(auth_url)

    print("Opening browser for authentication...", file=sys.stderr)
    print("Waiting for callback... (press Ctrl+C to cancel)", file=sys.stderr)

    # Wait for callback
    try:
        server.timeout = 300
        print("Server started, waiting for callback...", file=sys.stderr)
        # Handle request blocks until a request is received
        server.handle_request()

        # If we didn't get an auth code in the first request, wait for another
        if not server.auth_code:
            print("No auth code received, waiting for another request...", file=sys.stderr)
            server.handle_request()
    finally:
        server.server_close()

    return server.auth_code

authenticate()

Performs the full authentication flow, getting the auth code and exchanging it for tokens.

Source code in src/pypropertyme/auth.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def authenticate(self) -> None | dict[str, Any]:
    """Performs the full authentication flow, getting the auth code and exchanging it for tokens."""
    try:
        auth_code = self.get_auth_code()
        if not auth_code:
            print("Authentication failed: No authorization code received.", file=sys.stderr)
            return

        print(f"Authorization code received: {auth_code[:10]}...", file=sys.stderr)

        # Exchange for tokens
        print("Exchanging authorization code for tokens...", file=sys.stderr)
        token = asyncio.run(self.get_tokens_from_auth_code(auth_code))
        print("Authentication successful!", file=sys.stderr)

        return token
    except Exception as e:
        raise ValueError(f"Error during authentication: {str(e)}") from e

PropertyMeAuthProvider(token, client_id, client_secret, token_saver_callback=None)

Bases: AuthenticationProvider

Async authentication provider for PropertyME API.

Provider extends the Kiota's AuthenticationProvider class and implements the authentication logic for PropertyME API using Authlib for OAuth2.

Initialize the authentication provider with optional token file path.

Parameters:

Name Type Description Default
token dict[str, Any]

OAuth2 token dictionary containing 'access_token', 'refresh_token', 'expires_at', etc.

required
client_id str

OAuth2 client ID for the PropertyME API application.

required
client_secret str

OAuth2 client secret for the PropertyME API application.

required
token_saver_callback TokenSaverCallbackT | None

Optional async callback function to save updated tokens when they are refreshed. The callback receives the updated token dictionary as an argument.

None
Source code in src/pypropertyme/auth.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def __init__(
    self,
    token: dict[str, Any],
    client_id: str,
    client_secret: str,
    token_saver_callback: TokenSaverCallbackT | None = None,
):
    """Initialize the authentication provider with optional token file path.

    Args:
        token: OAuth2 token dictionary containing 'access_token', 'refresh_token', 'expires_at', etc.
        client_id: OAuth2 client ID for the PropertyME API application.
        client_secret: OAuth2 client secret for the PropertyME API application.
        token_saver_callback: Optional async callback function to save updated tokens when they are refreshed.
            The callback receives the updated token dictionary as an argument.
    """
    self.token = token
    self.client_id = client_id
    self.client_secret = client_secret
    self.token_saver_callback = token_saver_callback

    self._init_client()

authenticate_request(request, additional_authentication_context={}) async

Authenticate the request with the current access token.

Parameters:

Name Type Description Default
request RequestInformation

The request information object to authenticate.

required
additional_authentication_context dict[str, Any]

Additional context for authentication (not used).

{}
Source code in src/pypropertyme/auth.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
async def authenticate_request(
    self, request: RequestInformation, additional_authentication_context: dict[str, Any] = {}
) -> None:
    """Authenticate the request with the current access token.

    Args:
        request: The request information object to authenticate.
        additional_authentication_context: Additional context for authentication (not used).
    """
    if not request.request_headers:
        request.headers = HeadersCollection()

    await self._client.ensure_active_token(self.token)
    request.headers.add("Authorization", f"Bearer {self.token.get('access_token')}")

Base Model

Base class for all PyPropertyMe models.

BasePMeModel pydantic-model

Bases: BaseModel

A base model that facilitates conversion between Pydantic models and Kiota models.

This class provides functionality to convert between Pydantic models (used for validation and serialization) and Kiota models (used for Microsoft Graph API communication).

Attributes:

Name Type Description
kiota_model T

The Kiota Model. Each subclass should set this to allow conversion from a Pydantic model to its Kiota counterpart.

Config:

  • use_attribute_docstrings: True

Fields:

model_config = ConfigDict(use_attribute_docstrings=True) class-attribute instance-attribute

datamodel-codegen --use-field-description emits PropertyMe's field documentation as attribute docstrings. Pydantic ignores those unless this is set, which would leave field.description empty and strip the documentation out of model_json_schema() -- exactly what LLM agents consume.

model_validate_api(kiota_model, client) classmethod

Convert a Kiota model to this Pydantic model.

This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

Optionally injects the client into the Pydantic model.

Parameters:

Name Type Description Default
kiota_model Parsable

The Kiota model to convert

required
client PropertyMeClient | None

The client to interact with the API.

required

Returns:

Name Type Description
Self Self

An instance of this class populated with data from the Kiota model

Source code in src/pypropertyme/base.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@classmethod
def model_validate_api(cls, kiota_model: Parsable, client: Client) -> Self:
    """Convert a Kiota model to this Pydantic model.

    This method takes a Kiota model, serializes it to JSON, and then creates a Pydantic model from that JSON.

    Optionally injects the client into the Pydantic model.

    Args:
        kiota_model (Parsable): The Kiota model to convert
        client (PropertyMeClient | None): The client to interact with the API.

    Returns:
        Self: An instance of this class populated with data from the Kiota model
    """
    # Example of model_validate https://github.com/SIMBAChain/simba-sdk-for-python/blob/9a185ac85cecb2f41316aa463507edefb0e9ccdf/simba_sdk/core/domain.py#L31
    # JSON Serialization example: https://github.com/nir-ontar/ontar-poc/blob/6cc0dcb5be3d47f013da9ab55046011367d28683/backend/utils/data.py#L13
    writer = _json_writer_factory.get_serialization_writer("application/json")
    kiota_model.serialize(writer)
    m = cls.model_validate_json(writer.get_serialized_content())
    m._client = client
    return m

model_dump_api()

Convert this Pydantic model to its Kiota counterpart.

This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

Returns:

Name Type Description
T T

The Kiota model populated with data from this Pydantic model

Raises:

Type Description
ValueError

If kiota_model is not set on the class.

Source code in src/pypropertyme/base.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def model_dump_api(self) -> T:
    """Convert this Pydantic model to its Kiota counterpart.

    This method serializes the Pydantic model to JSON and then deserializes it into a Kiota model.

    Returns:
        T: The Kiota model populated with data from this Pydantic model

    Raises:
        ValueError: If ``kiota_model`` is not set on the class.
    """
    if not self._kiota_model:
        raise ValueError("kiota_model is not set")

    parse_node = ParseNodeFactoryRegistry().get_root_parse_node(
        content_type="application/json", content=self.model_dump_json(by_alias=True).encode("utf-8")
    )
    return parse_node.get_object_value(self._kiota_model)