Skip to content

Account

pyfamilysafety.account.Account

Account(api)

A family member with Digital Safety enabled.

Attributes:

Name Type Description
user_id

Microsoft member identifier.

role

Family role from the roster API.

first_name

Member first name.

surname

Member last name.

profile_picture

Profile image URL.

devices list[Device]

Registered devices, populated by :meth:update.

applications list[Application]

Apps from the activity report, populated by :meth:update.

today_screentime_usage int

Total device screen time today in milliseconds.

average_screentime_usage float

Daily average screen time from the API.

screentime_usage dict

Raw device screen-time report JSON.

application_usage dict

Raw app activity report JSON.

blocked_platforms list[OverrideTarget]

Platforms with an active device override block.

account_balance float

Microsoft Store allowance balance when available.

account_currency str

Currency code for account_balance.

experimental bool

Mirrors the parent :class:FamilySafety experimental flag.

Init an account.

Source code in pyfamilysafety/account.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __init__(self, api) -> None:
    """Init an account."""
    self.user_id = None
    self.role = None
    self.profile_picture = None
    self.first_name = None
    self.surname = None
    self.devices: list[Device] = None
    self.applications: list[Application] = []
    self.today_screentime_usage: int = None
    self.average_screentime_usage: float = None
    self.screentime_usage: dict = None
    self.application_usage: dict = None
    self.blocked_platforms: list[OverrideTarget] = None
    self.experimental: bool = False
    self._api: FamilySafetyAPI = api
    self.account_balance: float = 0.0
    self.account_currency: str = ""
    self._account_callbacks: list = []

user_id instance-attribute

user_id = None

role instance-attribute

role = None

profile_picture instance-attribute

profile_picture = None

first_name instance-attribute

first_name = None

surname instance-attribute

surname = None

devices instance-attribute

devices = None

applications instance-attribute

applications = []

today_screentime_usage instance-attribute

today_screentime_usage = None

average_screentime_usage instance-attribute

average_screentime_usage = None

screentime_usage instance-attribute

screentime_usage = None

application_usage instance-attribute

application_usage = None

blocked_platforms instance-attribute

blocked_platforms = None

experimental instance-attribute

experimental = False

account_balance instance-attribute

account_balance = 0.0

account_currency instance-attribute

account_currency = ''

add_account_callback

add_account_callback(callback)

Add a callback to the account.

Source code in pyfamilysafety/account.py
60
61
62
63
64
65
def add_account_callback(self, callback):
    """Add a callback to the account."""
    if not callable(callback):
        raise ValueError("Object must be callable.")
    if callback not in self._account_callbacks:
        self._account_callbacks.append(callback)

remove_account_callback

remove_account_callback(callback)

Remove a given account callback.

Source code in pyfamilysafety/account.py
67
68
69
70
71
72
def remove_account_callback(self, callback):
    """Remove a given account callback."""
    if not callable(callback):
        raise ValueError("Object must be callable.")
    if callback in self._account_callbacks:
        self._account_callbacks.remove(callback)

update async

update()

Update all account details.

Source code in pyfamilysafety/account.py
 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
async def update(self) -> None:
    """Update all account details."""
    begin_time, end_time = self._default_usage_time_range()
    (
        devices_response,
        device_usage,
        application_usage,
        overrides_response,
        _,
    ) = await asyncio.gather(
        self._api.async_get_user_devices(user_id=self.user_id),
        self._api.async_get_user_device_screentime_usage(
            user_id=self.user_id,
            begin_time=begin_time,
            end_time=end_time,
            device_count=4,
            platform="ALL",
        ),
        self._api.async_get_user_app_screentime_usage(
            user_id=self.user_id,
            begin_time=begin_time,
            end_time=end_time,
            platform="ALL",
        ),
        self._api.async_get_override_device_restrictions(user_id=self.user_id),
        self._get_account_balance(),
    )
    self._apply_screentime_usage(device_usage.get("json"), application_usage.get("json"))
    self.devices = Device.from_dict(devices_response.get("json"), self.screentime_usage)
    self._apply_applications()
    self._update_device_blocked(overrides_response.get("json"))
    for cb in self._account_callbacks:
        if is_awaitable(cb):
            await cb()
        else:
            cb()

get_screentime_usage async

get_screentime_usage(start_time=None, end_time=None, device_count=4, platform='ALL')

Return screen time usage for a time range.

Parameters:

Name Type Description Default
start_time datetime

Range start; defaults to start of today (local).

None
end_time datetime

Range end; defaults to end of today (local).

None
device_count int

Maximum devices in the device usage report.

4
platform str

Platform filter (e.g. ALL, WINDOWS, XBOX).

'ALL'

Returns:

Type Description
dict

When default times are used, returns cached screentime_usage after

dict

updating account fields. Otherwise a dict with devices and

dict

applications keys containing raw JSON payloads.

Source code in pyfamilysafety/account.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
async def get_screentime_usage(self,
                               start_time: datetime = None,
                               end_time: datetime = None,
                               device_count: int = 4,
                               platform: str = "ALL") -> dict:
    """Return screen time usage for a time range.

    Args:
        start_time: Range start; defaults to start of today (local).
        end_time: Range end; defaults to end of today (local).
        device_count: Maximum devices in the device usage report.
        platform: Platform filter (e.g. ``ALL``, ``WINDOWS``, ``XBOX``).

    Returns:
        When default times are used, returns cached ``screentime_usage`` after
        updating account fields. Otherwise a dict with ``devices`` and
        ``applications`` keys containing raw JSON payloads.
    """
    default = False
    if start_time is None:
        default = True
        start_time = localise_datetime(datetime.combine(date.today(), time(0,0,0), tzinfo=API_TIMEZONE))
    if end_time is None:
        default = True
        end_time = localise_datetime(datetime.combine(date.today(), time(23,59,59), tzinfo=API_TIMEZONE))

    begin_time = quote_plus(start_time.strftime('%Y-%m-%dT%H:%M:%S%z'))
    end_time_param = quote_plus(end_time.strftime('%Y-%m-%dT%H:%M:%S%z'))

    device_usage, application_usage = await asyncio.gather(
        self._api.async_get_user_device_screentime_usage(
            user_id=self.user_id,
            begin_time=begin_time,
            end_time=end_time_param,
            device_count=device_count,
            platform=platform
        ),
        self._api.async_get_user_app_screentime_usage(
            user_id=self.user_id,
            begin_time=begin_time,
            end_time=end_time_param,
            platform=platform
        ),
    )

    if default:
        self._apply_screentime_usage(device_usage.get("json"), application_usage.get("json"))
        return self.screentime_usage
    return {
        "devices": device_usage.get("json"),
        "applications": application_usage.get("json")
    }

get_device

get_device(device_id)

Returns a single device.

Source code in pyfamilysafety/account.py
222
223
224
def get_device(self, device_id) -> Device:
    """Returns a single device."""
    return [x for x in self.devices if x.device_id == device_id][0]

get_application

get_application(application_id)

Returns a single application.

Source code in pyfamilysafety/account.py
226
227
228
def get_application(self, application_id) -> Application:
    """Returns a single application."""
    return [x for x in self.applications if x.app_id == application_id][0]

set_device_limits async

set_device_limits(schedule)

Set screen time limits for a platform on the account.

Parameters:

Name Type Description Default
schedule DeviceLimitsSchedule

Platform-specific limits built from :class:~pyfamilysafety.schedule.DeviceLimitsSchedule.

required

Returns:

Type Description
dict

The API response body, if present.

Source code in pyfamilysafety/account.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
async def set_device_limits(self, schedule: DeviceLimitsSchedule) -> dict:
    """Set screen time limits for a platform on the account.

    Args:
        schedule: Platform-specific limits built from
            :class:`~pyfamilysafety.schedule.DeviceLimitsSchedule`.

    Returns:
        The API response body, if present.
    """
    body = schedule.to_dict()
    body["time"] = localise_datetime(datetime.now()).strftime("%Y-%m-%dT%H:%M:%S%z")
    response = await self._api.async_update_schedule(
        user_id=self.user_id,
        body=body,
    )
    return response.get("json")

override_device async

override_device(target, override, valid_until=None, culture='en-GB')

Block or unblock a platform for this member.

Parameters:

Name Type Description Default
target OverrideTarget

Platform to override (:class:~pyfamilysafety.enum.OverrideTarget).

required
override OverrideType

OverrideType.UNTIL to block until a time, or OverrideType.CANCEL to remove a block.

required
valid_until datetime

Required when override is UNTIL; ignored for CANCEL (set to now internally).

None
culture str

Locale string sent to the API (default en-GB).

'en-GB'

Returns:

Type Description
bool

True on success (implicit; method updates local block state).

Raises:

Type Description
ValueError

If override is UNTIL and valid_until is omitted.

Source code in pyfamilysafety/account.py
248
249
250
251
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
278
279
280
281
282
async def override_device(self,
                          target: OverrideTarget,
                          override: OverrideType,
                          valid_until: datetime = None,
                          culture: str = "en-GB") -> bool:
    """Block or unblock a platform for this member.

    Args:
        target: Platform to override (:class:`~pyfamilysafety.enum.OverrideTarget`).
        override: ``OverrideType.UNTIL`` to block until a time, or
            ``OverrideType.CANCEL`` to remove a block.
        valid_until: Required when ``override`` is ``UNTIL``; ignored for
            ``CANCEL`` (set to now internally).
        culture: Locale string sent to the API (default ``en-GB``).

    Returns:
        ``True`` on success (implicit; method updates local block state).

    Raises:
        ValueError: If ``override`` is ``UNTIL`` and ``valid_until`` is omitted.
    """
    if override == OverrideType.UNTIL and valid_until is None:
        raise ValueError("valid_until is required if using OverrideType.UNTIL")
    if override == OverrideType.CANCEL:
        valid_until = datetime.now()
    response = await self._api.async_override_device_restriction(
        user_id=self.user_id,
        body={
            "target": str(target),
            "overrideType": str(override),
            "validUntil": standardise_datetime(valid_until).strftime("%Y-%m-%dT%H:%M:%S.000%z"),
            "culture": culture,
        }
    )
    self._update_device_blocked(response.get("json"))

get_web_restrictions async

get_web_restrictions()

Return current web filtering settings for this member.

Returns:

Type Description
dict

Parsed JSON from the web restrictions API.

Source code in pyfamilysafety/account.py
284
285
286
287
288
289
290
291
async def get_web_restrictions(self) -> dict:
    """Return current web filtering settings for this member.

    Returns:
        Parsed JSON from the web restrictions API.
    """
    response = await self._api.async_get_user_web_restrictions(user_id=self.user_id)
    return response.get("json")

update_web_restrictions async

update_web_restrictions(operations)

Apply web filtering changes using JSON Patch operations.

Parameters:

Name Type Description Default
operations list[dict]

List of patch operation dicts (op, path, value, etc.).

required

Returns:

Type Description
dict

Parsed JSON response from the API, if present.

Source code in pyfamilysafety/account.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
async def update_web_restrictions(self, operations: list[dict]) -> dict:
    """Apply web filtering changes using JSON Patch operations.

    Args:
        operations: List of patch operation dicts (``op``, ``path``, ``value``, etc.).

    Returns:
        Parsed JSON response from the API, if present.
    """
    response = await self._api.async_update_web_restrictions(
        user_id=self.user_id,
        body={"operations": operations},
    )
    return response.get("json")

add_web_exception async

add_web_exception(website, allowed=False, source='')

Add a web filtering exception for a specific website.

Parameters:

Name Type Description Default
website str

Domain or URL to except from the default filter.

required
allowed bool

True to always allow; False to always block.

False
source str

Optional source label sent to the API.

''

Returns:

Type Description
dict

Parsed JSON response from the API, if present.

Source code in pyfamilysafety/account.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
async def add_web_exception(
        self,
        website: str,
        allowed: bool = False,
        source: str = "") -> dict:
    """Add a web filtering exception for a specific website.

    Args:
        website: Domain or URL to except from the default filter.
        allowed: ``True`` to always allow; ``False`` to always block.
        source: Optional source label sent to the API.

    Returns:
        Parsed JSON response from the API, if present.
    """
    return await self.update_web_restrictions([{
        "op": "Add",
        "path": "/exceptions",
        "source": source,
        "value": {
            "website": website,
            "allowed": allowed,
        },
    }])

from_dict async classmethod

from_dict(api, raw_response, experimental)

Converts a roster request response to an array.

Source code in pyfamilysafety/account.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
@classmethod
async def from_dict(cls, api: FamilySafetyAPI, raw_response: dict, experimental: bool) -> list['Account']:
    """Converts a roster request response to an array."""
    accounts = []
    if "members" in raw_response.keys():
        members = raw_response.get("members")
        for member in members:
            if member.get("isDigitalSafetyEnabled"):
                account = cls(api)
                account.user_id = member.get("id")
                account.role = member.get("role")
                account.profile_picture = member.get("profilePicUrl")
                account.first_name = member.get("user").get("firstName")
                account.surname = member.get("user").get("lastName")
                account.experimental = experimental
                accounts.append(account)
        if accounts:
            await asyncio.gather(*(account.update() for account in accounts))

    return accounts