Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 36 additions & 33 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,67 +1,70 @@
name: Build and Publish Universal Wheel
name: Build and Publish

on:
push:
tags:
- 'v*' # Trigger on version tags
branches:
- main
- develop
pull_request:
types:
- closed
- main # Build on main for testing
pull_request: # Build on PRs for testing

jobs:
build_wheels:
name: Build universal wheel
build:
name: Build wheel
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: '3.11'

- name: Install build dependencies
run: python -m pip install --upgrade pip build

- name: Build wheel
run: |
python -m pip install wheel # Ensure wheel is installed
python setup.py bdist_wheel --universal
mkdir -p wheelhouse
mv dist/*.whl wheelhouse/
run: python -m build --wheel

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: wheels
path: ./wheelhouse/*.whl
name: dist
path: ./dist/*.whl

upload_wheels:
needs: build_wheels
publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')

steps:
- name: Download wheels
uses: actions/download-artifact@v4
with:
name: wheels
name: dist
path: dist/

- name: Set up Python 3.8
uses: actions/setup-python@v2
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: '3.11'

- name: Install twine
run: python -m pip install twine
run: python -m pip install --upgrade pip twine

- name: Publish to Test PyPI
if: contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha')
run: twine upload --skip-existing --repository testpypi dist/*.whl
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}

- name: Publish wheels to PyPI
run: |
if [ "${{ github.ref }}" = "refs/heads/develop" ]; then
twine upload --skip-existing --repository-url https://test.pypi.org/legacy/ dist/*.whl
else
twine upload --skip-existing --repository-url https://upload.pypi.org/legacy/ dist/*.whl
fi
- name: Publish to PyPI
if: ${{ !contains(github.ref, '-rc') && !contains(github.ref, '-beta') && !contains(github.ref, '-alpha') }}
run: twine upload --skip-existing dist/*.whl
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_AZPYPE }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
test_assets/
TODO.txt
.venv/
*.venv/
nb_tests/
misc/
# Byte-compiled / optimized / DLL files
Expand Down
10 changes: 5 additions & 5 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include setup/assets/bin/azcopy_darwin_amd64_10.18.1/azcopy
include setup/assets/bin/azcopy_linux_amd64_10.18.1/azcopy
include setup/assets/bin/azcopy_linux_arm64_10.18.1/azcopy
include setup/assets/bin/azcopy_windows_amd64_10.18.1/azcopy.exe
include setup/assets/config_templates/copy_config.yaml
include azpype/assets/bin/azcopy_darwin_amd64_10.18.1/azcopy
include azpype/assets/bin/azcopy_linux_amd64_10.18.1/azcopy
include azpype/assets/bin/azcopy_linux_arm64_10.18.1/azcopy
include azpype/assets/bin/azcopy_windows_amd64_10.18.1/azcopy.exe
include azpype/assets/config_templates/copy_config.yaml
include requirements.txt
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ Install via pip
pip install azpype
```

Then from the terminal run
```
azpype-init
```
This will install and setup a `~/.azpype` directory and download the appropriate azcopy binary and baseline config.
No separate init step is required. The AzCopy binary and a baseline config template ship with the package.
On first use, azpype will create `~/.azpype/copy_config.yaml` from the bundled template if it doesn't exist.

---
## Usage
Expand Down Expand Up @@ -90,7 +87,7 @@ For shared access signature use the `sas_token` parameter in the `Copy()` comman

### Configuration

When pip installed a directory called `~/.azpype` will be created, underneath it there will be a configuration file called `copy_config.yaml`. These are default key-values that are options/arguments to the `Copy` command.
When installed, azpype includes a configuration template. On first run, a `~/.azpype/copy_config.yaml` file will be created if missing. These are default key-values that are options/arguments to the `Copy` command.
For example the yaml could have values like this:
```yaml
# Overwrite the conflicting files and blobs at the destination if this flag is set to true.
Expand Down
File renamed without changes.
9 changes: 5 additions & 4 deletions azpype/commands/base_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from abc import ABC, abstractmethod
from azpype.retry import RetryPolicy
from azpype.resource_paths import get_azcopy_path, ensure_user_config
from azpype.logging_config import NullLogger
from azpype.validators import validate_azcopy_envs, validate_login_type, validate_network_available

Expand All @@ -13,14 +14,14 @@ class BaseCommand(ABC):
def __init__(self, command_name: str, retry_policy=None):
self.command_name = command_name
self.retry_policy = retry_policy or RetryPolicy()
self.azcopy_path = os.path.expanduser("~/.azpype/azcopy") #Executable
self.azcopy_path = get_azcopy_path()
self.logger = NullLogger(__name__)


def build_flags(self, options: dict):
config = {}
config_path = Path("~/.azpype/copy_config.yaml").expanduser() #Hardcoded for now
with open(config_path,'r') as f:
config = {}
config_path = ensure_user_config()
with open(config_path, 'r') as f:
try:
config = yaml.safe_load(f)
except yaml.YAMLError as exc:
Expand Down
45 changes: 45 additions & 0 deletions azpype/resource_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from pathlib import Path
import platform
import stat


def _assets_root() -> Path:
pkg_dir = Path(__file__).resolve().parent
return pkg_dir / 'assets'


def get_azcopy_path() -> str:
system = platform.system()
machine = platform.machine()
base = _assets_root() / 'bin'
if system == 'Darwin':
path = base / 'azcopy_darwin_amd64_10.18.1' / 'azcopy'
elif system == 'Windows':
path = base / 'azcopy_windows_amd64_10.18.1' / 'azcopy.exe'
elif system == 'Linux':
if machine == 'x86_64':
path = base / 'azcopy_linux_amd64_10.18.1' / 'azcopy'
elif machine == 'aarch64':
path = base / 'azcopy_linux_arm64_10.18.1' / 'azcopy'
else:
raise RuntimeError(f'Unsupported Linux architecture: {machine}')
else:
raise RuntimeError(f'Unsupported platform: {system}')

if system != 'Windows':
try:
mode = path.stat().st_mode
path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
except FileNotFoundError:
pass
return str(path)


def ensure_user_config() -> Path:
src = _assets_root() / 'config_templates' / 'copy_config.yaml'
dst_dir = Path.home() / '.azpype'
dst_dir.mkdir(parents=True, exist_ok=True)
dst = dst_dir / 'copy_config.yaml'
if not dst.exists() and src.exists():
dst.write_bytes(src.read_bytes())
return dst
53 changes: 1 addition & 52 deletions azpype/setup.py
Original file line number Diff line number Diff line change
@@ -1,52 +1 @@
import requests
from pathlib import Path
import platform
import urllib3
from requests.packages.urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)



def download_file(url, target_path):
response = requests.get(url, stream=True, verify=False)
response.raise_for_status()
with open(target_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)

def main():
try:
home = Path.home()
dir = home / '.azpype'
dir.mkdir(parents=True, exist_ok=True)
print(f"Created directory: {dir}")

config_template_base_url = "https://github.com/yusuf-jkhan1/azpype/blob/main/setup/assets/config_templates"
config_template_files = ["copy_config.yaml"] # Add more when/if needed

for config_file in config_template_files:
download_file(f"{config_template_base_url}/{config_file}?raw=true", dir / config_file)
print(f"Downloaded config file: {config_file}")

binary_base_url = "https://github.com/yusuf-jkhan1/azpype/blob/main/setup/assets/bin"
binary_name = None

if platform.system() == 'Darwin':
binary_name = 'azcopy_darwin_amd64_10.18.1/azcopy'
elif platform.system() == 'Windows':
binary_name = 'azcopy_windows_amd64_10.18.1/azcopy.exe'
elif platform.system() == 'Linux':
if platform.machine() == 'x86_64':
binary_name = 'azcopy_linux_amd64_10.18.1/azcopy'
elif platform.machine() == 'aarch64':
binary_name = 'azcopy_linux_arm64_10.18.1/azcopy'

if binary_name:
download_file(f"{binary_base_url}/{binary_name}?raw=true", dir / binary_name.split('/')[-1])
print(f"Downloaded binary: {binary_name}")

except Exception as e:
print(f"An error occurred: {e}")

if __name__ == "__main__":
main()
"""Deprecated: no-op module retained for backward compatibility."""
35 changes: 2 additions & 33 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
import os
import sys
from setuptools import setup, find_packages
from setuptools.command.install import install
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel


class bdist_wheel(_bdist_wheel):
def finalize_options(self):
_bdist_wheel.finalize_options(self)
self.root_is_pure = False

def get_tag(self):
python, abi, plat = 'py2.py3', 'none', 'any'
return python, abi, plat


class PostInstallCommand(install):
"""Post-installation for installation mode."""
def run(self):
install.run(self)
setup_dir = os.path.dirname(os.path.realpath(__file__))
post_install_script = os.path.join(setup_dir, 'setup', 'post_install.py')
os.system(f"{sys.executable} {post_install_script}")

with open('requirements.txt') as f:
requirements = f.read().splitlines()
Expand All @@ -43,16 +20,8 @@ def run(self):
packages=find_packages(exclude=("tests",)),
include_package_data=True,
package_data={
# Include all files in the setup/assets/bin directory
'': ['setup/assets/bin/*/*', 'setup/assets/bin/*/*.exe'],
'azpype': ['assets/bin/*/*', 'assets/bin/*/*.exe', 'assets/config_templates/*.yaml'],
},
install_requires=requirements,
entry_points={
'console_scripts': [
'azpype-init=azpype.setup:main',
],
},
cmdclass={
'bdist_wheel': bdist_wheel
}
cmdclass={}
)
39 changes: 0 additions & 39 deletions setup/post_install.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from test_basecommand import TestBaseCommand
from .test_basecommand import TestBaseCommand
4 changes: 2 additions & 2 deletions tests/test_basecommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import unittest
from unittest.mock import patch, Mock
from azpype.commands.base_command import BaseCommand
from azpype.resource_paths import get_azcopy_path
import subprocess
from pathlib import Path


class TestBaseCommand(unittest.TestCase):
Expand All @@ -14,7 +14,7 @@ def setUp(self):
def test_build_command(self):
args = ["arg1", "arg2"]
options = {"option1": "value1", "option2": True}
expected_command = [str(Path("~/.azpype/azcopy").expanduser()), "test_command", "arg1", "arg2", "--option1=value1", "--option2=true"]
expected_command = [get_azcopy_path(), "test_command", "arg1", "arg2", "--option1=value1", "--option2=true"]
self.assertEqual(self.command.build_command(args, options), expected_command)

@patch("subprocess.run")
Expand Down