eggpool 0.2.0__tar.gz → 0.2.1__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 (198) hide show
  1. {eggpool-0.2.0 → eggpool-0.2.1}/CHANGELOG.md +12 -0
  2. {eggpool-0.2.0 → eggpool-0.2.1}/PKG-INFO +1 -1
  3. {eggpool-0.2.0 → eggpool-0.2.1}/pyproject.toml +1 -1
  4. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/cli.py +12 -17
  5. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/lifecycle/uninstall.py +67 -3
  6. {eggpool-0.2.0 → eggpool-0.2.1}/uv.lock +1 -1
  7. {eggpool-0.2.0 → eggpool-0.2.1}/.env.example +0 -0
  8. {eggpool-0.2.0 → eggpool-0.2.1}/.github/workflows/ci.yml +0 -0
  9. {eggpool-0.2.0 → eggpool-0.2.1}/.github/workflows/release.yml +0 -0
  10. {eggpool-0.2.0 → eggpool-0.2.1}/.gitignore +0 -0
  11. {eggpool-0.2.0 → eggpool-0.2.1}/AGENTS.md +0 -0
  12. {eggpool-0.2.0 → eggpool-0.2.1}/LICENSE +0 -0
  13. {eggpool-0.2.0 → eggpool-0.2.1}/README.md +0 -0
  14. {eggpool-0.2.0 → eggpool-0.2.1}/architecture/README.md +0 -0
  15. {eggpool-0.2.0 → eggpool-0.2.1}/config-examples/claude-code.env +0 -0
  16. {eggpool-0.2.0 → eggpool-0.2.1}/config-examples/opencode.jsonc +0 -0
  17. {eggpool-0.2.0 → eggpool-0.2.1}/config.example.toml +0 -0
  18. {eggpool-0.2.0 → eggpool-0.2.1}/deploy/eggpool-logrotate.conf +0 -0
  19. {eggpool-0.2.0 → eggpool-0.2.1}/deploy/eggpool.service +0 -0
  20. {eggpool-0.2.0 → eggpool-0.2.1}/deploy/env.example +0 -0
  21. {eggpool-0.2.0 → eggpool-0.2.1}/docs/backup-restore.md +0 -0
  22. {eggpool-0.2.0 → eggpool-0.2.1}/docs/deployment.md +0 -0
  23. {eggpool-0.2.0 → eggpool-0.2.1}/docs/filesystem-layout.md +0 -0
  24. {eggpool-0.2.0 → eggpool-0.2.1}/docs/firewall.md +0 -0
  25. {eggpool-0.2.0 → eggpool-0.2.1}/docs/model-limits.md +0 -0
  26. {eggpool-0.2.0 → eggpool-0.2.1}/docs/providers.md +0 -0
  27. {eggpool-0.2.0 → eggpool-0.2.1}/docs/proxy.md +0 -0
  28. {eggpool-0.2.0 → eggpool-0.2.1}/docs/raspberry-pi.md +0 -0
  29. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/__init__.py +0 -0
  30. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/check_database.py +0 -0
  31. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/install.sh +0 -0
  32. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/install_prompt.py +0 -0
  33. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/smoke_test.py +0 -0
  34. {eggpool-0.2.0 → eggpool-0.2.1}/scripts/verify_upstream_auth.py +0 -0
  35. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/__init__.py +0 -0
  36. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/__main__.py +0 -0
  37. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/_share/.env.example +0 -0
  38. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/_share/config.example.toml +0 -0
  39. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/accounts/__init__.py +0 -0
  40. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/accounts/registry.py +0 -0
  41. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/accounts/state.py +0 -0
  42. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/__init__.py +0 -0
  43. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/chat_completions.py +0 -0
  44. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/errors.py +0 -0
  45. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/messages.py +0 -0
  46. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/models.py +0 -0
  47. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/proxy_request.py +0 -0
  48. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/api/stats.py +0 -0
  49. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/app.py +0 -0
  50. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/auth.py +0 -0
  51. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/background/__init__.py +0 -0
  52. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/background/cleanup.py +0 -0
  53. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/__init__.py +0 -0
  54. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/cache.py +0 -0
  55. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/fetcher.py +0 -0
  56. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/limits.py +0 -0
  57. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/normalizer.py +0 -0
  58. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/pricing.py +0 -0
  59. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/protocols.py +0 -0
  60. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/catalog/service.py +0 -0
  61. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/config.py +0 -0
  62. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/constants.py +0 -0
  63. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/__init__.py +0 -0
  64. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/_resources.py +0 -0
  65. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/escape.py +0 -0
  66. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/render.py +0 -0
  67. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/routes.py +0 -0
  68. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
  69. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/static/dashboard.css +0 -0
  70. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/static/favicon.svg +0 -0
  71. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/theme.py +0 -0
  72. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
  73. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
  74. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
  75. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
  76. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
  77. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
  78. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
  79. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
  80. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Discord.toml +0 -0
  81. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
  82. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
  83. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
  84. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
  85. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
  86. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
  87. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
  88. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
  89. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
  90. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
  91. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
  92. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Nord.toml +0 -0
  93. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
  94. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
  95. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
  96. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
  97. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
  98. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
  99. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
  100. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
  101. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
  102. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
  103. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
  104. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/acton.toml +0 -0
  105. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/bam.toml +0 -0
  106. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
  107. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/berlin.toml +0 -0
  108. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
  109. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/broc.toml +0 -0
  110. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/cork.toml +0 -0
  111. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/ferra.toml +0 -0
  112. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/forest.toml +0 -0
  113. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
  114. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/midnight.toml +0 -0
  115. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/oslo.toml +0 -0
  116. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/plum.toml +0 -0
  117. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/portland.toml +0 -0
  118. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/sunset.toml +0 -0
  119. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/tofino.toml +0 -0
  120. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
  121. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/dashboard/themes/vik.toml +0 -0
  122. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/__init__.py +0 -0
  123. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/connection.py +0 -0
  124. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/migrations.py +0 -0
  125. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/repositories.py +0 -0
  126. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0001_initial.sql +0 -0
  127. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0002_indexes.sql +0 -0
  128. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
  129. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
  130. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
  131. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
  132. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
  133. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
  134. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
  135. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
  136. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
  137. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
  138. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
  139. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
  140. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
  141. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
  142. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
  143. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
  144. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
  145. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
  146. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
  147. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
  148. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/db/schema/checksums.json +0 -0
  149. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/deploy/__init__.py +0 -0
  150. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/errors.py +0 -0
  151. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/health/__init__.py +0 -0
  152. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/health/circuit_breaker.py +0 -0
  153. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/health/health_manager.py +0 -0
  154. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/integrations/__init__.py +0 -0
  155. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/integrations/opencode.py +0 -0
  156. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/lifecycle/__init__.py +0 -0
  157. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/lifecycle/backup.py +0 -0
  158. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/logging.py +0 -0
  159. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/models/__init__.py +0 -0
  160. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/models/api.py +0 -0
  161. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/models/config.py +0 -0
  162. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/models/database.py +0 -0
  163. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/models/domain.py +0 -0
  164. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/onboard.py +0 -0
  165. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/__init__.py +0 -0
  166. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/_templates.toml +0 -0
  167. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/auth.py +0 -0
  168. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/client_pool.py +0 -0
  169. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/connect.py +0 -0
  170. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/contract.py +0 -0
  171. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/providers/pproxy_transport.py +0 -0
  172. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/proxy/__init__.py +0 -0
  173. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/proxy/client.py +0 -0
  174. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/proxy/sse_observer.py +0 -0
  175. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/proxy/usage.py +0 -0
  176. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/py.typed +0 -0
  177. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/quota/__init__.py +0 -0
  178. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/quota/estimation.py +0 -0
  179. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/quota/reservation.py +0 -0
  180. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/quota/scorer.py +0 -0
  181. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/__init__.py +0 -0
  182. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/attempt_finalizer.py +0 -0
  183. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/body.py +0 -0
  184. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/coordinator.py +0 -0
  185. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/finalizer.py +0 -0
  186. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/request/limits.py +0 -0
  187. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/retry/__init__.py +0 -0
  188. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/retry/classification.py +0 -0
  189. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/routing/__init__.py +0 -0
  190. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/routing/eligibility.py +0 -0
  191. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/routing/provider.py +0 -0
  192. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/routing/router.py +0 -0
  193. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/security/__init__.py +0 -0
  194. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/security/redaction.py +0 -0
  195. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/stats/__init__.py +0 -0
  196. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/stats/queries.py +0 -0
  197. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/stats/service.py +0 -0
  198. {eggpool-0.2.0 → eggpool-0.2.1}/src/eggpool/toml_edit.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-06-24
11
+
12
+ ### Fixed
13
+
14
+ - `eggpool serve` returning 500 with `TypeError: 'NoneType' object is not
15
+ callable` on Python 3.14 / spawn-based multiprocessing start methods.
16
+ Granian workers re-import `eggpool.cli` in a fresh interpreter, so the
17
+ module-level `_app` set by the parent process was `None` in the worker
18
+ and the `target_loader` returned `None` as the ASGI callback. `_app_loader`
19
+ now rebuilds the `FastAPI` app from the config path inside each worker.
20
+ Follow-up to the [0.1.4] module-level loader fix.
21
+
10
22
  ## [0.2.0] - 2026-06-24
11
23
 
12
24
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eggpool
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
5
5
  Project-URL: Homepage, https://github.com/eggstack/eggpool
6
6
  Project-URL: Repository, https://github.com/eggstack/eggpool
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eggpool"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -91,16 +91,18 @@ class _ConfigPathGroup(click.Group):
91
91
  # Re-apply the group class after the decorator
92
92
  cli.__class__ = _ConfigPathGroup
93
93
 
94
- # Module-level app reference for Granian's multiprocessing pickling.
95
- # Granian spawns worker processes via multiprocessing which requires
96
- # serializable target_loader callables. Local functions (closures) cannot
97
- # be pickled, so _app_loader must live at module level.
98
- _app: Any = None
99
94
 
95
+ def _app_loader(target: str) -> Any:
96
+ """Build the ASGI app for a Granian worker from the config-path target.
100
97
 
101
- def _app_loader(_target: str) -> Any: # noqa: ARG001
102
- """Return the pre-built ASGI app for Granian workers."""
103
- return _app
98
+ Granian spawns worker subprocesses via multiprocessing, which produces
99
+ a fresh Python interpreter that re-imports ``eggpool.cli``. Module-level
100
+ mutable state set in the parent process is not inherited by the worker,
101
+ so the app must be reconstructed from the config path inside the worker.
102
+ """
103
+ from eggpool.app import create_app
104
+
105
+ return create_app(config_path=target)
104
106
 
105
107
 
106
108
  @cli.command()
@@ -132,23 +134,16 @@ def serve(ctx: click.Context) -> None:
132
134
 
133
135
  configure_logging(level=config.server.log_level)
134
136
 
135
- from functools import partial
136
-
137
- from eggpool.app import create_app
138
-
139
- global _app # noqa: PLW0603
140
- _app = create_app(config, config_path=config_path)
141
-
142
137
  log_level = config.server.log_level.lower()
143
138
  Granian(
144
- "unused", # ignored when target_loader is provided
139
+ config_path,
145
140
  address=config.server.host,
146
141
  port=config.server.port,
147
142
  interface="asgi", # type: ignore[reportArgumentType]
148
143
  workers=1,
149
144
  log_level=log_level, # type: ignore[reportArgumentType]
150
145
  log_access=config.server.access_log,
151
- ).serve(target_loader=partial(_app_loader)) # type: ignore[reportArgumentType]
146
+ ).serve(target_loader=_app_loader) # type: ignore[reportArgumentType]
152
147
 
153
148
 
154
149
  @cli.group(invoke_without_command=True)
@@ -176,9 +176,13 @@ def tomllib_load(blob: bytes) -> dict[str, object]:
176
176
 
177
177
 
178
178
  def _pipx_uninstall(env: dict[str, str]) -> subprocess.CompletedProcess[str]:
179
- """Run ``pipx uninstall eggpool`` in the detected Python."""
180
- python = env.get("EGGPOOL_PYTHON") or sys.executable
181
- cmd = [python, "-m", "pipx", "uninstall", "eggpool"]
179
+ """Run ``pipx uninstall eggpool`` using the correct interpreter.
180
+
181
+ pipx is not installed inside the eggpool venv that pipx itself
182
+ manages, so we cannot use ``sys.executable`` directly. See
183
+ :func:`_find_pipx_invocation` for resolution details.
184
+ """
185
+ cmd = _find_pipx_invocation(env)
182
186
  return subprocess.run( # noqa: S603
183
187
  cmd,
184
188
  capture_output=True,
@@ -188,6 +192,66 @@ def _pipx_uninstall(env: dict[str, str]) -> subprocess.CompletedProcess[str]:
188
192
  )
189
193
 
190
194
 
195
+ def _find_pipx_invocation(env: dict[str, str]) -> list[str]:
196
+ """Resolve the command argv that runs ``pipx`` on this machine.
197
+
198
+ Resolution order:
199
+
200
+ 1. The ``pipx`` binary on ``PATH`` (preferred — it has its own
201
+ shebang pointing at the Python pipx was installed under).
202
+ 2. ``EGGPOOL_PYTHON`` env override (escape hatch for unusual setups).
203
+ 3. A Python interpreter that has ``pipx`` importable, scanned from
204
+ common locations (``/usr/bin/python3``, ``/usr/local/bin/python3``,
205
+ then ``shutil.which`` fallbacks).
206
+ 4. ``sys.executable`` as a last-resort fallback so we surface pipx's
207
+ own error instead of silently no-oping.
208
+ """
209
+ pipx_path = shutil.which("pipx")
210
+ if pipx_path:
211
+ return [pipx_path, "uninstall", "eggpool"]
212
+
213
+ override = env.get("EGGPOOL_PYTHON")
214
+ if override and Path(override).is_file():
215
+ return [override, "-m", "pipx", "uninstall", "eggpool"]
216
+
217
+ for candidate in _candidate_pythons():
218
+ if _python_has_pipx(candidate):
219
+ return [candidate, "-m", "pipx", "uninstall", "eggpool"]
220
+
221
+ return [sys.executable, "-m", "pipx", "uninstall", "eggpool"]
222
+
223
+
224
+ def _candidate_pythons() -> list[str]:
225
+ """Return Python interpreters worth probing for a ``pipx`` install."""
226
+ found: list[str] = []
227
+ seen: set[str] = set()
228
+ for raw in ("/usr/bin/python3", "/usr/local/bin/python3"):
229
+ if Path(raw).is_file() and raw not in seen:
230
+ found.append(raw)
231
+ seen.add(raw)
232
+ for name in ("python3", "python"):
233
+ resolved = shutil.which(name)
234
+ if resolved and resolved not in seen:
235
+ found.append(resolved)
236
+ seen.add(resolved)
237
+ return found
238
+
239
+
240
+ def _python_has_pipx(python: str) -> bool:
241
+ """Return True if ``python -m pipx --version`` exits 0."""
242
+ try:
243
+ proc = subprocess.run( # noqa: S603
244
+ [python, "-m", "pipx", "--version"],
245
+ capture_output=True,
246
+ text=True,
247
+ check=False,
248
+ timeout=5,
249
+ )
250
+ except (OSError, subprocess.SubprocessError):
251
+ return False
252
+ return proc.returncode == 0
253
+
254
+
191
255
  def _uv_tool_uninstall(env: dict[str, str]) -> subprocess.CompletedProcess[str]:
192
256
  """Run ``uv tool uninstall eggpool``."""
193
257
  cmd = ["uv", "tool", "uninstall", "eggpool"]
@@ -196,7 +196,7 @@ wheels = [
196
196
 
197
197
  [[package]]
198
198
  name = "eggpool"
199
- version = "0.2.0"
199
+ version = "0.2.1"
200
200
  source = { editable = "." }
201
201
  dependencies = [
202
202
  { name = "aiosqlite" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes