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.
- {omdev-0.0.0.dev290/omdev.egg-info → omdev-0.0.0.dev291}/PKG-INFO +2 -2
- omdev-0.0.0.dev291/README.md +31 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/clients.py +25 -5
- omdev-0.0.0.dev291/omdev/ci/github/api/v2/azure.py +185 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/client.py +37 -2
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/pkg.py +5 -2
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/ci.py +237 -7
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/pyproject.py +5 -2
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291/omdev.egg-info}/PKG-INFO +2 -2
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/SOURCES.txt +2 -1
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/requires.txt +1 -1
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/pyproject.toml +2 -2
- omdev-0.0.0.dev290/README.rst +0 -38
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/LICENSE +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/MANIFEST.in +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/.manifests.json +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/__about__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/gen.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/imports.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/main.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/manifests.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/resources.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/srcfiles.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/strip.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/types.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/amalg/typing.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/consts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/antlr/gen.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/contexts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/currents.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/fns.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/resolvers.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/storage.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/compute/types.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/actions.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/consts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/defaults.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/manifests.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cache/data/specs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cdeps.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cdeps.toml +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cc/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_boilerplate.cc +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/LICENSE +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/build_ext.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/ccompiler.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/options.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/compilers/unixccompiler.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/dir_util.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/errors.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/extension.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/file_util.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/modified.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/spawn.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/sysconfig.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/util.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/_distutils/version.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/build.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/cmake.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/importhook.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/magic.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cexts/scan.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/ci.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/compose.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/consts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/buildcaching.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cacheserved/manifests.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/cmds.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/dataserver.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/imagepulling.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/packing.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/repositories.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/docker/utils.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/api.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v1/client.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/api/v2/api.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/bootstrap.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/cache.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/env.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/github/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/requirements.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/shell.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ci/utils.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/_pathhack.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/clicli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/install.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/main.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/managers.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cli/types.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/clipboard.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/darwin_cf.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/clipboard/linux_x11.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/cmake.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/handlers.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/http.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/routes.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/server.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/dataserver/targets.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/magic.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/revisions.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/shallow.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/git/status.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/paths.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/secrets.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/home/shadow.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/imgur.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/default.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/inspect.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/base.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/running.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/standalone.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/providers/system.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/install.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/provider.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/pyenv/pyenv.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/resolvers.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/types.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/provider.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/uv/uv.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/interp/venvs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/find.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/magic.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/prepare.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/magic/styles.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/build.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/dumping.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/manifests/main.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/mypy/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/mypy/debug.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/building.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/compression.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/data.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/datarefs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/dataserver.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/loading.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/media.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/packing.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/repositories.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/pack/unpacking.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/repositories.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/oci/tars.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/marshal.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/names.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/requires.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/revisions.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/specifiers.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/versions.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/packaging/wheelfile.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pip.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/base.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/git.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/lite.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/main.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/manifests.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/precheck/scripts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/apps/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/ptk/apps/ncdu.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/attrdocs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/bracepy.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/classdot.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/findimports.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/bumpversion.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/execstat.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/scripts/importtrace.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/srcheaders.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/importscan.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/py/tools/mkrelimp.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/cexts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/configs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/inject.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/reqs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/docker-dev.sh +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/resources/python.sh +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/pyproject/venvs.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/interp.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/slowcat.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/scripts/tmpexec.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tagstrings.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/all.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/tokenizert.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tokens/utils.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/cloc.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/diff.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/doc.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/docker.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/consts.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/git/messages.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/intellij.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/cli.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/formats.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/io.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/parsing.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/processing.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/json/rendering.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/linehisto.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/mkenv.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/notebook.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/__init__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/__main__.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pawk/pawk.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/pip.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/prof.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/qr.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/shadow.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev/tools/sqlrepl.py +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/dependency_links.txt +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/entry_points.txt +0 -0
- {omdev-0.0.0.dev290 → omdev-0.0.0.dev291}/omdev.egg-info/top_level.txt +0 -0
- {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.
|
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.
|
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
|
-
|
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
|
-
) ->
|
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
|
-
|
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
|
-
|
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
|
-
|
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=
|
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
|
-
|
179
|
-
|
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
|
-
|
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
|
-
) ->
|
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
|
-
|
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
|
-
|
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
|
-
|
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=
|
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
|
-
|
7976
|
-
|
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:
|