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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
334
335
336
337
338
339
340
341
342
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
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 APIError(message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404)
    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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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
168
169
170
171
172
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
223
224
225
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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
279
280
281
282
283
284
285
286
287
288
289
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
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 APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            )
    except APIError as e:
        if e.response_status_code == 404:
            raise APIError(
                message=f"{target_model.__name__} with ID {id} does not exist.", response_status_code=404
            ) 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:

  • _client (Client)
  • id (str | None)
  • customer_id (str | None)
  • special_type (str | None)
  • roles (list[str] | None)
  • archived_on (datetime | None)
  • account_details (list[FolioAccount] | None)
  • reference (str | None)
  • website (str | None)
  • abn (str | None)
  • person_migrated (bool | None)
  • labels (str | None)
  • notes (str | None)
  • name_text (str | None)
  • postal_address_text (str | None)
  • physical_address_text (str | None)
  • has_tenant_invoice_account (bool | None)
  • home_phone (str | None)
  • work_phone (str | None)
  • cell_phone (str | None)
  • trade_name (str | None)
  • email (str | None)
  • supplier_chart_account_id (str | None)
  • is_archived (bool | None)
  • is_supplier (bool | None)
  • is_tenant (bool | None)
  • is_owner (bool | None)
  • is_seller (bool | None)
  • phone_text (str | None)
  • work_phone_text (str | None)
  • contact_phone (str | None)
  • created_on (datetime | None)
  • updated_on (datetime | None)
  • contact_persons (list[ContactPerson] | None)
  • primary_contact_person (ContactPerson | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • contact (Contact | None)
  • contact_persons (list[ContactPersonInfo] | None)
  • code (str | None)
  • folio_id (str | None)
  • payment_priority (int | None)
  • auto_approve_bill (bool | None)
  • tenant_invoice_chart_account_id (str | None)
  • reminder_count (int | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • owner_contact_reference (str | None)
  • tenant_contact_reference (str | None)
  • rent_amount (float | None)
  • rent_period (str | None)
  • tenancy_start (datetime | None)
  • tenancy_end (datetime | None)
  • agreement_start (datetime | None)
  • agreement_end (datetime | None)
  • owner_contact_id (str | None)
  • tenant_contact_id (str | None)
  • vacant (bool | None)
  • manager_name (str | None)
  • effective_paid_to (datetime | None)
  • ownership_updated_on (datetime | None)
  • tenancy_updated_on (datetime | None)
  • sale_agreement_updated_on (datetime | None)
  • strata_manager_contact_name (str | None)
  • has_access_details (bool | None)
  • timestamp (int | None)
  • property_type (str | None)
  • commercial_category (str | None)
  • id (str | None)
  • customer_id (str | None)
  • reference (str | None)
  • address (AddressDetail | None)
  • address_text (str | None)
  • primary_type (str | None)
  • property_subtype (str | None)
  • bedrooms (int | None)
  • bathrooms (int | None)
  • car_spaces (int | None)
  • area (float | None)
  • area_unit (str | None)
  • land_area (float | None)
  • land_area_unit (str | None)
  • description (str | None)
  • notes (str | None)
  • next_inspection_on (datetime | None)
  • key_number (str | None)
  • archived_on (datetime | None)
  • ad_rent_amount (float | None)
  • ad_rent_period (str | None)
  • active_ownership_id (str | None)
  • active_sale_agreement_id (str | None)
  • active_tenancy_id (str | None)
  • longitude (float | None)
  • latitude (float | None)
  • inspection_frequency (int | None)
  • inspection_frequency_type (str | None)
  • initial_inspection_frequency (int | None)
  • initial_inspection_frequency_type (str | None)
  • main_photo_document_id (str | None)
  • active_manager_member_id (str | None)
  • labels (str | None)
  • strata_manager_contact_id (str | None)
  • rural_category (str | None)
  • display_address (bool | None)
  • is_rental (bool | None)
  • is_archived (bool | None)
  • created_on (datetime | None)
  • updated_on (datetime | None)
  • external_listing_id (str | None)
  • active_rental_listing_id (str | None)
  • active_sale_listing_id (str | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • ownership (OwnershipSqlApiData | None)
  • active_ownership_id (str | None)
  • active_sale_agreement_id (str | None)
  • active_tenancy_id (str | None)
  • active_rental_listing_id (str | None)
  • active_sale_listing_id (str | None)
  • address (AddressDetail | None)
  • address_text (str | None)
  • archived_on (datetime | None)
  • bathrooms (int | None)
  • bedrooms (int | None)
  • car_spaces (int | None)
  • area (float | None)
  • area_unit (str | None)
  • land_area (float | None)
  • land_area_unit (str | None)
  • description (str | None)
  • id (str | None)
  • key_number (str | None)
  • labels (str | None)
  • next_inspection_on (datetime | None)
  • notes (str | None)
  • property_manager (str | None)
  • primary_type (str | None)
  • property_subtype (str | None)
  • reference (str | None)
  • longitude (float | None)
  • latitude (float | None)
  • task_reminders_count (int | None)
  • tenancy (TenancyBalanceData | None)
  • sale_listing (ListingInfo | None)
  • rental_listing (ListingInfo | None)
  • has_listing_provider_import_in_progress (bool | None)
  • rent_owing_pre_vacate (float | None)
  • rent_overpaid (float | None)
  • pro_rata_status (FolioBalanceInfo | None)
  • strata_manager_contact_id (str | None)
  • strata_manager_contact_name (str | None)
  • has_active_invoice_template (bool | None)
  • has_access_details (bool | None)
  • response_status (ResponseStatus | None)
  • is_successful (bool | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • contact_reference (str | None)
  • lot_address (str | None)
  • lot_reference (str | None)
  • folio_number (int | None)
  • contact_email (str | None)
  • contact_normalised_mobile_phone (str | None)
  • contact_cell_phone (str | None)
  • contact_work_phone (str | None)
  • contact_home_phone (str | None)
  • contact_phone (str | None)
  • is_active (bool | None)
  • is_closed (bool | None)
  • active_ownership_id (str | None)
  • code (str | None)
  • is_client_access_disabled (bool | None)
  • label (str | None)
  • search_text (Object | None)
  • name (str | None)
  • has_been_receipted (bool | None)
  • id (str | None)
  • customer_id (str | None)
  • lot_id (str | None)
  • contact_id (str | None)
  • tenancy_start (datetime | None)
  • agreement_start (datetime | None)
  • agreement_end (datetime | None)
  • periodic (bool | None)
  • tenancy_end (datetime | None)
  • termination (datetime | None)
  • break_lease (datetime | None)
  • notes (str | None)
  • rent_amount (float | None)
  • rent_period (str | None)
  • bond_amount (float | None)
  • open_bond_received (float | None)
  • bond_reference (str | None)
  • bond_in_trust (float | None)
  • bank_reference (str | None)
  • tax_on_rent (bool | None)
  • generate_rent_invoice (bool | None)
  • rent_invoice_days_in_advance (int | None)
  • receipt_warning (str | None)
  • next_increase_amount (float | None)
  • next_increase_date (datetime | None)
  • rent_sequence (int | None)
  • paid_to (datetime | None)
  • effective_paid_to (datetime | None)
  • part_paid (float | None)
  • prorata_to (datetime | None)
  • review_frequency (int | None)
  • next_review_date (datetime | None)
  • last_reviewed_on (datetime | None)
  • direct_debit (bool | None)
  • direct_debit_fixed_amount (float | None)
  • direct_debit_frequency (str | None)
  • next_direct_debit_date (datetime | None)
  • created_on (datetime | None)
  • updated_on (datetime | None)
  • exclude_arrears_automation (bool | None)
  • is_water_usage_charged (bool | None)
  • bond_due_date (datetime | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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

Tenancy with financial balance information.

Superset of Tenancy including rent, arrears, and bond data.

Config:

  • populate_by_name: True

Fields:

  • _client (Client)
  • first_name (str | None)
  • last_name (str | None)
  • salutation (str | None)
  • lot_address (str | None)
  • lot_reference (str | None)
  • contact_reference (str | None)
  • contact_email (str | None)
  • home_phone (str | None)
  • work_phone (str | None)
  • cell_phone (str | None)
  • normalised_mobile_phone (str | None)
  • folio_number (int | None)
  • code (str | None)
  • is_active (bool | None)
  • bond_receipted (float | None)
  • deposited (float | None)
  • direct_deposited (float | None)
  • arrears_days (int | None)
  • invoice_days_in_arrears (int | None)
  • invoice_arrears (float | None)
  • closed_on (datetime | None)
  • prorata_rent_due (float | None)
  • most_rent_due (float | None)
  • rent_due_by_period (float | None)
  • total_rent_paid (float | None)
  • uncleared_balance (float | None)
  • pending_payments (float | None)
  • pending_rent_payments (float | None)
  • pending_invoice_payments (float | None)
  • pending_deposit_payments (float | None)
  • pending_bond_payments (float | None)
  • mepay_status (str | None)
  • active_ownership_id (str | None)
  • has_active_owner (bool | None)
  • contact_person_count (int | None)
  • days_in_arrears (int | None)
  • rent_arrears (float | None)
  • rent_arrears_by_period (float | None)
  • folio_id (str | None)
  • bond_arrears (float | None)
  • total_arrears (float | None)
  • total_arrears_by_period (float | None)
  • is_closed (bool | None)
  • label (str | None)
  • search_text (str | None)
  • name (str | None)
  • id (str | None)
  • customer_id (str | None)
  • lot_id (str | None)
  • contact_id (str | None)
  • tenancy_start (datetime | None)
  • agreement_start (datetime | None)
  • agreement_end (datetime | None)
  • periodic (bool | None)
  • tenancy_end (datetime | None)
  • termination (datetime | None)
  • break_lease (datetime | None)
  • notes (str | None)
  • rent_amount (float | None)
  • rent_period (str | None)
  • bond_amount (float | None)
  • open_bond_received (float | None)
  • bond_reference (str | None)
  • bond_in_trust (float | None)
  • bank_reference (str | None)
  • tax_on_rent (bool | None)
  • generate_rent_invoice (bool | None)
  • rent_invoice_days_in_advance (int | None)
  • receipt_warning (str | None)
  • next_increase_amount (float | None)
  • next_increase_date (datetime | None)
  • rent_sequence (int | None)
  • paid_to (datetime | None)
  • effective_paid_to (datetime | None)
  • part_paid (float | None)
  • prorata_to (datetime | None)
  • allow_mepay_payments (bool | None)
  • review_frequency (int | None)
  • next_review_date (datetime | None)
  • last_reviewed_on (datetime | None)
  • direct_debit (bool | None)
  • direct_debit_fixed_amount (float | None)
  • direct_debit_frequency (str | None)
  • next_direct_debit_date (datetime | None)
  • created_on (datetime | None)
  • updated_on (datetime | None)
  • exclude_arrears_automation (bool | None)
  • is_water_usage_charged (bool | None)
  • bond_due_date (datetime | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • id (str | None)
  • customer_id (str | None)
  • user_id (int | None)
  • role (str | None)
  • expire_on (datetime | None)
  • first_name (str | None)
  • last_name (str | None)
  • company_name (str | None)
  • registered_email (str | None)
  • registered_on (datetime | None)
  • work_phone (str | None)
  • mobile_phone (str | None)
  • is_activated (bool | None)
  • agree_conditions_on (datetime | None)
  • region_code (str | None)
  • permissions (str | None)
  • current_member_access_id (str | None)
  • job_title (str | None)
  • teams (str | None)
  • is_support (bool | None)
  • is_billing_recipient (bool | None)
  • is_two_factor_authentication_enabled (bool | None)
  • name (str | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • lot_reference (str | None)
  • tenant_reference (str | None)
  • owner_reference (str | None)
  • contact_reference (str | None)
  • manager_name (str | None)
  • active_property_manager_id (str | None)
  • task_checklists (list[TaskChecklistMobile] | None)
  • task_type (str | None)
  • timestamp (int | None)
  • type (str | None)
  • priority (int | None)
  • task_status (str | None)
  • created_by (str | None)
  • closed_by (str | None)
  • id (str | None)
  • customer_id (str | None)
  • due_date (datetime | None)
  • created_on (datetime | None)
  • closed_on (datetime | None)
  • summary (str | None)
  • description (str | None)
  • lot_id (str | None)
  • tenant_contact_id (str | None)
  • owner_contact_id (str | None)
  • contact_id (str | None)
  • manager_member_id (str | None)
  • labels (str | None)
  • updated_on (datetime | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • lot_reference (str | None)
  • address_text (str | None)
  • key_number (str | None)
  • longitude (float | None)
  • latitude (float | None)
  • tenant_reference (str | None)
  • owner_reference (str | None)
  • publish_on (datetime | None)
  • lot_main_photo_document_id (str | None)
  • manager_name (str | None)
  • timestamp (int | None)
  • inspection_report (InspectionReport | None)
  • previous_exit_report (InspectionReport | None)
  • current_rent_amount (float | None)
  • current_rent_period (str | None)
  • status_text (str | None)
  • start_time_text (str | None)
  • is_published (bool | None)
  • start_time (datetime | None)
  • duration (int | None)
  • type (str | None)
  • status (str | None)
  • listing_id (str | None)
  • tenant_return_date (datetime | None)
  • assigned_to_tenant_date (datetime | None)
  • inspection_snapshot_id (str | None)
  • status_prior_to_closing (str | None)
  • id (str | None)
  • customer_id (str | None)
  • due_date (datetime | None)
  • created_on (datetime | None)
  • closed_on (datetime | None)
  • summary (str | None)
  • description (str | None)
  • lot_id (str | None)
  • tenant_contact_id (str | None)
  • owner_contact_id (str | None)
  • contact_id (str | None)
  • manager_member_id (str | None)
  • labels (str | None)
  • updated_on (datetime | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • inspection (InspectionTaskData | None)
  • property (Lot | None)
  • tenant (Contact | None)
  • owner (Contact | None)
  • inspection_report (InspectionReport | None)
  • current_rent_amount (float | None)
  • current_rent_period (str | None)
  • owner_folio_id (str | None)
  • listing_info (str | None)
  • inspection_tenant_status (InspectionTenantStatusDto | None)
  • tenant_has_reviewed (bool | None)
  • subscription_allows_tenant_to_review_ecr (bool | None)
  • requires_tenant_reviews_ecr_feature (bool | None)
  • response_status (ResponseStatus | None)
  • is_successful (bool | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • lot_reference (str | None)
  • tenant_reference (str | None)
  • owner_reference (str | None)
  • contact_reference (str | None)
  • manager_name (str | None)
  • is_letter_statement (bool | None)
  • statement_id (str | None)
  • task_type (str | None)
  • timestamp (int | None)
  • owner_attending (bool | None)
  • tenant_attending (bool | None)
  • supplier_reference (str | None)
  • display_number (str | None)
  • number (int | None)
  • status (str | None)
  • reported_contact_type (str | None)
  • access (str | None)
  • main_photo_document_id (str | None)
  • document_link_text_name (str | None)
  • quote_id (str | None)
  • supplier_instructions (str | None)
  • id (str | None)
  • customer_id (str | None)
  • due_date (datetime | None)
  • created_on (datetime | None)
  • closed_on (datetime | None)
  • summary (str | None)
  • description (str | None)
  • lot_id (str | None)
  • tenant_contact_id (str | None)
  • owner_contact_id (str | None)
  • contact_id (str | None)
  • manager_member_id (str | None)
  • labels (str | None)
  • updated_on (datetime | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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:

  • _client (Client)
  • unit (str | None)
  • number (str | None)
  • street (str | None)
  • suburb (str | None)
  • locality (str | None)
  • postal_code (str | None)
  • state (str | None)
  • country (str | None)
  • building_name (str | None)
  • mailbox_name (str | None)
  • latitude (float | None)
  • longitude (float | None)
  • street_long_name (str | None)
  • text (str | None)
  • reference (str | None)
  • _kiota_model (ClassVar)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
20
21
22
23
24
25
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
65
66
67
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
 69
 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
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: {query}")

    # 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]}")
        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]}...")
        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")
        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
111
112
113
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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]}...")

    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())}")
            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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
115
116
117
118
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
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}")
    webbrowser.open(auth_url)

    print("Opening browser for authentication...")
    print("Waiting for callback... (press Ctrl+C to cancel)")

    # Wait for callback
    try:
        server.timeout = 300
        print("Server started, waiting for callback...")
        # 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...")
            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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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.")
            return

        print(f"Authorization code received: {auth_code[:10]}...")

        # Exchange for tokens
        print("Exchanging authorization code for tokens...")
        token = asyncio.run(self.get_tokens_from_auth_code(auth_code))
        print("Authentication successful!")

        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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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.

Fields:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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)