Skip to content

Commit e34c0dd

Browse files
Merge pull request #54 from CivicDataLab/dev
Prod release - 12th March
2 parents 9bae6e6 + a8ee340 commit e34c0dd

79 files changed

Lines changed: 7874 additions & 1496 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish-sdk.yml

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
- uses: actions/checkout@v3
2424
with:
2525
fetch-depth: 0
26+
ref: ${{ github.ref }}
2627

2728
- name: Set up Python
2829
uses: actions/setup-python@v4
@@ -35,13 +36,30 @@ jobs:
3536
pip install build twine
3637
pip install -e ".[dev]"
3738
38-
- name: Update version in setup.py
39+
- name: Update version in __version__.py
3940
run: |
40-
sed -i "s/version=\".*\"/version=\"${{ inputs.version }}\"/" setup.py
41+
echo '"""Version information for DataSpace SDK."""' > dataspace_sdk/__version__.py
42+
echo "" >> dataspace_sdk/__version__.py
43+
echo "__version__ = \"${{ inputs.version }}\"" >> dataspace_sdk/__version__.py
4144
42-
- name: Update version in pyproject.toml
45+
- name: Update version in pyproject.toml (if exists)
4346
run: |
44-
sed -i "s/version = \".*\"/version = \"${{ inputs.version }}\"/" pyproject.toml
47+
if [ -f pyproject.toml ]; then
48+
sed -i "s/version = \".*\"/version = \"${{ inputs.version }}\"/" pyproject.toml
49+
fi
50+
51+
- name: Commit and push version changes
52+
run: |
53+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
54+
git config --local user.name "github-actions[bot]"
55+
git add dataspace_sdk/__version__.py
56+
if [ -f pyproject.toml ]; then git add pyproject.toml; fi
57+
git commit -m "Bump SDK version to ${{ inputs.version }}" || echo "No changes to commit"
58+
59+
# Get current branch name
60+
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
61+
echo "Pushing to branch: $BRANCH_NAME"
62+
git push origin $BRANCH_NAME || echo "No changes to push"
4563
4664
- name: Run tests
4765
run: |
@@ -65,14 +83,6 @@ jobs:
6583
run: |
6684
twine upload dist/*
6785
68-
- name: Commit version changes
69-
run: |
70-
git config --local user.email "github-actions[bot]@users.noreply.github.com"
71-
git config --local user.name "github-actions[bot]"
72-
git add setup.py pyproject.toml
73-
git commit -m "Bump version to ${{ inputs.version }}" || echo "No changes to commit"
74-
git push || echo "No changes to push"
75-
7686
- name: Create GitHub Release
7787
uses: actions/create-release@v1
7888
env:

DataSpace/settings.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
2222

2323
from .cache_settings import *
2424

25-
env = environ.Env(DEBUG=(bool, False))
26-
DEBUG = env.bool("DEBUG", default=True)
2725
# Build paths inside the project like this: BASE_DIR / 'subdir'.
2826
BASE_DIR = Path(__file__).resolve().parent.parent
27+
28+
# Load .env file FIRST, before reading any env variables
29+
env = environ.Env(DEBUG=(bool, False))
2930
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))
3031

32+
DEBUG = env.bool("DEBUG", default=True)
33+
3134
# Quick-start development settings - unsuitable for production
3235
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
3336

@@ -274,6 +277,9 @@
274277
"search.documents.dataset_document": "dataset",
275278
"search.documents.usecase_document": "usecase",
276279
"search.documents.aimodel_document": "aimodel",
280+
"search.documents.collaborative_document": "collaborative",
281+
"search.documents.publisher_document.OrganizationPublisherDocument": "organization_publisher",
282+
"search.documents.publisher_document.UserPublisherDocument": "user_publisher",
277283
}
278284

279285

@@ -301,9 +307,7 @@
301307

302308
# Swagger Settings
303309
SWAGGER_SETTINGS = {
304-
"SECURITY_DEFINITIONS": {
305-
"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}
306-
}
310+
"SECURITY_DEFINITIONS": {"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}}
307311
}
308312

309313
# Structured Logging Configuration
@@ -370,17 +374,11 @@
370374

371375
# OpenTelemetry Sampling Configuration
372376
OTEL_TRACES_SAMPLER = "parentbased_traceidratio"
373-
OTEL_TRACES_SAMPLER_ARG = os.getenv(
374-
"OTEL_TRACES_SAMPLER_ARG", "1.0"
375-
) # Sample 100% in dev
377+
OTEL_TRACES_SAMPLER_ARG = os.getenv("OTEL_TRACES_SAMPLER_ARG", "1.0") # Sample 100% in dev
376378

377379
# OpenTelemetry Metrics Configuration
378-
OTEL_METRIC_EXPORT_INTERVAL_MILLIS = int(
379-
os.getenv("OTEL_METRIC_EXPORT_INTERVAL_MILLIS", "30000")
380-
)
381-
OTEL_METRIC_EXPORT_TIMEOUT_MILLIS = int(
382-
os.getenv("OTEL_METRIC_EXPORT_TIMEOUT_MILLIS", "30000")
383-
)
380+
OTEL_METRIC_EXPORT_INTERVAL_MILLIS = int(os.getenv("OTEL_METRIC_EXPORT_INTERVAL_MILLIS", "30000"))
381+
OTEL_METRIC_EXPORT_TIMEOUT_MILLIS = int(os.getenv("OTEL_METRIC_EXPORT_TIMEOUT_MILLIS", "30000"))
384382

385383
# OpenTelemetry Instrumentation Configuration
386384
OTEL_PYTHON_DJANGO_INSTRUMENT = True

Dockerfile

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
FROM python:3.10
22
ENV PYTHONDONTWRITEBYTECODE=1
33
ENV PYTHONUNBUFFERED=1
4-
RUN echo 'deb http://archive.debian.org/debian stretch main contrib non-free' >> /etc/apt/sources.list && \
5-
apt-get update && \
4+
5+
# Install system dependencies
6+
RUN apt-get update && \
67
apt-get autoremove -y && \
7-
apt-get install -y libssl1.0-dev curl git nano wget && \
8-
apt-get install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \
9-
rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/partial/*
8+
apt-get install -y \
9+
curl \
10+
git \
11+
nano \
12+
wget \
13+
ca-certificates \
14+
fonts-liberation \
15+
libasound2 \
16+
libatk1.0-0 \
17+
libcairo2 \
18+
libcups2 \
19+
libdbus-1-3 \
20+
libexpat1 \
21+
libfontconfig1 \
22+
libgdk-pixbuf-2.0-0 \
23+
libglib2.0-0 \
24+
libgtk-3-0 \
25+
libnspr4 \
26+
libnss3 \
27+
libpango-1.0-0 \
28+
libpangocairo-1.0-0 \
29+
libx11-6 \
30+
libx11-xcb1 \
31+
libxcb1 \
32+
libxcomposite1 \
33+
libxcursor1 \
34+
libxdamage1 \
35+
libxext6 \
36+
libxfixes3 \
37+
libxi6 \
38+
libxrandr2 \
39+
libxrender1 \
40+
libxss1 \
41+
libxtst6 \
42+
lsb-release \
43+
xdg-utils && \
44+
rm -rf /var/lib/apt/lists/*
1045

1146

1247
WORKDIR /code

api/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class AIModelAdmin(admin.ModelAdmin):
5959
list_filter = (
6060
"provider",
6161
"model_type",
62+
"domain",
6263
"status",
6364
"is_public",
6465
"is_active",
@@ -85,7 +86,7 @@ class AIModelAdmin(admin.ModelAdmin):
8586
"Schema",
8687
{"fields": ("input_schema", "output_schema"), "classes": ("collapse",)},
8788
),
88-
("Metadata", {"fields": ("tags", "metadata"), "classes": ("collapse",)}),
89+
("Metadata", {"fields": ("tags", "domain", "metadata"), "classes": ("collapse",)}),
8990
("Status & Visibility", {"fields": ("status", "is_public", "is_active")}),
9091
(
9192
"Performance Metrics",

api/middleware/rate_limit.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
import logging
2+
import os
23
import time
34
from typing import Any, Callable, Optional, cast
45

6+
from django.conf import settings
57
from django.core.cache import cache
68
from django.http import HttpRequest, HttpResponse
79
from redis.exceptions import RedisError
810

911
logger = logging.getLogger(__name__)
1012

1113

14+
def get_whitelisted_ips() -> set[str]:
15+
"""Get the set of whitelisted IPs from settings or environment variable.
16+
17+
Can be configured via:
18+
- RATE_LIMIT_WHITELIST_IPS in Django settings (list)
19+
- RATE_LIMIT_WHITELIST_IPS environment variable (comma-separated string)
20+
"""
21+
# First check Django settings
22+
whitelist = getattr(settings, "RATE_LIMIT_WHITELIST_IPS", None)
23+
if whitelist:
24+
return set(whitelist)
25+
26+
# Fall back to environment variable
27+
env_whitelist = os.environ.get("RATE_LIMIT_WHITELIST_IPS", "")
28+
if env_whitelist:
29+
return {ip.strip() for ip in env_whitelist.split(",") if ip.strip()}
30+
31+
return set()
32+
33+
1234
class HttpResponseTooManyRequests(HttpResponse):
1335
status_code = 429
1436

1537

1638
def rate_limit_middleware(
17-
get_response: Callable[[HttpRequest], HttpResponse]
39+
get_response: Callable[[HttpRequest], HttpResponse],
1840
) -> Callable[[HttpRequest], HttpResponse]:
1941
"""Rate limiting middleware that uses a simple cache-based counter."""
2042

@@ -78,10 +100,18 @@ def check_rate_limit(request: HttpRequest) -> bool:
78100
return True # Allow request on unexpected error
79101

80102
def middleware(request: HttpRequest) -> HttpResponse:
103+
client_ip = get_client_ip(request)
104+
whitelisted_ips = get_whitelisted_ips()
105+
106+
# Skip rate limiting for whitelisted IPs
107+
if client_ip in whitelisted_ips:
108+
logger.debug(f"Skipping rate limit for whitelisted IP: {client_ip}")
109+
return get_response(request)
110+
81111
if not check_rate_limit(request):
82112
logger.warning(
83113
f"Rate limited - Method: {request.method}, "
84-
f"Path: {request.path}, IP: {get_client_ip(request)}"
114+
f"Path: {request.path}, IP: {client_ip}"
85115
)
86116
return HttpResponseTooManyRequests()
87117

api/models/AIModel.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
EndpointAuthType,
1313
EndpointHTTPMethod,
1414
HFModelClass,
15+
PromptDomain,
1516
)
1617

1718
User = get_user_model()
@@ -82,8 +83,6 @@ class AIModel(models.Model):
8283
blank=True,
8384
related_name="ai_models",
8485
)
85-
# API Configuration
86-
# Endpoints are stored in separate ModelEndpoint table for flexibility
8786

8887
# Model Capabilities
8988
supports_streaming = models.BooleanField(default=False)
@@ -92,19 +91,34 @@ class AIModel(models.Model):
9291
)
9392
supported_languages = models.JSONField(
9493
default=list,
94+
blank=True,
95+
null=True,
9596
help_text="List of supported language codes (e.g., ['en', 'es', 'fr'])",
9697
)
9798

9899
# Input/Output Schema
99-
input_schema = models.JSONField(default=dict, help_text="Expected input format and parameters")
100-
output_schema = models.JSONField(default=dict, help_text="Expected output format")
100+
input_schema = models.JSONField(
101+
default=dict, blank=True, null=True, help_text="Expected input format and parameters"
102+
)
103+
output_schema = models.JSONField(
104+
default=dict, blank=True, null=True, help_text="Expected output format"
105+
)
101106

102107
# Metadata
103108
tags = models.ManyToManyField("api.Tag", blank=True)
104109
sectors = models.ManyToManyField("api.Sector", blank=True, related_name="ai_models")
105110
geographies = models.ManyToManyField("api.Geography", blank=True, related_name="ai_models")
111+
domain = models.CharField(
112+
max_length=200,
113+
choices=PromptDomain.choices,
114+
blank=True,
115+
null=True,
116+
help_text="Domain or category (e.g., healthcare, education, legal)",
117+
)
106118
metadata = models.JSONField(
107119
default=dict,
120+
blank=True,
121+
null=True,
108122
help_text="Additional metadata (training data info, limitations, etc.)",
109123
)
110124

0 commit comments

Comments
 (0)