Getting data out

Every reader returns a Table. Export nested records for another program or flat columns for a spreadsheet.

import fmsave

with fmsave.open("career.fm") as career_save:
    squad = career_save.players().where(club_uid=career_save.managed_clubs()[0].club_uid)

rows = squad.to_dicts()  # nested dicts of Python values
columns = squad.to_columns()  # flat columns of Python values
frame = squad.to_pandas()  # needs fmsave[pandas]

squad.write_csv("squad.csv")  # flat columns
squad.write_json("squad.json")  # nested, one array
squad.write_jsonl("squad.jsonl")  # nested, one record per line

to_polars() is there too, and needs fmsave[polars]. Tables keep working after the save is closed, so exporting outside the with block is fine.

Nested or flat

A record is nested. A player carries an ability group, an attributes group, a contract, and so on. Exports either keep that shape or flatten it.

  • Nested: to_dicts(), write_json(), write_jsonl().

  • Flat: to_columns(), to_pandas(), to_polars(), write_csv().

The flattening rules are short:

  • A nested group becomes <field>_<subfield>: ability_current, contract_wage, attributes_finishing. These are the names pandas.json_normalize(sep="_") would give.

  • A coded value becomes 2 columns: a label column and a <field>_code column holding the raw number, so contract_squad_status and contract_squad_status_code.

  • A tuple stays a single value: a JSON array in JSON, a ;-joined string in CSV, or compact JSON text in CSV when its items are groups.

To see the column names for any record type without opening a save:

import fmsave.export

fmsave.export.column_names(fmsave.Player)  # ("uid", "unique_id", "name", ...)

Nested JSON carries the label and raw code for coded values. Dates become ISO strings, and tuples become arrays. CSV is convenient for spreadsheets; use JSON when nested structure matters.

to_dicts(json_ready=True) gives the same nesting write_json() uses: dates as ISO strings, tuples as lists, everything JSON-serialisable.

From the command line

fmsave export does the same work without Python, and needs a table and a scope.

fmsave export career.fm players --managed-club -o squad.csv
fmsave export career.fm players --managed-club --columns name,age,ability_current,contract_wage
fmsave export career.fm players --all --format json -o players.json
fmsave export career.fm fixtures --managed-club --format jsonl -o fixtures.jsonl
fmsave export career.fm player-season-stats --managed-club -o season.csv

Supply exactly 1 scope supported by the table:

  • --managed-club: the club you run.

  • --club VALUE: one club by uid, name or short name, in any case. A name that matches more than one club is an error that lists the matches, so you can pick the uid.

  • --competition VALUE: one competition by id, or by name once --competition-names supplies one.

  • --nation VALUE: one nation, by nation id.

  • --all: every row.

The rest:

  • --format {csv,json,jsonl}, default csv.

  • --columns NAMES: flat column names, comma-separated, written in the order you give them.

  • -o PATH: a file instead of standard output.

  • --competition-names PATH: a UTF-8 CSV of database_id,name. See What a save holds.

  • --strict: stop rather than write when a reader’s checks fail. See Trusting a number.

Table names on the command line use - where the Python method uses _: managed-clubs, league-tables, player-match-stats, player-season-stats, set-pieces.

Not every table accepts every scope. An unsupported scope returns an error listing the accepted alternatives. For example, transfer-windows and injury-types require --all; competition-rules accepts --competition or --all.

For player-season-stats, a club or nation scope selects current players and includes their rows for other teams this season. It does not select a single named competition. To export league rows for your club in Python:

with fmsave.open("career.fm") as career_save:
    league = career_save.player_season_stats().where(
        club_uid=career_save.managed_clubs()[0].club_uid,
        kind=fmsave.SeasonStatsKind.LEAGUE,
    )
league.write_csv("league.csv")

Keep counts such as minutes alongside rates such as expected_goals_per_90 and pass_completion_percent. Rates are included automatically and become None when the count does not apply or the denominator is zero.

training, mentoring, tactics and set-pieces return data for the club you manage. Selecting another club returns no rows.