daftar 0.1.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.
daftar-0.1.0/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,5 @@
1
+ include LICENSE
2
+ include README.md
3
+ include PUBLISHING.md
4
+ recursive-include examples *.py
5
+ recursive-include tests *.py
daftar-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: daftar
3
+ Version: 0.1.0
4
+ Summary: Record what produced each computational result: environment, code, parameters, seeds, inputs.
5
+ Author: Aslan Satary Dizaji
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/aslansd/daftar
8
+ Project-URL: Repository, https://github.com/aslansd/daftar
9
+ Project-URL: Issues, https://github.com/aslansd/daftar/issues
10
+ Keywords: reproducibility,provenance,simulation,computational-science
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Provides-Extra: jaxley
19
+ Requires-Dist: jaxley; extra == "jaxley"
20
+ Provides-Extra: cpm
21
+ Requires-Dist: cpm-toolbox; extra == "cpm"
22
+ Provides-Extra: meltingpot
23
+ Requires-Dist: dm-meltingpot; extra == "meltingpot"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7; extra == "dev"
26
+ Requires-Dist: numpy; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # daftar
30
+
31
+ **دفتر** — *ledger, register, record-book.* The same word in Persian, Turkish
32
+ (*defter*), Azerbaijani, Arabic, Urdu, and Hindi.
33
+
34
+ Record what produced each computational result — the environment, code version,
35
+ parameters, random seeds, and input files — so two runs can be compared and any
36
+ past run can be rebuilt.
37
+
38
+ Experiment trackers solved this for deep learning, where an experiment is
39
+ `model.fit()`. They do not help when the experiment is a Hodgkin–Huxley
40
+ simulation of 302 neurons, a hierarchical fit across 60 participants, or a
41
+ 200-episode multi-agent sweep. Those runs have no epochs, no loss curves, and no
42
+ checkpoints. They have parameter grids, solver tolerances, random seeds, and
43
+ derived quantities.
44
+
45
+ **No dependencies. Runs offline. No account, no server, no network call.**
46
+
47
+ ```bash
48
+ pip install daftar
49
+ ```
50
+
51
+ ---
52
+
53
+ ## The thirty-second version
54
+
55
+ ```python
56
+ import daftar # no alias: `df` would collide with the pandas convention
57
+
58
+ with daftar.track("celegans", params={"dt": 0.025, "solver": "bwd_euler"}, seed=42) as run:
59
+ run.add_input("data/connectome.csv")
60
+ v = simulate(dt=0.025)
61
+ run.log_result("mean_rate_hz", float(v.mean()))
62
+ ```
63
+
64
+ Then, next Thursday, when the number is different:
65
+
66
+ ```
67
+ $ daftar diff r-4f21ab r-88c07e
68
+
69
+ --- r-4f21ab celegans 2026-05-14T14:02:11+00:00
70
+ +++ r-88c07e celegans 2026-05-16T09:47:03+00:00
71
+
72
+ candidate causes (1)
73
+ env.jax 0.4.35 -> 0.4.41
74
+
75
+ observed effects (1)
76
+ result.mean_rate_hz 4.812 -> 4.796
77
+
78
+ 2 meaningful field(s) differ, 27 identical
79
+
80
+ Results differ, and so do things that could explain it.
81
+ The candidate causes below are where to look.
82
+ ```
83
+
84
+ Same code, same seed, same inputs. A patch-level JAX upgrade moved the answer.
85
+ That is the question this tool exists to answer, and it answers it the same
86
+ afternoon you install it.
87
+
88
+ ---
89
+
90
+ ## Why this and not the eight tools that came before
91
+
92
+ Binder, ReproZip, Sciunit, Whole Tale, Gigantum, Renku, Code Ocean: in a
93
+ published survey, exactly **one of 38 researchers** had ever used a dedicated
94
+ platform to save and re-run a computational experiment.
95
+
96
+ Every one of those tools asked scientists to change how they work today in
97
+ exchange for a benefit arriving in three years. That trade never closes.
98
+
99
+ `daftar` inverts it. `diff` answers a question people ask weekly — *why is
100
+ this number different from Tuesday's?* — and provenance arrives as a free side
101
+ effect of a tool they already wanted. Reproducibility is the by-product, not
102
+ the pitch.
103
+
104
+ Three consequences of taking that seriously:
105
+
106
+ - **One line and one indent.** No DSL, no workflow language, no migration. If
107
+ wrapping an existing simulation costs more than that, nobody does it.
108
+ - **The manifest is plain JSON with sorted, dotted keys.** Readable and
109
+ greppable without this package installed, and `git diff`-able. If daftar
110
+ disappears, the record survives.
111
+ - **Failed runs are recorded too.** A crashed run that took four hours is
112
+ exactly the one you will want to look at later.
113
+
114
+ ---
115
+
116
+ ## What gets captured
117
+
118
+ | Namespace | Contents |
119
+ |---|---|
120
+ | `code.*` | git commit, branch, dirty flag, **hash of uncommitted changes**, entrypoint, argv |
121
+ | `param.*` | everything you chose, including defaults you never passed |
122
+ | `seed.*` | seeds **applied** to `random`, numpy, torch; the JAX root key |
123
+ | `input.*` | sha256 and size of every declared input file or directory |
124
+ | `env.*` | interpreter, OS, and versions of packages the run actually imported |
125
+ | `result.*` | scalar outcomes worth comparing |
126
+ | `output.*` | sha256 of produced files |
127
+ | `cost.*` | wall clock, host, cpu count |
128
+
129
+ Two of these are less obvious than they look.
130
+
131
+ **Seeds are applied, not merely recorded.** A tool that only writes down the
132
+ seed is close to useless: if the code seeded itself from the clock, recording
133
+ that fact tells you the run is irreproducible but does nothing to fix it.
134
+ `track(seed=...)` sets every RNG it can reach, then records what it set. If you
135
+ pass no seed, one is generated, applied, and recorded — an accidental seed that
136
+ is written down is reproducible; a deliberate one that isn't, is not.
137
+
138
+ **Dirty working trees get their diff hashed.** "Dirty" alone tells you there
139
+ were uncommitted edits but not whether they were the *same* edits. Hashing the
140
+ diff means two dirty runs can still be proven identical, which is the common
141
+ case during a debugging session.
142
+
143
+ ---
144
+
145
+ ## The four commands
146
+
147
+ ```bash
148
+ daftar list # what has been run
149
+ daftar show r-4f21ab # one run's full manifest
150
+ daftar diff r-4f21ab r-88c07e # what changed, and whether it mattered
151
+ daftar vary -l my-sweep # which fields differ across many runs
152
+ daftar replay r-4f21ab # what it would take to reproduce this
153
+ daftar export r-4f21ab -o run.zip
154
+ ```
155
+
156
+ `diff` exits 0 if the second run reproduces the first and 1 otherwise, so it
157
+ works in CI as a regression check on your own results.
158
+
159
+ ### Verdicts
160
+
161
+ `diff` does not just list changed fields. It separates fields that could have
162
+ **caused** a difference (`code`, `param`, `seed`, `input`, `env`) from fields
163
+ that merely **record** one (`result`, `output`), and reports what the
164
+ combination implies:
165
+
166
+ | Verdict | Meaning |
167
+ |---|---|
168
+ | `identical` | Nothing meaningful moved. |
169
+ | `explained` | Results differ and so do plausible causes. Here they are. |
170
+ | `no_effect` | Environment changed, results didn't. Evidence of robustness. |
171
+ | **`nondeterministic`** | **Results differ and nothing that could have caused it does.** |
172
+ | `incomparable` | The runs recorded different result fields. |
173
+
174
+ That fourth verdict is the valuable one. An unseeded RNG buried three libraries
175
+ deep can survive for years because nobody ever compares two runs precisely
176
+ enough to notice. `daftar` reports it as a finding rather than a glitch.
177
+
178
+ ---
179
+
180
+ ## Sweeps
181
+
182
+ ```python
183
+ result = daftar.sweep(
184
+ simulate, label="tau-sweep", seed=42,
185
+ dt=[0.025, 0.01], tau=[5.0, 10.0, 20.0],
186
+ )
187
+ print(result.table())
188
+ ```
189
+
190
+ Each grid point is a separate run with its own manifest — not one run with a
191
+ nested table. That means a sweep point and a run you did by hand last Tuesday
192
+ are the same kind of object, and `diff` works across them. A sweep that fails at
193
+ point 3 of 40 keeps the first two results.
194
+
195
+ ---
196
+
197
+ ## Replay
198
+
199
+ `daftar replay` prints a plan, and deliberately does **not** execute
200
+ anything. Re-running arbitrary recorded code would mean this package executes
201
+ whatever a manifest tells it to, and it still could not restore your CUDA
202
+ driver. What it does honestly is state the target state, check the current state
203
+ against it, and list every discrepancy:
204
+
205
+ ```
206
+ Replay plan for r-4f21ab
207
+
208
+ entrypoint sim/celegans_hh.py::run_network
209
+ commit 9c1d0ae
210
+ seed 42
211
+
212
+ BLOCKERS -- this run cannot be reproduced as recorded:
213
+ - input file changed since the run: data/connectome.csv
214
+ (was a7f39b21, now 3e0c77af)
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Export
220
+
221
+ `daftar export` writes a zip containing `README.md`, `manifest.json`,
222
+ `fields.tsv`, and the referenced input and output files. The README is generated
223
+ in plain English at the archive root, so a successor learns what they are
224
+ looking at without installing anything. A bundle that needs our tool to be
225
+ understood defeats its own purpose.
226
+
227
+ ---
228
+
229
+ ## Framework adapters
230
+
231
+ The core tracks any Python function. An adapter earns its existence only by
232
+ knowing something a generic tracker cannot infer.
233
+
234
+ | Adapter | Records what you'd otherwise lose |
235
+ |---|---|
236
+ | `jaxley` | morphology (compartments, branches, channels, synapses), `jx.integrate` defaults you never passed, `jax_enable_x64`, backend |
237
+ | `cpm` | parameter **bounds and priors**, estimator and its scipy method/tolerance, per-participant convergence counts, cohort hash |
238
+ | `meltingpot` | resolved substrate ConfigDict hash, roles, episode-length cap, pinned bot checkpoints, per-player returns and Gini |
239
+
240
+ ```python
241
+ from daftar.adapters import jaxley as jxa
242
+
243
+ with daftar.track("hh-cell", seed=0) as run:
244
+ v = jxa.integrate(cell, run, t_max=10.0, delta_t=0.025)
245
+ ```
246
+
247
+ Adapters never import their framework at module load, so `import daftar`
248
+ works with none of them installed. Every probe is best-effort: a provenance tool
249
+ that crashes a four-hour simulation because a framework renamed an attribute has
250
+ done far more harm than the missing field was worth.
251
+
252
+ See `examples/adapter_usage.py` for the full pattern for each.
253
+
254
+ ### Why not Concordia
255
+
256
+ Every Concordia agent step calls `LanguageModel.sample_text()`, and no major
257
+ provider guarantees token-level determinism even with a fixed seed. `replay`
258
+ there cannot mean what it means everywhere else, and shipping an adapter whose
259
+ replay silently does not replay would undermine the one property this package
260
+ sells.
261
+
262
+ The right design is a different contract — wrap the model, hash every
263
+ `(prompt, response)` pair in order, and have `diff` report the first step at
264
+ which two runs diverged. That turns Concordia into the strongest argument for
265
+ this whole package rather than an awkward fit, because LLM-driven simulation is
266
+ the case where nobody can currently audit anything. It comes after the
267
+ deterministic adapters have users.
268
+
269
+ ---
270
+
271
+ ## Try it
272
+
273
+ ```bash
274
+ python examples/demo_end_to_end.py
275
+ ```
276
+
277
+ No frameworks needed. Walks through a clean reproduction, a deliberate parameter
278
+ change, a genuine nondeterminism catch, a sweep, a replay plan, and an export.
279
+
280
+ ## Development
281
+
282
+ ```bash
283
+ pip install -e ".[dev]"
284
+ pytest -q
285
+ ```
286
+
287
+ ## Licence
288
+
289
+ Apache 2.0.
@@ -0,0 +1,112 @@
1
+ # Publishing daftar
2
+
3
+ ## Why your TestPyPI upload returned 403
4
+
5
+ **TestPyPI is a completely separate service from PyPI.** Separate database,
6
+ separate accounts, separate API tokens. A token issued by `pypi.org` is
7
+ meaningless to `test.pypi.org` and is rejected with a bare `403 Forbidden` —
8
+ which is exactly what you saw.
9
+
10
+ In likelihood order:
11
+
12
+ 1. **You used a PyPI token on TestPyPI.** Overwhelmingly the most common cause.
13
+ 2. **You have no TestPyPI account.** Registering on PyPI does not register you
14
+ on TestPyPI. Sign up separately at
15
+ <https://test.pypi.org/account/register/>.
16
+ 3. **Email not verified** on the TestPyPI account. Unverified accounts cannot
17
+ upload, and the error is a 403 with no explanation.
18
+ 4. **The name is taken on TestPyPI.** Its namespace is full of abandoned junk
19
+ and is pruned irregularly. If someone holds `daftar` there you get a 403,
20
+ and it says nothing about real PyPI, where `daftar` is confirmed free.
21
+
22
+ To rule out 1–3, get a token from
23
+ <https://test.pypi.org/manage/account/token/> and retry with `--verbose`:
24
+
25
+ ```bash
26
+ python -m twine upload --repository testpypi --verbose dist/*
27
+ ```
28
+
29
+ The verbose output distinguishes "bad credentials" from "you do not own this
30
+ project name", which the plain error does not.
31
+
32
+ ## The thing worth noticing
33
+
34
+ Your upload reached **100%** before failing. The bytes crossed the network and
35
+ were rejected at the authorization layer. Uploading to the Python package index
36
+ from where you are is not blocked. That was the open question, and it is now
37
+ answered.
38
+
39
+ ## Recommendation: skip TestPyPI
40
+
41
+ TestPyPI's purpose is rehearsing a release. You have already done the parts that
42
+ matter:
43
+
44
+ - `python -m build` succeeds with no warnings
45
+ - `python -m twine check dist/*` passes both artifacts
46
+ - the wheel installs into a clean virtualenv and imports with zero dependencies
47
+ - 34 tests pass
48
+
49
+ A successful TestPyPI upload would tell you nothing further, and every day spent
50
+ debugging it is a day `daftar` remains unclaimed on the index that matters.
51
+
52
+ ```bash
53
+ # 1. Account on pypi.org, email verified
54
+ # 2. Token: pypi.org -> Account settings -> API tokens -> "Add API token"
55
+ # Scope "Entire account" for the first upload; re-scope to the project after.
56
+ python -m twine upload dist/*
57
+ ```
58
+
59
+ When prompted, paste the token including the `pypi-` prefix. Twine 7 asks for
60
+ the token directly, so you do not need to type `__token__` as a username.
61
+
62
+ To avoid re-pasting, put it in `~/.pypirc` (mode 600):
63
+
64
+ ```ini
65
+ [distutils]
66
+ index-servers = pypi testpypi
67
+
68
+ [pypi]
69
+ username = __token__
70
+ password = pypi-AgEIcHlwaS5vcmc...
71
+
72
+ [testpypi]
73
+ repository = https://test.pypi.org/legacy/
74
+ username = __token__
75
+ password = pypi-AgENdGVzdC5weXBpLm9yZw...
76
+ ```
77
+
78
+ Note the two tokens differ: TestPyPI tokens encode `dGVzdC5weXBpLm9yZw` —
79
+ base64 for `test.pypi.org`. If yours does not, you are holding the wrong one.
80
+
81
+ ## A version number can never be reused
82
+
83
+ Not even after deleting the release. If 0.1.0 turns out wrong, ship 0.1.1. Do
84
+ not agonise over the first upload; the name is the scarce thing, not the
85
+ version.
86
+
87
+ ## Also claim
88
+
89
+ - GitHub `github.com/aslansd/daftar` (the org name `daftar` was free when checked)
90
+ - Update `[project.urls]` in `pyproject.toml` if you use a different path
91
+
92
+ ## Before announcing
93
+
94
+ - [ ] `pytest -q` green
95
+ - [ ] `python examples/demo_end_to_end.py` runs on a fresh clone
96
+ - [ ] wheel installed into a clean venv and imported
97
+ - [ ] README renders correctly on the live PyPI page
98
+ - [ ] **run daftar across three of your own repos, unmodified**
99
+ - [ ] `git tag v0.1.0 && git push --tags`
100
+
101
+ The fifth item is the month-7 milestone from the feasibility plan and the only
102
+ real test of whether the API is unobtrusive enough that you keep using it when
103
+ nobody is watching. Do it before the announcement, not after.
104
+
105
+ ## On the licence
106
+
107
+ `LICENSE` now contains the full Apache-2.0 text, and `pyproject.toml` declares
108
+ `license = "Apache-2.0"` as an SPDX expression. This is not bookkeeping: the
109
+ strategy rests on publishing freely available source code, which has generally
110
+ been treated as "information and informational materials". A public repository
111
+ with a permissive licence and a licence file present is a different legal object
112
+ from a private or licence-gated artifact. Keep it that way.