dividendcase 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.
Files changed (202) hide show
  1. dividendcase-0.1.0/.github/workflows/build.yml +87 -0
  2. dividendcase-0.1.0/.github/workflows/release.yml +151 -0
  3. dividendcase-0.1.0/.github/workflows/tests.yml +39 -0
  4. dividendcase-0.1.0/.gitignore +39 -0
  5. dividendcase-0.1.0/.python-version +1 -0
  6. dividendcase-0.1.0/CHANGELOG.md +19 -0
  7. dividendcase-0.1.0/LICENSE +662 -0
  8. dividendcase-0.1.0/PKG-INFO +172 -0
  9. dividendcase-0.1.0/README.md +124 -0
  10. dividendcase-0.1.0/pyproject.toml +93 -0
  11. dividendcase-0.1.0/scripts/build_web.py +44 -0
  12. dividendcase-0.1.0/scripts/check_dist.py +54 -0
  13. dividendcase-0.1.0/scripts/smoke_test.py +91 -0
  14. dividendcase-0.1.0/src/dividendcase/__init__.py +7 -0
  15. dividendcase-0.1.0/src/dividendcase/__main__.py +4 -0
  16. dividendcase-0.1.0/src/dividendcase/api/__init__.py +0 -0
  17. dividendcase-0.1.0/src/dividendcase/api/v1/__init__.py +0 -0
  18. dividendcase-0.1.0/src/dividendcase/api/v1/data.py +34 -0
  19. dividendcase-0.1.0/src/dividendcase/api/v1/deps.py +10 -0
  20. dividendcase-0.1.0/src/dividendcase/api/v1/dividends.py +238 -0
  21. dividendcase-0.1.0/src/dividendcase/api/v1/excel_io.py +1093 -0
  22. dividendcase-0.1.0/src/dividendcase/api/v1/fx.py +29 -0
  23. dividendcase-0.1.0/src/dividendcase/api/v1/health.py +38 -0
  24. dividendcase-0.1.0/src/dividendcase/api/v1/investment.py +261 -0
  25. dividendcase-0.1.0/src/dividendcase/api/v1/portfolio.py +706 -0
  26. dividendcase-0.1.0/src/dividendcase/api/v1/portfolios.py +147 -0
  27. dividendcase-0.1.0/src/dividendcase/api/v1/router.py +17 -0
  28. dividendcase-0.1.0/src/dividendcase/api/v1/search.py +181 -0
  29. dividendcase-0.1.0/src/dividendcase/api/v1/stocks.py +292 -0
  30. dividendcase-0.1.0/src/dividendcase/api/v1/user.py +108 -0
  31. dividendcase-0.1.0/src/dividendcase/api/v1/watchlist.py +289 -0
  32. dividendcase-0.1.0/src/dividendcase/cli.py +72 -0
  33. dividendcase-0.1.0/src/dividendcase/config.py +45 -0
  34. dividendcase-0.1.0/src/dividendcase/crud/__init__.py +7 -0
  35. dividendcase-0.1.0/src/dividendcase/crud/dividend.py +57 -0
  36. dividendcase-0.1.0/src/dividendcase/crud/stock.py +78 -0
  37. dividendcase-0.1.0/src/dividendcase/database.py +38 -0
  38. dividendcase-0.1.0/src/dividendcase/main.py +115 -0
  39. dividendcase-0.1.0/src/dividendcase/migrate.py +161 -0
  40. dividendcase-0.1.0/src/dividendcase/migrations/env.py +35 -0
  41. dividendcase-0.1.0/src/dividendcase/migrations/script.py.mako +24 -0
  42. dividendcase-0.1.0/src/dividendcase/migrations/versions/0001_baseline.py +180 -0
  43. dividendcase-0.1.0/src/dividendcase/migrations/versions/0002_check_for_updates.py +26 -0
  44. dividendcase-0.1.0/src/dividendcase/migrations/versions/0003_fx_rates_and_setup.py +41 -0
  45. dividendcase-0.1.0/src/dividendcase/models/__init__.py +21 -0
  46. dividendcase-0.1.0/src/dividendcase/models/dividend.py +18 -0
  47. dividendcase-0.1.0/src/dividendcase/models/fx_rate.py +14 -0
  48. dividendcase-0.1.0/src/dividendcase/models/scheduler_run.py +14 -0
  49. dividendcase-0.1.0/src/dividendcase/models/stock.py +29 -0
  50. dividendcase-0.1.0/src/dividendcase/models/user_investment.py +24 -0
  51. dividendcase-0.1.0/src/dividendcase/models/user_portfolio.py +12 -0
  52. dividendcase-0.1.0/src/dividendcase/models/user_preferences.py +22 -0
  53. dividendcase-0.1.0/src/dividendcase/models/user_watchlist.py +17 -0
  54. dividendcase-0.1.0/src/dividendcase/models/user_watchlist_group.py +12 -0
  55. dividendcase-0.1.0/src/dividendcase/scheduler/__init__.py +0 -0
  56. dividendcase-0.1.0/src/dividendcase/scheduler/tasks.py +7 -0
  57. dividendcase-0.1.0/src/dividendcase/schemas/__init__.py +9 -0
  58. dividendcase-0.1.0/src/dividendcase/schemas/dividend.py +55 -0
  59. dividendcase-0.1.0/src/dividendcase/schemas/investment.py +21 -0
  60. dividendcase-0.1.0/src/dividendcase/schemas/stock.py +47 -0
  61. dividendcase-0.1.0/src/dividendcase/schemas/user_investment.py +126 -0
  62. dividendcase-0.1.0/src/dividendcase/schemas/user_preferences.py +34 -0
  63. dividendcase-0.1.0/src/dividendcase/schemas/watchlist.py +42 -0
  64. dividendcase-0.1.0/src/dividendcase/services/__init__.py +0 -0
  65. dividendcase-0.1.0/src/dividendcase/services/derived.py +33 -0
  66. dividendcase-0.1.0/src/dividendcase/services/fx.py +204 -0
  67. dividendcase-0.1.0/src/dividendcase/services/refresh.py +318 -0
  68. dividendcase-0.1.0/src/dividendcase/services/universe.py +193 -0
  69. dividendcase-0.1.0/src/dividendcase/services/updates.py +115 -0
  70. dividendcase-0.1.0/src/dividendcase/services/yahoo_fetcher.py +856 -0
  71. dividendcase-0.1.0/src/dividendcase/web/.gitkeep +0 -0
  72. dividendcase-0.1.0/src/dividendcase/web/404/index.html +1 -0
  73. dividendcase-0.1.0/src/dividendcase/web/404.html +1 -0
  74. dividendcase-0.1.0/src/dividendcase/web/__next.__PAGE__.txt +23 -0
  75. dividendcase-0.1.0/src/dividendcase/web/__next._full.txt +21 -0
  76. dividendcase-0.1.0/src/dividendcase/web/__next._tree.txt +5 -0
  77. dividendcase-0.1.0/src/dividendcase/web/_next/static/DhRV7h_G5oje4BFbueGOW/_buildManifest.js +11 -0
  78. dividendcase-0.1.0/src/dividendcase/web/_next/static/DhRV7h_G5oje4BFbueGOW/_clientMiddlewareManifest.js +1 -0
  79. dividendcase-0.1.0/src/dividendcase/web/_next/static/DhRV7h_G5oje4BFbueGOW/_ssgManifest.js +1 -0
  80. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0c87cux8r1ss5.js +1 -0
  81. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0cev4x3kzarbu.js +2 -0
  82. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  83. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0g-bslilw6tiu.js +1 -0
  84. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0izc_4m5nyqrd.js +1 -0
  85. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0lfebwmsk92ym.js +1 -0
  86. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0nmuijbkm3ipd.js +1 -0
  87. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/0ozczpntpufql.js +1 -0
  88. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/1-ift1mtsuqvh.js +1 -0
  89. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/10oy-ed_zrrd9.js +1 -0
  90. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/13-u82nfffhrp.js +1 -0
  91. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/138wmfea-yk08.js +22 -0
  92. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/14jt62gawmtd9.js +1 -0
  93. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/15m8s8ua7v57_.js +1 -0
  94. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/193r1px8kfm06.js +1 -0
  95. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/1l-w55cdq629z.js +1 -0
  96. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/1q2bxv0xj7vse.js +1 -0
  97. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/1s5tue3q2i-8b.js +1 -0
  98. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/1xrz4-jij46i_.js +1 -0
  99. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/24f36pdyspt8c.js +1 -0
  100. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/2aqpz6z087bo4.js +1 -0
  101. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/2ft48jvqut7tm.js +1 -0
  102. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/2i51e627rllld.js +1 -0
  103. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/2o72ftwupypgx.js +1 -0
  104. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/2vq-8ocdpjh4a.css +4 -0
  105. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/33-cg7-y-5zm5.js +1 -0
  106. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/3cju952ndgmmn.js +1 -0
  107. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/3il2yixgws-43.js +1 -0
  108. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/3mc6dra1m0098.js +31 -0
  109. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/431n5e16l34_0.js +1 -0
  110. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/453cd1tlxz5wi.js +1 -0
  111. dividendcase-0.1.0/src/dividendcase/web/_next/static/chunks/turbopack-1che7mqzsn0nh.js +1 -0
  112. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
  113. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
  114. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
  115. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
  116. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
  117. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
  118. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
  119. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/7ebf22b5a21034f8-s.p.3j3877k49yy0l.woff2 +0 -0
  120. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
  121. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
  122. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
  123. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/fc0a7394b875fa43-s.2ytmazkdrg7t9.woff2 +0 -0
  124. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
  125. dividendcase-0.1.0/src/dividendcase/web/_next/static/media/icon.0kxjuvpgwz_c6.svg +85 -0
  126. dividendcase-0.1.0/src/dividendcase/web/_not-found/__next._full.txt +16 -0
  127. dividendcase-0.1.0/src/dividendcase/web/_not-found/__next._not-found.__PAGE__.txt +22 -0
  128. dividendcase-0.1.0/src/dividendcase/web/_not-found/__next._tree.txt +2 -0
  129. dividendcase-0.1.0/src/dividendcase/web/_not-found/index.html +1 -0
  130. dividendcase-0.1.0/src/dividendcase/web/_not-found/index.txt +16 -0
  131. dividendcase-0.1.0/src/dividendcase/web/dashboard/__next._full.txt +24 -0
  132. dividendcase-0.1.0/src/dividendcase/web/dashboard/__next._tree.txt +5 -0
  133. dividendcase-0.1.0/src/dividendcase/web/dashboard/__next.dashboard.__PAGE__.txt +27 -0
  134. dividendcase-0.1.0/src/dividendcase/web/dashboard/about/__next._full.txt +26 -0
  135. dividendcase-0.1.0/src/dividendcase/web/dashboard/about/__next._tree.txt +5 -0
  136. dividendcase-0.1.0/src/dividendcase/web/dashboard/about/__next.dashboard.about.__PAGE__.txt +30 -0
  137. dividendcase-0.1.0/src/dividendcase/web/dashboard/about/index.html +1 -0
  138. dividendcase-0.1.0/src/dividendcase/web/dashboard/about/index.txt +26 -0
  139. dividendcase-0.1.0/src/dividendcase/web/dashboard/calendar/__next._full.txt +26 -0
  140. dividendcase-0.1.0/src/dividendcase/web/dashboard/calendar/__next._tree.txt +5 -0
  141. dividendcase-0.1.0/src/dividendcase/web/dashboard/calendar/__next.dashboard.calendar.__PAGE__.txt +30 -0
  142. dividendcase-0.1.0/src/dividendcase/web/dashboard/calendar/index.html +1 -0
  143. dividendcase-0.1.0/src/dividendcase/web/dashboard/calendar/index.txt +26 -0
  144. dividendcase-0.1.0/src/dividendcase/web/dashboard/compare/__next._full.txt +26 -0
  145. dividendcase-0.1.0/src/dividendcase/web/dashboard/compare/__next._tree.txt +5 -0
  146. dividendcase-0.1.0/src/dividendcase/web/dashboard/compare/__next.dashboard.compare.__PAGE__.txt +30 -0
  147. dividendcase-0.1.0/src/dividendcase/web/dashboard/compare/index.html +1 -0
  148. dividendcase-0.1.0/src/dividendcase/web/dashboard/compare/index.txt +26 -0
  149. dividendcase-0.1.0/src/dividendcase/web/dashboard/data/__next._full.txt +23 -0
  150. dividendcase-0.1.0/src/dividendcase/web/dashboard/data/__next._tree.txt +5 -0
  151. dividendcase-0.1.0/src/dividendcase/web/dashboard/data/__next.dashboard.data.__PAGE__.txt +27 -0
  152. dividendcase-0.1.0/src/dividendcase/web/dashboard/data/index.html +1 -0
  153. dividendcase-0.1.0/src/dividendcase/web/dashboard/data/index.txt +23 -0
  154. dividendcase-0.1.0/src/dividendcase/web/dashboard/drip/__next._full.txt +26 -0
  155. dividendcase-0.1.0/src/dividendcase/web/dashboard/drip/__next._tree.txt +5 -0
  156. dividendcase-0.1.0/src/dividendcase/web/dashboard/drip/__next.dashboard.drip.__PAGE__.txt +30 -0
  157. dividendcase-0.1.0/src/dividendcase/web/dashboard/drip/index.html +1 -0
  158. dividendcase-0.1.0/src/dividendcase/web/dashboard/drip/index.txt +26 -0
  159. dividendcase-0.1.0/src/dividendcase/web/dashboard/index.html +1 -0
  160. dividendcase-0.1.0/src/dividendcase/web/dashboard/index.txt +24 -0
  161. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/__next._full.txt +26 -0
  162. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/__next._tree.txt +5 -0
  163. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/__next.dashboard.investments.__PAGE__.txt +30 -0
  164. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/index.html +1 -0
  165. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/index.txt +26 -0
  166. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/portfolio/__next._full.txt +26 -0
  167. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/portfolio/__next._tree.txt +5 -0
  168. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/portfolio/__next.dashboard.investments.portfolio.__PAGE__.txt +31 -0
  169. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/portfolio/index.html +1 -0
  170. dividendcase-0.1.0/src/dividendcase/web/dashboard/investments/portfolio/index.txt +26 -0
  171. dividendcase-0.1.0/src/dividendcase/web/dashboard/screener/__next._full.txt +26 -0
  172. dividendcase-0.1.0/src/dividendcase/web/dashboard/screener/__next._tree.txt +5 -0
  173. dividendcase-0.1.0/src/dividendcase/web/dashboard/screener/__next.dashboard.screener.__PAGE__.txt +30 -0
  174. dividendcase-0.1.0/src/dividendcase/web/dashboard/screener/index.html +1 -0
  175. dividendcase-0.1.0/src/dividendcase/web/dashboard/screener/index.txt +26 -0
  176. dividendcase-0.1.0/src/dividendcase/web/dashboard/settings/__next._full.txt +26 -0
  177. dividendcase-0.1.0/src/dividendcase/web/dashboard/settings/__next._tree.txt +5 -0
  178. dividendcase-0.1.0/src/dividendcase/web/dashboard/settings/__next.dashboard.settings.__PAGE__.txt +30 -0
  179. dividendcase-0.1.0/src/dividendcase/web/dashboard/settings/index.html +1 -0
  180. dividendcase-0.1.0/src/dividendcase/web/dashboard/settings/index.txt +26 -0
  181. dividendcase-0.1.0/src/dividendcase/web/dashboard/stock/__next._full.txt +26 -0
  182. dividendcase-0.1.0/src/dividendcase/web/dashboard/stock/__next._tree.txt +5 -0
  183. dividendcase-0.1.0/src/dividendcase/web/dashboard/stock/__next.dashboard.stock.__PAGE__.txt +30 -0
  184. dividendcase-0.1.0/src/dividendcase/web/dashboard/stock/index.html +1 -0
  185. dividendcase-0.1.0/src/dividendcase/web/dashboard/stock/index.txt +26 -0
  186. dividendcase-0.1.0/src/dividendcase/web/dashboard/watchlist/__next._full.txt +26 -0
  187. dividendcase-0.1.0/src/dividendcase/web/dashboard/watchlist/__next._tree.txt +5 -0
  188. dividendcase-0.1.0/src/dividendcase/web/dashboard/watchlist/__next.dashboard.watchlist.__PAGE__.txt +30 -0
  189. dividendcase-0.1.0/src/dividendcase/web/dashboard/watchlist/index.html +1 -0
  190. dividendcase-0.1.0/src/dividendcase/web/dashboard/watchlist/index.txt +26 -0
  191. dividendcase-0.1.0/src/dividendcase/web/icon.svg +85 -0
  192. dividendcase-0.1.0/src/dividendcase/web/index.html +1 -0
  193. dividendcase-0.1.0/src/dividendcase/web/index.txt +21 -0
  194. dividendcase-0.1.0/src/dividendcase/web/logo-full.svg +105 -0
  195. dividendcase-0.1.0/src/dividendcase/web/logo-icon.svg +85 -0
  196. dividendcase-0.1.0/src/dividendcase/web/tickers.json +1 -0
  197. dividendcase-0.1.0/tests/conftest.py +198 -0
  198. dividendcase-0.1.0/tests/test_api.py +229 -0
  199. dividendcase-0.1.0/tests/test_derived_and_updates.py +39 -0
  200. dividendcase-0.1.0/tests/test_fx_and_setup.py +189 -0
  201. dividendcase-0.1.0/tests/test_migrations.py +83 -0
  202. dividendcase-0.1.0/uv.lock +1954 -0
@@ -0,0 +1,87 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: build-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ interface:
18
+ name: Interface and website
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 15
21
+ steps:
22
+ - uses: actions/checkout@v5
23
+ - uses: actions/setup-node@v5
24
+ with:
25
+ node-version: 22
26
+ cache: npm
27
+ - name: Install
28
+ run: npm ci --no-audit --no-fund
29
+ - name: Type-check
30
+ run: |
31
+ npx tsc --noEmit -p frontend
32
+ npx tsc --noEmit -p site
33
+ - name: Build the website
34
+ run: npm run build -w site
35
+
36
+ package:
37
+ name: Package
38
+ runs-on: ubuntu-latest
39
+ timeout-minutes: 15
40
+ steps:
41
+ - uses: actions/checkout@v5
42
+ - uses: actions/setup-node@v5
43
+ with:
44
+ node-version: 22
45
+ cache: npm
46
+ - uses: astral-sh/setup-uv@v6
47
+ with:
48
+ enable-cache: true
49
+ - name: Build the interface into the package
50
+ run: uv run python scripts/build_web.py
51
+ - name: Serve the built pages
52
+ run: uv run pytest tests/test_api.py -k interface
53
+ - name: Build the wheel and sdist
54
+ run: uv build
55
+ - name: Check them
56
+ run: uv run python scripts/check_dist.py
57
+ - uses: actions/upload-artifact@v4
58
+ with:
59
+ name: dist
60
+ path: dist/
61
+ retention-days: 7
62
+
63
+ install:
64
+ name: Install and run (${{ matrix.os }})
65
+ needs: package
66
+ strategy:
67
+ fail-fast: false
68
+ matrix:
69
+ os: ${{ github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]') || fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]') }}
70
+ runs-on: ${{ matrix.os }}
71
+ timeout-minutes: 15
72
+ defaults:
73
+ run:
74
+ shell: bash
75
+ steps:
76
+ - uses: actions/checkout@v5
77
+ - uses: actions/download-artifact@v5
78
+ with:
79
+ name: dist
80
+ path: dist/
81
+ - uses: astral-sh/setup-uv@v6
82
+ - name: uv tool install, as the README says
83
+ run: |
84
+ uv tool install "$(ls dist/*.whl)"
85
+ echo "$(uv tool dir --bin)" >> "$GITHUB_PATH"
86
+ - name: Start it and check it works
87
+ run: uv run --no-project python scripts/smoke_test.py
@@ -0,0 +1,151 @@
1
+ name: Release
2
+
3
+ # A release is a tag: bump `version` in pyproject.toml, add a CHANGELOG.md section with the
4
+ # same number, merge, then push the tag `v<version>`. PyPI trusts this workflow directly
5
+ # (trusted publishing), so there are no tokens or passwords in the repository.
6
+ #
7
+ # Run it by hand ("Run workflow") for a dry run on TestPyPI instead.
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+ workflow_dispatch:
13
+ inputs:
14
+ testpypi:
15
+ description: Publish to TestPyPI (a dry run; real releases come from a v* tag)
16
+ type: boolean
17
+ default: true
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ concurrency:
23
+ group: release-${{ github.ref }}
24
+ cancel-in-progress: false
25
+
26
+ jobs:
27
+ build:
28
+ name: Build and test
29
+ runs-on: ubuntu-latest
30
+ timeout-minutes: 20
31
+ outputs:
32
+ version: ${{ steps.version.outputs.version }}
33
+ steps:
34
+ - uses: actions/checkout@v5
35
+ - uses: actions/setup-node@v5
36
+ with:
37
+ node-version: 22
38
+ cache: npm
39
+ - uses: astral-sh/setup-uv@v6
40
+ with:
41
+ enable-cache: true
42
+ - name: Version matches the tag
43
+ id: version
44
+ run: |
45
+ version=$(uv run --no-project python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
46
+ echo "version=$version" >> "$GITHUB_OUTPUT"
47
+ if [ "$GITHUB_REF_TYPE" = "tag" ] && [ "$GITHUB_REF_NAME" != "v$version" ]; then
48
+ echo "::error::The tag $GITHUB_REF_NAME doesn't match the version in pyproject.toml ($version)"
49
+ exit 1
50
+ fi
51
+ if ! grep -q "^## $version\$" CHANGELOG.md; then
52
+ echo "::error::CHANGELOG.md has no '## $version' section"
53
+ exit 1
54
+ fi
55
+ - name: Tests
56
+ run: |
57
+ uv sync --locked
58
+ uv run pytest
59
+ uv run python -m dividendcase.migrate check
60
+ - name: Build the interface into the package
61
+ run: uv run python scripts/build_web.py
62
+ - name: Build the wheel and sdist
63
+ run: uv build
64
+ - name: Check them
65
+ run: uv run python scripts/check_dist.py "${{ steps.version.outputs.version }}"
66
+ - uses: actions/upload-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+ retention-days: 30
71
+
72
+ install:
73
+ name: Install and run (${{ matrix.os }})
74
+ needs: build
75
+ strategy:
76
+ fail-fast: false
77
+ matrix:
78
+ os: [ubuntu-latest, macos-latest, windows-latest]
79
+ runs-on: ${{ matrix.os }}
80
+ timeout-minutes: 15
81
+ defaults:
82
+ run:
83
+ shell: bash
84
+ steps:
85
+ - uses: actions/checkout@v5
86
+ - uses: actions/download-artifact@v5
87
+ with:
88
+ name: dist
89
+ path: dist/
90
+ - uses: astral-sh/setup-uv@v6
91
+ - name: uv tool install, as the README says
92
+ run: |
93
+ uv tool install "$(ls dist/*.whl)"
94
+ echo "$(uv tool dir --bin)" >> "$GITHUB_PATH"
95
+ - name: Start it and check it works
96
+ run: uv run --no-project python scripts/smoke_test.py
97
+
98
+ testpypi:
99
+ name: Publish to TestPyPI
100
+ if: github.event_name == 'workflow_dispatch' && inputs.testpypi
101
+ needs: [build, install]
102
+ runs-on: ubuntu-latest
103
+ permissions:
104
+ id-token: write # trusted publishing
105
+ steps:
106
+ - uses: actions/download-artifact@v5
107
+ with:
108
+ name: dist
109
+ path: dist/
110
+ - uses: pypa/gh-action-pypi-publish@release/v1
111
+ with:
112
+ repository-url: https://test.pypi.org/legacy/
113
+ skip-existing: true
114
+
115
+ pypi:
116
+ name: Publish to PyPI
117
+ if: github.ref_type == 'tag'
118
+ needs: [build, install]
119
+ runs-on: ubuntu-latest
120
+ permissions:
121
+ id-token: write # trusted publishing
122
+ steps:
123
+ - uses: actions/download-artifact@v5
124
+ with:
125
+ name: dist
126
+ path: dist/
127
+ - uses: pypa/gh-action-pypi-publish@release/v1
128
+
129
+ github-release:
130
+ name: GitHub release
131
+ if: github.ref_type == 'tag'
132
+ needs: [build, pypi]
133
+ runs-on: ubuntu-latest
134
+ permissions:
135
+ contents: write
136
+ steps:
137
+ - uses: actions/checkout@v5
138
+ - uses: actions/download-artifact@v5
139
+ with:
140
+ name: dist
141
+ path: dist/
142
+ - name: Create the release with the changelog's notes
143
+ env:
144
+ GH_TOKEN: ${{ github.token }}
145
+ VERSION: ${{ needs.build.outputs.version }}
146
+ run: |
147
+ awk -v heading="## $VERSION" '$0 == heading {found = 1; next} /^## / {found = 0} found' CHANGELOG.md > notes.md
148
+ printf '\n**Install:** `uv tool install dividendcase` · **Update:** `uv tool upgrade dividendcase`\n' >> notes.md
149
+ prerelease=""
150
+ if echo "$VERSION" | grep -q '[a-z]'; then prerelease="--prerelease"; fi
151
+ gh release create "$GITHUB_REF_NAME" dist/* --title "DividendCase $VERSION" --notes-file notes.md $prerelease
@@ -0,0 +1,39 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: tests-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ tests:
18
+ name: Python ${{ matrix.python }} (${{ matrix.os }})
19
+ # Pull requests: Linux only. main: every supported Python on Linux, plus macOS and Windows,
20
+ # where people install the app
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ include: ${{ fromJSON(github.event_name == 'pull_request' && '[{"os":"ubuntu-latest","python":"3.12"}]' || '[{"os":"ubuntu-latest","python":"3.11"},{"os":"ubuntu-latest","python":"3.12"},{"os":"ubuntu-latest","python":"3.13"},{"os":"macos-latest","python":"3.12"},{"os":"windows-latest","python":"3.12"}]') }}
25
+ runs-on: ${{ matrix.os }}
26
+ timeout-minutes: 15
27
+ env:
28
+ UV_PYTHON: ${{ matrix.python }}
29
+ steps:
30
+ - uses: actions/checkout@v5
31
+ - uses: astral-sh/setup-uv@v6
32
+ with:
33
+ enable-cache: true
34
+ - name: Install
35
+ run: uv sync --locked
36
+ - name: Tests (real SQLite, no network)
37
+ run: uv run pytest
38
+ - name: Models and migrations agree
39
+ run: uv run python -m dividendcase.migrate check
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Interface, website and shared packages (npm workspaces)
10
+ node_modules/
11
+ .next/
12
+ out/
13
+ *.tsbuildinfo
14
+ next-env.d.ts
15
+
16
+ # The built interface is copied into the package at release time
17
+ src/dividendcase/web/*
18
+ !src/dividendcase/web/.gitkeep
19
+
20
+ # Local data and secrets: never commit these
21
+ *.db
22
+ *.db-shm
23
+ *.db-wal
24
+ .env
25
+ .env.*
26
+ !.env.example
27
+ # Only points `npm run dev` at the local API; no secrets
28
+ !frontend/.env.development
29
+
30
+ # OS / editors
31
+ .DS_Store
32
+ .idea/
33
+ .vscode/
34
+
35
+ # Personal Claude Code settings
36
+ .claude/
37
+
38
+ # Throwaway data folder for testing (DIVIDENDCASE_DATA_DIR)
39
+ .test-data/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to DividendCase. Versions follow [semantic versioning](https://semver.org/);
4
+ while the version starts with 0, a minor release (0.2, 0.3…) may change things.
5
+
6
+ ## 0.1.0
7
+
8
+ The first release of DividendCase as an app that runs on your own computer.
9
+
10
+ - **Income home:** what your holdings should pay over the next 12 months, month by month, with upcoming payments, where the income comes from and a few plain-English notes.
11
+ - **One currency:** totals across USD, GBP, EUR, INR, CAD, AUD and more are converted into your home currency with the European Central Bank's daily reference rates. Past amounts use the rate on their date, future income the latest rate.
12
+ - **First-run setup:** tax residence, home currency and which markets the screener downloads.
13
+ - **Stock pages:** 10 years of dividend history, TTM and forward yield, a safety score, dividend growth and growth streaks.
14
+ - **Screener:** index members of the S&P 500, NIFTY 50, TSX 60, FTSE 100, ISEQ 20 and ASX 200, plus high-yield groups, filtered by market, yield and how often they pay.
15
+ - **Portfolios:** up to four, with individual purchase lots in any currency, performance against a benchmark and diversification charts.
16
+ - **Income calendar, watchlists, side-by-side comparison and a DRIP calculator.**
17
+ - **Excel import and export** of portfolios, holdings and watchlists, and an Excel report.
18
+ - **⌘K search** for any stock, page or action.
19
+ - **Your data stays on your computer:** one SQLite file, market data fetched by your computer from Yahoo Finance for your own use, a copy saved before every upgrade, and a daily check for new versions that you can turn off.