Creating Models
Models are the core of how data is fetched and formatted within xivapy. In general, model creation determines how robust the data is you recieve from a sheet
or search
call within the client.
Creating a basic model¶
The most basic model is one that fetches all parameters by their given name in the dictionary. It's important that the annotated type (str, bool, etc) are correct, or the model will fail to validate.
class ContentFinderCondition(xivapy.Model):
row_id: int
Name: str
ContentMemberType: dict
Let's use it to get data on T5:
>>> async with xivapy.Client() as client:
... await client.sheet(ContentFinderCondition, row=97)
ContentFinderCondition(row_id=97, Name='the Binding Coil of Bahamut - Turn 5', ContentMemberType={'value': 3, 'sheet': 'ContentMemberType', 'row_id': 3, 'fields': {'HealersPerParty': 2, 'MeleesPerParty': 2, 'RangedPerParty': 2, 'TanksPerParty': 2, 'Unknown0': 0, 'Unknown1': 0, 'Unknown10': False, 'Unknown11': True, 'Unknown12': False, 'Unknown13': False, 'Unknown14': True, 'Unknown15': 0, 'Unknown16': 1, 'Unknown2': 8, 'Unknown3': 8, 'Unknown4': 8, 'Unknown5': 1, 'Unknown6': 0, 'Unknown7': False, 'Unknown8': False, 'Unknown9': False}})
This will likely suit the needs for most people to fetch in this way; you only really need the more advanced features below to customize how this data is fetched.
Note
If you have multiple models that represent the same sheet (let's say you're searching for data with one Model, but fetching data from the sheet with a more comprehensive Model, you can use __sheetname__
to override the name):
class JustTheName(xivapy.Model):
__sheetname__ = 'ContentFinderCondition'
Name: str
class NameAndLevel(xivapy.Model):
__sheetname__ = 'ContentFinderCondition'
Name: str
ClassJobLevelRequired: int
Warning
row_id
is the only special case out of all the fields you can request - the rest of the data is technically part of a fields
parameter in the return json, but row_id
is not part of those params; as a convenience, the row_id
field is moved 'downwards' into the params so the model can have access to this.
Extra field mapping parameters¶
The first thing you might notice is that fields like Name
and ClassJobLevelRequired
don't quite fit the python scheme. In these cases when you want to rename the fields themselves to be more pythonic, you can use xivapy.QueryField
with xivapy.FieldMapping
to map the API field to the python field:
class ContentFinderCondition(xivapy.Model):
name: xivapy.QueryField[str] = xivapy.QueryField(xivapy.FieldMapping('Name'))
content_member_type: xivapy.QueryField[dict] = xivapy.QueryField(xivapy.FieldMapping('ContentMemberType'))
This effectively works the same as the first example, but the fields have more pythonic names.
But what if we actually cared about the party composition for this content and don't want to parse the dictionary? You can use the FieldMapping
class to customize that data too if you know the field:
class ContentFinderCondition(xivapy.Model):
id: xivapy.QueryField[int] = xivapy.QueryField(xivapy.FieldMapping('row_id'))
name: xivapy.QueryField[str] = xivapy.QueryField(xivapy.FieldMapping('Name'))
tanks_required: xivapy.QueryField[int] = xivapy.QueryField(xivapy.FieldMapping('ContentMemberType.TanksPerParty'))
healers_required: xivapy.QueryField[int] = xivapy.QueryField(xivapy.FieldMapping('ContentMemberType.HealersPerParty'))
melees_required: xivapy.QueryField[int] = xivapy.QueryField(xivapy.FieldMapping('ContentMemberType.MeleesPerParty'))
ranged_required: xivapy.QueryField[int] = xivapy.QueryField(xivapy.FieldMapping('ContentMemberType.RangedPerParty'))
Now the same request returns the data that you asked for:
ContentFinderCondition(id=97, name='the Binding Coil of Bahamut - Turn 5', tanks_required=2, healers_required=2, melees_required=2, ranged_required=2)
By using Field.Subfield
as part of your mapping, the field mapping will 'lift' the data up from the subfield, eliminating the need for the nested dictionary looking and gives you the data that you're actually asking for.
Getting extra languages¶
You can also use xivapy.FieldMapping
to get languages (for fields that support it). Back to our example, let's say we want to know all the (supported) languages for the name(s) of T5:
from xivapy import QueryField, FieldMapping, Model, LangDict
class ContentFinderCondition(Model):
# modified from the previous model
name: QueryField[LangDict] = QueryField(FieldMapping('Name', languages=['en', 'fr', 'de', 'ja']))
# everything else is the same
And now, the model has I18n data:
ContentFinderCondition(name={'en': 'the Binding Coil of Bahamut - Turn 5', 'de': 'Verschlungene Schatten 5', 'ja': '大迷宮バハムート:邂逅編5'}, tanks_required=2, healers_required=2, melees_required=2, ranged_required=2)
Note
You'll need to change the type from str
to xivapy.LangDict
to get this particular data output. The result is a dictionary with all the languages you request as keys of the dictionary
Model API¶
Model¶
Bases: BaseModel
Base model for all xivapy queries.
get_queryfield_mappings
classmethod
¶
get_queryfield_mappings() -> dict[str, QueryDescriptor]
Returns a dict of all the fields and their corresponding mapping type.
get_sheet_name
classmethod
¶
get_sheet_name() -> str
Returns the sheet name, defaulting to the class name if sheetname not set.
get_fields_str
classmethod
¶
get_fields_str() -> str
Returns all model fields as a comma-separated string list for XIVAPI queries.
get_xivapi_fields
classmethod
¶
get_xivapi_fields() -> set[str]
Get a set of all defined field names.
process_xivapi_response
classmethod
¶
process_xivapi_response(
data: dict[str, Any],
) -> dict[str, Any]
Model validator that processes xivapi-specific response data for pydantic validation.
FieldMapping¶
Map a single model field to multiple XIVAPI fields.
to_field_specs ¶
to_field_specs() -> list[str]
Transforms a Model field into an xivapi-understood field.