Skip to content
Open
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
43 changes: 35 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ Tools for developers
Installation
------------

Requires recent pip
First, install UV if you don't have it:

```
sudo pip3 install -U pip
timeout-tools uv-install
```

Install globally so its always avalible
Or install UV manually:
- macOS: `brew install uv` or `curl -LsSf https://astral.sh/uv/install.sh | sh`
- Linux: `curl -LsSf https://astral.sh/uv/install.sh | sh`

Then install timeout-tools globally:

```
sudo pip3 install git+https://github.com/timeoutdigital/timeout-tools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sudo pip3 install git+https://github.com/timeoutdigital/timeout-tools
uv tool install git+https://github.com/timeoutdigital/timeout-tools

?

Expand All @@ -36,8 +40,31 @@ This:

- Clones the app into `<branch_name>--<app>`
- Checkout branch `<branch_name>` if it exists or creates it
- Installs python version specified in repos `PYTHON_VERSION` file, using pyenv
- Creates a pyenv virtualenv named `<app>-<version>`
- Installs requirements.txt (and requirements-dev.txt if it exists) in the virtualenv
- Creates `.python-version` file for pyenv-virtualenv to read
- Runs `pre-commit install`
- Installs python version specified in repos `PYTHON_VERSION` file, using UV
- Creates a standard `.venv` virtual environment
- Installs requirements.txt (and requirements-dev.txt if it exists) using UV's fast pip
- Installs and runs `pre-commit install`

### Working with the Virtual Environment

After creating a workspace:

**Traditional activation**:
```
cd <branch_name>--<app>
source .venv/bin/activate
```

### Other Commands

- **Install UV**: `timeout-tools uv-install`
- **Setup Python environment**: `timeout-tools python-setup` (from within a repo)
- **Remove Python environment**: `timeout-tools python-remove`

### Migrating from pyenv

If you were using the old pyenv-based version:
1. Install UV: `timeout-tools uv-install`
2. Remove old pyenv virtualenvs: `pyenv virtualenv-delete <name>`
3. Use the new UV-based commands

120 changes: 59 additions & 61 deletions timeout_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def main():
title="commands",
)

parser_pyenv_install = subparsers.add_parser(
'pyenv-install',
help='install pyenv',
parser_uv_install = subparsers.add_parser(
'uv-install',
help='install uv',
)
parser_pyenv_install.set_defaults(func=pyenv_install)
parser_uv_install.set_defaults(func=uv_install)

parser_python_setup = subparsers.add_parser(
'python-setup',
Expand Down Expand Up @@ -119,36 +119,33 @@ def run(cmd):
return (res.returncode, res.stderr.decode())


def pyenv_install(args):
def uv_install(args):
home_directory = os.path.expanduser('~')
ret, out = run(f'ls -d {home_directory}/.pyenv')
ret, out = run('which uv')
if ret == 0:
logging.debug("$HOME/.pyenv already exists")
sys.exit(1)
shell_rc = '.bashrc'
print("UV is already installed")
sys.exit(0)

if platform.system() == 'Linux':
ret, out = run('curl -s https://pyenv.run | bash')
ret, out = run('curl -LsSf https://astral.sh/uv/install.sh | sh')
elif platform.system() == 'Darwin':
shell_rc = '.zshrc'
ret, out = run('brew install pkg-config [email protected] xz gdbm tcl-tk')
ret, out = run('brew install pyenv')
ret, out = run('brew install pyenv-virtualenv')
# try brew first
ret, out = run('which brew')
if ret == 0:
ret, out = run('brew install uv')
else:
ret, out = run('curl -LsSf https://astral.sh/uv/install.sh | sh')
else:
print(f'{platform.system()} unknown system')
sys.exit(1)

if ret == 0:
ret, out = run(f'grep "TIMEOUT-TOOLS START" {home_directory}/{shell_rc}')
if ret == 0:
logging.debug("pyenv already configured in .bashrc\n")
sys.exit(1)
with open(f'{home_directory}/{shell_rc}', 'a') as shellrc:
shellrc.write('\n## TIMEOUT-TOOLS START\n')
# shellrc.write('export PYENV_VIRTUALENV_DISABLE_PROMPT=1\n')
shellrc.write('export PATH="$HOME/.pyenv/bin:$PATH"\n')
shellrc.write('eval "$(pyenv init --path)"\n')
shellrc.write('eval "$(pyenv virtualenv-init -)"\n')
shellrc.write('\n## TIMEOUT-TOOLS END\n')
run(f'. {home_directory}/{shell_rc}')
print('UV installed successfully')
print('You may need to restart your shell')
else:
print('Failed to install UV')
print(out)
sys.exit(1)


def python_setup_func(args):
Expand All @@ -158,79 +155,80 @@ def python_setup_func(args):


def python_setup(app, branch, python_version):
pyenv_name = f'{app}-{python_version}'
print(f'- Creating virtualenv `{pyenv_name}`', end='', flush=True)
run(f'pyenv install -s {python_version}')
ret, out = run(f'pyenv virtualenv {python_version} {pyenv_name}')
# install the Python version if needed
print(f'- Installing Python {python_version}', end='', flush=True)
ret, out = run(f'uv python install {python_version}')
if ret != 0:
if 'already exists' in out:
print(' (already exists) ✅')
else:
print(' ❌')
print(out)
sys.exit(1)
else:
print(' ✅')
run(f'echo {pyenv_name} > .python-version')

init_active = f'eval "$(pyenv init -)" && pyenv activate {pyenv_name}'
print('- Upgrading pip', end='', flush=True)
ret, out = run(f'{init_active} && pip install -U pip')
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')

print(f'- Creating virtual environment', end='', flush=True)
ret, out = run(f'uv venv --python {python_version}')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')


# try installing requirements using uv pip sync
print('- Installing requirements.txt', end='', flush=True)
ret, out = run(f'{init_active} && pip install -r requirements.txt')
ret, out = run('uv pip sync requirements.txt')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')

# install the dev requirements if they exist
ret, out = run('ls requirements-dev.txt')
if ret == 0:
print('- Installing requirements-dev.txt', end='', flush=True)
ret, out = run(f'{init_active} && pip install -r requirements-dev.txt')
ret, out = run('uv pip install -r requirements-dev.txt')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')

print('- Running `pre-commit install`', end='', flush=True)
ret, out = run(f'{init_active} && pre-commit install')
# pre-commit
print('- Installing pre-commit', end='', flush=True)
ret, out = run('uv pip install pre-commit')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')


def python_remove(args):
ret, out = run('cat .python-version')
if ret != 0:
print(out)
sys.exit(1)
pyenv_name = out.rstrip()

print('- Deleting `.python-version`', end='', flush=True)
ret, out = run('rm .python-version')

# pre-commit install
print('- Running `pre-commit install`', end='', flush=True)
ret, out = run('uv run pre-commit install')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')

print(f'- Deleting `{pyenv_name}` virtualenv', end='', flush=True)
ret, out = run(f'pyenv virtualenv-delete -f {pyenv_name}')

def python_remove(args):
print('- Deleting `.venv` virtualenv', end='', flush=True)
ret, out = run('rm -rf .venv')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')

ret, out = run('ls .python-version')
if ret == 0:
print('- Deleting `.python-version`', end='', flush=True)
ret, out = run('rm .python-version')
if ret != 0:
print(' ❌')
print(out)
sys.exit(1)
print(' ✅')


def ws(args):
Expand Down