Skip to content

Commit

Permalink
Merge pull request #55 from CrumpetDev/setup/tooling-and-env
Browse files Browse the repository at this point in the history
Setup project tooling (improved DX)
  • Loading branch information
tomtitherington authored Feb 11, 2024
2 parents 73e70b9 + 4c2c2b1 commit 8b14613
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .dictionary/custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ ZIZW
zustand
ifneq
psql
pathspec
pyproject
23 changes: 20 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
{
"python.envFile": "${workspaceFolder}/backend/.env",
"prettier.configPath": "./frontend/.prettierrc.js",
"prettier.bracketSpacing": false,
"python.formatting.provider": "black",
"black-formatter.args": ["--line-length", "120"],
"python.defaultInterpreterPath": "./.venv/bin/python",
"python.terminal.activateEnvironment": true,
"black-formatter.args": ["--config=${workspaceFolder}/backend/pyproject.toml"],
"flake8.args": ["--config=${workspaceFolder}/backend/setup.cfg"],
"flake8.importStrategy": "fromEnvironment",
"flake8.showNotification": "onError",
"flake8.lintOnChange": true,
"isort.args": [
"--profile",
"black",
"--settings-file",
"${workspaceFolder}/backend/pyproject.toml"
],
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"python.terminal.activateEnvInCurrentTerminal": true,
"python.analysis.typeCheckingMode": "basic",
"python.testing.unittestArgs": ["-v", "-s", "./backend", "-p", "test_*.py"],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
Expand Down
5 changes: 0 additions & 5 deletions backend/.flake8

This file was deleted.

6 changes: 0 additions & 6 deletions backend/.vscode/settings.json

This file was deleted.

14 changes: 8 additions & 6 deletions backend/app/models/environment.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
from django.db import models

import uuid

from django.db import models


class Environment(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
identifier = models.SlugField(max_length=100, blank=False, null=False)
identifier = models.SlugField(max_length=100, blank=False, null=False)
project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="environments")
is_default = models.BooleanField(default=False, blank=False, null=False)

class Meta:
unique_together = ["identifier", "project"]
verbose_name = "Environment"
verbose_name_plural = "Environments"

def __str__(self):
return self.name

def save(self, *args, **kwargs):
if not self.identifier:
# If identifier is not provided, assign a GUID.
self.identifier = str(uuid.uuid4())
super(Environment, self).save(*args, **kwargs)

def __str__(self):
return self.name
10 changes: 9 additions & 1 deletion backend/app/models/flow_instance.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from app.models import UUIDModel, Person, Environment, StepSchema, FlowSchemaVersion, TransitionSchema
from typing import TYPE_CHECKING

from django.db import models

from app.models import Environment, FlowSchemaVersion, Person, StepSchema, TransitionSchema, UUIDModel

if TYPE_CHECKING:
from django.db.models.manager import RelatedManager


class FlowInstance(UUIDModel):
"""
Expand All @@ -22,6 +28,8 @@ class FlowState(models.TextChoices):
state = models.CharField(max_length=20, choices=FlowState.choices, default=FlowState.ACTIVE)
environment = models.ForeignKey(Environment, on_delete=models.CASCADE)

steps: "RelatedManager[StepInstance]"

@property
def active_steps(self):
"""
Expand Down
11 changes: 9 additions & 2 deletions backend/app/models/flow_schema.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from typing import Optional
from app.models import UUIDModel, Environment
from typing import TYPE_CHECKING, Optional

from django.db import models

from app.models import Environment, UUIDModel

if TYPE_CHECKING:
from django.db.models.manager import RelatedManager


class FlowSchema(UUIDModel):
"""
Expand All @@ -16,6 +21,8 @@ class FlowSchema(UUIDModel):
current_version = models.ForeignKey("FlowSchemaVersion", on_delete=models.SET_NULL, null=True, related_name="+")
environment = models.ForeignKey(Environment, on_delete=models.CASCADE, related_name="schemas")

versions: "RelatedManager[FlowSchemaVersion]"

def latest_version(self) -> Optional["FlowSchemaVersion"]:
"""Retrieves the most recent version of the flow schema based on the 'created_at' timestamp."""
return self.versions.order_by("-created_at").first()
Expand Down
21 changes: 13 additions & 8 deletions backend/app/models/person.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
from django.db import models
import jsonschema
from django.core.exceptions import ValidationError
from django.db import models
from jsonschema.exceptions import SchemaError as JSONSchemaError
from jsonschema.exceptions import ValidationError as JSONValidationError

import jsonschema

class Person(models.Model):
email = models.EmailField(unique=True, primary_key=True)
data = models.JSONField()
schema = models.JSONField()
environment = models.ForeignKey("Environment", on_delete=models.CASCADE, related_name="people")

def __str__(self):
return self.email

def save(self, *args, **kwargs):
self.full_clean() # Make sure we call the clean method on save
super().save(*args, **kwargs)

def clean(self) -> None:
"""
Validates the JSON schema and data.
Expand All @@ -18,15 +27,11 @@ def clean(self) -> None:
"""
try:
jsonschema.Draft202012Validator.check_schema(self.schema)
except jsonschema.exceptions.SchemaError as e:
except JSONSchemaError as e:
raise ValidationError({"schema": f"Invalid JSON schema: {e}"})

validator = jsonschema.Draft202012Validator(self.schema)
try:
validator.validate(self.data)
except jsonschema.exceptions.ValidationError as e:
except JSONValidationError as e:
raise ValidationError({"data": f"Data does not adhere to the schema: {e}"})

def save(self, *args, **kwargs):
self.full_clean() # Make sure we call the clean method on save
super().save(*args, **kwargs)
8 changes: 6 additions & 2 deletions backend/app/models/project.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# from django.utils.crypto import get_random_string
import uuid

from app.models import User
from django.db import models

from app.models import User


class Project(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
members = models.ManyToManyField(User, through="ProjectMembership", related_name="projects")
# TODO: Replace this with proper key generation
api_key = models.CharField(max_length=256, unique=True, default=uuid.uuid4, null=False, blank=False)
api_key = models.CharField(max_length=256, unique=True, default=uuid.uuid4, null=False, blank=False) # type: ignore

def __str__(self):
return self.name
Expand All @@ -28,3 +29,6 @@ class MembershipType(models.TextChoices):
choices=MembershipType.choices,
default=MembershipType.MEMBER,
)

def __str__(self):
return f"{self.user.email} - {self.project.name}"
8 changes: 4 additions & 4 deletions backend/app/models/segments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TODO
# Segments are groups of people who share similar characteristics.
# - They can define grouping conditions that use the underlying data (schema) and potentially
#  use something like JSON Path https://www.postgresql.org/docs/current/functions-json.html
# - They can define a name and description
# - They can be used to trigger flows => when person enters a segment, trigger a flow
# - They can define grouping conditions that use the underlying data (schema) and potentially
# use something like JSON Path https://www.postgresql.org/docs/current/functions-json.html
# - They can define a name and description
# - They can be used to trigger flows => when person enters a segment, trigger a flow
4 changes: 2 additions & 2 deletions backend/app/models/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import List, Type

from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext as _

from typing import List, Type


class UserManager(BaseUserManager):
"""
Expand Down
36 changes: 36 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# A Python code formatter
[tool.black]
exclude = '''
/(
| .git
| .venv
| __pycache__
)/
'''
include = '\.pyi?$' # Include .py and .pyi files
line-length = 120 # Set max line length to 120 characters
target-version = ['py310'] # Target Python version (adjust as necessary)

# A static type checker that helps to catch type errors
[tool.mypy]
disallow_untyped_defs = true # Disallow defining functions without type annotations
ignore_missing_imports = true # Ignore errors for missing imports
plugins = [
"mypy_django_plugin.main", # Enable Django plugin for MyPy
]
python_version = 3.10 # Target Python version

# A linting tool that helps find syntax errors, and enforces coding style.
[mypy.plugins.django-stubs]
django_settings_module = "backend.settings" # Point to the project's settings module

# A tool for sorting imports alphabetically, and automatically separated into sections.
[tool.isort]
ensure_newline_before_comments = true # Ensure a newline before comments
force_grid_wrap = 0 # Don't force grid wrap
include_trailing_comma = true # Include a trailing comma on multi-line
line_length = 120 # Set max line length to 120 characters
multi_line_output = 3 # Use vertical hanging indent format
profile = "black" # Make isort compatible with black
skip = [".venv", "__pycache__", ".git"] # Directories to skip
use_parentheses = true # Use parentheses for line continuation
12 changes: 12 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ asgiref==3.6.0
astroid==2.15.6
async-timeout==4.0.3
attrs==23.1.0
black==24.1.1
certifi==2022.12.7
cfgv==3.3.1
channels==4.0.0
Expand All @@ -17,8 +18,11 @@ dj-database-url==1.2.0
Django==4.1.5
django-cors-headers==3.13.0
django-rest-swagger==2.2.0
django-stubs==4.2.7
django-stubs-ext==4.2.7
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2
djangorestframework-stubs==3.14.5
drf-extensions==0.7.1
et-xmlfile==1.1.0
exceptiongroup==1.1.3
Expand All @@ -39,9 +43,13 @@ lazy-object-proxy==1.9.0
MarkupSafe==2.1.2
mccabe==0.7.0
msgpack==1.0.7
mypy==1.8.0
mypy-extensions==1.0.0
nodeenv==1.7.0
openapi-codec==1.3.2
openpyxl==3.1.1
packaging==23.2
pathspec==0.12.1
platformdirs==3.0.0
pre-commit==3.0.4
psycopg2==2.9.7
Expand All @@ -64,6 +72,10 @@ sniffio==1.3.0
sqlparse==0.4.3
tomli==2.0.1
tomlkit==0.11.8
types-pytz==2024.1.0.20240203
types-PyYAML==6.0.12.12
types-requests==2.31.0.6
types-urllib3==1.26.25.14
typing_extensions==4.7.1
uritemplate==4.1.1
urllib3==1.26.14
Expand Down
1 change: 0 additions & 1 deletion backend/runtime.txt

This file was deleted.

36 changes: 36 additions & 0 deletions backend/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[flake8]
# Set the maximum allowed line length in your Python code
max-line-length = 120

# List of patterns to exclude from checks (mostly to speed up linting)
exclude =
.git, # Ignore all files in the .git directory
__pycache__, # Ignore compiled Python files in __pycache__ directories
.venv, # Exclude files in the .venv virtual environment directory
migrations, # Ignore all Django migration files
settings.py, # Ignore Django settings file

# List of error codes to ignore
ignore =
E203
E266
W503
E501
F403
F401
DJ10
DJ11

# E203 Whitespace before ':' (conflicts with black)
# E266 Too many leading '#' for block comment
# E501 Line too long (handled by max-line-length)
# W503 Line break occurred before a binary operator (conflicts with PEP 8 recommendation)
# F403 ‘from module import *’ used; unable to detect undefined names
# F401 Module imported but unused
# DJ10 Model should define verbose_name on its Meta inner class (flake8-django)
# DJ11 Model should define verbose_name_plural on its Meta inner class (flake8-django)

# Special rules for specific files
# Ignore "imported but unused" in __init__.py files
per-file-ignores =
__init__.py:F401
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dictionaries:
ignorePaths:
- "**/migrations/**"
- "**/node_modules/**"
- "**/.vscode/**"

0 comments on commit 8b14613

Please sign in to comment.