omdev 0.0.0.dev290__tar.gz → 0.0.0.dev291__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 (280) hide show
  1. {omdev-0.0.0.dev290/omdev.egg-info → omdev-0.0.0.dev291}/PKG-INFO +2 -2
  2. omdev-0.0.0.dev291/README.md +31 -0
  3. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/clients.py +25 -5
  4. omdev-0.0.0.dev291/omdev/ci/github/api/v2/azure.py +185 -0
  5. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/client.py +37 -2
  6. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/pkg.py +5 -2
  7. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/ci.py +237 -7
  8. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/pyproject.py +5 -2
  9. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291/omdev.egg-info}/PKG-INFO +2 -2
  10. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/SOURCES.txt +2 -1
  11. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/requires.txt +1 -1
  12. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/pyproject.toml +2 -2
  13. omdev-0.0.0.dev290/README.rst +0 -38
  14. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/LICENSE +0 -0
  15. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/MANIFEST.in +0 -0
  16. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/.manifests.json +0 -0
  17. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/__about__.py +0 -0
  18. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/__init__.py +0 -0
  19. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/__init__.py +0 -0
  20. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/__main__.py +0 -0
  21. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/gen.py +0 -0
  22. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/imports.py +0 -0
  23. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/main.py +0 -0
  24. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/manifests.py +0 -0
  25. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/resources.py +0 -0
  26. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/srcfiles.py +0 -0
  27. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/strip.py +0 -0
  28. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/types.py +0 -0
  29. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/typing.py +0 -0
  30. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/__init__.py +0 -0
  31. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/consts.py +0 -0
  32. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/gen.py +0 -0
  33. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/__init__.py +0 -0
  34. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/__init__.py +0 -0
  35. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/cache.py +0 -0
  36. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/contexts.py +0 -0
  37. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/currents.py +0 -0
  38. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/fns.py +0 -0
  39. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/resolvers.py +0 -0
  40. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/storage.py +0 -0
  41. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/types.py +0 -0
  42. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/__init__.py +0 -0
  43. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/actions.py +0 -0
  44. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/cache.py +0 -0
  45. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/consts.py +0 -0
  46. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/defaults.py +0 -0
  47. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/manifests.py +0 -0
  48. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/specs.py +0 -0
  49. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/__init__.py +0 -0
  50. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/__main__.py +0 -0
  51. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cdeps.py +0 -0
  52. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cdeps.toml +0 -0
  53. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cli.py +0 -0
  54. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/__init__.py +0 -0
  55. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_boilerplate.cc +0 -0
  56. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/LICENSE +0 -0
  57. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/__init__.py +0 -0
  58. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/build_ext.py +0 -0
  59. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/__init__.py +0 -0
  60. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/ccompiler.py +0 -0
  61. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/options.py +0 -0
  62. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/unixccompiler.py +0 -0
  63. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/dir_util.py +0 -0
  64. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/errors.py +0 -0
  65. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/extension.py +0 -0
  66. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/file_util.py +0 -0
  67. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/modified.py +0 -0
  68. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/spawn.py +0 -0
  69. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/sysconfig.py +0 -0
  70. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/util.py +0 -0
  71. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/version.py +0 -0
  72. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/build.py +0 -0
  73. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/cmake.py +0 -0
  74. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/importhook.py +0 -0
  75. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/magic.py +0 -0
  76. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/scan.py +0 -0
  77. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/__init__.py +0 -0
  78. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/__main__.py +0 -0
  79. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/cache.py +0 -0
  80. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/ci.py +0 -0
  81. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/cli.py +0 -0
  82. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/compose.py +0 -0
  83. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/consts.py +0 -0
  84. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/__init__.py +0 -0
  85. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/buildcaching.py +0 -0
  86. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cache.py +0 -0
  87. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/__init__.py +0 -0
  88. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/cache.py +0 -0
  89. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/manifests.py +0 -0
  90. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cmds.py +0 -0
  91. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/dataserver.py +0 -0
  92. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/imagepulling.py +0 -0
  93. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/inject.py +0 -0
  94. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/packing.py +0 -0
  95. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/repositories.py +0 -0
  96. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/utils.py +0 -0
  97. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/__init__.py +0 -0
  98. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/__init__.py +0 -0
  99. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/__init__.py +0 -0
  100. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/api.py +0 -0
  101. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/client.py +0 -0
  102. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/__init__.py +0 -0
  103. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/api.py +0 -0
  104. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/bootstrap.py +0 -0
  105. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/cache.py +0 -0
  106. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/cli.py +0 -0
  107. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/env.py +0 -0
  108. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/inject.py +0 -0
  109. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/inject.py +0 -0
  110. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/requirements.py +0 -0
  111. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/shell.py +0 -0
  112. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/utils.py +0 -0
  113. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/__init__.py +0 -0
  114. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/__main__.py +0 -0
  115. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/_pathhack.py +0 -0
  116. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/clicli.py +0 -0
  117. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/install.py +0 -0
  118. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/main.py +0 -0
  119. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/managers.py +0 -0
  120. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/types.py +0 -0
  121. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/__init__.py +0 -0
  122. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/clipboard.py +0 -0
  123. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/darwin_cf.py +0 -0
  124. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/linux_x11.py +0 -0
  125. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cmake.py +0 -0
  126. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/__init__.py +0 -0
  127. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/handlers.py +0 -0
  128. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/http.py +0 -0
  129. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/routes.py +0 -0
  130. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/server.py +0 -0
  131. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/targets.py +0 -0
  132. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/__init__.py +0 -0
  133. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/magic.py +0 -0
  134. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/revisions.py +0 -0
  135. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/shallow.py +0 -0
  136. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/status.py +0 -0
  137. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/__init__.py +0 -0
  138. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/paths.py +0 -0
  139. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/secrets.py +0 -0
  140. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/shadow.py +0 -0
  141. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/imgur.py +0 -0
  142. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/__init__.py +0 -0
  143. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/__main__.py +0 -0
  144. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/cli.py +0 -0
  145. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/default.py +0 -0
  146. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/inject.py +0 -0
  147. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/inspect.py +0 -0
  148. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/__init__.py +0 -0
  149. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/base.py +0 -0
  150. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/inject.py +0 -0
  151. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/running.py +0 -0
  152. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/standalone.py +0 -0
  153. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/system.py +0 -0
  154. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/__init__.py +0 -0
  155. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/inject.py +0 -0
  156. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/install.py +0 -0
  157. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/provider.py +0 -0
  158. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/pyenv.py +0 -0
  159. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/resolvers.py +0 -0
  160. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/types.py +0 -0
  161. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/__init__.py +0 -0
  162. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/inject.py +0 -0
  163. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/provider.py +0 -0
  164. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/uv.py +0 -0
  165. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/venvs.py +0 -0
  166. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/__init__.py +0 -0
  167. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/__main__.py +0 -0
  168. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/cli.py +0 -0
  169. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/find.py +0 -0
  170. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/magic.py +0 -0
  171. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/prepare.py +0 -0
  172. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/styles.py +0 -0
  173. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/__init__.py +0 -0
  174. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/__main__.py +0 -0
  175. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/build.py +0 -0
  176. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/dumping.py +0 -0
  177. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/main.py +0 -0
  178. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/mypy/__init__.py +0 -0
  179. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/mypy/debug.py +0 -0
  180. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/__init__.py +0 -0
  181. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/building.py +0 -0
  182. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/compression.py +0 -0
  183. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/data.py +0 -0
  184. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/datarefs.py +0 -0
  185. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/dataserver.py +0 -0
  186. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/loading.py +0 -0
  187. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/media.py +0 -0
  188. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/__init__.py +0 -0
  189. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/packing.py +0 -0
  190. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/repositories.py +0 -0
  191. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/unpacking.py +0 -0
  192. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/repositories.py +0 -0
  193. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/tars.py +0 -0
  194. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/__init__.py +0 -0
  195. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/marshal.py +0 -0
  196. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/names.py +0 -0
  197. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/requires.py +0 -0
  198. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/revisions.py +0 -0
  199. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/specifiers.py +0 -0
  200. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/versions.py +0 -0
  201. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/wheelfile.py +0 -0
  202. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pip.py +0 -0
  203. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/__init__.py +0 -0
  204. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/__main__.py +0 -0
  205. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/base.py +0 -0
  206. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/git.py +0 -0
  207. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/lite.py +0 -0
  208. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/main.py +0 -0
  209. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/manifests.py +0 -0
  210. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/scripts.py +0 -0
  211. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/__init__.py +0 -0
  212. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/apps/__init__.py +0 -0
  213. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/apps/ncdu.py +0 -0
  214. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/__init__.py +0 -0
  215. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/attrdocs.py +0 -0
  216. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/bracepy.py +0 -0
  217. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/classdot.py +0 -0
  218. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/findimports.py +0 -0
  219. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/__init__.py +0 -0
  220. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/bumpversion.py +0 -0
  221. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/execstat.py +0 -0
  222. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/importtrace.py +0 -0
  223. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/srcheaders.py +0 -0
  224. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/__init__.py +0 -0
  225. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/importscan.py +0 -0
  226. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/mkrelimp.py +0 -0
  227. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/__init__.py +0 -0
  228. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/__main__.py +0 -0
  229. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/cexts.py +0 -0
  230. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/cli.py +0 -0
  231. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/configs.py +0 -0
  232. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/inject.py +0 -0
  233. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/reqs.py +0 -0
  234. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/__init__.py +0 -0
  235. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/docker-dev.sh +0 -0
  236. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/python.sh +0 -0
  237. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/venvs.py +0 -0
  238. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/__init__.py +0 -0
  239. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/interp.py +0 -0
  240. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/slowcat.py +0 -0
  241. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/tmpexec.py +0 -0
  242. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tagstrings.py +0 -0
  243. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/__init__.py +0 -0
  244. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/all.py +0 -0
  245. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/tokenizert.py +0 -0
  246. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/utils.py +0 -0
  247. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/__init__.py +0 -0
  248. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/cloc.py +0 -0
  249. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/diff.py +0 -0
  250. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/doc.py +0 -0
  251. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/docker.py +0 -0
  252. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/__init__.py +0 -0
  253. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/__main__.py +0 -0
  254. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/cli.py +0 -0
  255. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/consts.py +0 -0
  256. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/messages.py +0 -0
  257. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/intellij.py +0 -0
  258. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/__init__.py +0 -0
  259. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/__main__.py +0 -0
  260. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/cli.py +0 -0
  261. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/formats.py +0 -0
  262. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/io.py +0 -0
  263. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/parsing.py +0 -0
  264. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/processing.py +0 -0
  265. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/rendering.py +0 -0
  266. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/linehisto.py +0 -0
  267. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/mkenv.py +0 -0
  268. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/notebook.py +0 -0
  269. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/__init__.py +0 -0
  270. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/__main__.py +0 -0
  271. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/pawk.py +0 -0
  272. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pip.py +0 -0
  273. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/prof.py +0 -0
  274. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/qr.py +0 -0
  275. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/shadow.py +0 -0
  276. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/sqlrepl.py +0 -0
  277. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/dependency_links.txt +0 -0
  278. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/entry_points.txt +0 -0
  279. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/top_level.txt +0 -0
  280. {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omdev
3
- Version: 0.0.0.dev290
3
+ Version: 0.0.0.dev291
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev290
15
+ Requires-Dist: omlish==0.0.0.dev291
16
16
  Provides-Extra: all
17
17
  Requires-Dist: black~=25.1; extra == "all"
18
18
  Requires-Dist: pycparser~=2.22; extra == "all"
@@ -0,0 +1,31 @@
1
+ It's like my previous python monorepo-ey thing [`omnibus`](https://github.com/wrmsr/omnibus/tree/wrmsr_exp_split)... ish.
2
+
3
+ Core packages begin with `om`, scratch app is in `app`, temp / dump code is in `x`.
4
+
5
+ ----
6
+
7
+ The core packages are:
8
+
9
+ - **omlish**: core foundational code
10
+ - **omdev**: development utilities
11
+ - **omserv**: production web server
12
+ - **ominfra**: infrastructure and cloud code
13
+ - **ommlx**: ml / ai code
14
+
15
+ ----
16
+
17
+ Core packages installable from pypi, or from git via:
18
+
19
+ ```bash
20
+ pip install 'git+https://github.com/wrmsr/omlish@master#subdirectory=.pkg/<pkg>'
21
+ ```
22
+
23
+ Core packages have no required dependencies, but numerous optional ones - see their respective `pyproject.toml` files for details.
24
+
25
+ The CLI is installable through uvx or pipx via:
26
+
27
+ ```bash
28
+ curl -LsSf 'https://raw.githubusercontent.com/wrmsr/omlish/master/omdev/cli/install.py' | python3 -
29
+ ```
30
+
31
+ Additional deps to be injected may be appended to the command.
@@ -430,7 +430,7 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
430
430
  ):
431
431
  await self._upload_file_chunk_(chunk)
432
432
 
433
- async def _upload_file_chunks(
433
+ def _generate_file_upload_chunks(
434
434
  self,
435
435
  *,
436
436
  in_file: str,
@@ -438,7 +438,7 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
438
438
  key: str,
439
439
 
440
440
  file_size: ta.Optional[int] = None,
441
- ) -> None:
441
+ ) -> ta.List[_UploadChunk]:
442
442
  check.state(os.path.isfile(in_file))
443
443
 
444
444
  if file_size is None:
@@ -446,17 +446,37 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
446
446
 
447
447
  #
448
448
 
449
- upload_tasks = []
449
+ upload_chunks: ta.List[BaseGithubCacheClient._UploadChunk] = []
450
450
  chunk_size = self._chunk_size
451
451
  for i in range((file_size // chunk_size) + (1 if file_size % chunk_size else 0)):
452
452
  offset = i * chunk_size
453
453
  size = min(chunk_size, file_size - offset)
454
- upload_tasks.append(self._upload_file_chunk(self._UploadChunk(
454
+ upload_chunks.append(self._UploadChunk(
455
455
  url=url,
456
456
  key=key,
457
457
  in_file=in_file,
458
458
  offset=offset,
459
459
  size=size,
460
- )))
460
+ ))
461
+
462
+ return upload_chunks
463
+
464
+ async def _upload_file_chunks(
465
+ self,
466
+ *,
467
+ in_file: str,
468
+ url: str,
469
+ key: str,
470
+
471
+ file_size: ta.Optional[int] = None,
472
+ ) -> None:
473
+ upload_tasks = []
474
+ for chunk in self._generate_file_upload_chunks(
475
+ in_file=in_file,
476
+ url=url,
477
+ key=key,
478
+ file_size=file_size,
479
+ ):
480
+ upload_tasks.append(self._upload_file_chunk(chunk))
461
481
 
462
482
  await asyncio_wait_concurrent(upload_tasks, self._concurrency)
@@ -0,0 +1,185 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ TODO:
5
+ - ominfra? no, circdep
6
+ """
7
+ import base64
8
+ import dataclasses as dc
9
+ import datetime
10
+ import typing as ta
11
+ import urllib.parse
12
+ import xml.etree.ElementTree as ET
13
+
14
+ from omlish.asyncs.asyncio.utils import asyncio_wait_concurrent
15
+ from omlish.lite.check import check
16
+ from omlish.lite.logs import log
17
+ from omlish.lite.timing import log_timing_context
18
+
19
+
20
+ ##
21
+
22
+
23
+ class AzureBlockBlobUploader:
24
+ """
25
+ https://learn.microsoft.com/en-us/rest/api/storageservices/put-block
26
+ https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list
27
+ """
28
+
29
+ DEFAULT_CONCURRENCY = 4
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class Request:
33
+ method: str
34
+ url: str
35
+ headers: ta.Optional[ta.Dict[str, str]] = None
36
+ body: ta.Optional[bytes] = None
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class Response:
40
+ status: int
41
+ headers: ta.Optional[ta.Mapping[str, str]] = None
42
+ data: ta.Optional[bytes] = None
43
+
44
+ def get_header(self, name: str) -> ta.Optional[str]:
45
+ for k, v in (self.headers or {}).items():
46
+ if k.lower() == name.lower():
47
+ return v
48
+ return None
49
+
50
+ def __init__(
51
+ self,
52
+ blob_url_with_sas: str,
53
+ make_request: ta.Callable[[Request], ta.Awaitable[Response]],
54
+ *,
55
+ api_version: str = '2020-10-02',
56
+ concurrency: int = DEFAULT_CONCURRENCY,
57
+ ) -> None:
58
+ """
59
+ blob_url_with_sas should be of the form:
60
+ https://<account>.blob.core.windows.net/<container>/<blob>?<SAS-token>
61
+ """
62
+
63
+ super().__init__()
64
+
65
+ self._make_request = make_request
66
+ self._api_version = api_version
67
+ check.arg(concurrency >= 1)
68
+ self._concurrency = concurrency
69
+
70
+ parsed = urllib.parse.urlparse(blob_url_with_sas)
71
+ self._base_url = f'{parsed.scheme}://{parsed.netloc}'
72
+ parts = parsed.path.lstrip('/').split('/', 1)
73
+ self._container = parts[0]
74
+ self._blob_name = parts[1]
75
+ self._sas = parsed.query
76
+
77
+ def _headers(self) -> ta.Dict[str, str]:
78
+ """Standard headers for Azure Blob REST calls."""
79
+
80
+ now = datetime.datetime.now(datetime.UTC).strftime('%a, %d %b %Y %H:%M:%S GMT')
81
+ return {
82
+ 'x-ms-date': now,
83
+ 'x-ms-version': self._api_version,
84
+ }
85
+
86
+ @dc.dataclass(frozen=True)
87
+ class FileChunk:
88
+ in_file: str
89
+ offset: int
90
+ size: int
91
+
92
+ async def _upload_file_chunk_(
93
+ self,
94
+ block_id: str,
95
+ chunk: FileChunk,
96
+ ) -> None:
97
+ with open(chunk.in_file, 'rb') as f: # noqa
98
+ f.seek(chunk.offset)
99
+ data = f.read(chunk.size)
100
+
101
+ check.equal(len(data), chunk.size)
102
+
103
+ params = {
104
+ 'comp': 'block',
105
+ 'blockid': block_id,
106
+ }
107
+ query = self._sas + '&' + urllib.parse.urlencode(params)
108
+ url = f'{self._base_url}/{self._container}/{self._blob_name}?{query}'
109
+
110
+ log.debug(f'Uploading azure blob chunk {chunk} with block id {block_id}') # noqa
111
+
112
+ resp = await self._make_request(self.Request(
113
+ 'PUT',
114
+ url,
115
+ headers=self._headers(),
116
+ body=data,
117
+ ))
118
+ if resp.status not in (201, 202):
119
+ raise RuntimeError(f'Put Block failed: {block_id=} {resp.status=}')
120
+
121
+ async def _upload_file_chunk(
122
+ self,
123
+ block_id: str,
124
+ chunk: FileChunk,
125
+ ) -> None:
126
+ with log_timing_context(f'Uploading azure blob chunk {chunk} with block id {block_id}'):
127
+ await self._upload_file_chunk_(
128
+ block_id,
129
+ chunk,
130
+ )
131
+
132
+ async def upload_file(
133
+ self,
134
+ chunks: ta.List[FileChunk],
135
+ ) -> ta.Dict[str, ta.Any]:
136
+ block_ids = []
137
+
138
+ # 1) Stage each block
139
+ upload_tasks = []
140
+ for idx, chunk in enumerate(chunks):
141
+ # Generate a predictable block ID (must be URL-safe base64)
142
+ raw_id = f'{idx:08d}'.encode()
143
+ block_id = base64.b64encode(raw_id).decode('utf-8')
144
+ block_ids.append(block_id)
145
+
146
+ upload_tasks.append(self._upload_file_chunk(
147
+ block_id,
148
+ chunk,
149
+ ))
150
+
151
+ await asyncio_wait_concurrent(upload_tasks, self._concurrency)
152
+
153
+ # 2) Commit block list
154
+ root = ET.Element('BlockList')
155
+ for bid in block_ids:
156
+ elm = ET.SubElement(root, 'Latest')
157
+ elm.text = bid
158
+ body = ET.tostring(root, encoding='utf-8', method='xml')
159
+
160
+ params = {'comp': 'blocklist'}
161
+ query = self._sas + '&' + urllib.parse.urlencode(params)
162
+ url = f'{self._base_url}/{self._container}/{self._blob_name}?{query}'
163
+
164
+ log.debug(f'Putting azure blob chunk list block ids {block_ids}') # noqa
165
+
166
+ resp = await self._make_request(self.Request(
167
+ 'PUT',
168
+ url,
169
+ headers={
170
+ **self._headers(),
171
+ 'Content-Type': 'application/xml',
172
+ },
173
+ body=body,
174
+ ))
175
+ if resp.status not in (200, 201):
176
+ raise RuntimeError(f'Put Block List failed: {resp.status} {resp.data!r}')
177
+
178
+ ret = {
179
+ 'status_code': resp.status,
180
+ 'etag': resp.get_header('ETag'),
181
+ }
182
+
183
+ log.debug(f'Uploaded azure blob chunk {ret}') # noqa
184
+
185
+ return ret
@@ -2,6 +2,7 @@
2
2
  import dataclasses as dc
3
3
  import os
4
4
  import typing as ta
5
+ import urllib.request
5
6
 
6
7
  from omlish.lite.check import check
7
8
  from omlish.lite.logs import log
@@ -13,6 +14,7 @@ from ..clients import GithubCacheClient
13
14
  from .api import GithubCacheServiceV2
14
15
  from .api import GithubCacheServiceV2RequestT
15
16
  from .api import GithubCacheServiceV2ResponseT
17
+ from .azure import AzureBlockBlobUploader
16
18
 
17
19
 
18
20
  ##
@@ -137,19 +139,52 @@ class GithubCacheServiceV2Client(BaseGithubCacheClient):
137
139
 
138
140
  #
139
141
 
140
- await self._upload_file_chunks(
142
+ upload_chunks = self._generate_file_upload_chunks(
141
143
  in_file=in_file,
142
144
  url=reserve_resp.signed_upload_url,
143
145
  key=fixed_key,
144
146
  file_size=file_size,
145
147
  )
146
148
 
149
+ az_chunks = [
150
+ AzureBlockBlobUploader.FileChunk(
151
+ in_file=in_file,
152
+ offset=c.offset,
153
+ size=c.size,
154
+ )
155
+ for c in upload_chunks
156
+ ]
157
+
158
+ async def az_make_request(req: AzureBlockBlobUploader.Request) -> AzureBlockBlobUploader.Response:
159
+ u_req = urllib.request.Request( # noqa
160
+ req.url,
161
+ method=req.method,
162
+ headers=req.headers or {},
163
+ data=req.body,
164
+ )
165
+
166
+ u_resp, u_body = await self._send_urllib_request(u_req)
167
+
168
+ return AzureBlockBlobUploader.Response(
169
+ status=u_resp.status,
170
+ headers=dict(u_resp.headers),
171
+ data=u_body,
172
+ )
173
+
174
+ az_uploader = AzureBlockBlobUploader(
175
+ reserve_resp.signed_upload_url,
176
+ az_make_request,
177
+ concurrency=self._concurrency,
178
+ )
179
+
180
+ await az_uploader.upload_file(az_chunks)
181
+
147
182
  #
148
183
 
149
184
  commit_resp = check.not_none(await self._send_method_request(
150
185
  GithubCacheServiceV2.FINALIZE_CACHE_ENTRY_METHOD, # type: ignore[arg-type]
151
186
  GithubCacheServiceV2.FinalizeCacheEntryUploadRequest(
152
- key=key,
187
+ key=fixed_key,
153
188
  size_bytes=file_size,
154
189
  version=version,
155
190
  ),
@@ -175,8 +175,11 @@ class BasePyprojectPackageGenerator(abc.ABC):
175
175
  #
176
176
 
177
177
  _STANDARD_FILES: ta.Sequence[str] = [
178
- 'LICENSE',
179
- 'README.rst',
178
+ *[
179
+ ''.join([n, x])
180
+ for n in ('LICENSE', 'README')
181
+ for x in ('', '.txt', '.md', '.rst')
182
+ ],
180
183
  ]
181
184
 
182
185
  def _symlink_standard_files(self) -> None:
@@ -71,6 +71,7 @@ import urllib.parse
71
71
  import urllib.request
72
72
  import uuid
73
73
  import weakref
74
+ import xml.etree.ElementTree as ET
74
75
 
75
76
 
76
77
  ########################################
@@ -6478,7 +6479,7 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
6478
6479
  ):
6479
6480
  await self._upload_file_chunk_(chunk)
6480
6481
 
6481
- async def _upload_file_chunks(
6482
+ def _generate_file_upload_chunks(
6482
6483
  self,
6483
6484
  *,
6484
6485
  in_file: str,
@@ -6486,7 +6487,7 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
6486
6487
  key: str,
6487
6488
 
6488
6489
  file_size: ta.Optional[int] = None,
6489
- ) -> None:
6490
+ ) -> ta.List[_UploadChunk]:
6490
6491
  check.state(os.path.isfile(in_file))
6491
6492
 
6492
6493
  if file_size is None:
@@ -6494,22 +6495,218 @@ class BaseGithubCacheClient(GithubCacheClient, abc.ABC):
6494
6495
 
6495
6496
  #
6496
6497
 
6497
- upload_tasks = []
6498
+ upload_chunks: ta.List[BaseGithubCacheClient._UploadChunk] = []
6498
6499
  chunk_size = self._chunk_size
6499
6500
  for i in range((file_size // chunk_size) + (1 if file_size % chunk_size else 0)):
6500
6501
  offset = i * chunk_size
6501
6502
  size = min(chunk_size, file_size - offset)
6502
- upload_tasks.append(self._upload_file_chunk(self._UploadChunk(
6503
+ upload_chunks.append(self._UploadChunk(
6503
6504
  url=url,
6504
6505
  key=key,
6505
6506
  in_file=in_file,
6506
6507
  offset=offset,
6507
6508
  size=size,
6508
- )))
6509
+ ))
6510
+
6511
+ return upload_chunks
6512
+
6513
+ async def _upload_file_chunks(
6514
+ self,
6515
+ *,
6516
+ in_file: str,
6517
+ url: str,
6518
+ key: str,
6519
+
6520
+ file_size: ta.Optional[int] = None,
6521
+ ) -> None:
6522
+ upload_tasks = []
6523
+ for chunk in self._generate_file_upload_chunks(
6524
+ in_file=in_file,
6525
+ url=url,
6526
+ key=key,
6527
+ file_size=file_size,
6528
+ ):
6529
+ upload_tasks.append(self._upload_file_chunk(chunk))
6509
6530
 
6510
6531
  await asyncio_wait_concurrent(upload_tasks, self._concurrency)
6511
6532
 
6512
6533
 
6534
+ ########################################
6535
+ # ../github/api/v2/azure.py
6536
+ """
6537
+ TODO:
6538
+ - ominfra? no, circdep
6539
+ """
6540
+
6541
+
6542
+ ##
6543
+
6544
+
6545
+ class AzureBlockBlobUploader:
6546
+ """
6547
+ https://learn.microsoft.com/en-us/rest/api/storageservices/put-block
6548
+ https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list
6549
+ """
6550
+
6551
+ DEFAULT_CONCURRENCY = 4
6552
+
6553
+ @dc.dataclass(frozen=True)
6554
+ class Request:
6555
+ method: str
6556
+ url: str
6557
+ headers: ta.Optional[ta.Dict[str, str]] = None
6558
+ body: ta.Optional[bytes] = None
6559
+
6560
+ @dc.dataclass(frozen=True)
6561
+ class Response:
6562
+ status: int
6563
+ headers: ta.Optional[ta.Mapping[str, str]] = None
6564
+ data: ta.Optional[bytes] = None
6565
+
6566
+ def get_header(self, name: str) -> ta.Optional[str]:
6567
+ for k, v in (self.headers or {}).items():
6568
+ if k.lower() == name.lower():
6569
+ return v
6570
+ return None
6571
+
6572
+ def __init__(
6573
+ self,
6574
+ blob_url_with_sas: str,
6575
+ make_request: ta.Callable[[Request], ta.Awaitable[Response]],
6576
+ *,
6577
+ api_version: str = '2020-10-02',
6578
+ concurrency: int = DEFAULT_CONCURRENCY,
6579
+ ) -> None:
6580
+ """
6581
+ blob_url_with_sas should be of the form:
6582
+ https://<account>.blob.core.windows.net/<container>/<blob>?<SAS-token>
6583
+ """
6584
+
6585
+ super().__init__()
6586
+
6587
+ self._make_request = make_request
6588
+ self._api_version = api_version
6589
+ check.arg(concurrency >= 1)
6590
+ self._concurrency = concurrency
6591
+
6592
+ parsed = urllib.parse.urlparse(blob_url_with_sas)
6593
+ self._base_url = f'{parsed.scheme}://{parsed.netloc}'
6594
+ parts = parsed.path.lstrip('/').split('/', 1)
6595
+ self._container = parts[0]
6596
+ self._blob_name = parts[1]
6597
+ self._sas = parsed.query
6598
+
6599
+ def _headers(self) -> ta.Dict[str, str]:
6600
+ """Standard headers for Azure Blob REST calls."""
6601
+
6602
+ now = datetime.datetime.now(datetime.UTC).strftime('%a, %d %b %Y %H:%M:%S GMT')
6603
+ return {
6604
+ 'x-ms-date': now,
6605
+ 'x-ms-version': self._api_version,
6606
+ }
6607
+
6608
+ @dc.dataclass(frozen=True)
6609
+ class FileChunk:
6610
+ in_file: str
6611
+ offset: int
6612
+ size: int
6613
+
6614
+ async def _upload_file_chunk_(
6615
+ self,
6616
+ block_id: str,
6617
+ chunk: FileChunk,
6618
+ ) -> None:
6619
+ with open(chunk.in_file, 'rb') as f: # noqa
6620
+ f.seek(chunk.offset)
6621
+ data = f.read(chunk.size)
6622
+
6623
+ check.equal(len(data), chunk.size)
6624
+
6625
+ params = {
6626
+ 'comp': 'block',
6627
+ 'blockid': block_id,
6628
+ }
6629
+ query = self._sas + '&' + urllib.parse.urlencode(params)
6630
+ url = f'{self._base_url}/{self._container}/{self._blob_name}?{query}'
6631
+
6632
+ log.debug(f'Uploading azure blob chunk {chunk} with block id {block_id}') # noqa
6633
+
6634
+ resp = await self._make_request(self.Request(
6635
+ 'PUT',
6636
+ url,
6637
+ headers=self._headers(),
6638
+ body=data,
6639
+ ))
6640
+ if resp.status not in (201, 202):
6641
+ raise RuntimeError(f'Put Block failed: {block_id=} {resp.status=}')
6642
+
6643
+ async def _upload_file_chunk(
6644
+ self,
6645
+ block_id: str,
6646
+ chunk: FileChunk,
6647
+ ) -> None:
6648
+ with log_timing_context(f'Uploading azure blob chunk {chunk} with block id {block_id}'):
6649
+ await self._upload_file_chunk_(
6650
+ block_id,
6651
+ chunk,
6652
+ )
6653
+
6654
+ async def upload_file(
6655
+ self,
6656
+ chunks: ta.List[FileChunk],
6657
+ ) -> ta.Dict[str, ta.Any]:
6658
+ block_ids = []
6659
+
6660
+ # 1) Stage each block
6661
+ upload_tasks = []
6662
+ for idx, chunk in enumerate(chunks):
6663
+ # Generate a predictable block ID (must be URL-safe base64)
6664
+ raw_id = f'{idx:08d}'.encode()
6665
+ block_id = base64.b64encode(raw_id).decode('utf-8')
6666
+ block_ids.append(block_id)
6667
+
6668
+ upload_tasks.append(self._upload_file_chunk(
6669
+ block_id,
6670
+ chunk,
6671
+ ))
6672
+
6673
+ await asyncio_wait_concurrent(upload_tasks, self._concurrency)
6674
+
6675
+ # 2) Commit block list
6676
+ root = ET.Element('BlockList')
6677
+ for bid in block_ids:
6678
+ elm = ET.SubElement(root, 'Latest')
6679
+ elm.text = bid
6680
+ body = ET.tostring(root, encoding='utf-8', method='xml')
6681
+
6682
+ params = {'comp': 'blocklist'}
6683
+ query = self._sas + '&' + urllib.parse.urlencode(params)
6684
+ url = f'{self._base_url}/{self._container}/{self._blob_name}?{query}'
6685
+
6686
+ log.debug(f'Putting azure blob chunk list block ids {block_ids}') # noqa
6687
+
6688
+ resp = await self._make_request(self.Request(
6689
+ 'PUT',
6690
+ url,
6691
+ headers={
6692
+ **self._headers(),
6693
+ 'Content-Type': 'application/xml',
6694
+ },
6695
+ body=body,
6696
+ ))
6697
+ if resp.status not in (200, 201):
6698
+ raise RuntimeError(f'Put Block List failed: {resp.status} {resp.data!r}')
6699
+
6700
+ ret = {
6701
+ 'status_code': resp.status,
6702
+ 'etag': resp.get_header('ETag'),
6703
+ }
6704
+
6705
+ log.debug(f'Uploaded azure blob chunk {ret}') # noqa
6706
+
6707
+ return ret
6708
+
6709
+
6513
6710
  ########################################
6514
6711
  # ../../dataserver/targets.py
6515
6712
 
@@ -8043,19 +8240,52 @@ class GithubCacheServiceV2Client(BaseGithubCacheClient):
8043
8240
 
8044
8241
  #
8045
8242
 
8046
- await self._upload_file_chunks(
8243
+ upload_chunks = self._generate_file_upload_chunks(
8047
8244
  in_file=in_file,
8048
8245
  url=reserve_resp.signed_upload_url,
8049
8246
  key=fixed_key,
8050
8247
  file_size=file_size,
8051
8248
  )
8052
8249
 
8250
+ az_chunks = [
8251
+ AzureBlockBlobUploader.FileChunk(
8252
+ in_file=in_file,
8253
+ offset=c.offset,
8254
+ size=c.size,
8255
+ )
8256
+ for c in upload_chunks
8257
+ ]
8258
+
8259
+ async def az_make_request(req: AzureBlockBlobUploader.Request) -> AzureBlockBlobUploader.Response:
8260
+ u_req = urllib.request.Request( # noqa
8261
+ req.url,
8262
+ method=req.method,
8263
+ headers=req.headers or {},
8264
+ data=req.body,
8265
+ )
8266
+
8267
+ u_resp, u_body = await self._send_urllib_request(u_req)
8268
+
8269
+ return AzureBlockBlobUploader.Response(
8270
+ status=u_resp.status,
8271
+ headers=dict(u_resp.headers),
8272
+ data=u_body,
8273
+ )
8274
+
8275
+ az_uploader = AzureBlockBlobUploader(
8276
+ reserve_resp.signed_upload_url,
8277
+ az_make_request,
8278
+ concurrency=self._concurrency,
8279
+ )
8280
+
8281
+ await az_uploader.upload_file(az_chunks)
8282
+
8053
8283
  #
8054
8284
 
8055
8285
  commit_resp = check.not_none(await self._send_method_request(
8056
8286
  GithubCacheServiceV2.FINALIZE_CACHE_ENTRY_METHOD, # type: ignore[arg-type]
8057
8287
  GithubCacheServiceV2.FinalizeCacheEntryUploadRequest(
8058
- key=key,
8288
+ key=fixed_key,
8059
8289
  size_bytes=file_size,
8060
8290
  version=version,
8061
8291
  ),
@@ -7972,8 +7972,11 @@ class BasePyprojectPackageGenerator(abc.ABC):
7972
7972
  #
7973
7973
 
7974
7974
  _STANDARD_FILES: ta.Sequence[str] = [
7975
- 'LICENSE',
7976
- 'README.rst',
7975
+ *[
7976
+ ''.join([n, x])
7977
+ for n in ('LICENSE', 'README')
7978
+ for x in ('', '.txt', '.md', '.rst')
7979
+ ],
7977
7980
  ]
7978
7981
 
7979
7982
  def _symlink_standard_files(self) -> None: