fmsave 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. fmsave-0.4.0/LICENSE +21 -0
  2. fmsave-0.4.0/PKG-INFO +164 -0
  3. fmsave-0.4.0/README.md +135 -0
  4. fmsave-0.4.0/pyproject.toml +102 -0
  5. fmsave-0.4.0/pyproject.toml.orig +87 -0
  6. fmsave-0.4.0/src/fmsave/__init__.py +194 -0
  7. fmsave-0.4.0/src/fmsave/__main__.py +4 -0
  8. fmsave-0.4.0/src/fmsave/_checks.py +2510 -0
  9. fmsave-0.4.0/src/fmsave/_container.py +627 -0
  10. fmsave-0.4.0/src/fmsave/_context.py +385 -0
  11. fmsave-0.4.0/src/fmsave/_errors.py +52 -0
  12. fmsave-0.4.0/src/fmsave/_frozen.py +43 -0
  13. fmsave-0.4.0/src/fmsave/_layouts/__init__.py +2036 -0
  14. fmsave-0.4.0/src/fmsave/_layouts/fm26_26_3_2.py +1488 -0
  15. fmsave-0.4.0/src/fmsave/_package.py +8 -0
  16. fmsave-0.4.0/src/fmsave/_reader_stats.py +729 -0
  17. fmsave-0.4.0/src/fmsave/_save.py +2305 -0
  18. fmsave-0.4.0/src/fmsave/_scan.py +195 -0
  19. fmsave-0.4.0/src/fmsave/_status.py +179 -0
  20. fmsave-0.4.0/src/fmsave/_version.py +248 -0
  21. fmsave-0.4.0/src/fmsave/checks.py +38 -0
  22. fmsave-0.4.0/src/fmsave/cli.py +1288 -0
  23. fmsave-0.4.0/src/fmsave/export.py +1238 -0
  24. fmsave-0.4.0/src/fmsave/models/__init__.py +138 -0
  25. fmsave-0.4.0/src/fmsave/models/affiliates.py +57 -0
  26. fmsave-0.4.0/src/fmsave/models/clubs.py +90 -0
  27. fmsave-0.4.0/src/fmsave/models/common.py +72 -0
  28. fmsave-0.4.0/src/fmsave/models/competitions.py +118 -0
  29. fmsave-0.4.0/src/fmsave/models/contracts.py +309 -0
  30. fmsave-0.4.0/src/fmsave/models/facilities.py +109 -0
  31. fmsave-0.4.0/src/fmsave/models/finances.py +143 -0
  32. fmsave-0.4.0/src/fmsave/models/fixtures.py +184 -0
  33. fmsave-0.4.0/src/fmsave/models/injuries.py +180 -0
  34. fmsave-0.4.0/src/fmsave/models/jobs.py +76 -0
  35. fmsave-0.4.0/src/fmsave/models/league_tables.py +277 -0
  36. fmsave-0.4.0/src/fmsave/models/managed.py +45 -0
  37. fmsave-0.4.0/src/fmsave/models/matches.py +182 -0
  38. fmsave-0.4.0/src/fmsave/models/meta.py +94 -0
  39. fmsave-0.4.0/src/fmsave/models/players.py +566 -0
  40. fmsave-0.4.0/src/fmsave/models/rules.py +251 -0
  41. fmsave-0.4.0/src/fmsave/models/stadiums.py +131 -0
  42. fmsave-0.4.0/src/fmsave/models/staff.py +286 -0
  43. fmsave-0.4.0/src/fmsave/models/suspensions.py +149 -0
  44. fmsave-0.4.0/src/fmsave/models/tactics.py +256 -0
  45. fmsave-0.4.0/src/fmsave/models/training.py +153 -0
  46. fmsave-0.4.0/src/fmsave/name_maps.py +186 -0
  47. fmsave-0.4.0/src/fmsave/py.typed +0 -0
  48. fmsave-0.4.0/src/fmsave/readers/__init__.py +1 -0
  49. fmsave-0.4.0/src/fmsave/readers/_common.py +102 -0
  50. fmsave-0.4.0/src/fmsave/readers/affiliates.py +121 -0
  51. fmsave-0.4.0/src/fmsave/readers/clubs.py +618 -0
  52. fmsave-0.4.0/src/fmsave/readers/competitions.py +281 -0
  53. fmsave-0.4.0/src/fmsave/readers/contracts.py +1546 -0
  54. fmsave-0.4.0/src/fmsave/readers/facilities.py +84 -0
  55. fmsave-0.4.0/src/fmsave/readers/finances.py +460 -0
  56. fmsave-0.4.0/src/fmsave/readers/fixtures.py +357 -0
  57. fmsave-0.4.0/src/fmsave/readers/injuries.py +633 -0
  58. fmsave-0.4.0/src/fmsave/readers/jobs.py +281 -0
  59. fmsave-0.4.0/src/fmsave/readers/league_tables.py +595 -0
  60. fmsave-0.4.0/src/fmsave/readers/managed.py +352 -0
  61. fmsave-0.4.0/src/fmsave/readers/matches.py +601 -0
  62. fmsave-0.4.0/src/fmsave/readers/names.py +178 -0
  63. fmsave-0.4.0/src/fmsave/readers/persons.py +642 -0
  64. fmsave-0.4.0/src/fmsave/readers/player_scan.py +440 -0
  65. fmsave-0.4.0/src/fmsave/readers/players.py +536 -0
  66. fmsave-0.4.0/src/fmsave/readers/results.py +425 -0
  67. fmsave-0.4.0/src/fmsave/readers/rules.py +399 -0
  68. fmsave-0.4.0/src/fmsave/readers/span.py +1068 -0
  69. fmsave-0.4.0/src/fmsave/readers/stadiums.py +681 -0
  70. fmsave-0.4.0/src/fmsave/readers/staff.py +1067 -0
  71. fmsave-0.4.0/src/fmsave/readers/stages.py +374 -0
  72. fmsave-0.4.0/src/fmsave/readers/suspensions.py +353 -0
  73. fmsave-0.4.0/src/fmsave/readers/tactics.py +751 -0
  74. fmsave-0.4.0/src/fmsave/readers/training.py +511 -0
  75. fmsave-0.4.0/src/fmsave/table.py +441 -0
fmsave-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Randal S. Olson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fmsave-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: fmsave
3
+ Version: 0.4.0
4
+ Summary: Read Football Manager 26 save files into Python records, DataFrames, CSV and JSON.
5
+ Keywords: football-manager,fm26,save,parser,data
6
+ Author: Randal S. Olson
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Games/Entertainment
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: backports-zstd>=1.7 ; python_full_version < '3.14'
21
+ Requires-Dist: pandas>=2.2 ; extra == 'pandas'
22
+ Requires-Dist: polars>=1.0 ; extra == 'polars'
23
+ Requires-Python: >=3.12
24
+ Project-URL: Repository, https://github.com/rhiever/fmsave
25
+ Project-URL: Issues, https://github.com/rhiever/fmsave/issues
26
+ Provides-Extra: pandas
27
+ Provides-Extra: polars
28
+ Description-Content-Type: text/markdown
29
+
30
+ # fmsave
31
+
32
+ Read your Football Manager 26 saves from disk into Python records, DataFrames, CSV and JSON.
33
+
34
+ ## Install
35
+
36
+ fmsave needs Python 3.12 or newer.
37
+
38
+ ```console
39
+ uvx fmsave info path/to/career.fm # try it without installing
40
+ pip install fmsave
41
+ pip install "fmsave[pandas]" # adds Table.to_pandas()
42
+ ```
43
+
44
+ ## Quickstart
45
+
46
+ ```python
47
+ import fmsave
48
+
49
+ with fmsave.open("career.fm") as career_save:
50
+ my_club = career_save.managed_clubs()[0] # empty when you are between jobs
51
+ squad = career_save.players().where(club_uid=my_club.club_uid)
52
+
53
+ for player in squad.sorted_by(lambda player: player.ability.current, reverse=True)[:5]:
54
+ print(player.name, player.age, player.ability.current, player.attributes.finishing)
55
+ # "Alex Example" 24 148 16
56
+
57
+ squad_frame = squad.to_pandas() # needs fmsave[pandas]
58
+ squad.write_csv("squad.csv")
59
+ ```
60
+
61
+ A player carries his names, birth date and age, nationality, club, height, positions, all 24
62
+ attributes, the eight personality attributes, current and potential ability, reputation, transfer
63
+ value, condition, traits, his contract and any unserved ban. Records and tables are immutable and
64
+ keep working after the save is closed.
65
+
66
+ Each reader returns a `Table`: `where(...)`, `filter(...)`, `sorted_by(...)`, `find(name=...)`,
67
+ `by_uid(...)`, `by_id(...)` and `coverage`, plus `to_dicts()`, `to_columns()`, `to_pandas()`,
68
+ `to_polars()`, `write_csv(...)`, `write_json(...)` and `write_jsonl(...)`.
69
+
70
+ ## What else it reads
71
+
72
+ Twenty-six readers in all, each returning a `Table` of records:
73
+
74
+ - **People** — `players()`, `contracts()`, `suspensions()`, `staff()`, `staff_lists()`
75
+ - **Clubs** — `clubs()`, `managed_clubs()`, `finances()`, `sponsorships()`, `facilities()`,
76
+ `stadiums()`, `affiliates()`, `job_vacancies()`
77
+ - **Competitions** — `stages()`, `competitions()`, `fixtures()`, `league_tables()`,
78
+ `competition_rules()`, `transfer_windows()`, `player_match_stats()`
79
+ - **Injuries** — `injury_types()`, `injuries()`
80
+ - **Your own club's work**, which only the club you manage stores — `training()`, `mentoring()`,
81
+ `tactics()`, `set_pieces()`
82
+
83
+ Every field is either verified against the game or unconfirmed; ask with
84
+ `fmsave.field_status(fmsave.Player, "contract.wage")`. Values come from the save as stored, a value
85
+ fmsave cannot read is `None` rather than a guess, and money is kept in the unit the game stores it
86
+ in, which is not the currency it displays.
87
+
88
+ Each reader also measures what it decoded against loose bounds drawn from a couple of careers. A
89
+ save unlike them can miss one and still be read perfectly well, so a missed bound is a warning and
90
+ you get the table anyway. Pass `strict=True` to `fmsave.open` to have one raise instead.
91
+
92
+ ## What it cannot read
93
+
94
+ - **Names the game renders from its own installed database.** Competitions, leagues, nations,
95
+ cities and all but a couple of hundred grounds. fmsave ships none of these and reads nothing from
96
+ your game install. Competitions carry `database_id`, the id every outside name source is keyed
97
+ on, so you can supply your own map — see [Competition names](#competition-names).
98
+ - **Links the save does not store**, such as which competition a rules block belongs to, or which
99
+ ground a club plays at. Worked out from the fixture calendar where possible, left empty where not.
100
+ - **Meanings for numbers the game never displayed**, such as staff job titles or the settings
101
+ inside a tactic. These come back as raw numbers rather than as labels fmsave guessed at.
102
+ - **What the save keeps only in part or not at all** — scores for about three quarters of a
103
+ career's matches, injuries older than about two years, and today's availability, line-ups, staff
104
+ attribute values, card counts, club debt and asking prices.
105
+
106
+ ## Competition names
107
+
108
+ ```python
109
+ competition_names = fmsave.read_competition_names("competition-names.csv")
110
+ with fmsave.open("career.fm", competition_names=competition_names) as career_save:
111
+ for competition in career_save.competitions():
112
+ print(competition.database_id, competition.name) # 12345 "Example League"
113
+ ```
114
+
115
+ The file is UTF-8, two columns, `database_id` then `name`; a leading header row is skipped. Any
116
+ mapping of database id to name works too. Without a map, `name` and every denormalised
117
+ `competition_name` is `None`. About one competition in ten carries no database id and can never be
118
+ named, and one the game created during a career carries an id no outside source holds.
119
+
120
+ ## Command line
121
+
122
+ ```console
123
+ fmsave info career.fm
124
+ fmsave export career.fm players --managed-club -o squad.csv
125
+ fmsave export career.fm players --nation 7 --columns name,age,club_name,contract_wage
126
+ fmsave validate career.fm --json
127
+ ```
128
+
129
+ `export` writes any of the twenty-six tables as CSV, JSON or JSON Lines, and needs a scope:
130
+ `--club`, `--managed-club`, `--competition`, `--nation` or `--all`. Nested groups flatten to
131
+ `contract_wage`-style columns and a coded value gives a label column plus a `_code` column, so JSON
132
+ is the lossless format. `validate` runs every reader and reports its checks, counts and coverage,
133
+ holding no names, uids or text from the save, so it is safe to paste into an issue.
134
+
135
+ ## Where saves live
136
+
137
+ - macOS: `~/Library/Application Support/Sports Interactive/Football Manager 26/games/`
138
+ - Windows: usually `Documents\Sports Interactive\Football Manager 26\games\`
139
+ - Steam Cloud and other stores may keep saves somewhere else.
140
+
141
+ If the game is running, copy the save and read the copy.
142
+
143
+ ## Safety
144
+
145
+ fmsave is read-only. It never modifies saves, makes no network connections, and does not read game
146
+ memory. Using it to gain an advantage in shared online careers may break platform or community
147
+ rules.
148
+
149
+ ## Support
150
+
151
+ fmsave is a hobby project, maintained on a best-effort basis. Report problems through
152
+ [GitHub issues](https://github.com/rhiever/fmsave/issues), and never attach save files. See
153
+ [CONTRIBUTING.md](CONTRIBUTING.md) for how to report a wrong value without sharing real data. To
154
+ request removal of any content, open an issue with the rights request form.
155
+
156
+ ## Disclaimer
157
+
158
+ fmsave is an unofficial fan project. It is not affiliated with, endorsed by, or sponsored by Sports
159
+ Interactive or SEGA. Football Manager, Sports Interactive and SEGA are trademarks or registered
160
+ trademarks of their respective owners.
161
+
162
+ ## License
163
+
164
+ MIT. See [LICENSE](LICENSE).
fmsave-0.4.0/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # fmsave
2
+
3
+ Read your Football Manager 26 saves from disk into Python records, DataFrames, CSV and JSON.
4
+
5
+ ## Install
6
+
7
+ fmsave needs Python 3.12 or newer.
8
+
9
+ ```console
10
+ uvx fmsave info path/to/career.fm # try it without installing
11
+ pip install fmsave
12
+ pip install "fmsave[pandas]" # adds Table.to_pandas()
13
+ ```
14
+
15
+ ## Quickstart
16
+
17
+ ```python
18
+ import fmsave
19
+
20
+ with fmsave.open("career.fm") as career_save:
21
+ my_club = career_save.managed_clubs()[0] # empty when you are between jobs
22
+ squad = career_save.players().where(club_uid=my_club.club_uid)
23
+
24
+ for player in squad.sorted_by(lambda player: player.ability.current, reverse=True)[:5]:
25
+ print(player.name, player.age, player.ability.current, player.attributes.finishing)
26
+ # "Alex Example" 24 148 16
27
+
28
+ squad_frame = squad.to_pandas() # needs fmsave[pandas]
29
+ squad.write_csv("squad.csv")
30
+ ```
31
+
32
+ A player carries his names, birth date and age, nationality, club, height, positions, all 24
33
+ attributes, the eight personality attributes, current and potential ability, reputation, transfer
34
+ value, condition, traits, his contract and any unserved ban. Records and tables are immutable and
35
+ keep working after the save is closed.
36
+
37
+ Each reader returns a `Table`: `where(...)`, `filter(...)`, `sorted_by(...)`, `find(name=...)`,
38
+ `by_uid(...)`, `by_id(...)` and `coverage`, plus `to_dicts()`, `to_columns()`, `to_pandas()`,
39
+ `to_polars()`, `write_csv(...)`, `write_json(...)` and `write_jsonl(...)`.
40
+
41
+ ## What else it reads
42
+
43
+ Twenty-six readers in all, each returning a `Table` of records:
44
+
45
+ - **People** — `players()`, `contracts()`, `suspensions()`, `staff()`, `staff_lists()`
46
+ - **Clubs** — `clubs()`, `managed_clubs()`, `finances()`, `sponsorships()`, `facilities()`,
47
+ `stadiums()`, `affiliates()`, `job_vacancies()`
48
+ - **Competitions** — `stages()`, `competitions()`, `fixtures()`, `league_tables()`,
49
+ `competition_rules()`, `transfer_windows()`, `player_match_stats()`
50
+ - **Injuries** — `injury_types()`, `injuries()`
51
+ - **Your own club's work**, which only the club you manage stores — `training()`, `mentoring()`,
52
+ `tactics()`, `set_pieces()`
53
+
54
+ Every field is either verified against the game or unconfirmed; ask with
55
+ `fmsave.field_status(fmsave.Player, "contract.wage")`. Values come from the save as stored, a value
56
+ fmsave cannot read is `None` rather than a guess, and money is kept in the unit the game stores it
57
+ in, which is not the currency it displays.
58
+
59
+ Each reader also measures what it decoded against loose bounds drawn from a couple of careers. A
60
+ save unlike them can miss one and still be read perfectly well, so a missed bound is a warning and
61
+ you get the table anyway. Pass `strict=True` to `fmsave.open` to have one raise instead.
62
+
63
+ ## What it cannot read
64
+
65
+ - **Names the game renders from its own installed database.** Competitions, leagues, nations,
66
+ cities and all but a couple of hundred grounds. fmsave ships none of these and reads nothing from
67
+ your game install. Competitions carry `database_id`, the id every outside name source is keyed
68
+ on, so you can supply your own map — see [Competition names](#competition-names).
69
+ - **Links the save does not store**, such as which competition a rules block belongs to, or which
70
+ ground a club plays at. Worked out from the fixture calendar where possible, left empty where not.
71
+ - **Meanings for numbers the game never displayed**, such as staff job titles or the settings
72
+ inside a tactic. These come back as raw numbers rather than as labels fmsave guessed at.
73
+ - **What the save keeps only in part or not at all** — scores for about three quarters of a
74
+ career's matches, injuries older than about two years, and today's availability, line-ups, staff
75
+ attribute values, card counts, club debt and asking prices.
76
+
77
+ ## Competition names
78
+
79
+ ```python
80
+ competition_names = fmsave.read_competition_names("competition-names.csv")
81
+ with fmsave.open("career.fm", competition_names=competition_names) as career_save:
82
+ for competition in career_save.competitions():
83
+ print(competition.database_id, competition.name) # 12345 "Example League"
84
+ ```
85
+
86
+ The file is UTF-8, two columns, `database_id` then `name`; a leading header row is skipped. Any
87
+ mapping of database id to name works too. Without a map, `name` and every denormalised
88
+ `competition_name` is `None`. About one competition in ten carries no database id and can never be
89
+ named, and one the game created during a career carries an id no outside source holds.
90
+
91
+ ## Command line
92
+
93
+ ```console
94
+ fmsave info career.fm
95
+ fmsave export career.fm players --managed-club -o squad.csv
96
+ fmsave export career.fm players --nation 7 --columns name,age,club_name,contract_wage
97
+ fmsave validate career.fm --json
98
+ ```
99
+
100
+ `export` writes any of the twenty-six tables as CSV, JSON or JSON Lines, and needs a scope:
101
+ `--club`, `--managed-club`, `--competition`, `--nation` or `--all`. Nested groups flatten to
102
+ `contract_wage`-style columns and a coded value gives a label column plus a `_code` column, so JSON
103
+ is the lossless format. `validate` runs every reader and reports its checks, counts and coverage,
104
+ holding no names, uids or text from the save, so it is safe to paste into an issue.
105
+
106
+ ## Where saves live
107
+
108
+ - macOS: `~/Library/Application Support/Sports Interactive/Football Manager 26/games/`
109
+ - Windows: usually `Documents\Sports Interactive\Football Manager 26\games\`
110
+ - Steam Cloud and other stores may keep saves somewhere else.
111
+
112
+ If the game is running, copy the save and read the copy.
113
+
114
+ ## Safety
115
+
116
+ fmsave is read-only. It never modifies saves, makes no network connections, and does not read game
117
+ memory. Using it to gain an advantage in shared online careers may break platform or community
118
+ rules.
119
+
120
+ ## Support
121
+
122
+ fmsave is a hobby project, maintained on a best-effort basis. Report problems through
123
+ [GitHub issues](https://github.com/rhiever/fmsave/issues), and never attach save files. See
124
+ [CONTRIBUTING.md](CONTRIBUTING.md) for how to report a wrong value without sharing real data. To
125
+ request removal of any content, open an issue with the rights request form.
126
+
127
+ ## Disclaimer
128
+
129
+ fmsave is an unofficial fan project. It is not affiliated with, endorsed by, or sponsored by Sports
130
+ Interactive or SEGA. Football Manager, Sports Interactive and SEGA are trademarks or registered
131
+ trademarks of their respective owners.
132
+
133
+ ## License
134
+
135
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,102 @@
1
+ [project]
2
+ name = "fmsave"
3
+ version = "0.4.0"
4
+ description = "Read Football Manager 26 save files into Python records, DataFrames, CSV and JSON."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "football-manager",
11
+ "fm26",
12
+ "save",
13
+ "parser",
14
+ "data",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: Science/Research",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Games/Entertainment",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = ["backports.zstd>=1.7; python_version < '3.14'"]
30
+
31
+ [[project.authors]]
32
+ name = "Randal S. Olson"
33
+
34
+ [project.optional-dependencies]
35
+ pandas = ["pandas>=2.2"]
36
+ polars = ["polars>=1.0"]
37
+
38
+ [project.urls]
39
+ Repository = "https://github.com/rhiever/fmsave"
40
+ Issues = "https://github.com/rhiever/fmsave/issues"
41
+
42
+ [project.scripts]
43
+ fmsave = "fmsave.cli:main"
44
+
45
+ [dependency-groups]
46
+ test = [
47
+ "pytest>=9.1",
48
+ "pytest-cov>=7.1",
49
+ "pytest-timeout>=2.4",
50
+ "hypothesis>=6.168",
51
+ "syrupy>=6.1",
52
+ "pandas>=2.2",
53
+ "polars>=1.0",
54
+ ]
55
+ dev = [
56
+ { include-group = "test" },
57
+ "ruff>=0.16.7,<0.17",
58
+ "pyright>=1.1.414",
59
+ ]
60
+
61
+ [build-system]
62
+ requires = ["uv_build>=0.12.13,<0.13"]
63
+ build-backend = "uv_build"
64
+
65
+ [tool.uv]
66
+ default-groups = ["dev"]
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ target-version = "py312"
71
+
72
+ [tool.pyright]
73
+ include = [
74
+ "src",
75
+ "tests",
76
+ "scripts",
77
+ ]
78
+ typeCheckingMode = "standard"
79
+ strict = [
80
+ "src",
81
+ "scripts",
82
+ ]
83
+
84
+ [tool.pytest.ini_options]
85
+ testpaths = ["tests"]
86
+ pythonpath = ["."]
87
+ strict = true
88
+ addopts = ["-ra"]
89
+ timeout = "300"
90
+ markers = [
91
+ "corpus: needs the local private corpus (never runs in CI)",
92
+ "corpus_fast: corpus tier for open, info, players and clubs",
93
+ "corpus_full: corpus tier for every reader",
94
+ ]
95
+
96
+ [tool.coverage.run]
97
+ branch = true
98
+ source = ["fmsave"]
99
+
100
+ [tool.coverage.report]
101
+ fail_under = 90
102
+ show_missing = true
@@ -0,0 +1,87 @@
1
+ [project]
2
+ name = "fmsave"
3
+ version = "0.4.0"
4
+ description = "Read Football Manager 26 save files into Python records, DataFrames, CSV and JSON."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Randal S. Olson" }]
10
+ keywords = ["football-manager", "fm26", "save", "parser", "data"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Intended Audience :: Science/Research",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Games/Entertainment",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = ["backports.zstd>=1.7; python_version < '3.14'"]
25
+
26
+ [project.optional-dependencies]
27
+ pandas = ["pandas>=2.2"]
28
+ polars = ["polars>=1.0"]
29
+
30
+ [project.urls]
31
+ Repository = "https://github.com/rhiever/fmsave"
32
+ Issues = "https://github.com/rhiever/fmsave/issues"
33
+
34
+ [project.scripts]
35
+ fmsave = "fmsave.cli:main"
36
+
37
+ [dependency-groups]
38
+ test = [
39
+ "pytest>=9.1",
40
+ "pytest-cov>=7.1",
41
+ "pytest-timeout>=2.4",
42
+ "hypothesis>=6.168",
43
+ "syrupy>=6.1",
44
+ "pandas>=2.2",
45
+ "polars>=1.0",
46
+ ]
47
+ dev = [
48
+ { include-group = "test" },
49
+ "ruff>=0.16.7,<0.17",
50
+ "pyright>=1.1.414",
51
+ ]
52
+
53
+ [build-system]
54
+ requires = ["uv_build>=0.12.13,<0.13"]
55
+ build-backend = "uv_build"
56
+
57
+ [tool.uv]
58
+ default-groups = ["dev"]
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ target-version = "py312"
63
+
64
+ [tool.pyright]
65
+ include = ["src", "tests", "scripts"]
66
+ typeCheckingMode = "standard"
67
+ strict = ["src", "scripts"]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ pythonpath = ["."]
72
+ strict = true
73
+ addopts = ["-ra"]
74
+ timeout = "300"
75
+ markers = [
76
+ "corpus: needs the local private corpus (never runs in CI)",
77
+ "corpus_fast: corpus tier for open, info, players and clubs",
78
+ "corpus_full: corpus tier for every reader",
79
+ ]
80
+
81
+ [tool.coverage.run]
82
+ branch = true
83
+ source = ["fmsave"]
84
+
85
+ [tool.coverage.report]
86
+ fail_under = 90
87
+ show_missing = true