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
57 changes: 57 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CI

on:
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest

services:
db:
image: postgres:15-alpine
env:
POSTGRES_DB: expedition_db
POSTGRES_USER: expedition_user
POSTGRES_PASSWORD: expedition_pass
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U expedition_user -d expedition_db"
--health-interval 5s
--health-timeout 5s
--health-retries 5

redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 5

env:
DATABASE_URL: postgres://expedition_user:expedition_pass@localhost:5432/expedition_db
REDIS_URL: redis://localhost:6379
SECRET_KEY: test-secret-key
DEBUG: "True"

steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run migrations
run: python manage.py migrate --noinput

- name: Run tests
run: pytest tests/
2 changes: 2 additions & 0 deletions authentication/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from .models import Session, User

class SessionAuthentication(BaseAuthentication):
def authenticate_header(self, request):
return 'Bearer'
def authenticate(self, request: HttpRequest):
header = request.headers.get("Authorization")
if not header:
Expand Down
19 changes: 19 additions & 0 deletions tests/test_cycles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest
import os
from tools.check_cycles import CycleDetector

PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))

class TestDependencyCycles:
@pytest.fixture(autouse=True)
def setup(self):
self.cycle_detector = CycleDetector(PROJECT_ROOT)

def test_no_cycles_exist(self):
self.cycle_detector.add_directory('/expeditions/')
self.cycle_detector.add_directory('/authentication/')
cycles = self.cycle_detector.detect()
assert len(cycles) == 0, (
f"Circular dependencies detected:\n"
+ "\n".join(" -> ".join(c) for c in cycles)
)
Empty file added tools/__init__.py
Empty file.
73 changes: 73 additions & 0 deletions tools/check_cycles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import ast
import os
from collections import defaultdict


class CycleDetector:
def __init__(self, root):
self._root = os.path.abspath(root)
self._dirs = set()

def add_directory(self, directory):
self._dirs.add(self._root+directory)

@staticmethod
def _find_imports(filepath):
with open(filepath, 'r') as f:
tree = ast.parse(f.read())

imports = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
imports.append(alias.name)
elif isinstance(node, ast.ImportFrom):
if node.module:
imports.append(node.module)
return imports

@staticmethod
def _detect_cycles(graph):
visited = set()
in_stack = set()
cycles = []

def dfs(node, path):
visited.add(node)
in_stack.add(node)
path.append(node)

for neighbor in graph.get(node, []):
if neighbor not in visited:
dfs(neighbor, path)
elif neighbor in in_stack:
cycle_start = path.index(neighbor)
cycles.append(path[cycle_start:] + [neighbor])

path.pop()
in_stack.remove(node)

for node in graph:
if node not in visited:
dfs(node, [])

return cycles

def _build_dependency_graph(self, project_path):
graph = defaultdict(set)

for root, dirs, files in os.walk(project_path):
for f in files:
if f.endswith('.py'):
filepath = os.path.join(root, f)
module = filepath.replace(self._root, '').replace('/', '.').replace('.py', '').lstrip('.')
imports = self._find_imports(filepath)
for imp in imports:
graph[module].add(imp)
return graph

def detect(self):
graph = defaultdict(set)
for directory in self._dirs:
graph.update(self._build_dependency_graph(directory))
return self._detect_cycles(graph)
Loading