Save¶
fmsave.open returns a Save; its reader methods hand back one Table each. validate_save
runs every reader at once and reports how each fared, without raising or warning.
Opening a save¶
- fmsave.open(path, *, strict=False, competition_names=None)¶
Open a Football Manager 26 or 27 save file for reading.
- Parameters:
strict (bool) – Whether a failed reader check stops the reader. The checks are bounds measured on a handful of saves, so a save stranger than those is read and warned about by default: a reader whose checks fail issues ReaderCheckWarning and hands its table back. With True it raises ReaderCheckError instead, which is what a pipeline that would rather stop than read a table it cannot vouch for wants. A structural failure, where the decode has no table to hand back, raises either way. FM27 reader checks always raise, even with strict=False.
competition_names (Mapping[int, str] | str | PathLike[str] | None) – Names for competitions, keyed on Competition.database_id, either as a mapping or as the path of a two-column CSV that fmsave.read_competition_names reads. No save stores a competition name and fmsave ships none, so without this every Competition.name and every denormalised competition_name is None. Tables are read once and then kept, so the map cannot be changed afterwards: open the save again to read it under a different one.
- Raises:
FileNotFoundError – The file does not exist.
NotAFmSaveError – The file is not a Football Manager save.
CorruptSaveError – The save is damaged or was being written.
UnsupportedGameError – The save is from another Football Manager version, or from an FM27 build with no layouts registered whose save summary or game_info section schema differs from the registered build’s.
ReaderCheckError – The save metadata does not match the expected layout.
OSError – A competition name CSV cannot be opened or read.
TypeError – A competition name mapping holds a key that is not an int, or a name that is not a str.
ValueError – A competition name CSV is malformed, or a supplied name is empty.
- Warns:
UnknownBuildWarning – An FM26 build lacks layout tables, an FM27 build has no layouts registered and borrows them by schema, or FM27 coverage is incomplete.
- Return type:
Save¶
- class fmsave.Save(container_index, info, *, strict=False, competition_names={})[source]¶
A supported Football Manager save opened for reading.
Opening reads only the header, the directory and the save metadata; the file is not kept open. Each reader reopens the file, checks that it has not changed since it was opened, and reads only the parts it needs.
Every reader measures what it decoded against the bounds its checks carry. A failed check warns with ReaderCheckWarning and the reader hands its table back; a save opened with strict=True raises ReaderCheckError instead. A structural failure, where the decode has no table to hand back, raises either way. fmsave.validate_save reports every check of every reader without raising or warning.
A Save is not thread-safe: use one Save per thread. Records and tables it returns are immutable, safe to share, and keep working after the Save is closed.
- Parameters:
- experimental_span()[source]¶
Partially decoded raw FM27 fixture cores and counted rules, not semantic tables.
Available for supported FM27 builds. This does not identify the authoritative fixture calendar or link rules to competitions. Unknown status is None, never an assumed unplayed match; each raw record names unsupported fields. The bounded pass is cached without retaining decompressed section bytes.
- Return type:
Fm27Span
- clubs()[source]¶
Every club in the save’s game database, in club index order.
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The club table cannot be read completely, no club record is accepted, a club uid or club index appears in two records, or a team id is listed twice (by one club or by two).
- Warns:
ReaderCheckWarning – On a full-size save, the club records fall outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- players()[source]¶
Every player in the save’s game database, in record offset order.
Person fields (name, birth date, nationality, personality, traits and the rest) are filled in from each player’s person block, or stay None or empty when no block validates. The table is read on the first call to players(), contracts() or suspensions(), from one decode pass; later calls to any of them return the same tables. When a check of that pass fails, none of the three tables is kept, so each of them raises, except that a failed FM27 suspension check stops only suspensions().
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written, a player’s relation header or entry list runs past its record window, or a legal name is not valid UTF-8.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice (by one club or by two), no player records were found, two player records share a uid, or the save’s in-game date is unreadable. With a name map, also anything the competition reader raises, since the pass names each ban’s competition from that table.
- Warns:
ReaderCheckWarning – On a full-size save, the players, contracts or suspensions decoded fall outside the checks’ bounds, or, with a name map, the competition table does. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- contracts()[source]¶
Every player’s contract in the save’s game database, in player order.
Only players whose contract is not None appear. The table is read on the first call to players(), contracts() or suspensions(), from one decode pass; later calls to any of them return the same tables. When a check of that pass fails, none of the three tables is kept, so each of them raises, except that a failed FM27 suspension check stops only suspensions().
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written, a player’s relation header or entry list runs past its record window, or a legal name is not valid UTF-8.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice (by one club or by two), no player records were found, two player records share a uid, or the save’s in-game date is unreadable. With a name map, also anything the competition reader raises, since the pass names each ban’s competition from that table.
- Warns:
ReaderCheckWarning – On a full-size save, the players, contracts or suspensions decoded fall outside the checks’ bounds, or, with a name map, the competition table does. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- suspensions()[source]¶
Every unserved suspension in the save’s game database, in player order.
Every unserved ban the save holds is listed, including bans the game no longer displays. Each player’s suspensions keep the order the save stores them in, and each row carries the club the player belongs to, which for a player registered with a team another club controls is that controlling club. The table is read on the first call to players(), contracts() or suspensions(), from one decode pass; later calls to any of them return the same tables. When a check of that pass fails, none of the three tables is kept, so each of them raises. On FM27 the suspension checks are judged on their own: when only they fail, this reader raises, and players() and contracts() still read, with every Player.suspensions None. FM27 ban coverage has not been compared with the game.
A ban covering one competition carries competition_id in the stage id space, which joins stages(), competitions(), fixtures(), league_tables() and player_match_stats(); a nation-wide ban carries nation_id instead, and scope says which. competition_name is None unless the save was opened with a name map, and stays None for a competition the game itself created during the career, whose database id no name source outside the save carries.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written, a player’s relation header or entry list runs past its record window, or a legal name is not valid UTF-8.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice (by one club or by two), no player records were found, two player records share a uid, or the save’s in-game date is unreadable. With a name map, also anything the competition reader raises, since every competition name a ban carries comes from that table.
- Warns:
ReaderCheckWarning – On a full-size save, the players, contracts or suspensions decoded fall outside the checks’ bounds, or, with a name map, the competition table does. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- managed_clubs()[source]¶
The club run by the save’s first human manager.
An empty table means no managed club was found: the save has no human manager, or the manager has no current club, for example while between jobs. The link from a human manager to a club is proven only on saves with a single human manager; on a save with several, only the first is listed. The club comes from the manager’s current contract and is checked against the save summary, which also stores the manager’s name. The table is read on the first call; later calls return the same table. It does not decode players.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice, the save’s in-game date is unreadable, the contract and the save summary link the manager to different clubs, or, with no current contract, the save summary links the manager to more than one club.
- Return type:
- stages()[source]¶
Every stage of every competition, in the order the save’s stage table stores them.
A stage is one part of a competition: a league season is one stage, a cup round is one, and each leg of a two-legged tie is its own stage carrying the same round. Stage ids are what fixtures, league-table groups and per-match records join through. No save stores a competition name, so competition_name is None unless the save was opened with a name map, and then it is filled in for the competitions that map names. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No stage table was found in the tail of the game database, or a stage id appears in two rows. With a name map the competition reader runs first, so anything it raises is raised here too, since a stage’s competition name is read out of that table.
- Warns:
ReaderCheckWarning – On a full-size save, the stage table falls outside the checks’ bounds, or, with a name map, the competition table does. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- competitions()[source]¶
Every competition the save’s stage table names, in ascending competition id.
Each row carries the ids of its stages and, for about nine competitions in ten, the competition’s id in the game’s own editor database, which is the same value on every save and is what an external name source is keyed on. The save stores no competition names, so name is None on every row until a name map is supplied. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No stage table was found in the tail of the game database, or a stage id appears in two rows.
- Warns:
ReaderCheckWarning – On a full-size save, fewer competitions were found than the checks allow. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- nations()[source]¶
Every save-local nation id paired with its game database id.
Join other tables’ nation fields to Nation.nation_id, then use database_id with an external name map. The save stores no nation names. The first call reads the table; later calls return the cached table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed after opening.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – A check fails on a strict save.
- Warns:
ReaderCheckWarning – A full-size save has no unambiguous nation table or its database id copies disagree.
- Return type:
- fixtures()[source]¶
Matches recovered from the fixture calendar, in date and kick-off order.
The save holds several copies of the calendar; only the largest is returned, so the block of template matches every save carries is left out. Each row joins to its competition through its stage, and to a club through each team id; an id the save does not resolve leaves its fields empty rather than being guessed.
home_goals and away_goals carry unambiguous decoded scores; coverage varies between saves. A played match with empty goals has no unambiguous decoded result. This does not establish whether the save retained its score, and does not mean the match finished goalless.
stadium_uid names the ground each match is played at, which comes from the stadium table: this reader therefore needs that table and raises when the game database holds none.
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice, no stage table was found in the tail of the game database, a stage id appears in two rows, no stadium table was found, or the save’s in-game date is unreadable. With a competition name map, the competition reader runs first and raises here too, so no row is named from a table that could not be read.
- Warns:
ReaderCheckWarning – On a full-size span, the calendar or the scores joined onto it fall outside the checks’ bounds. The stadium table’s checks run here as well, and with a name map the competition table’s, since a row carries a ground and a name from each. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- stadiums()[source]¶
Every ground the save’s database holds, in the order the save stores them.
A row carries the ground’s capacities, its pitch, when it was built and rebuilt, and the club that owns it. Most rows carry no name: the game takes a ground’s name from its installed database and only a couple of hundred grounds store one in the save.
home_club_uids says which clubs play their home matches there, and it is not a stored link: no club record points at a ground, so it comes from the fixture calendar, as the ground a club used for most of its first-team home matches. A ground no club used often enough lists none, and a shared ground lists every club that uses it. Owning a ground and playing at it are different things and are separate fields.
A ground with no stored owner is the council’s. The game’s facilities screen shows one such ground as owned by the council, so an empty owner_club_uid is what the game displays that way rather than an owner fmsave failed to read.
The table’s closing header marks the end of its ground records and is not returned as a ground.
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No stadium table was found, or a stadium uid appears in two rows. The club and fixture readers run first and raise here too, since a row carries club names and a calendar-derived link. On FM27 a calendar that cannot be read leaves home_club_uids None instead.
- Warns:
ReaderCheckWarning – On a full-size save, what the table or the calendar link decoded falls outside the checks’ bounds. The club and fixture checks run here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- transfer_windows()[source]¶
Every transfer window the save’s rules database holds, in stored order.
A window says when a transfer window opens and closes in a season. It is database content rather than career state: every save made from one installed database holds the same windows, whatever has happened in the career. The dates are season-relative, so each carries an offset from the season’s start year rather than a calendar date, and a window has no name of its own. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
- Warns:
ReaderCheckWarning – On a full-size save, fewer windows were decoded than the checks allow, or the windows that were found did not decode their dates. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- injury_types()[source]¶
Every injury the game can hand out that the save names, in stored order.
The names are the game’s own text, as the save stores it, in the language the save was written in. They are in no section of the save: each per-match file it holds carries one copy of the table, so a save with no per-match file has no names at all and this table is then empty. On the saves measured, a new career and a pre-season save held none, and one appeared after the first competitive match. Some injury codes have no entry in the table, so a code an injury carries need not be named here. This table holds only what the save itself stores: fmsave ships no injury names of its own, so a code the save leaves out has no name here or anywhere else.
Only as many per-match files are read as it takes to find the table, whatever a save lists, and no game database is read at all. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – A per-match file is damaged or was being written.
- Warns:
ReaderCheckWarning – On a full-size save listing at least one per-match file, fewer records of the table were read than the checks allow. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- injuries()[source]¶
Every injury the save still remembers, in stored order, of both kinds.
A HISTORY row is an injury the save remembers happening: when it happened, the team the person was at, whether it happened in training or in a match, and how bad it was, both of those in the game’s own words. The save keeps only about the last two years of them: the oldest row on every save measured is 742 days before the in-game date, and rows older than that are gone rather than kept, so this is a rolling window and not a whole career.
These are not the rows the game’s Injury History tab lists. That tab reads the full-career store, which fmsave does not ship because nothing in it says which person a history belongs to: it shows injuries from years outside this window, so what is here is the recent part of the career that tab shows whole.
A TYPED row carries the injury type of a recent or current episode, and its date is the day the player is expected back rather than the day he was hurt, which is why so many of these dates sit ahead of the in-game date: adding each displayed injury’s date to the time it kept the player out lands on the date stored here, checked on eleven players. A date behind the in-game date is a return the game has already reached, and how far behind those go depends on the save, so nothing here treats a week as a rule. A typed row is not a list of injuries the player has recovered from: what it names is the type, which no HISTORY row carries. About one type code in fifteen has no entry in Save.injury_types, and type_name is empty for those: fmsave ships no injury names of its own.
A row whose person the save no longer keeps as a player leaves player_uid and player_name empty, which is about one HISTORY row in twenty. A HISTORY row whose team no club lists, or which names no team at all, leaves team_id and the three club fields empty rather than guessing; that is about one row in five hundred. Two views this table has no field for:
history = career_save.injuries() own = history.where(player_uid=player_uid) typed_date = next(row.date for row in own if row.kind == "typed") # The latest history row on or before a typed row's date, for the same player. before = [ row.date for row in own if row.kind == "history" and row.date is not None and row.date <= typed_date ] days_between = (typed_date - max(before)).days if before else None
days_between is the gap between two dates and never how long the player was out for, which no row of this table holds. Filtering by a player’s club today is where(player_uid=…) against Save.players, since club_uid here is the club at the time.
This reader joins through the player records, so a cold call pays the player pass over the game database; a warm one decompresses only this section. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The save’s in-game date is unreadable, so an injury date cannot be judged against it; a count in the section runs past the end of it, the section’s tail is not where it belongs, or bytes follow it; or no club or player record is accepted.
- Warns:
ReaderCheckWarning – On a full-size section, the rows fall outside the checks’ bounds. Every index whose data this reader hands out is read through the reader that enforces that index’s own checks, so those checks are reported here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- affiliates()[source]¶
Every group of clubs the save stores together, in stored order.
What groups a set of clubs is not established. These groups live in a section of their own and carry nothing that says what the grouping means, so every club field is unconfirmed.
They are not the affiliates a club’s Affiliated Clubs screen lists. The club whose screen listed 26 affiliates belongs to no group here, and the one group naming a club that screen also names pairs it with its own academy rather than with the viewing club. A multi-club ownership group is recognisable in the largest group of every save measured, which would suit an ownership reading, but no screen has said so. So a club’s own affiliates, and the kind of each affiliation, are in no field fmsave reads.
This is not the parent link Club.parent_club_uid carries, which comes from the team lists inside the club records; the two relations share no pair at all, although the parent link is what a club’s B and C sides on that screen come through. A member whose stored club index no club record claims leaves that entry’s uid and name empty rather than being guessed, which is about one member in sixty.
The other members of the groups a club belongs to, without the club itself:
groups = career_save.affiliates() own_groups = groups.filter(lambda group: club_uid in group.club_uids) others = tuple( member_uid for group in own_groups for member_uid in group.club_uids if member_uid is not None and member_uid != club_uid )
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The group section is too short to hold its header, a group’s member count is outside what a group may hold, a group runs past the end of the section, the groups do not end on the section’s last byte, no club record is accepted, a club uid or club index appears in two records, or a team id is listed twice.
- Warns:
ReaderCheckWarning – On a full-size save whose groups hold a member, fewer members resolve to a club than the checks allow, or the club table falls outside its own bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- job_vacancies()[source]¶
Every open job the save’s job-centre feed holds, in stored order.
The feed keeps old rows. The earliest advertised date on the saves measured is about ten years before the in-game date, and nothing in a row says whether the job has since been filled, so this is what the save still remembers rather than what the game would list today.
A vacancy is stored against a team, so team_id is the save’s own key and the club fields come from the club that fields that team; a team no club lists leaves them empty, and a record naming no team at all leaves team_id empty too. league_position is the team’s current position in the competition the row names, which is empty on about a quarter of rows because the save stores none. The job title, the second date and two further numbers have no confirmed meaning and ship in unknown rather than under names they have not earned.
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The feed section is too short to hold its header, or its size is not its header and its record size times the count it claims; the save’s in-game date is unreadable, so an advertised date cannot be judged against it; no club record is accepted; a club uid or club index appears in two records; a team id is listed twice; or no stage table was found in the tail of the game database. With a competition name map the competition reader runs first and raises here too, so no row is named from a table that could not be read.
- Warns:
ReaderCheckWarning – On a feed of at least twenty records, the records fall outside the checks’ bounds. The club and stage tables’ checks run here as well, and with a name map the competition table’s. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- league_tables()[source]¶
Every live league table the save holds, in the order the save stores them.
One table is one run of blocks the save stores together, told from the next by the index each block keeps of its own place in its table. The save stores each block several times over, the copies agreeing in every field fmsave decodes and differing only in undecoded head bytes, so repeated content is dropped before the tables are built; keeping it would break the tables apart. The save also keeps older states of a row, which break off into tables of one club; such a table is dropped where a row kept elsewhere holds every match it holds and more. Nothing in a table names its competition, so it is voted for from the fixture calendar and is empty on the few tables the vote cannot settle, which are still returned; one competition names several tables, since a cup’s group stage holds a table per group. Rows are in the order the save stores, which is the table’s own standings order, so position is the league position. Each match slot says whether it was played at home or away, from the parity the save alternates venue by; the same calendar checks that parity on every table whose results account for one season of it. The table is read on the first call; later calls return the same table.
A table is read only once its clubs have played in it. Before a division’s first match there is no table for it here, although the game shows one with every club on zero; fixtures() lists its clubs and matches. Rules blocks take their competition from these tables, so competition_rules() links fewer blocks before the season starts.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted; a club uid or club index appears in two records; a team id is listed twice; no stage table was found in the tail of the game database; a stage id appears in two rows; or the save’s in-game date is unreadable.
- Warns:
ReaderCheckWarning – On a full-size span, the blocks fall outside the checks’ bounds. Every index whose data this reader hands out is read through the accessor that enforces that index’s own checks, so the checks of the fixture calendar the vote runs on, and of the club, stage and competition tables this reader joins through, are reported here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- competition_rules()[source]¶
Every competition-rules block the save holds, in the order the save stores them.
A block carries a competition’s promotion, play-off and relegation places, its tie-break codes, its prize money by finishing position and its round calendar.
A row’s competition comes from where the save keeps the block, not from a field it stores. The span alternates rules blocks and league-table blocks, and the table stored right after a block is the one that block’s rules govern. competition_id and competition_name are that table’s, and are empty where the run of blocks after the preamble is not exactly one table league_tables() returned with a competition of its own, which on the saves measured is 32% to 45% of rows. It is a position in the file rather than a stored link, and the competition it hands over is itself a vote on the fixture calendar, so both fields are as strong as LeagueTable.competition_id and no stronger. A block’s club count and its points deduction for administration are not read at all, because no fixed position in the block carries either; the linked table’s own club_count is where a club count comes from.
The squad and financial rules the save’s rules database holds are not read: nothing readable ties one of those groups to a competition, and their content is database content, identical on every save of one installed database. So kind is PREAMBLE on every row. Transfer windows are their own table, transfer_windows().
A cold call builds the league tables and the fixture calendar, since that is where the competition comes from. A caller who has already read either pays nothing extra; one who has read neither pays for both here. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted; a club uid or club index appears in two records; a team id is listed twice; no stage table was found in the tail of the game database; a stage id appears in two rows; no stadium table was found; or the save’s in-game date is unreadable, so the span pass cannot run.
- Warns:
ReaderCheckWarning – On a full-size span, the blocks or the link fall outside the checks’ bounds. The checks of the league tables this reader links to, of the fixture calendar their vote runs on, and of the club, stage and competition tables those readers join through, are reported here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- player_match_stats()[source]¶
Every match one of the save’s players played that it still holds a record of.
Rows come in player order and, inside each player, in the order the save stores his matches. This is never a whole season and never a career: the save keeps about twenty matches per player per spell at a team, counted across all competitions at once rather than per competition, and drops the oldest as new ones arrive. A player who has played more than that in his current spell has only his latest matches here, so a per-competition total summed from these rows is short without saying so. Nothing in a row marks it as truncated. On FM26, rows also cover club friendlies, internationals and matches for a club’s other teams, so filter on competition_id before adding rows up.
Each row carries the competition id the record itself stores, which is in the stage id space, and joins to a club through the opponent’s team id; a team id no club lists leaves the opponent fields empty rather than guessing them. A row with has_stats false carries its date, competition and opponent, and every field the statistics would fill is empty; on the saves measured it is a match of his team’s he did not play in.
On a full-size save, an empty table passes this reader’s checks only when every accepted player has an independently located null or explicitly empty stored history. An unsupported or malformed predecessor remains unresolved; it is not treated as an empty history.
This reader decodes the players to fill player_name, so a cold call pays for the player pass; a caller who has already called players() pays nothing extra for it. The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted; a club uid or club index appears in two records; a team id is listed twice; no player records were found; two player records share a uid; no stage table was found in the tail of the game database; or the save’s in-game date is unreadable, so the years a match may be dated in cannot be worked out.
UnsupportedGameError – On FM27, a player’s history declaration is unknown, or the histories declared nonempty are not exactly the lists decoded.
- Warns:
ReaderCheckWarning – On a full-size save, the records decoded fall outside the checks’ bounds. The checks of the club and player readers this one joins through are reported here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- player_season_stats()[source]¶
Every player’s stats for the current season, as the squad and player screens show them.
One row per competition type (SeasonStatsKind) and team. OVERALL is the season total. A player also has rows for any other team they played for this season, such as a B team or a club they left. Rows carry the game’s per-90 and percentage figures too; see PlayerSeasonStats.
This reader decodes the players to fill player_name, so a first call also pays for players(). The table is read on the first call; later calls return the same table. A full save gives about 400,000 rows, which takes a few seconds and a few hundred megabytes.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The season statistics are missing or not laid out as expected, or the club or player reader this one joins through fails.
- Warns:
ReaderCheckWarning – On a full-size save, the records decoded fall outside the checks’ bounds. The checks of the club and player readers this one joins through are reported here as well. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- player_season_history()[source]¶
Archived player statistics, including completed seasons retained by the save.
Rows carry the same statistics and computed rates as PlayerSeasonStats, together with an unconfirmed candidate season year and competition reference. Stored order need not be chronological. Detailed counts may be sparse for imported history; zeros are returned as stored. Goalkeeper fields use current natural positions. A club or nation export scope selects players by their current membership.
The first call also reads players and clubs. Later calls return the cached table. Missing archive/index structures and malformed or ambiguous ownership raise ReaderCheckError. A valid empty history returns an empty table. Reader checks of players and clubs apply as usual. Closing the save releases its cached tables.
- Return type:
- finances()[source]¶
Every month of club money the save keeps, by club and then oldest month first.
Only some clubs have a series at all. The save keeps one for the clubs of the one or two league nations it tracks, not for every club it holds, and which nations those are changes as a career moves on, so a club with no rows here is ordinary rather than a failure. On the saves measured 57 to 335 clubs of the 48,000 to 51,000 each save holds had one, of between 3 and 60 months each; 60 months is as many as a club keeps. A save whose clubs keep none at all returns an empty table, and on a save with no human manager that is also what a search finding nothing would return: the checks bound the rows that were decoded, and the one count they bound from below applies only where the save lists a managed club. fmsave validate reports the clubs with a series and the club records searched either way.
balance is the balance at the end of the row’s month, and net_transfers is positive for a net spend. Money is a whole number in the save’s base currency, which can differ from the currency the game displays: on one FM26 save the balance, both budgets, the wage bill and the month’s income and expenditure all display at about 1.157 times the stored value, one rate fitting every one of them, while one FM27 career shown in pounds displays the stored figures unchanged. Nothing here is converted and no rate is applied. The weekly fields are weekly; every other money field is one month’s amount. transfer_budget_allocated is not the budget the board allocated: that screen showed a budget well above it with almost none of it spent, so what the field is stays unnamed.
month is worked out from the save’s own date rather than stored: the last row of a club is the month before the save’s month, which is the lag that put that club’s two oldest rows on the months its own screen labelled them with. Several useful figures are arithmetic on these rows rather than fields: wage headroom is wage_budget_weekly less wage_payroll_weekly, a yearly figure is a weekly one times 52, a club’s latest month is its last row, and a club summary is its rows grouped by club_uid.
The table is read on the first call to finances() or sponsorships(), from one decode pass; later calls to either return the same tables. When a check of that pass fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice, or the save’s in-game date is unreadable, so no month can be dated.
- Warns:
ReaderCheckWarning – On a full-size save, the months and sponsors decoded fall outside the checks’ bounds, or the club table falls outside its own. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- sponsorships()[source]¶
Every sponsorship contract the save lists for a club, in stored order.
The clubs are the ones finances() keeps a series for, in the same order, and each club’s contracts keep the order the save stores them in. The list keeps contracts that have ended, and an ended contract’s annual_value can remain non-zero. Dates and values are retained as stored. Filtering by end alone has not been established to reproduce the game’s displayed sponsorship income. An invalid structurally located FM27 list raises ReaderCheckError, regardless of save size.
kind carries a label only for the codes an FM27 sponsorship screen displayed a type against; every other code, and every FM26 code, is UNKNOWN. Take a contract’s length in years from total_value / annual_value, as the screen’s Duration does, not from start and end. On one FM27 club the screen’s total income was the sum of annual_value over its running rows, except one unexpired kind 6 row it left out. Money is a whole number in the save’s base currency, as in finances().
The table is read on the first call to finances() or sponsorships(), from one decode pass; later calls to either return the same tables. When a check of that pass fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, a team id is listed twice, or the save’s in-game date is unreadable, so no month can be dated.
- Warns:
ReaderCheckWarning – On a full-size save, the months and sponsors decoded fall outside the checks’ bounds, or the club table falls outside its own. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- facilities()[source]¶
Every club’s corporate facilities rating, in club index order.
The clubs are the ones `finances()` covers, because the rating is stored behind a club’s monthly finance chain and only the clubs of the one or two league nations a save tracks keep one. A club with no rows in finances() has no row here either, which is most clubs of a save and is ordinary rather than a failure; a save whose clubs keep no series returns an empty table, and fmsave validate reports the counts.
corporate_facilities is the rating the game’s Facilities screen shows as a word on its Corporate line, and the words rise with the stored number. Nineteen codes carry the word one club’s own screen displayed against that exact number, from 0 None and 1 Basic to 20 Excellent; FM27 screens named 0, 3, 4 and 16 and FM26 screens the rest. Two more, 8 and 14, carry the word of the codes either side of them, each of which a screen displayed the same word for. A code above 20 reads UNKNOWN with the raw number kept, rather than taking a word from where it sits. The screen’s other facility lines, the stadium and pitch, the training and youth grounds and the academy, are not stored anywhere near this byte and are not read at all.
The table is read on the first call; later calls return the same table.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – No club record is accepted, a club uid or club index appears in two records, or a team id is listed twice.
- Warns:
ReaderCheckWarning – On a full-size save, the ratings decoded fall outside the checks’ bounds, or the club table falls outside its own. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- staff()[source]¶
Everyone a club employs who is not a player, in the order their objects are stored.
A row is one person at one club: the department lists the club keeps him in, what his contract pays and until when, his ability and the preferences a staff profile shows. A person an affiliate side lists whose contract is with that side’s parent is one row at the parent, with the side that lists him in listed_club_uid, the same rule a player on a B team follows. A person a club lists but pays nothing has no contract to read, so his wage and both dates are empty rather than zero; about one listed person in seven of a save is like that.
The save’s human manager is a row of his own, with is_human_manager true. His object is laid out differently, so no ability, no preferences and none of the ability-block unknown keys read for him, while his name, birth date, personality and contract read as anyone’s do.
The contract codes are not player squad statuses. unknown[“contract_e36”] comes from the byte a player’s squad status is read from, but it takes different values on staff and no displayed label has named one, so it and the three codes beside it ship as raw numbers.
On FM27 every row carries its `job`, and `unknown[“r4”]` is not it. A club’s staff screen ruled r4 out: over 78 displayed rows, 8 of the 16 codes on show carried two or more different job titles. On FM27 a person who holds a club role with no contract, a board member for one, is a row with has_contract false and the role’s team, and a contract a person has only been offered gives no row. On FM26 job is None, offers are read as contracts, and the board is not in this table: the president, the director and the managing director appear on a club’s staff screen with no row here.
Several useful figures are arithmetic on these rows rather than fields: a club’s non-playing wage bill is the sum of wage over its rows, how many of its staff it lists is where(in_club_lists=True), and who it pays without listing is where(has_contract=True, in_club_lists=False).
The table is read on the first call to staff() or staff_lists(), from one decode pass; later calls to either return the same tables. When a check of that pass fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written, a contract record runs past the end of the game database, or a name block’s relation list runs past the window it was found in.
ReaderCheckError – The club or player readers raised, or the save’s in-game date is unreadable, so no contract can be dated.
- Warns:
ReaderCheckWarning – On a full-size save, the staff decoded fall outside the checks’ bounds, or the club or player checks this reader runs first do. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- staff_lists()[source]¶
The three staff lists each club record holds, in club index order.
A club that lists nobody has no row at all, and a club that lists somebody has all three rows, empty lists included. Only 733 to 1,957 of a save’s 48,000 to 51,000 clubs list anybody, so most clubs are absent, which is ordinary rather than a failure.
The lists split a club’s staff into groups whose codes differ from list to list. They look like the three departments a club’s staff screen shows: on one club the screen’s medical, coaching and recruitment panels and that club’s senior rows in lists 0, 1 and 2 held the same people, person for person by name, save for the human manager and one contracted person no list holds. That is arithmetic on one club rather than a label on a list, so list_index stays a number. People a list names who turn out to be players are dropped, and fmsave validate reports how many; so are the few whose object cannot be told from another’s, so a list’s people are those staff() also has a row for. A list’s size is len(staff_uids).
The table is read on the first call to staff() or staff_lists(), from one decode pass; later calls to either return the same tables. When a check of that pass fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written, a contract record runs past the end of the game database, or a name block’s relation list runs past the window it was found in.
ReaderCheckError – The club or player readers raised, or the save’s in-game date is unreadable, so no contract can be dated.
- Warns:
ReaderCheckWarning – On a full-size save, the staff decoded fall outside the checks’ bounds, or the club or player checks this reader runs first do. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- tactics()[source]¶
Every tactic the manager’s teams store, by team and then in stored order.
A tactic belongs to a team, not to a club. The save keeps each team’s own copy of every tactic the manager has, so the same tactic name appears once per team and a row is one stored copy, keyed on team_id and index. The copies are not byte-identical. Nothing stored says which tactic is selected, so there is no such field: the word that might hold it is the same on every block that carries a tactic at all.
Only the manager’s own teams are here. No other club stores a tactic in this form, and the line-ups the section keeps for thousands of other clubs are a different structure that fmsave does not read.
mentality carries the word the game shows: the seven codes are the seven mentalities it offers, from Very Defensive to Very Attacking, confirmed against a manager’s own tactics. Eleven position bits carry the label a tactics screen showed for them, from goalkeeper to centre striker; the others read UNKNOWN with their bit number kept. Roles have no label. The 19 team instructions and the setting units of each slot are unidentified numbers in the same way, and ship in unknown and as TacticSettingUnit values rather than under names they have not earned.
The table is read on the first call to tactics(), set_pieces() or selections(), from one walk; later calls to any of them return the same tables. When a tactic or set-piece check fails, no table is kept, so all three raise; a failed selection check stops only selections(). A cold call pays for the player pass, because the selectors the reader’s checks judge resolve through the player records.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The tactics section is too short to hold its header, a constant a team block is built around is not where fmsave expects it, a count inside a block runs past the end of the section, a name does not decode, no club record is accepted, a club uid or club index appears in two records, or a team id is listed twice.
- Warns:
ReaderCheckWarning – On a full-size save that lists a managed club, what the walk read falls outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- set_pieces()[source]¶
Every set-piece routine slot of the manager’s teams, by team and then slot.
Every default routine and every member of a counted user-routine group is a row. slot gives stored order within the team, and name is None for an unnamed routine. Groups can contain multiple routines, so a team can have more than twenty rows. A routine is recognised by its record structure, never by its name.
The table is read on the first call to tactics(), set_pieces() or selections(), from one walk; later calls to any of them return the same tables. When a tactic or set-piece check fails, no table is kept, so all three raise; a failed selection check stops only selections().
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The tactics section is too short to hold its header, a constant a team block is built around is not where fmsave expects it, a count inside a block runs past the end of the section, a name does not decode, no club record is accepted, a club uid or club index appears in two records, or a team id is listed twice.
- Warns:
ReaderCheckWarning – On a full-size save that lists a managed club, what the walk read falls outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- selections()[source]¶
Every stored pick of the manager’s teams: starters, then substitutes, by team.
A row is one starter or substitute place that holds a pick; an empty place has no row. A starter’s slot is the tactic slot it fills, in the tactic the team has selected, which nothing stored identifies. The substitutes are the stored list rather than the next match’s bench: places past the bench size of the next match can still hold players picked for an earlier one. Only the manager’s own teams are here.
The table is read on the first call to tactics(), set_pieces() or selections(), from one walk; later calls to any of them return the same tables. When a tactic or set-piece check fails, no table is kept, so all three raise. A failed selection check stops only this reader on a strict save. A cold call pays for the player pass, because every pick resolves through the player records.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The tactics section is too short to hold its header, a constant a team block is built around is not where fmsave expects it, a count inside a block runs past the end of the section, a name does not decode, no club record is accepted, a club uid or club index appears in two records, or a team id is listed twice.
- Warns:
ReaderCheckWarning – On a full-size save that lists a managed club, what the walk read falls outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- training()[source]¶
The training calendar of each team of the club the save’s manager runs.
One row per team, in the order the save stores them: the first team, the reserves, the youth side and each team the club fields at a club it controls, which maps back to the managed club with the slot it holds in that club’s list. No other club has a calendar at all, and a save whose manager runs no club returns an empty table.
Which week runs which schedule is settled. Each weekly record holds the week’s start date and then a schedule name, and the name is the schedule of the week whose date came before it: a manager’s Training screen showed exactly that schedule for four consecutive weeks, where pairing each name with the following week would have been a week out on all four. The dates themselves are solid too: consecutive weeks step exactly seven days on every pair of every calendar measured, and the calendar runs from well before the save’s date to well after it.
The active week of a team is the latest one that has started. A week whose stored date does not decode carries None, and a save whose date is unreadable has nothing to compare against, so both are stepped around:
clock = career_save.info.game_date weeks = career_save.training()[0].weeks started = [ week for week in weeks if week.week_start is not None and clock is not None and week.week_start <= clock ] active = max(started, default=None, key=lambda week: week.week_start)
Each week carries its seven days and each day its three sessions, as TrainingSession codes: on FM27 the sessions an FM27 Training screen named carry their label, and every other code, including every FM26 code, reads UNKNOWN with its raw number kept. schedule_library is the manager’s own saved schedules, which the save keeps once per section rather than per team, so every row carries the same tuple, and the save may hold the same schedule twice over under two ids.
The table is read on the first call to training() or mentoring(), from one walk; later calls to either return the same tables. When a check of that walk fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The training section is too short to hold its header list, a block the walk accepted holds a weekly record or a mentoring group it cannot read, no club record is accepted, a club uid or club index appears in two records, a team id is listed twice, or no player record is found.
- Warns:
ReaderCheckWarning – On a full-size save whose manager runs a club, the blocks and weeks read fall outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
- mentoring()[source]¶
Every mentoring group of every team of the club the save’s manager runs.
A group belongs to a team, not to the club: the rows come team by team in the order the save stores the teams, and within a team in stored order. Only the managed club has groups at all, and a team with none has no row here, which is ordinary – on the saves measured only the first team and one other side mentor anybody.
label is the stored text: the game’s own default wording with the group’s number, or the name the group was given in game. A member whose stored selector names no player record leaves that entry’s uid and name empty rather than guessing.
The table is read on the first call to training() or mentoring(), from one walk; later calls to either return the same tables. When a check of that walk fails, neither table is kept, so both raise.
- Raises:
SaveClosedError – The save is closed.
SaveChangedError – The file changed on disk after it was opened.
CorruptSaveError – The save is damaged or was being written.
ReaderCheckError – The training section is too short to hold its header list, a block the walk accepted holds a weekly record or a mentoring group it cannot read, no club record is accepted, a club uid or club index appears in two records, a team id is listed twice, or no player record is found.
- Warns:
ReaderCheckWarning – On a full-size save whose manager runs a club, the members read fall outside the checks’ bounds. A save opened with strict=True raises ReaderCheckError instead.
- Return type:
Experimental raw span¶
Save.experimental_span() is available for supported FM27 builds. It returns
partially decoded raw fixture cores and counted rules, with unsupported fields explicitly
identified. It is not the authoritative fixture calendar or semantic competition-rules
table. The validation report includes only its numeric coverage counts, never raw identities.
FM27 finance interpretation¶
FM27 support retains the prior-month interpretation of finance snapshots. It does not expose the current month’s live income and expenditure. Snapshot timing and payroll semantics still need independent FM27 validation. Money is returned in the save’s stored units without display-currency conversion; wages are weekly amounts. Comparing these rows with the game’s current finance overview requires matching the period and units.
Save metadata¶
- class fmsave.SaveInfo(game, build, build_number, known_build, db_version, game_date, time_slot, save_name, sections, summary_strings, section_schemas, file_name)[source]¶
Facts about a save, read when it is opened.
The repr leaves out the save name, the summary strings and the section list.
- Parameters:
- known_build¶
Whether fmsave has a layout registered for every reader for this build. It does not mean field meanings are confirmed: FM27 fields stay unconfirmed, and fmsave info shows an FM27 build as partial coverage. It is False for an FM27 build that borrows the registered build’s layouts.
- Type:
- game_date¶
In-game date, falling back to the structured save summary before the first Continue when the normal clock is null; None when neither is readable (unconfirmed).
- Type:
datetime.date | None
- sections¶
Every named section in file order.
- Type:
- summary_strings¶
The readable strings stored in the save summary, in file order, including the build string. They can hold the human manager’s name and club and competition names, so fmsave info does not show them (unconfirmed).
- section_schemas¶
Schema number of every named section, read-only.
- Type:
Competition names¶
Competition.database_id joins the id a name source keyed outside a save uses; this reads such
a source into the map fmsave.open takes.
- fmsave.read_competition_names(path)[source]¶
Read a two-column UTF-8 CSV of database_id,name into a competition name map.
The first column is the competition’s editor database id, which is what Competition.database_id carries. A first row whose first cell is not a whole number is taken for a header and skipped. Blank lines are skipped, and surrounding whitespace is stripped from both cells. A byte order mark at the head of the file, which Excel writes when it saves “CSV UTF-8”, is ignored.
One id may be listed twice with the same name. Two different names for one id are a contradiction and raise, because neither is more likely to be the one meant.
A row is exactly two columns wide. A third column is a fault as much as a missing one, since the extra cell leaves the row ambiguous: a name that holds a comma belongs in quotes rather than spread across cells.
Every error names the file and never the folder holding it, so a message a reader pastes into a bug report carries no part of their directory layout.
- Raises:
OSError – The file cannot be opened or read. It is left to the caller, which knows the path it passed.
ValueError – The file is not UTF-8 text, a row has other than two columns, an id is not a whole number, a name is empty, or one id is listed twice under two names.
- Parameters:
- Return type:
Validation¶
- fmsave.validate_save(career_save)[source]¶
Run every reader on a save and report how each fared.
Readers run in the order clubs, players, contracts, suspensions, managed clubs, stages, competitions, fixtures, league tables, transfer windows, competition rules, per-match player stats, stadiums, finances, sponsorships, affiliates, job vacancies, staff, staff lists, injury types, injury history, training, mentoring, tactics, set pieces, selections, club facilities. The order puts each reader after the ones whose work it reuses, so a shared decode that fails is reported where it failed: stadiums follow the fixtures whose calendar they read, injury types precede the injury history that names its types, and the two readers of each shared pass sit side by side. A reader whose checks fail is reported “failed” with its checks, and one that raises another fmsave error is reported “error” without the error’s text; the remaining readers still run. The report holds only structural facts, counts and rates, never names, uids or other values from the save.
Readers run here exactly as they would for any other caller, with their warnings suppressed for the length of the run: this report lists every check each reader ran, so a warning beside it would only repeat what it already says. Running them leaves the save no different from how a caller who read those tables himself would leave it. On a save opened the default way nothing raises for a failed check, so every table this decodes is kept: the reader calls a caller makes afterwards are free of charge, and say nothing further about a check this report has already reported.
Some readers share one decode: the players, contracts and suspensions of the player pass, the fixtures, league tables, competition rules and stadiums that all need the one streamed pass over the span, and the finance, staff, training and tactics pairs, each built by one pass of its own. When that shared decode raises before it finishes, its error is reported for every reader of the pass and the decode is not attempted again, so the span is streamed once however many of its readers report it. A reader that failed after its pass had been decoded failed on its own, and the others still run.
- Raises:
SaveClosedError – The save is closed.
- Parameters:
career_save (Save)
- Return type:
- class fmsave.ValidationReport(fmsave_version, python_version, os, game, build, known_build, section_schemas, readers, field_statuses, experimental_counts=<factory>)[source]¶
What validate_save found, holding no names, uids or other values from the save.
- Parameters:
- known_build¶
Whether fmsave has a layout registered for every reader for this build. It does not mean field meanings are confirmed: FM27 fields stay unconfirmed, and fmsave info shows an FM27 build as partial coverage. It is False for an FM27 build that borrows the registered build’s layouts.
- Type:
- section_schemas¶
Schema number of every named section.
- Type:
- readers¶
Each reader’s outcome, in the order they ran.
- Type:
- field_statuses¶
Whether each public field’s meaning is “verified” or “unconfirmed”.
- Type:
- class fmsave.ReaderValidation(reader, status, record_count, gates, coverage, anomalies)[source]¶
How one reader fared in validate_save.
A reader is “failed” when a check of its own did not pass, and it is the only one reported so: the readers that join through it read the table it returned and are judged on their own checks.
A save opened with strict=True is reported differently, because a failed check stops the reader there and everything downstream of it with it. Players, contracts and suspensions are decoded in one pass, so when a check of any of the three fails, all three are reported “failed” and none of their tables is read, and each of them still lists its own gates: a reader can be “failed” while every one of its own gates passed. Fixtures, league tables, competition rules and stadiums share the one streamed pass over the span the same way, and are reported together when that pass itself is what failed. Four more pairs share a decode in the same way: finances with sponsorships, staff with staff lists, training with mentoring, and tactics with set pieces and selections. A structural failure spreads the same way on any save, since no mode reads a table from a decode that produced none.
- Parameters:
- reader¶
The reader: “clubs”, “players”, “contracts”, “suspensions”, “managed_clubs”, “stages”, “competitions”, “fixtures”, “league_tables”, “transfer_windows”, “competition_rules”, “player_match_stats”, “player_season_stats”, “player_season_history”, “stadiums”, “finances”, “sponsorships”, “affiliates”, “job_vacancies”, “staff”, “staff_lists”, “injury_types”, “injuries”, “training”, “mentoring”, “tactics”, “set_pieces”, “selections” or “facilities”.
- Type:
- status¶
“ok” when the reader returned its table and every applied check of its own passed, “failed” when a check did not, and “error” when it raised another fmsave error.
- Type:
fmsave._checks.ReaderStatus
- record_count¶
How many records the reader decoded, or None when it did not get far enough to count them.
- Type:
int | None
- gates¶
The reader’s checks, in a fixed order; empty when the reader has no checks of its own, as the managed-club reader does not, or when it did not get far enough.
- Type:
- coverage¶
The table’s coverage (the share of non-None values per column); empty unless the status is “ok”.
- Type:
- anomalies¶
Counts of records the reader decoded but flags, such as unresolved teams.
- Type:
- class fmsave.ReaderCheck(reader, record_count, gates, anomalies)[source]¶
One reader’s evaluated checks, its record count and its anomaly counts.
It is what a GateCheckError carries, so a caller can read a failure from its numbers rather than from the message text.
- Parameters:
- gates¶
The reader’s checks, in a fixed order; empty when it has none of its own.
- Type:
- anomalies¶
Counts of records the reader decoded but flags, such as unresolved teams.
- Type:
Package metadata¶
- fmsave.__version__ = '0.9.1'¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.
- fmsave.OUTPUT_SCHEMA_VERSION = 2¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4