Compare commits

..

7 Commits

Author SHA1 Message Date
Matthew Gordon 3d4a34611b Add basic build instructions to README.md 2024-03-02 20:14:46 -04:00
Matthew Gordon d458be52ac Add unit test runner 2024-03-02 20:06:15 -04:00
Matthew Gordon 4b2d297367 Simplify devupdate 2024-03-02 20:01:48 -04:00
Matthew Gordon 91b4d81192 Don't whow stack trace when terminating test run
Don't show a Python exception and stack trace when the user stops
a test run with CTRL-C
2024-03-02 19:40:43 -04:00
Matthew Gordon e2f6a0a995 Break dev scripts into modules 2024-03-02 19:20:30 -04:00
Matthew Gordon 188e461ceb Add pyproject.toml with autopep8 config 2024-03-02 16:40:08 -04:00
Matthew Gordon 3673145a5d Move env variables from config.toml to dev script 2024-03-02 16:34:57 -04:00
7 changed files with 146 additions and 68 deletions

View File

@ -1,3 +0,0 @@
[env]
LOCALITY_STATIC_FILE_PATH = { value = "static", relative = true }
LOCALITY_HMAC_SECRET = "Not-secret testing secret"

3
.gitignore vendored
View File

@ -20,4 +20,5 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
/.python_venv
/.python_venv
__pycache__

View File

@ -1,2 +1,27 @@
# localhub
# Locality
The [dev](./dev) script starts a Docker image for the Postgres server,
runs `cargo run` or `cargo test`, and then removes the created Docker
container.
Docker must be set up to run rootless for this script to work.
The script is able to install it's own dependencies into a Python
virtual environment:
```sh
# Install
./dev devupdate
```
Build and run the server locally:
```sh
./dev run
```
Run unit tests:
```sh
./dev unittest
```

86
dev.py
View File

@ -1,79 +1,35 @@
import argparse
import os
import subprocess
import shutil
import sys
import time
root_dir = sys.path[0]
# The directory containing this script should be the root directory of the
# locality repository.
ROOT_DIR = sys.path[0]
def devupdate(args):
subprocess.check_call(
[
sys.executable,
'-m',
'pip',
'install',
'-r',
os.path.join(root_dir, 'dev-python-requirements.txt'),
]
)
subprocess.run(
[sys.executable, '-m', 'pip', 'install', '-r',
os.path.join(ROOT_DIR, 'dev-python-requirements.txt'), ])
def import_run():
"""Import the scripts.run module and return it.
We do this in a function so that devupdate() can be run without everything
needed by this module being available."""
import scripts.run
scripts.run.ROOT_DIR = ROOT_DIR
return scripts.run
def run(args):
import docker
import_run().run(args)
POSTGRES_USER = 'locality'
POSTGRES_PASSWORD = 'wkyhjofg2837f'
POSTGRES_DB = 'locality'
postgres_env = {
'POSTGRES_USER': POSTGRES_USER,
'POSTGRES_PASSWORD': POSTGRES_PASSWORD,
'POSTGRES_DB': POSTGRES_DB,
}
locality_env = {
'LOCALITY_DATABASE_URL': 'postgres://{}:{}@localhost/{}'.format(
POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
)
}
docker_client = docker.from_env()
postgres_container = docker_client.containers.run(
'postgres:14.11',
environment=postgres_env,
detach=True,
ports={'5432/tcp': ('127.0.0.1', 5432)},
)
try:
docker_stream = postgres_container.attach(stream=True)
for line_binary in docker_stream:
line = line_binary.decode('utf-8')
print(line)
if 'listening on IPv4 address "0.0.0.0", port 5432' in line:
break
for line_binary in docker_stream:
line = line_binary.decode('utf-8')
print(line)
if 'database system is ready to accept connections' in line:
break
cargo_bin = shutil.which('cargo')
locality_process = subprocess.Popen(
[cargo_bin, 'run'], env=locality_env, cwd=root_dir
)
try:
while locality_process.poll() is None:
time.sleep(0.5)
finally:
if locality_process.poll() is None:
locality_process.terminate()
finally:
postgres_container.stop()
postgres_container.remove()
def unit_tests(args):
import_run().unit_tests(args)
parser = argparse.ArgumentParser()
@ -82,6 +38,10 @@ run_parser = subparsers.add_parser(
'run', help='Run a test instance of locality'
)
run_parser.set_defaults(func=run)
run_parser = subparsers.add_parser(
'unittest', help='Run unit tests'
)
run_parser.set_defaults(func=unit_tests)
devupdate_parser = subparsers.add_parser(
'devupdate', help='Install or update packages used by this script'
)

3
pyproject.toml Normal file
View File

@ -0,0 +1,3 @@
[tool.autopep8]
max_line_length = 80
aggressive = 2

View File

@ -0,0 +1,48 @@
import docker
import os
class PostgresContainer():
def __init__(self):
self.postgres_user = 'locality'
self.postgres_password = 'wkyhjofg2837f'
self.postgres_db = 'locality'
def get_url(self):
return 'postgres://{}:{}@localhost/{}'.format(
self.postgres_user,
self.postgres_password,
self.postgres_db)
def __enter__(self):
postgres_env = {
'POSTGRES_USER': self.postgres_user,
'POSTGRES_PASSWORD': self.postgres_password,
'POSTGRES_DB': self.postgres_db,
}
self.docker_client = docker.from_env()
self.postgres_container = self.docker_client.containers.run(
'postgres:14.11',
environment=postgres_env,
detach=True,
ports={'5432/tcp': ('127.0.0.1', 5432)},
)
self.docker_stream = self.postgres_container.attach(stream=True)
for line_binary in self.docker_stream:
line = line_binary.decode('utf-8')
print(line)
if 'listening on IPv4 address "0.0.0.0", port 5432' in line:
break
for line_binary in self.docker_stream:
line = line_binary.decode('utf-8')
print(line)
if 'database system is ready to accept connections' in line:
break
return self
def __exit__(self, type, value, traceback):
self.postgres_container.stop()
self.postgres_container.remove()

44
scripts/run.py Normal file
View File

@ -0,0 +1,44 @@
import os
import shutil
import subprocess
import time
from .postgres_container import PostgresContainer
ROOT_DIR = None
def cargo(*args):
global ROOT_DIR
with PostgresContainer() as postgres:
locality_env = {
'LOCALITY_DATABASE_URL': postgres.get_url(),
'LOCALITY_TEST_DATABASE_URL': postgres.get_url(),
'LOCALITY_STATIC_FILE_PATH': os.path.join(
ROOT_DIR,
'static'),
'LOCALITY_HMAC_SECRET': 'iknf4390-8guvmr3'
}
locality_env = os.environ.copy() | locality_env
cargo_bin = shutil.which('cargo')
locality_process = subprocess.Popen(
[cargo_bin, *args], env=locality_env, cwd=ROOT_DIR
)
try:
while locality_process.poll() is None:
time.sleep(0.5)
except KeyboardInterrupt:
pass
finally:
if locality_process.poll() is None:
locality_process.terminate()
def run(args):
cargo('run')
def unit_tests(args):
cargo("test")