Skip to content

Items API Models

DestinyItemComponent

Bases: BaseModel

The base item component, filled with properties that are generally useful to know in any item request or that don't feel worthwhile to put in their own component.

Manifest Information

This model has some attributes which can be filled with additional information found in the manifest (manifest_...). Without additional work, these attributes will be None, since they require additional requests and database lookups.

To fill the manifest dependent attributes, either:

  • Run await ThisClass.fetch_manifest_information(), see here
  • Set Client.always_return_manifest_information to True, see here

Attributes:

Name Type Description
bind_status Union[ItemBindStatus, int]

If the item is bound to a location, it will be specified in this enum.

bucket_hash int

The hash identifier for the specific inventory bucket in which the item is located.

expiration_date datetime

If the item can expire, this is the date at which it will/did expire.

is_wrapper bool

If this is true, the object is actually a "wrapper" of the object it's representing. This means that it's not the actual item itself, but rather an item that must be "opened" in game before you have and can use the item. Wrappers are an evolution of "bundles", which give an easy way to let you preview the contents of what you purchased while still letting you get a refund before you "open" it.

item_hash int

The identifier for the item's definition, which is where most of the useful static information for the item can be found.

item_instance_id int

If the item is instanced, it will have an instance ID. Lack of an instance ID implies that the item has no distinct local qualities aside from stack size.

item_value_visibility list[bool]

If available, a list that describes which item values (rewards) should be shown (true) or hidden (false).

location Union[ItemLocation, int]

An easy reference for where the item is located. Redundant if you got the item from an Inventory, but useful when making detail calls on specific items.

lockable bool

If the item can be locked, this will indicate that state.

metric_hash int

The identifier for the currently-selected metric definition, to be displayed on the emblem nameplate.

metric_objective DestinyObjectiveProgress

The objective progress for the currently-selected metric definition, to be displayed on the emblem nameplate.

override_style_item_hash int

If populated, this is the hash of the item whose icon (and other secondary styles, but not the human readable strings) should override whatever icons/styles are on the item being sold. If you don't do this, certain items whose styles are being overridden by socketed items - such as the "Recycle Shader" item - would show whatever their default icon/style is, and it wouldn't be pretty or look accurate.

quantity int

The quantity of the item in this stack. Note that Instanced items cannot stack. If an instanced item, this value will always be 1 (as the stack has exactly one item in it)

state Union[ItemState, int]

A flags enumeration indicating the transient/custom states of the item that affect how it is rendered: whether it's tracked or locked for example, or whether it has a masterwork plug inserted.

tooltip_notification_indexes list[int]

If this is populated, it is a list of indexes into DestinyInventoryItemDefinition.tooltipNotifications for any special tooltip messages that need to be shown for this item.

transfer_status Union[TransferStatuses, int]

If there is a known error state that would cause this item to not be transferable, this Flags enum will indicate all of those error states. Otherwise, it will be 0 (CanTransfer).

version_number int

The version of this item, used to index into the versions list in the item definition quality block.

manifest_bucket_hash Optional[DestinyInventoryBucketDefinition]

Manifest information for bucket_hash

manifest_item_hash Optional[DestinyInventoryItemDefinition]

Manifest information for item_hash

manifest_metric_hash Optional[DestinyMetricDefinition]

Manifest information for metric_hash

manifest_override_style_item_hash Optional[DestinyInventoryItemDefinition]

Manifest information for override_style_item_hash

Source code in src/bungio/models/bungie/destiny/entities/items.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
@custom_define()
class DestinyItemComponent(BaseModel):
    """
    The base item component, filled with properties that are generally useful to know in any item request or that don't feel worthwhile to put in their own component.

    Tip: Manifest Information
        This model has some attributes which can be filled with additional information found in the manifest (`manifest_...`).
        Without additional work, these attributes will be `None`, since they require additional requests and database lookups.

        To fill the manifest dependent attributes, either:

        - Run `await ThisClass.fetch_manifest_information()`, see [here](/API Reference/Models/base)
        - Set `Client.always_return_manifest_information` to `True`, see [here](/API Reference/client)

    Attributes:
        bind_status: If the item is bound to a location, it will be specified in this enum.
        bucket_hash: The hash identifier for the specific inventory bucket in which the item is located.
        expiration_date: If the item can expire, this is the date at which it will/did expire.
        is_wrapper: If this is true, the object is actually a "wrapper" of the object it's representing. This means that it's not the actual item itself, but rather an item that must be "opened" in game before you have and can use the item.  Wrappers are an evolution of "bundles", which give an easy way to let you preview the contents of what you purchased while still letting you get a refund before you "open" it.
        item_hash: The identifier for the item's definition, which is where most of the useful static information for the item can be found.
        item_instance_id: If the item is instanced, it will have an instance ID. Lack of an instance ID implies that the item has no distinct local qualities aside from stack size.
        item_value_visibility: If available, a list that describes which item values (rewards) should be shown (true) or hidden (false).
        location: An easy reference for where the item is located. Redundant if you got the item from an Inventory, but useful when making detail calls on specific items.
        lockable: If the item can be locked, this will indicate that state.
        metric_hash: The identifier for the currently-selected metric definition, to be displayed on the emblem nameplate.
        metric_objective: The objective progress for the currently-selected metric definition, to be displayed on the emblem nameplate.
        override_style_item_hash: If populated, this is the hash of the item whose icon (and other secondary styles, but *not* the human readable strings) should override whatever icons/styles are on the item being sold. If you don't do this, certain items whose styles are being overridden by socketed items - such as the "Recycle Shader" item - would show whatever their default icon/style is, and it wouldn't be pretty or look accurate.
        quantity: The quantity of the item in this stack. Note that Instanced items cannot stack. If an instanced item, this value will always be 1 (as the stack has exactly one item in it)
        state: A flags enumeration indicating the transient/custom states of the item that affect how it is rendered: whether it's tracked or locked for example, or whether it has a masterwork plug inserted.
        tooltip_notification_indexes: If this is populated, it is a list of indexes into DestinyInventoryItemDefinition.tooltipNotifications for any special tooltip messages that need to be shown for this item.
        transfer_status: If there is a known error state that would cause this item to not be transferable, this Flags enum will indicate all of those error states. Otherwise, it will be 0 (CanTransfer).
        version_number: The version of this item, used to index into the versions list in the item definition quality block.
        manifest_bucket_hash: Manifest information for `bucket_hash`
        manifest_item_hash: Manifest information for `item_hash`
        manifest_metric_hash: Manifest information for `metric_hash`
        manifest_override_style_item_hash: Manifest information for `override_style_item_hash`
    """

    bind_status: Union["ItemBindStatus", int] = custom_field(converter=enum_converter("ItemBindStatus"))
    bucket_hash: int = custom_field()
    expiration_date: datetime = custom_field()
    is_wrapper: bool = custom_field()
    item_hash: int = custom_field()
    item_instance_id: int = custom_field(metadata={"int64": True})
    item_value_visibility: list[bool] = custom_field(metadata={"type": """list[bool]"""})
    location: Union["ItemLocation", int] = custom_field(converter=enum_converter("ItemLocation"))
    lockable: bool = custom_field()
    metric_hash: int = custom_field()
    metric_objective: "DestinyObjectiveProgress" = custom_field()
    override_style_item_hash: int = custom_field()
    quantity: int = custom_field()
    state: Union["ItemState", int] = custom_field(converter=enum_converter("ItemState"))
    tooltip_notification_indexes: list[int] = custom_field(metadata={"type": """list[int]"""})
    transfer_status: Union["TransferStatuses", int] = custom_field(converter=enum_converter("TransferStatuses"))
    version_number: int = custom_field()
    manifest_bucket_hash: Optional["DestinyInventoryBucketDefinition"] = custom_field(default=None)
    manifest_item_hash: Optional["DestinyInventoryItemDefinition"] = custom_field(default=None)
    manifest_metric_hash: Optional["DestinyMetricDefinition"] = custom_field(default=None)
    manifest_override_style_item_hash: Optional["DestinyInventoryItemDefinition"] = custom_field(default=None)

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemInstanceComponent

Bases: BaseModel

If an item is "instanced", this will contain information about the item's instance that doesn't fit easily into other components. One might say this is the "essential" instance data for the item. Items are instanced if they require information or state that can vary. For instance, weapons are Instanced: they are given a unique identifier, uniquely generated stats, and can have their properties altered. Non-instanced items have none of these things: for instance, Glimmer has no unique properties aside from how much of it you own. You can tell from an item's definition whether it will be instanced or not by looking at the DestinyInventoryItemDefinition's definition.inventory.isInstanceItem property.

Manifest Information

This model has some attributes which can be filled with additional information found in the manifest (manifest_...). Without additional work, these attributes will be None, since they require additional requests and database lookups.

To fill the manifest dependent attributes, either:

  • Run await ThisClass.fetch_manifest_information(), see here
  • Set Client.always_return_manifest_information to True, see here

Attributes:

Name Type Description
breaker_type Union[DestinyBreakerType, int]

If populated, this item has a breaker type corresponding to the given value. See DestinyBreakerTypeDefinition for more details.

breaker_type_hash int

If populated, this is the hash identifier for the item's breaker type. See DestinyBreakerTypeDefinition for more details.

can_equip bool

If this is an equippable item, you can check it here. There are permanent as well as transitory reasons why an item might not be able to be equipped: check cannotEquipReason for details.

cannot_equip_reason Union[EquipFailureReason, int]

If you cannot equip the item, this is a flags enum that enumerates all of the reasons why you couldn't equip the item. You may need to refine your UI further by using unlockHashesRequiredToEquip and equipRequiredLevel.

damage_type Union[DamageType, int]

If the item has a damage type, this is the item's current damage type.

damage_type_hash int

The current damage type's hash, so you can look up localized info and icons for it.

energy DestinyItemInstanceEnergy

IF populated, this item supports Energy mechanics (i.e. Armor 2.0), and these are the current details of its energy type and available capacity to spend energy points.

equip_required_level int

If the item cannot be equipped until you reach a certain level, that level will be reflected here.

gear_tier int

Gear Tier, if applicable, fished up from the unlock value items.gear_tier

is_equipped bool

Is the item currently equipped on the given character?

item_level int

The Item's "Level" has the most significant bearing on its stats, such as Light and Power.

primary_stat DestinyStat

The item stat that we consider to be "primary" for the item. For instance, this would be "Attack" for Weapons or "Defense" for armor.

quality int

The "Quality" of the item has a lesser - but still impactful - bearing on stats like Light and Power.

unlock_hashes_required_to_equip list[int]

Sometimes, there are limitations to equipping that are represented by character-level flags called "unlocks". This is a list of flags that they need in order to equip the item that the character has not met. Use these to look up the descriptions to show in your UI by looking up the relevant DestinyUnlockDefinitions for the hashes.

manifest_breaker_type_hash Optional[DestinyBreakerTypeDefinition]

Manifest information for breaker_type_hash

manifest_damage_type_hash Optional[DestinyDamageTypeDefinition]

Manifest information for damage_type_hash

Source code in src/bungio/models/bungie/destiny/entities/items.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@custom_define()
class DestinyItemInstanceComponent(BaseModel):
    """
    If an item is "instanced", this will contain information about the item's instance that doesn't fit easily into other components. One might say this is the "essential" instance data for the item. Items are instanced if they require information or state that can vary. For instance, weapons are Instanced: they are given a unique identifier, uniquely generated stats, and can have their properties altered. Non-instanced items have none of these things: for instance, Glimmer has no unique properties aside from how much of it you own. You can tell from an item's definition whether it will be instanced or not by looking at the DestinyInventoryItemDefinition's definition.inventory.isInstanceItem property.

    Tip: Manifest Information
        This model has some attributes which can be filled with additional information found in the manifest (`manifest_...`).
        Without additional work, these attributes will be `None`, since they require additional requests and database lookups.

        To fill the manifest dependent attributes, either:

        - Run `await ThisClass.fetch_manifest_information()`, see [here](/API Reference/Models/base)
        - Set `Client.always_return_manifest_information` to `True`, see [here](/API Reference/client)

    Attributes:
        breaker_type: If populated, this item has a breaker type corresponding to the given value. See DestinyBreakerTypeDefinition for more details.
        breaker_type_hash: If populated, this is the hash identifier for the item's breaker type. See DestinyBreakerTypeDefinition for more details.
        can_equip: If this is an equippable item, you can check it here. There are permanent as well as transitory reasons why an item might not be able to be equipped: check cannotEquipReason for details.
        cannot_equip_reason: If you cannot equip the item, this is a flags enum that enumerates all of the reasons why you couldn't equip the item. You may need to refine your UI further by using unlockHashesRequiredToEquip and equipRequiredLevel.
        damage_type: If the item has a damage type, this is the item's current damage type.
        damage_type_hash: The current damage type's hash, so you can look up localized info and icons for it.
        energy: IF populated, this item supports Energy mechanics (i.e. Armor 2.0), and these are the current details of its energy type and available capacity to spend energy points.
        equip_required_level: If the item cannot be equipped until you reach a certain level, that level will be reflected here.
        gear_tier: Gear Tier, if applicable, fished up from the unlock value items.gear_tier
        is_equipped: Is the item currently equipped on the given character?
        item_level: The Item's "Level" has the most significant bearing on its stats, such as Light and Power.
        primary_stat: The item stat that we consider to be "primary" for the item. For instance, this would be "Attack" for Weapons or "Defense" for armor.
        quality: The "Quality" of the item has a lesser - but still impactful - bearing on stats like Light and Power.
        unlock_hashes_required_to_equip: Sometimes, there are limitations to equipping that are represented by character-level flags called "unlocks". This is a list of flags that they need in order to equip the item that the character has not met. Use these to look up the descriptions to show in your UI by looking up the relevant DestinyUnlockDefinitions for the hashes.
        manifest_breaker_type_hash: Manifest information for `breaker_type_hash`
        manifest_damage_type_hash: Manifest information for `damage_type_hash`
    """

    breaker_type: Union["DestinyBreakerType", int] = custom_field(converter=enum_converter("DestinyBreakerType"))
    breaker_type_hash: int = custom_field()
    can_equip: bool = custom_field()
    cannot_equip_reason: Union["EquipFailureReason", int] = custom_field(converter=enum_converter("EquipFailureReason"))
    damage_type: Union["DamageType", int] = custom_field(converter=enum_converter("DamageType"))
    damage_type_hash: int = custom_field()
    energy: "DestinyItemInstanceEnergy" = custom_field()
    equip_required_level: int = custom_field()
    gear_tier: int = custom_field()
    is_equipped: bool = custom_field()
    item_level: int = custom_field()
    primary_stat: "DestinyStat" = custom_field()
    quality: int = custom_field()
    unlock_hashes_required_to_equip: list[int] = custom_field(metadata={"type": """list[int]"""})
    manifest_breaker_type_hash: Optional["DestinyBreakerTypeDefinition"] = custom_field(default=None)
    manifest_damage_type_hash: Optional["DestinyDamageTypeDefinition"] = custom_field(default=None)

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemInstanceEnergy

Bases: BaseModel

No description given by bungie.

Manifest Information

This model has some attributes which can be filled with additional information found in the manifest (manifest_...). Without additional work, these attributes will be None, since they require additional requests and database lookups.

To fill the manifest dependent attributes, either:

  • Run await ThisClass.fetch_manifest_information(), see here
  • Set Client.always_return_manifest_information to True, see here

Attributes:

Name Type Description
energy_capacity int

The total capacity of Energy that the item currently has, regardless of if it is currently being used.

energy_type Union[DestinyEnergyType, int]

This is the enum version of the Energy Type value, for convenience.

energy_type_hash int

The type of energy for this item. Plugs that require Energy can only be inserted if they have the "Any" Energy Type or the matching energy type of this item. This is a reference to the DestinyEnergyTypeDefinition for the energy type, where you can find extended info about it.

energy_unused int

The amount of energy still available for inserting new plugs.

energy_used int

The amount of Energy currently in use by inserted plugs.

manifest_energy_type_hash Optional[DestinyEnergyTypeDefinition]

Manifest information for energy_type_hash

Source code in src/bungio/models/bungie/destiny/entities/items.py
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
@custom_define()
class DestinyItemInstanceEnergy(BaseModel):
    """
    _No description given by bungie._

    Tip: Manifest Information
        This model has some attributes which can be filled with additional information found in the manifest (`manifest_...`).
        Without additional work, these attributes will be `None`, since they require additional requests and database lookups.

        To fill the manifest dependent attributes, either:

        - Run `await ThisClass.fetch_manifest_information()`, see [here](/API Reference/Models/base)
        - Set `Client.always_return_manifest_information` to `True`, see [here](/API Reference/client)

    Attributes:
        energy_capacity: The total capacity of Energy that the item currently has, regardless of if it is currently being used.
        energy_type: This is the enum version of the Energy Type value, for convenience.
        energy_type_hash: The type of energy for this item. Plugs that require Energy can only be inserted if they have the "Any" Energy Type or the matching energy type of this item. This is a reference to the DestinyEnergyTypeDefinition for the energy type, where you can find extended info about it.
        energy_unused: The amount of energy still available for inserting new plugs.
        energy_used: The amount of Energy currently in use by inserted plugs.
        manifest_energy_type_hash: Manifest information for `energy_type_hash`
    """

    energy_capacity: int = custom_field()
    energy_type: Union["DestinyEnergyType", int] = custom_field(converter=enum_converter("DestinyEnergyType"))
    energy_type_hash: int = custom_field()
    energy_unused: int = custom_field()
    energy_used: int = custom_field()
    manifest_energy_type_hash: Optional["DestinyEnergyTypeDefinition"] = custom_field(default=None)

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemObjectivesComponent

Bases: BaseModel

Items can have objectives and progression. When you request this block, you will obtain information about any Objectives and progression tied to this item.

None Attributes: date_completed: If we have any information on when these objectives were completed, this will be the date of that completion. This won't be on many items, but could be interesting for some items that do store this information. flavor_objective: I may regret naming it this way - but this represents when an item has an objective that doesn't serve a beneficial purpose, but rather is used for "flavor" or additional information. For instance, when Emblems track specific stats, those stats are represented as Objectives on the item. objectives: If the item has a hard association with objectives, your progress on them will be defined here. Objectives are our standard way to describe a series of tasks that have to be completed for a reward.

Source code in src/bungio/models/bungie/destiny/entities/items.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@custom_define()
class DestinyItemObjectivesComponent(BaseModel):
    """
    Items can have objectives and progression. When you request this block, you will obtain information about any Objectives and progression tied to this item.

    None
    Attributes:
        date_completed: If we have any information on when these objectives were completed, this will be the date of that completion. This won't be on many items, but could be interesting for some items that do store this information.
        flavor_objective: I may regret naming it this way - but this represents when an item has an objective that doesn't serve a beneficial purpose, but rather is used for "flavor" or additional information. For instance, when Emblems track specific stats, those stats are represented as Objectives on the item.
        objectives: If the item has a hard association with objectives, your progress on them will be defined here.  Objectives are our standard way to describe a series of tasks that have to be completed for a reward.
    """

    date_completed: datetime = custom_field()
    flavor_objective: "DestinyObjectiveProgress" = custom_field()
    objectives: list["DestinyObjectiveProgress"] = custom_field(metadata={"type": """list[DestinyObjectiveProgress]"""})

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemPerksComponent

Bases: BaseModel

Instanced items can have perks: benefits that the item bestows. These are related to DestinySandboxPerkDefinition, and sometimes - but not always - have human readable info. When they do, they are the icons and text that you see in an item's tooltip. Talent Grids, Sockets, and the item itself can apply Perks, which are then summarized here for your convenience.

None Attributes: perks: The list of perks to display in an item tooltip - and whether or not they have been activated.

Source code in src/bungio/models/bungie/destiny/entities/items.py
 97
 98
 99
100
101
102
103
104
105
106
107
@custom_define()
class DestinyItemPerksComponent(BaseModel):
    """
    Instanced items can have perks: benefits that the item bestows. These are related to DestinySandboxPerkDefinition, and sometimes - but not always - have human readable info. When they do, they are the icons and text that you see in an item's tooltip. Talent Grids, Sockets, and the item itself can apply Perks, which are then summarized here for your convenience.

    None
    Attributes:
        perks: The list of perks to display in an item tooltip - and whether or not they have been activated.
    """

    perks: list["DestinyPerkReference"] = custom_field(metadata={"type": """list[DestinyPerkReference]"""})

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemRenderComponent

Bases: BaseModel

Many items can be rendered in 3D. When you request this block, you will obtain the custom data needed to render this specific instance of the item.

None Attributes: art_regions: A dictionary for rendering gear components, with: key = Art Arrangement Region Index value = The chosen Arrangement Index for the Region, based on the value of a stat on the item used for making the choice. use_custom_dyes: If you should use custom dyes on this item, it will be indicated here.

Source code in src/bungio/models/bungie/destiny/entities/items.py
209
210
211
212
213
214
215
216
217
218
219
220
221
@custom_define()
class DestinyItemRenderComponent(BaseModel):
    """
    Many items can be rendered in 3D. When you request this block, you will obtain the custom data needed to render this specific instance of the item.

    None
    Attributes:
        art_regions: A dictionary for rendering gear components, with: key = Art Arrangement Region Index value = The chosen Arrangement Index for the Region, based on the value of a stat on the item used for making the choice.
        use_custom_dyes: If you should use custom dyes on this item, it will be indicated here.
    """

    art_regions: dict[int, int] = custom_field(metadata={"type": """dict[int, int]"""})
    use_custom_dyes: bool = custom_field()

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemSocketState

Bases: BaseModel

The status of a given item's socket. (which plug is inserted, if any: whether it is enabled, what "reusable" plugs can be inserted, etc...) If I had it to do over, this would probably have a DestinyItemPlug representing the inserted item instead of most of these properties. 🤷

Manifest Information

This model has some attributes which can be filled with additional information found in the manifest (manifest_...). Without additional work, these attributes will be None, since they require additional requests and database lookups.

To fill the manifest dependent attributes, either:

  • Run await ThisClass.fetch_manifest_information(), see here
  • Set Client.always_return_manifest_information to True, see here

Attributes:

Name Type Description
enable_fail_indexes list[int]

If a plug is inserted but not enabled, this will be populated with indexes into the plug item definition's plug.enabledRules property, so that you can show the reasons why it is not enabled.

is_enabled bool

Even if a plug is inserted, it doesn't mean it's enabled. This flag indicates whether the plug is active and providing its benefits.

is_visible bool

A plug may theoretically provide benefits but not be visible - for instance, some older items use a plug's damage type perk to modify their own damage type. These, though they are not visible, still affect the item. This field indicates that state. An invisible plug, while it provides benefits if it is Enabled, cannot be directly modified by the user.

plug_hash int

The currently active plug, if any. Note that, because all plugs are statically defined, its effect on stats and perks can be statically determined using the plug item's definition. The stats and perks can be taken at face value on the plug item as the stats and perks it will provide to the user/item.

manifest_plug_hash Optional[DestinyInventoryItemDefinition]

Manifest information for plug_hash

Source code in src/bungio/models/bungie/destiny/entities/items.py
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
@custom_define()
class DestinyItemSocketState(BaseModel):
    """
    The status of a given item's socket. (which plug is inserted, if any: whether it is enabled, what "reusable" plugs can be inserted, etc...) If I had it to do over, this would probably have a DestinyItemPlug representing the inserted item instead of most of these properties. :shrug:

    Tip: Manifest Information
        This model has some attributes which can be filled with additional information found in the manifest (`manifest_...`).
        Without additional work, these attributes will be `None`, since they require additional requests and database lookups.

        To fill the manifest dependent attributes, either:

        - Run `await ThisClass.fetch_manifest_information()`, see [here](/API Reference/Models/base)
        - Set `Client.always_return_manifest_information` to `True`, see [here](/API Reference/client)

    Attributes:
        enable_fail_indexes: If a plug is inserted but not enabled, this will be populated with indexes into the plug item definition's plug.enabledRules property, so that you can show the reasons why it is not enabled.
        is_enabled: Even if a plug is inserted, it doesn't mean it's enabled. This flag indicates whether the plug is active and providing its benefits.
        is_visible: A plug may theoretically provide benefits but not be visible - for instance, some older items use a plug's damage type perk to modify their own damage type. These, though they are not visible, still affect the item. This field indicates that state. An invisible plug, while it provides benefits if it is Enabled, cannot be directly modified by the user.
        plug_hash: The currently active plug, if any. Note that, because all plugs are statically defined, its effect on stats and perks can be statically determined using the plug item's definition. The stats and perks can be taken at face value on the plug item as the stats and perks it will provide to the user/item.
        manifest_plug_hash: Manifest information for `plug_hash`
    """

    enable_fail_indexes: list[int] = custom_field(metadata={"type": """list[int]"""})
    is_enabled: bool = custom_field()
    is_visible: bool = custom_field()
    plug_hash: int = custom_field()
    manifest_plug_hash: Optional["DestinyInventoryItemDefinition"] = custom_field(default=None)

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemSocketsComponent

Bases: BaseModel

Instanced items can have sockets, which are slots on the item where plugs can be inserted. Sockets are a bit complex: be sure to examine the documentation on the DestinyInventoryItemDefinition's "socket" block and elsewhere on these objects for more details.

None Attributes: sockets: The list of all sockets on the item, and their status information.

Source code in src/bungio/models/bungie/destiny/entities/items.py
237
238
239
240
241
242
243
244
245
246
247
@custom_define()
class DestinyItemSocketsComponent(BaseModel):
    """
    Instanced items can have sockets, which are slots on the item where plugs can be inserted. Sockets are a bit complex: be sure to examine the documentation on the DestinyInventoryItemDefinition's "socket" block and elsewhere on these objects for more details.

    None
    Attributes:
        sockets: The list of all sockets on the item, and their status information.
    """

    sockets: list["DestinyItemSocketState"] = custom_field(metadata={"type": """list[DestinyItemSocketState]"""})

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemStatsComponent

Bases: BaseModel

If you want the stats on an item's instanced data, get this component. These are stats like Attack, Defense etc... and not historical stats. Note that some stats have additional computation in-game at runtime - for instance, Magazine Size - and thus these stats might not be 100% accurate compared to what you see in-game for some stats. I know, it sucks. I hate it too.

None Attributes: stats: If the item has stats that it provides (damage, defense, etc...), it will be given here.

Source code in src/bungio/models/bungie/destiny/entities/items.py
224
225
226
227
228
229
230
231
232
233
234
@custom_define()
class DestinyItemStatsComponent(BaseModel):
    """
    If you want the stats on an item's instanced data, get this component. These are stats like Attack, Defense etc... and *not* historical stats. Note that some stats have additional computation in-game at runtime - for instance, Magazine Size - and thus these stats might not be 100% accurate compared to what you see in-game for some stats. I know, it sucks. I hate it too.

    None
    Attributes:
        stats: If the item has stats that it provides (damage, defense, etc...), it will be given here.
    """

    stats: dict[int, "DestinyStat"] = custom_field(metadata={"type": """dict[int, DestinyStat]"""})

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result

DestinyItemTalentGridComponent

Bases: BaseModel

Well, we're here in Destiny 2, and Talent Grids are unfortunately still around. The good news is that they're pretty much only being used for certain base information on items and for Builds/Subclasses. The bad news is that they still suck. If you really want this information, grab this component. An important note is that talent grids are defined as such: A Grid has 1:M Nodes, which has 1:M Steps. Any given node can only have a single step active at one time, which represents the actual visual contents and effects of the Node (for instance, if you see a "Super Cool Bonus" node, the actual icon and text for the node is coming from the current Step of that node). Nodes can be grouped into exclusivity sets and as of D2, exclusivity groups (which are collections of exclusivity sets that affect each other). See DestinyTalentGridDefinition for more information. Brace yourself, the water's cold out there in the deep end.

Manifest Information

This model has some attributes which can be filled with additional information found in the manifest (manifest_...). Without additional work, these attributes will be None, since they require additional requests and database lookups.

To fill the manifest dependent attributes, either:

  • Run await ThisClass.fetch_manifest_information(), see here
  • Set Client.always_return_manifest_information to True, see here

Attributes:

Name Type Description
grid_progression DestinyProgression

If the item has a progression, it will be detailed here. A progression means that the item can gain experience. Thresholds of experience are what determines whether and when a talent node can be activated.

is_grid_complete bool

Indicates whether the talent grid on this item is completed, and thus whether it should have a gold border around it. Only will be true if the item actually has a talent grid, and only then if it is completed (i.e. every exclusive set has an activated node, and every non-exclusive set node has been activated)

nodes list[DestinyTalentNode]

Detailed information about the individual nodes in the talent grid. A node represents a single visual "pip" in the talent grid or Build detail view, though each node may have multiple "steps" which indicate the actual bonuses and visual representation of that node.

talent_grid_hash int

Most items don't have useful talent grids anymore, but Builds in particular still do. You can use this hash to lookup the DestinyTalentGridDefinition attached to this item, which will be crucial for understanding the node values on the item.

manifest_talent_grid_hash Optional[DestinyTalentGridDefinition]

Manifest information for talent_grid_hash

Source code in src/bungio/models/bungie/destiny/entities/items.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
@custom_define()
class DestinyItemTalentGridComponent(BaseModel):
    """
    Well, we're here in Destiny 2, and Talent Grids are unfortunately still around. The good news is that they're pretty much only being used for certain base information on items and for Builds/Subclasses. The bad news is that they still suck. If you really want this information, grab this component. An important note is that talent grids are defined as such: A Grid has 1:M Nodes, which has 1:M Steps. Any given node can only have a single step active at one time, which represents the actual visual contents and effects of the Node (for instance, if you see a "Super Cool Bonus" node, the actual icon and text for the node is coming from the current Step of that node). Nodes can be grouped into exclusivity sets *and* as of D2, exclusivity groups (which are collections of exclusivity sets that affect each other). See DestinyTalentGridDefinition for more information. Brace yourself, the water's cold out there in the deep end.

    Tip: Manifest Information
        This model has some attributes which can be filled with additional information found in the manifest (`manifest_...`).
        Without additional work, these attributes will be `None`, since they require additional requests and database lookups.

        To fill the manifest dependent attributes, either:

        - Run `await ThisClass.fetch_manifest_information()`, see [here](/API Reference/Models/base)
        - Set `Client.always_return_manifest_information` to `True`, see [here](/API Reference/client)

    Attributes:
        grid_progression: If the item has a progression, it will be detailed here. A progression means that the item can gain experience. Thresholds of experience are what determines whether and when a talent node can be activated.
        is_grid_complete: Indicates whether the talent grid on this item is completed, and thus whether it should have a gold border around it. Only will be true if the item actually *has* a talent grid, and only then if it is completed (i.e. every exclusive set has an activated node, and every non-exclusive set node has been activated)
        nodes: Detailed information about the individual nodes in the talent grid. A node represents a single visual "pip" in the talent grid or Build detail view, though each node may have multiple "steps" which indicate the actual bonuses and visual representation of that node.
        talent_grid_hash: Most items don't have useful talent grids anymore, but Builds in particular still do. You can use this hash to lookup the DestinyTalentGridDefinition attached to this item, which will be crucial for understanding the node values on the item.
        manifest_talent_grid_hash: Manifest information for `talent_grid_hash`
    """

    grid_progression: "DestinyProgression" = custom_field()
    is_grid_complete: bool = custom_field()
    nodes: list["DestinyTalentNode"] = custom_field(metadata={"type": """list[DestinyTalentNode]"""})
    talent_grid_hash: int = custom_field()
    manifest_talent_grid_hash: Optional["DestinyTalentGridDefinition"] = custom_field(default=None)

_convert_to_bungie_case(string) cached staticmethod

Convert a string to how it is represented by bungie: my_name_string -> myNameString

Parameters:

Name Type Description Default
string str

The og string

required

Returns:

Type Description
str

The bungie string

Source code in src/bungio/models/base.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
@staticmethod
@functools.cache
def _convert_to_bungie_case(string: str) -> str:
    """
    Convert a string to how it is represented by bungie: my_name_string -> myNameString

    Args:
        string: The og string

    Returns:
        The bungie string
    """

    if "_" not in string:
        return string
    else:
        split = string.split("_")
        return "".join((split[0], *(s.capitalize() for s in split[1:])))

fetch_manifest_information(include=None, exclude=None, _cache=None) async

Fill the model in-place with information from the manifest.

Example

Fill every attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information()
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is not None

Fill only some attribute

1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is not None
assert model.manifest_reference_id is None
1
2
3
4
model: DestinyHistoricalStatsActivity
await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
assert model.manifest_director_activity_hash is None
assert model.manifest_reference_id is not None

Parameters:

Name Type Description Default
include Optional[list[str]]

A list of attributes you want to include. Excludes everything not mentioned

None
exclude Optional[list[str]]

A list of attributes you want to exclude. Includes everything not mentioned

None
Source code in src/bungio/models/base.py
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
async def fetch_manifest_information(
    self, include: Optional[list[str]] = None, exclude: Optional[list[str]] = None, _cache: Optional[dict] = None
):
    """
    Fill the model in-place with information from the manifest.

    Example:
        Fill every attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information()
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is not None
        ```

        Fill only some attribute
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(include=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is not None
        assert model.manifest_reference_id is None
        ```
        ```py
        model: DestinyHistoricalStatsActivity
        await model.fetch_manifest_information(exclude=["manifest_director_activity_hash"])
        assert model.manifest_director_activity_hash is None
        assert model.manifest_reference_id is not None
        ```

    Args:
        include: A list of attributes you want to include. Excludes everything not mentioned
        exclude:  A list of attributes you want to exclude. Includes everything not mentioned
    """

    if not isinstance(self._client.manifest_storage, AsyncEngine):
        raise ValueError("Client.manifest_storage must be set up to use this")

    if not _cache:
        _cache = {}

    class_definition = attrs.fields_dict(type(self))  # noqa

    # loop through the class attributes
    for name in self.__dir__():
        if name.startswith("__"):
            continue

        if include and name not in include:
            continue
        if exclude and name in exclude:
            continue

        # manifest entries
        if name.startswith("manifest_"):
            striped_name = name.removeprefix("manifest_")
            value = getattr(self, striped_name)

            if value is MISSING:
                return

            # check the cache to avoid infinite recursion
            if cached := _cache.get(value, None):
                manifest_value = cached

            else:
                attr_definition = class_definition[name]
                manifest_class_name = attr_definition.type.__str__().removesuffix("')]").split("'")[-1]
                manifest_value = await self._client.manifest.fetch(
                    manifest_class=getattr(models, manifest_class_name), value=value
                )

                _cache[value] = manifest_value

                # check if the model has manifest models itself
                if manifest_value:
                    await manifest_value.fetch_manifest_information(_cache=_cache)

            setattr(self, name, manifest_value)

        # sub models which may have manifest entries too
        elif hasattr((value := getattr(self, name)), "fetch_manifest_information"):
            await value.fetch_manifest_information(_cache=_cache)

from_dict(data, client, recursive=False, *args, **kwargs) async classmethod

Convert json data to this model

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required
recursive bool

If this was called recursively

False

Returns:

Type Description
BaseModel

The model

Source code in src/bungio/models/base.py
274
275
276
277
278
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
async def from_dict(cls, data: dict, client: "Client", recursive: bool = False, *args, **kwargs) -> BaseModel:
    """
    Convert json data to this model

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj
        recursive: If this was called recursively

    Returns:
        The model
    """

    if isinstance(data, cls):
        return data

    if not isinstance(data, dict):
        raise ValueError

    if "Response" in data:
        data = data["Response"]

    # also use the kwargs as data
    data = kwargs | data
    data = cls.process_dict(data=data, client=client)

    prepared = {}
    for name, field in attrs.fields_dict(cls).items():
        if field.init and name != "_client":
            default = field.default

            # get the value we want. This also skips the manifest_... entries since they have no value and a default
            bungie_name = cls._convert_to_bungie_case(name)
            value = data.get(bungie_name, attrs.NOTHING)

            # bungie is veeery inconsistent and sometimes like to start their params with an upper case for some reason
            # only sometimes tho :)
            if value is attrs.NOTHING:
                value = data.get(f"{bungie_name[0].capitalize()}{bungie_name[1:]}", attrs.NOTHING)

            # sadly bungie sometimes does not return info without marking that fact in the api specs
            if value is attrs.NOTHING and default is attrs.NOTHING:
                default = MISSING

            if value is attrs.NOTHING:
                value = default

            else:
                value = await cls._convert_to_type(
                    field_type=field.type, field_metadata=field.metadata, value=value, client=client
                )

            # set the attr
            prepared[name] = value

    res = cls(**prepared)  # noqa

    # fill the manifest information
    if not recursive and res._client.always_return_manifest_information:
        await res.fetch_manifest_information()

    return res

process_dict(data, client, *args, **kwargs) staticmethod

Model specific cleanup

Parameters:

Name Type Description Default
data dict

The json representation of the model, usually received by bungie

required
client 'Client'

The client obj

required

Returns:

Type Description
dict

Clean json

Source code in src/bungio/models/base.py
260
261
262
263
264
265
266
267
268
269
270
271
272
@staticmethod
def process_dict(data: dict, client: "Client", *args, **kwargs) -> dict:
    """
    Model specific cleanup

    Args:
        data: The json representation of the model, usually received by bungie
        client: The client obj

    Returns:
        Clean json
    """
    return data

to_dict(_return_to_bungie_case=True)

Convert the model into a dict representation bungie accepts

Returns:

Type Description
dict

A dict which can be sent to bungie

Source code in src/bungio/models/base.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def to_dict(self, _return_to_bungie_case: bool = True) -> dict:
    """
    Convert the model into a dict representation bungie accepts

    Returns:
        A dict which can be sent to bungie
    """

    result = {}

    for name, field in attrs.fields_dict(type(self)).items():
        if name.startswith("_") or name.startswith("manifest_"):
            continue

        value = getattr(self, name)
        if _return_to_bungie_case:
            name = self._convert_to_bungie_case(name)

        if inspect.ismethod(value) or inspect.isfunction(value):
            continue

        elif isinstance(value, dict):
            raise NotImplementedError(
                "Nested dict conversion is not currently implemented, since bungie does never require that info"
            )

        elif isinstance(value, list):
            list_results = []
            for entry in value:
                if hasattr(entry, "to_dict"):
                    list_results.append(entry.to_dict())
                else:
                    list_results.append(entry)

            result[name] = list_results

        else:
            if hasattr(value, "to_dict"):
                result[name] = value.to_dict()
            else:
                # convert to string if they are int64
                if field.metadata.get("int64", None) is True:
                    value = str(value)

                result[name] = value

    return result