steam-data-reader 1.0.0.dev1__py3-none-any.whl
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.
- steam_data_reader/__init__.py +8 -0
- steam_data_reader/catalog.py +364 -0
- steam_data_reader/cli.py +94 -0
- steam_data_reader/database.py +271 -0
- steam_data_reader/sync.py +333 -0
- steam_data_reader-1.0.0.dev1.dist-info/METADATA +472 -0
- steam_data_reader-1.0.0.dev1.dist-info/RECORD +10 -0
- steam_data_reader-1.0.0.dev1.dist-info/WHEEL +4 -0
- steam_data_reader-1.0.0.dev1.dist-info/entry_points.txt +2 -0
- steam_data_reader-1.0.0.dev1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steam-data-reader
|
|
3
|
+
Version: 1.0.0.dev1
|
|
4
|
+
Summary: Offline DuckDB queries for downloadable current Steam snapshots
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: duckdb<2,>=1.4
|
|
12
|
+
Requires-Dist: pytz>=2025.2
|
|
13
|
+
Requires-Dist: steam-data-schema==1.0.0.dev1
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Steam Data Reader
|
|
17
|
+
|
|
18
|
+
**Development release `1.0.0.dev1` — snapshot schema `1.0`.** The initial snapshot has an input cutoff of **October 21, 2026, 19:10:39 UTC**. It contains 259,424 catalog entries, with details for 10,517 apps and review summaries for 10,807. Initial collection is incomplete: most apps still lack details, genres and reviews. Unknown values are not empty values or zero reviews. Genre and review filters therefore cover only the collected subset. The development download host is rate-limited; regular snapshot updates and production availability are not yet promised.
|
|
19
|
+
|
|
20
|
+
Download a current Steam catalog and query it locally with Python. You do not need an AWS account, Steam account, API key, or the collection application's source code. Queries run against a local DuckDB database; they do not contact Steam or AWS.
|
|
21
|
+
|
|
22
|
+
The dataset contains the latest known information for catalog apps, including games, demos, and DLC. It includes descriptions, classifications, developer/publisher labels, available review summaries and prices, relationships, and collection provenance. It excludes historical observations, individual reviews, raw responses, and media files. A description can contain HTML or media URLs; no referenced media is fetched automatically.
|
|
23
|
+
|
|
24
|
+
Current discovery requests games and DLC from Steam's catalog endpoint, excluding software, videos/series, and hardware. Demo AppIDs are additionally discovered through game relationships; the endpoint has no documented demo inclusion flag. This does not claim exhaustive discovery of every special Steam app type. First-seen timestamps record discovery in collected evidence, not an app's creation or release on Steam.
|
|
25
|
+
|
|
26
|
+
## Set up your environment
|
|
27
|
+
|
|
28
|
+
Use Python 3.11 or newer. Clean installations have been tested on Python 3.11–3.14 on Linux and Python 3.13 on macOS and Windows. The publisher's Python and AWS setup is not required.
|
|
29
|
+
|
|
30
|
+
Create a virtual environment and install the reader:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
python3 -m venv .venv
|
|
34
|
+
source .venv/bin/activate
|
|
35
|
+
python -m pip install "steam-data-reader==1.0.0.dev1"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This is a development prerelease. The exact version above selects it explicitly; to follow subsequent preview releases, use `python -m pip install --upgrade --pre steam-data-reader`. It does not claim a stable 1.0 API. The matching `steam-data-schema==1.0.0.dev1` dependency is installed automatically; its package version is separate from snapshot schema `1.0`.
|
|
39
|
+
|
|
40
|
+
Read the complete guide from the installed distribution, without this repository or an Internet connection:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
steam-snapshot --version
|
|
44
|
+
steam-snapshot readme > steam-data-reader-README.md
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
On Windows, use `py` to create the environment if appropriate, and activate it with `.venv\Scripts\Activate.ps1` in PowerShell.
|
|
48
|
+
|
|
49
|
+
The package installs DuckDB and its shared schema definitions. The initial design does not require pandas, PyArrow, boto3, or AWS CLI.
|
|
50
|
+
|
|
51
|
+
The reader also installs `pytz`, needed by DuckDB's conversion of UTC timestamps to Python datetime values. Python and JSON retain exact integers; JavaScript consumers should account for their language's integer precision limits when interpreting large JSON numbers.
|
|
52
|
+
|
|
53
|
+
Keep at least 10 GiB of free disk space before synchronization during preview testing. A 260,000-app synthetic capacity test used a roughly 445 MB baseline download, a 1.9 GB installed DuckDB file, and 7.37 GiB of peak reader working storage including staging. Weekly replacement also temporarily retains the existing database. These are synthetic measurements, not a forecast of the final Steam dataset: repeated templates affect compression. Published requirements will be checked against the complete real export. Query memory depends on the operation; the reader streams results where possible.
|
|
54
|
+
|
|
55
|
+
Synchronization uses a 2,048 MB DuckDB working-memory budget plus Python/runtime overhead and can spill work to temporary disk. Allow several GB of available RAM during sync. The default budget for ordinary read-only queries is 768 MB. These are engine budgets, not limits on the process's total memory.
|
|
56
|
+
|
|
57
|
+
## Fetch the latest catalog
|
|
58
|
+
|
|
59
|
+
The preview uses an anonymous HTTPS development endpoint. No hosting-provider account, API key, or browser interaction is required to download it.
|
|
60
|
+
|
|
61
|
+
Choose a local directory. First sync uses the built-in public R2 index URL; later syncs remember your selected source:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
steam-snapshot sync --directory ./steam-catalog
|
|
65
|
+
steam-snapshot info --directory ./steam-catalog
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The built-in index is **https://pub-93fc6f5a294444c1adec0db48e691e3a.r2.dev/latest.json**. You can override it with `sync --source HTTPS_INDEX_URL` or Python `sync(..., source_url=HTTPS_INDEX_URL)`. Precedence is explicit override, then the directory's saved source, then this built-in default; an existing custom source is not silently switched.
|
|
69
|
+
|
|
70
|
+
The first sync downloads the advertised full baseline and every available increment needed to reach the latest complete combined dataset. It verifies their checksums, installs a local database, and remembers the source URL. You do not choose archive filenames, infer dates, or apply increments yourself. Here **complete** means the complete published baseline-plus-increment state; it does **not** mean that Steam collection is complete or that every app has details.
|
|
71
|
+
|
|
72
|
+
Later, close any Python or DuckDB connections to that directory and run:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
steam-snapshot sync --directory ./steam-catalog
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Within the same baseline chain, sync downloads only missing increments. Whenever a replacement baseline is published, it stages that baseline plus all its advertised increments before replacing your installed database. A missed publication day does not require a nonexistent file; manifests identify the actual preceding publication. If no newer version is advertised, sync leaves the installed data in place and downloads no data bundles.
|
|
79
|
+
|
|
80
|
+
The intended cadence is **approximately weekly baselines and daily increments**, not a fixed Monday/midnight contract. Publication may be early, delayed, skipped, or consolidated into a catch-up increment; a new baseline can appear at any time, including for a schema transition. The reader follows the actual index and manifests, regardless of calendar dates. One successful sync reaches the latest version advertised by the index it fetched; run sync again to pick up any release published while it was running. During this initial preview, updates are manual and no daily service schedule is enabled.
|
|
81
|
+
|
|
82
|
+
The index and `info` output identify actual versions and input/publication times. Publication time does not guarantee that every app was collected then, and the full bootstrap may still be incomplete.
|
|
83
|
+
|
|
84
|
+
If a download or update fails, retry sync. The command exits nonzero on failure; Python raises `SyncError` with the installed and target versions. Partial files are not treated as installed data. For updates within one baseline chain, earlier successfully applied increments remain usable; the error reports the installed version and the target not yet reached. A failed replacement leaves the old usable database intact. If an old dependency has expired, the updater uses the newest baseline instead of skipping updates. If connections are open, close them before retrying; sync never kills your other processes.
|
|
85
|
+
|
|
86
|
+
An installed catalog works offline. Opening/querying it does not automatically check for updates. `info` reports what is installed locally, not whether a newer online version exists.
|
|
87
|
+
|
|
88
|
+
### If a new baseline needs a newer reader
|
|
89
|
+
|
|
90
|
+
Schema changes happen only at a new full baseline; all increments in that baseline's chain use the exact same schema version. Dataset publication versions, schema versions, and Python package versions are separate. Normal daily data updates do not require reinstalling Python software.
|
|
91
|
+
|
|
92
|
+
Sync checks compatibility from small manifests before downloading the large baseline or replacing installed data. If your reader does not support the new schema, it reports the incoming schema, required reader version, and installed version, and leaves the existing database usable. A reader already supporting the new schema proceeds normally.
|
|
93
|
+
|
|
94
|
+
When prompted, upgrade in the same virtual environment that runs your application, restart any running Python program that imported the old package, then retry:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
python -m pip install --upgrade --pre steam-data-reader
|
|
98
|
+
steam-snapshot sync --directory ./steam-catalog
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The necessary compatible schema dependency is installed through the reader package's dependency requirements; you normally do not upgrade it separately. The updater never runs pip automatically. If the required reader does not support your Python version, follow its environment requirements before retrying. A breaking schema change can also require changes to your own SQL or application code; consult the linked migration notes. Without upgrading you may continue querying your older installed data offline, but sync cannot claim to have installed the new version.
|
|
102
|
+
|
|
103
|
+
Python applications can perform the same explicit synchronization:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from steam_data_reader import sync
|
|
107
|
+
|
|
108
|
+
status = sync("./steam-catalog") # Built-in R2 source on first sync; saved source thereafter.
|
|
109
|
+
print(status.installed_version, status.complete)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Keep synchronization separate from an open query session. A session reads one dataset version from beginning to end.
|
|
113
|
+
|
|
114
|
+
## Run basic queries from the command line
|
|
115
|
+
|
|
116
|
+
You do not need to write Python code for the built-in queries. These commands use exactly the same filters, fields, ordering, and serializers as the Python functions below. They use only the installed local dataset; update it separately when you want new data.
|
|
117
|
+
|
|
118
|
+
List the catalog's minimal fields, or restrict the listing:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
steam-snapshot list-apps --directory ./steam-catalog --limit 20
|
|
122
|
+
steam-snapshot list-apps --directory ./steam-catalog --name-contains space --app-type game --min-reviews 100 --min-positive-percentage 80 --fields steam_appid,name,genres,total_reviews,positive_percentage --format json --output space-games.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Without `--limit`, all matches are returned. Available list flags mirror Python arguments: `--name-prefix`, `--name-contains`, repeatable `--appid`, `--app-type`, `--genre-id`, `--developer-name`, `--publisher-name`, `--genre-match any|all`, inclusive review/percentage minimum and maximum flags, `--fields`, `--order-by`, `--limit`, and `--after-appid`. Quote names containing spaces in your shell. Use repeated flags for sets; a comma-separated `--fields` value selects output fields. See the filter definitions below for exact semantics.
|
|
126
|
+
|
|
127
|
+
Get full details for specified appIDs, or export selected fields as TSV:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
steam-snapshot get-apps --directory ./steam-catalog 620 1794680 2230760
|
|
131
|
+
steam-snapshot get-apps --directory ./steam-catalog 620 1794680 --fields steam_appid,name,total_reviews --format tsv --output selected.tsv
|
|
132
|
+
steam-snapshot list-genres --directory ./steam-catalog --format tsv
|
|
133
|
+
steam-snapshot info --directory ./steam-catalog
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Record-query commands default to JSON on standard output. `--output PATH` writes a file instead; TSV file output also writes its metadata sidecar. Standard-output TSV contains a header and records only, while dataset/schema version and missing-ID notices go to standard error. Use file output when you need the full portable TSV/metadata pair. JSON carries its metadata envelope on both file and standard output. `info` prints local dataset metadata as JSON.
|
|
137
|
+
|
|
138
|
+
Progress, warnings, and diagnostics go to standard error so they do not corrupt redirected query output. Exit codes are 0 for success (including no matches or explicitly reported missing appIDs), 1 for operational failures, 2 for invalid arguments, and 3 when the reader/protocol/schema is incompatible. Use `steam-snapshot --help` or a subcommand's `--help` without opening a database or downloading data. Keep the installed version unchanged when paging across several CLI calls, and close other query sessions before syncing.
|
|
139
|
+
|
|
140
|
+
## Query from Python
|
|
141
|
+
|
|
142
|
+
The examples below use the implemented interface and illustrative Steam appIDs. Results depend on actual coverage in your downloaded version.
|
|
143
|
+
|
|
144
|
+
### List apps
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from steam_data_reader import Catalog
|
|
148
|
+
|
|
149
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
150
|
+
for app in catalog.list_apps():
|
|
151
|
+
print(app["steam_appid"], app["name"], app["app_type"])
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The default fields are `steam_appid`, `name`, `developers`, `publishers`, and `app_type`. Developers and publishers are lists. With no filter or limit, this enumerates the entire catalog, including entries with unavailable details. Names/types may be null for unresolved records.
|
|
155
|
+
|
|
156
|
+
### Filter and select fields
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
160
|
+
genres = catalog.list_genres().to_list()
|
|
161
|
+
print(genres) # Discover exact genre IDs and labels from this dataset.
|
|
162
|
+
|
|
163
|
+
result = catalog.list_apps(
|
|
164
|
+
name_contains="space",
|
|
165
|
+
app_types=["game"],
|
|
166
|
+
min_reviews=100,
|
|
167
|
+
min_positive_percentage=80,
|
|
168
|
+
fields=[
|
|
169
|
+
"steam_appid", "name", "developers", "genres",
|
|
170
|
+
"total_reviews", "positive_percentage",
|
|
171
|
+
],
|
|
172
|
+
limit=100,
|
|
173
|
+
)
|
|
174
|
+
apps = result.to_list()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
To add a genre filter, pass `genre_ids=["<id-from-list_genres>"]`. `genre_match="any"` matches at least one requested genre; `"all"` requires every requested genre. Genres, categories, and Steam tags are different classifications. Tag coverage is initially unsupported and must not be inferred from genres.
|
|
178
|
+
|
|
179
|
+
Available list filters:
|
|
180
|
+
|
|
181
|
+
- `appids`, `name_prefix`, `name_contains`, `app_types`.
|
|
182
|
+
- `genre_ids` and `genre_match`.
|
|
183
|
+
- `developer_names` and `publisher_names` for exact labels under the documented text normalization.
|
|
184
|
+
- Inclusive `min_reviews`, `max_reviews`, `min_positive_percentage`, and `max_positive_percentage` bounds.
|
|
185
|
+
|
|
186
|
+
Each app query returns at most one record per appID even if several genres or organizations match. Different filters combine with AND; values inside a set combine with OR except for `genre_match="all"`. A provided empty set matches nothing; an empty genre set with `"all"` and empty search text are rejected. Prefix/substring matching is literal, Unicode-normalized and case-insensitive; `%` and `_` have no wildcard meaning. Developer/publisher labels use the same normalization but must match the whole label, including punctuation.
|
|
187
|
+
|
|
188
|
+
Unknown well-formed genre IDs, app types, or organization names simply match no records. Invalid arguments or field names produce errors. Numeric bounds exclude null metrics. Positivity is a percentage on a 0–100 scale, not Steam's review-score category; it is null for zero or unavailable totals. Review filters use the snapshot's declared default review series.
|
|
189
|
+
|
|
190
|
+
Results default to appID ascending. `order_by="name"` sorts by normalized name with appID as a tie-breaker; `order_by="steam_appid"` is the default. With appID ordering, `after_appid=LAST_ID` and `limit=N` retrieve the next page from the same dataset version and filters. There is no hidden default limit.
|
|
191
|
+
|
|
192
|
+
### Get full information for selected apps
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
196
|
+
result = catalog.get_apps([620, 1794680, 2230760])
|
|
197
|
+
apps = result.to_list()
|
|
198
|
+
print(result.missing_appids)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
With no `fields` selection, this returns all supported exported app fields, current relationships, acquisition status, and field provenance. It does not include raw Steam responses, history, or fields the publisher has not acquired.
|
|
202
|
+
|
|
203
|
+
Repeated requested IDs are deduplicated and output follows their first-requested order. An empty list returns no apps. IDs absent from the catalog appear in `missing_appids`; an app retained in the catalog with unavailable details still appears as a record. Unknown values are not silently converted to empty strings or zero.
|
|
204
|
+
|
|
205
|
+
Use `fields=["steam_appid", "name", "genres", "total_reviews"]` to restrict results. appID is always included. `list_apps(fields=None)` uses minimal fields; `get_apps(fields=None)` requests full information. Fields not selected are omitted rather than returned as null.
|
|
206
|
+
|
|
207
|
+
### Export JSON or tab-delimited CSV
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
211
|
+
result = catalog.list_apps(
|
|
212
|
+
app_types=["game"],
|
|
213
|
+
fields=["steam_appid", "name", "genres", "total_reviews", "positive_percentage"],
|
|
214
|
+
)
|
|
215
|
+
result.write_json("games.json")
|
|
216
|
+
result.write_tsv("games.tsv")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Both exports refer to the same pinned version. JSON contains `dataset_version`, `schema_version`, `missing_appids`, and a `rows` array. Python records and JSON preserve lists/objects; JSON uses null for unavailable values.
|
|
220
|
+
|
|
221
|
+
TSV has a header and one logical record per app, with standard double-quote escaping for embedded tabs, newlines, and quotes. Lists/objects are compact JSON within a cell. Null uses `\N`; non-null scalar strings double their backslashes, so empty strings and a literal null-marker string remain distinguishable. Structured JSON cells retain JSON's own escaping. Timestamps are UTC ISO strings. The sibling `games.tsv.metadata.json` describes versions, types, encoding, missing IDs, row count, and the TSV hash/length. It is written last as the completion record. Keep it with the TSV; a missing sidecar or mismatched hash means the pair is incomplete or stale.
|
|
222
|
+
|
|
223
|
+
A CSV-aware parser must read TSV; splitting each physical line on tabs is insufficient for quoted multiline descriptions. No spreadsheet formula interpretation is intended.
|
|
224
|
+
|
|
225
|
+
`to_list()` materializes results in memory. Iteration and file exports stream rows where possible. Expensive descriptions and provenance are omitted from listing defaults; request them when needed.
|
|
226
|
+
|
|
227
|
+
### Inspect the installed dataset
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
231
|
+
print(catalog.dataset_info())
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This reports versions, publication/input boundaries, coverage, supported features, default contexts/review series, and freshness summaries. An app's description and review counts may have different observation times. A later failed check does not make an older retained value freshly observed. Consult `provenance` and `acquisition_status` when freshness matters.
|
|
235
|
+
|
|
236
|
+
## Build an independent client application
|
|
237
|
+
|
|
238
|
+
Everything required for a client is in the installed reader/schema distributions and this README. No publisher repository, prior conversation, AWS SDK, AWS credentials, Steam API key, or knowledge of ingestion internals is required. Install, sync explicitly, then query within a read-only `Catalog` session. The following example is a starting point for a separate application that needs game candidates:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from steam_data_reader import Catalog, sync
|
|
242
|
+
|
|
243
|
+
path = "./steam-catalog"
|
|
244
|
+
result = sync(path)
|
|
245
|
+
if not result.complete:
|
|
246
|
+
raise RuntimeError("Snapshot synchronization did not finish")
|
|
247
|
+
|
|
248
|
+
with Catalog.open(path) as catalog:
|
|
249
|
+
snapshot = catalog.dataset_info()
|
|
250
|
+
print(snapshot["dataset_version"], snapshot["input_boundary_at"])
|
|
251
|
+
print(catalog.list_genres().to_list())
|
|
252
|
+
for game in catalog.list_apps(
|
|
253
|
+
name_contains="space",
|
|
254
|
+
fields=["steam_appid", "name", "app_type", "developers", "publishers", "genres", "steam_url"],
|
|
255
|
+
limit=100,
|
|
256
|
+
):
|
|
257
|
+
print(game)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
For genre selection, discover IDs with `list_genres()` and use `genre_ids=[...]` with the filters documented above. For explicit games, use `get_apps([appid, ...])`. Use `steam_appid` as the join key for your own application data, and retain the snapshot version with derived results. Names are not unique identifiers; this dataset does not supply a comprehensive alias dictionary or a game-title-to-video matching algorithm. Demo and DLC appIDs remain distinct from the full game; inspect typed relationships when that distinction matters.
|
|
261
|
+
|
|
262
|
+
A YouTube discovery application can use those names/genres as candidate input and keep its YouTube API integration, credentials, search/matching logic, channels/videos and results in its own project. This package contains **no YouTube data or YouTube API functionality**. During the partial import, enumerate the broad catalog when useful, and do not interpret missing genres/review counts as evidence that an app has no genres/reviews. The documented Python methods and public `steam` views below are the supported client interfaces; transport tables and publisher code are not integration dependencies.
|
|
263
|
+
|
|
264
|
+
## Power users: use the database directly
|
|
265
|
+
|
|
266
|
+
The reader is a convenience layer, not a restriction on analysis. You can use the underlying read-only DuckDB connection or open the database with DuckDB yourself. Public views live in the `steam` schema. Private storage tables and cache filenames are not supported interfaces.
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
270
|
+
rows = catalog.connection.execute(
|
|
271
|
+
"""
|
|
272
|
+
SELECT steam_appid, name, total_reviews, positive_percentage
|
|
273
|
+
FROM steam.apps
|
|
274
|
+
WHERE app_type = ? AND total_reviews >= ?
|
|
275
|
+
ORDER BY total_reviews DESC, steam_appid
|
|
276
|
+
LIMIT ?
|
|
277
|
+
""",
|
|
278
|
+
["game", 1000, 20],
|
|
279
|
+
).fetchall()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
For an independent application, the installed database file is `catalog.duckdb` in your chosen directory:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
import duckdb
|
|
286
|
+
|
|
287
|
+
with duckdb.connect("./steam-catalog/catalog.duckdb", read_only=True) as connection:
|
|
288
|
+
version = connection.execute(
|
|
289
|
+
"SELECT dataset_version FROM steam.snapshot_info"
|
|
290
|
+
).fetchone()[0]
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Keep connections read-only and close them before sync. Do not alter the installed database to add your own application tables; use a separate database/file and join by `steam_appid`. A query session must stay on one dataset version. Parameters represent values; public schema names are fixed names from the reference below.
|
|
294
|
+
|
|
295
|
+
For general DuckDB/SQL guidance, consult the official [Python interface](https://duckdb.org/docs/stable/clients/python/overview) and [parameterized SQL interface](https://duckdb.org/docs/stable/clients/python/dbapi). This guide documents this dataset's contract rather than teaching SQL.
|
|
296
|
+
|
|
297
|
+
### Schema and types
|
|
298
|
+
|
|
299
|
+
The following is the schema version 1.0 contract, defined by `steam-data-schema`. All views represent current state only. Every schema change, including an additive one, occurs only at a new full baseline. Unknown unsupported versions fail explicitly before installation; see the upgrade workflow above.
|
|
300
|
+
|
|
301
|
+
Conventions:
|
|
302
|
+
|
|
303
|
+
- `steam_appid` is a `BIGINT`; counts and price minor units are exact integers. Source genre/category/tag IDs are `VARCHAR` and stay in their own namespaces.
|
|
304
|
+
- Times are nullable `TIMESTAMPTZ`, normalized to UTC. Python uses timezone-aware datetime values; exported JSON/TSV use ISO 8601 UTC strings.
|
|
305
|
+
- Structured fields are typed lists or DuckDB `JSON`, decoded into lists/objects by the convenience API. Source-supplied requirement objects can vary in shape and are retained as JSON.
|
|
306
|
+
- All descriptive/metric fields may be null unless explicitly identified as a required key. Null alone does not distinguish unsupported from unavailable; consult feature support, field provenance/state, and acquisition status.
|
|
307
|
+
- Initial descriptive/price context is US/English, with returned currency recorded. Additional contexts must be explicitly identified; default views never mix them implicitly.
|
|
308
|
+
|
|
309
|
+
**`steam.apps` — one row per retained `steam_appid`.** This convenient joined view is also the ordinary query field registry:
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
steam_appid BIGINT -- required, unique key
|
|
313
|
+
name VARCHAR; app_type VARCHAR
|
|
314
|
+
steam_url VARCHAR -- canonical convenience URL
|
|
315
|
+
short_description VARCHAR; detailed_description VARCHAR; about_the_game VARCHAR
|
|
316
|
+
website VARCHAR; supported_languages VARCHAR
|
|
317
|
+
required_age VARCHAR -- normalized textual source value; not a verified age rating
|
|
318
|
+
is_free BOOLEAN; controller_support VARCHAR
|
|
319
|
+
platforms JSON
|
|
320
|
+
pc_requirements JSON; mac_requirements JSON; linux_requirements JSON
|
|
321
|
+
release_info JSON -- source text, coming-soon, optional parsed date/precision
|
|
322
|
+
content_descriptors JSON; support_info JSON; ratings JSON
|
|
323
|
+
header_image VARCHAR; capsule_image VARCHAR; capsule_imagev5 VARCHAR -- URLs only
|
|
324
|
+
extensions JSON -- namespaced, documented current source extensions
|
|
325
|
+
first_seen_at TIMESTAMPTZ; last_seen_at TIMESTAMPTZ
|
|
326
|
+
name_origin VARCHAR -- details, catalog, or unknown; catalog fallback is marked
|
|
327
|
+
|
|
328
|
+
developers VARCHAR[]; publishers VARCHAR[]
|
|
329
|
+
genres JSON -- list of {genre_id, label}
|
|
330
|
+
categories JSON -- list of {category_id, label}
|
|
331
|
+
tags JSON -- list of {tag_id, label}; null if unsupported/unknown
|
|
332
|
+
|
|
333
|
+
default_review_series_id VARCHAR
|
|
334
|
+
reviews_observed_at TIMESTAMPTZ
|
|
335
|
+
total_positive BIGINT; total_negative BIGINT; total_reviews BIGINT
|
|
336
|
+
positive_percentage DOUBLE
|
|
337
|
+
review_score BIGINT; review_score_desc VARCHAR
|
|
338
|
+
|
|
339
|
+
price_country VARCHAR; price_currency VARCHAR
|
|
340
|
+
price_initial_minor BIGINT; price_final_minor BIGINT
|
|
341
|
+
price_discount_percentage BIGINT; price_observed_at TIMESTAMPTZ
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`name` may fall back to the most recent catalog label when details have never supplied a name; `name_origin` and field provenance identify that fallback. Preserve source `app_type` values such as `game`, `demo`, and `dlc`; unknown stays null. An empty known developer/genre set is `[]`, while unsupported/unknown is null with supporting state metadata. `last_seen_at` means latest successful catalog/source visibility under its declared definition, not proof of current purchase availability; the field registry must identify the contributing source. A free app, absent price, and zero-priced discounted offer are distinct states.
|
|
345
|
+
|
|
346
|
+
For full app records, the Python interface also supports structured fields `relationships`, `acquisition_status`, and `provenance`, assembled from the views below. They are not additional columns of `steam.apps`. `relationships` contains outgoing links; reverse links are available through direct SQL.
|
|
347
|
+
|
|
348
|
+
**`steam.review_summaries`** has key `(steam_appid, review_series_id)`:
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
steam_appid BIGINT; review_series_id VARCHAR
|
|
352
|
+
observed_at TIMESTAMPTZ; source_id VARCHAR; observation_id VARCHAR
|
|
353
|
+
total_positive BIGINT; total_negative BIGINT; total_reviews BIGINT
|
|
354
|
+
positive_percentage DOUBLE; review_score BIGINT; review_score_desc VARCHAR
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
A row represents the latest valid summary for that series. No row means no successful current retained summary; check acquisition status rather than inventing zero. `positive_percentage` is `100 * positive / total`, null for zero total. Counts must satisfy `positive + negative = total` and be nonnegative. `review_score`/label are separately supplied source categories.
|
|
358
|
+
|
|
359
|
+
**`steam.review_series`** has key `review_series_id VARCHAR`, with `definition_json JSON` and `description VARCHAR`. The definition identifies endpoint/version, language scope, purchase-type scope, sentiment selection, off-topic filtering, and interpretation version. `steam.apps` selects only the series named in `steam.snapshot_info`; never combine incompatible series. The initial intended series covers all languages/purchase types and both sentiments with off-topic filtering, subject to the validated source adapter's declared definition.
|
|
360
|
+
|
|
361
|
+
**`steam.prices`** has key `(steam_appid, country, language, currency)`:
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
steam_appid BIGINT; country VARCHAR; language VARCHAR; currency VARCHAR
|
|
365
|
+
initial_minor BIGINT; final_minor BIGINT; discount_percentage BIGINT
|
|
366
|
+
observed_at TIMESTAMPTZ; source_id VARCHAR; observation_id VARCHAR
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Currency is retained as returned; do not assume a minor-unit scale for every possible currency. The initial US context is expected to return USD where offered, but the value is explicit. The configured default context populates the convenience price columns in `steam.apps`; different currencies are not silently compared or converted.
|
|
370
|
+
|
|
371
|
+
**Classification dictionaries and memberships:**
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
steam.genres: genre_id VARCHAR [key], label VARCHAR
|
|
375
|
+
steam.app_genres: steam_appid BIGINT, genre_id VARCHAR [composite key]
|
|
376
|
+
steam.categories: category_id VARCHAR [key], label VARCHAR
|
|
377
|
+
steam.app_categories: steam_appid BIGINT, category_id VARCHAR [composite key]
|
|
378
|
+
steam.tags: tag_id VARCHAR [key], label VARCHAR
|
|
379
|
+
steam.app_tags: steam_appid BIGINT, tag_id VARCHAR [composite key],
|
|
380
|
+
source_rank BIGINT nullable, source_weight DOUBLE nullable
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Only retain tag rank/weight if actually supplied and defined by the source. Empty tag views plus `unsupported` feature coverage mean tags were not acquired, not that all apps were observed to have no tags. Labels are display values, not guaranteed unique identifiers. Membership keys reference their corresponding dictionary.
|
|
384
|
+
|
|
385
|
+
For example, use a genre ID returned by `list_genres()`:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
with Catalog.open("./steam-catalog") as catalog:
|
|
389
|
+
rows = catalog.connection.execute(
|
|
390
|
+
"""
|
|
391
|
+
SELECT a.steam_appid, a.name
|
|
392
|
+
FROM steam.apps AS a
|
|
393
|
+
JOIN steam.app_genres AS g USING (steam_appid)
|
|
394
|
+
WHERE g.genre_id = ?
|
|
395
|
+
ORDER BY a.steam_appid
|
|
396
|
+
""",
|
|
397
|
+
["<genre-id>"],
|
|
398
|
+
).fetchall()
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**`steam.app_organizations`** has key `(steam_appid, role, organization_id)`:
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
steam_appid BIGINT; role VARCHAR -- developer or publisher
|
|
405
|
+
organization_id VARCHAR; original_name VARCHAR
|
|
406
|
+
source_id VARCHAR; source_url VARCHAR nullable
|
|
407
|
+
identity_quality VARCHAR -- source_label or verified_source_identity
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Organization IDs are source-scoped identifiers, not universal company IDs. Exact source labels remain distinct until stronger evidence justifies a mapping. An organization can occupy both roles. Optional URLs are source-backed links, not proof that a generated search URL identifies a company.
|
|
411
|
+
|
|
412
|
+
**`steam.app_relationships`** has key `(steam_appid, relation_type, target_appid)`:
|
|
413
|
+
|
|
414
|
+
```text
|
|
415
|
+
steam_appid BIGINT; target_appid BIGINT; relation_type VARCHAR
|
|
416
|
+
source_id VARCHAR; observation_id VARCHAR; observed_at TIMESTAMPTZ
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The direction of `demo_of`/`dlc_of` is demo/DLC to full game. Other labels such as `related_content` preserve what the source actually establishes; a soundtrack must not be reclassified as DLC solely because a game links it. The source app must exist in the catalog; a target can be unresolved and is not an invitation to borrow the target's metrics. Multiple contributing observations are represented in provenance rather than duplicating a logical relationship row.
|
|
420
|
+
|
|
421
|
+
Identity relationship labels are distinct: `appdetails_identity_reference` points from a requested appID to the different ID in a quarantined details response (unresolved); `store_item_app` points from a store item to its explicitly reported application; `store_parent` records StoreBrowse's store-parent field and does not assert `dlc_of`. No label merges app records or borrows metadata or review counts. These labels and the `steam.store_browse` source use the existing open string fields in schema 1.0. A failed refresh preserves last-good relationships; a successful item can explicitly clear an earlier mapping or parent. Reference discovery can retain an app absent from the catalog, but cannot reconstruct all delisted apps that no retained source references.
|
|
422
|
+
|
|
423
|
+
**`steam.field_provenance`** has key `(steam_appid, field_name, context_id, provenance_id)`:
|
|
424
|
+
|
|
425
|
+
```text
|
|
426
|
+
steam_appid BIGINT; field_name VARCHAR; context_id VARCHAR; provenance_id VARCHAR
|
|
427
|
+
source_id VARCHAR nullable; observation_id VARCHAR nullable; observed_at TIMESTAMPTZ nullable
|
|
428
|
+
value_state VARCHAR; timestamp_quality VARCHAR
|
|
429
|
+
context_json JSON; parser_version VARCHAR; resolution_version VARCHAR
|
|
430
|
+
source_url VARCHAR nullable
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
`value_state` is `observed_value`, `observed_empty`, `unknown`, `unsupported`, or `unavailable`; `timestamp_quality` identifies observed, source-reported, derived, or unknown timing as applicable. The stable `provenance_id` identifies a contribution or declared field state. Unknown/unsupported states can have null source/observation identifiers; the exporter must not invent source observations to fill them. Missing provenance for a known field must not be interpreted as a successful empty observation. An unavailable latest check can coexist with an older `observed_value`; those different facts appear in the separate status view. Derived values carry provenance for their contributing inputs. All public source URLs omit credentials and private storage paths.
|
|
434
|
+
|
|
435
|
+
**`steam.acquisition_status`** has key `(steam_appid, source_id, data_family, context_id)`:
|
|
436
|
+
|
|
437
|
+
```text
|
|
438
|
+
steam_appid BIGINT; source_id VARCHAR; data_family VARCHAR; context_id VARCHAR
|
|
439
|
+
context_json JSON
|
|
440
|
+
latest_attempt_at TIMESTAMPTZ nullable
|
|
441
|
+
latest_attempt_status VARCHAR -- success, unavailable, failed, or not_attempted
|
|
442
|
+
last_success_at TIMESTAMPTZ nullable
|
|
443
|
+
support_status VARCHAR -- supported, unsupported, or unknown
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`last_success_at` refers to a successful acquisition and may differ from an individual selected field's observation time. A field absent from a later response does not acquire a newer value timestamp. Context IDs are stable identifiers defined by the schema contract for the canonical source/context definition, not account-specific identifiers.
|
|
447
|
+
|
|
448
|
+
**`steam.snapshot_info`** has exactly one row:
|
|
449
|
+
|
|
450
|
+
```text
|
|
451
|
+
dataset_id VARCHAR; dataset_version VARCHAR; chain_id VARCHAR
|
|
452
|
+
baseline_version VARCHAR; increment_sequence BIGINT; schema_version VARCHAR
|
|
453
|
+
published_at TIMESTAMPTZ; input_boundary_at TIMESTAMPTZ
|
|
454
|
+
source_release_id VARCHAR -- public opaque identifier, not an internal run ID
|
|
455
|
+
logical_state_sha256 VARCHAR -- excludes snapshot_info itself and redundant views
|
|
456
|
+
app_count BIGINT; default_review_series_id VARCHAR
|
|
457
|
+
metadata_country VARCHAR; metadata_language VARCHAR
|
|
458
|
+
price_country VARCHAR; price_language VARCHAR
|
|
459
|
+
coverage_json JSON; feature_support_json JSON; freshness_json JSON
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The row describes the installed version even when a newer version is advertised online. Coverage reports preserved unresolved apps and unsupported features. Collection timestamps, review-series definitions, and contexts should accompany analytical interpretations; publication date alone does not establish freshness.
|
|
463
|
+
|
|
464
|
+
### Compatibility and extension policy
|
|
465
|
+
|
|
466
|
+
The documented views and Python fields are the supported interface. Internal tables, transport shards, and caches may change without being query interfaces. New optional fields/views may be added in a new schema minor version at a baseline; incompatible column/key/type/unit/meaning changes require a schema major version. Increments must retain their baseline's exact schema version. The installed reader/shared contract declares which versions it supports; unrecognized future minor versions are not assumed compatible. New software releases can support both earlier and upcoming schemas, so a schema change does not always require another package upgrade. Do not rely on `SELECT *` having a permanent column order in your own application; select named fields.
|
|
467
|
+
|
|
468
|
+
Namespace extension fields by source and document their types/provenance. No extension can silently redefine an established field. History and source collection are outside this package; users who want custom analysis use these current-state views or join their own datasets by appID.
|
|
469
|
+
|
|
470
|
+
## Software license
|
|
471
|
+
|
|
472
|
+
The reader and shared-schema packages use the MIT license included in each distribution. This software license does not license Steam data or other third-party content in snapshots.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
steam_data_reader/__init__.py,sha256=E4iwjMvMuImlhihVuRL5EJ2rBA_ABc3_DQBwXXYJOwU,322
|
|
2
|
+
steam_data_reader/catalog.py,sha256=9jt9f_hky2D1oa1Ay0q37qaRTTDmeM-dcG_4wTiCAss,13562
|
|
3
|
+
steam_data_reader/cli.py,sha256=ZF0B8_9R7n5KR_R3Q7j1kG0rKeBPMScMigmgNUsK66w,4097
|
|
4
|
+
steam_data_reader/database.py,sha256=N1FiuUfJb5-bNuYmjTrIlD5SYJ8z8STlFM5b6-l_v6w,11458
|
|
5
|
+
steam_data_reader/sync.py,sha256=GPbbxglL93efrTCqUU_J5qUDrtBMksuSOtDGS053OMc,14944
|
|
6
|
+
steam_data_reader-1.0.0.dev1.dist-info/METADATA,sha256=aSioyGXlguJSkSN07cCg0ZIOmXREyVDWQXoTpGept-Q,36089
|
|
7
|
+
steam_data_reader-1.0.0.dev1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
8
|
+
steam_data_reader-1.0.0.dev1.dist-info/entry_points.txt,sha256=nkmmL9H2VWjakhdWAmTUfyTqWBbFHAYu8k5zh3waMzo,62
|
|
9
|
+
steam_data_reader-1.0.0.dev1.dist-info/licenses/LICENSE,sha256=eYDBQ0veiU5htuSPpskxfcqZ1IswqEmtLvRLM7U_fS8,1080
|
|
10
|
+
steam_data_reader-1.0.0.dev1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steam Data contributors
|
|
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.
|