diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
new file mode 100644
index 0000000..f362c9c
--- /dev/null
+++ b/.github/workflows/docker-build.yml
@@ -0,0 +1,107 @@
+name: Docker Build and Push
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+
+env:
+ SERVIZIO_OC: sparontologies
+ DOCKER_REGISTRY: opencitations
+
+jobs:
+ docker-build-push:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Read version from docker_version.txt
+ id: get_version
+ run: |
+ if [ ! -f docker_version.txt ]; then
+ echo "Error: docker_version.txt file not found"
+ exit 1
+ fi
+ VERSION=$(cat docker_version.txt | tr -d '\n\r' | xargs)
+ if [ -z "$VERSION" ]; then
+ echo "Error: empty version in docker_version.txt"
+ exit 1
+ fi
+ echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
+ echo "Found version: $VERSION"
+
+ - name: Check if image exists on DockerHub
+ id: check_image
+ env:
+ VERSION: ${{ steps.get_version.outputs.VERSION }}
+ run: |
+ echo "Checking if image exists: ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION}"
+
+ # Query DockerHub API to check if tag exists
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
+ "https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}/tags/${VERSION}/")
+
+ if [ "$HTTP_CODE" == "200" ]; then
+ echo "Image already exists on DockerHub"
+ echo "IMAGE_EXISTS=true" >> $GITHUB_OUTPUT
+ else
+ echo "Image not found on DockerHub, will build new one"
+ echo "IMAGE_EXISTS=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Set up Docker Buildx
+ if: steps.check_image.outputs.IMAGE_EXISTS == 'false'
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to DockerHub
+ if: steps.check_image.outputs.IMAGE_EXISTS == 'false'
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push Docker image
+ if: steps.check_image.outputs.IMAGE_EXISTS == 'false'
+ env:
+ VERSION: ${{ steps.get_version.outputs.VERSION }}
+ run: |
+ echo "Building image: ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION}"
+
+ # Build image with no cache
+ docker build --no-cache -t ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION} .
+ sleep 1
+
+ # Tag image
+ docker tag ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION} ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION}
+ sleep 1
+
+ # Push to registry
+ echo "Pushing image to DockerHub..."
+ docker push ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION}
+ sleep 1
+
+ # Check result
+ if [ $? == 0 ]; then
+ echo "ALL DONE !"
+ else
+ echo "NO NO NOOOOOOO !"
+ exit 1
+ fi
+
+ - name: Build summary
+ run: |
+ VERSION="${{ steps.get_version.outputs.VERSION }}"
+ IMAGE_EXISTS="${{ steps.check_image.outputs.IMAGE_EXISTS }}"
+
+ echo "Build Summary:"
+ echo "Version: ${VERSION}"
+ echo "Image: ${{ env.DOCKER_REGISTRY }}/${{ env.SERVIZIO_OC }}:${VERSION}"
+
+ if [ "$IMAGE_EXISTS" == "true" ]; then
+ echo "Status: Image already exists, skipped build"
+ else
+ echo "Status: New image built and pushed successfully"
+ fi
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d3e1d4c..5e6fa0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,33 @@
*.pyc
sparontologies_log.txt
nohup.out
-private/
\ No newline at end of file
+private/
+
+
+#log folder
+log/*
+!log/.gitkeep
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+src/__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+.DS_Store
\ No newline at end of file
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..2c07333
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.11
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..d8be142
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,39 @@
+# Base image: Python slim for a lightweight container
+FROM python:3.11-slim
+
+# Define environment variables with default values
+# These can be overridden during container runtime
+ENV BASE_URL="www.sparontologies.net"
+
+# Ensure Python output is unbuffered
+ENV PYTHONUNBUFFERED=1
+# Install system dependencies required for Python package compilation
+# We clean up apt cache after installation to reduce image size
+RUN apt-get update && \
+ apt-get install -y \
+ git \
+ curl \
+ ca-certificates \
+ python3-dev \
+ build-essential
+
+# Install uv for dependency management
+ADD https://astral.sh/uv/install.sh /uv-installer.sh
+RUN sh /uv-installer.sh && rm /uv-installer.sh
+ENV PATH="/root/.local/bin:$PATH"
+
+# Set the working directory for our application
+WORKDIR /website
+
+# Copy the application code from the repository to the container
+# The code is already present in the repo, no need to git clone
+COPY . .
+
+# Install Python dependencies using uv
+RUN uv sync --frozen --no-dev
+
+# Expose the port that our service will listen on
+EXPOSE 8080
+
+# Start the application with gunicorn instead of python directly
+CMD ["uv", "run", "gunicorn", "-c", "gunicorn.conf.py", "spar:application"]
\ No newline at end of file
diff --git a/docker_version.txt b/docker_version.txt
new file mode 100644
index 0000000..1cc5f65
--- /dev/null
+++ b/docker_version.txt
@@ -0,0 +1 @@
+1.1.0
\ No newline at end of file
diff --git a/docs/k8s.yaml b/docs/k8s.yaml
new file mode 100644
index 0000000..2851df6
--- /dev/null
+++ b/docs/k8s.yaml
@@ -0,0 +1,561 @@
+###
+# This file contains Kubernetes manifests for deploying SPAR Ontologies, Varnish caching,
+# and Traefik IngressRoutes on a Kubernetes cluster.
+###
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: varnish-external-services-config
+ namespace: default
+data:
+ default.vcl: |
+ vcl 4.0;
+
+ import vsthrottle;
+ import std;
+
+ # Backend: SPAR Ontologies service
+ backend sparontologies {
+ .host = "spar-ontologies-service.default.svc.cluster.local";
+ .port = "80";
+ .first_byte_timeout = 120s;
+ .between_bytes_timeout = 120s;
+ }
+
+ # Rate limiting subroutine
+ sub rate_limit {
+ # Check if host is sparontologies.net domain
+ if (req.http.host ~ "sparontologies\.net$") {
+ # Use real IP if available, otherwise fallback to client.ip
+ if (req.http.X-Real-IP) {
+ # Apply rate limit: 300 requests per 60 seconds
+ if (vsthrottle.is_denied(req.http.X-Real-IP, 300, 60s)) {
+ set req.http.X-Rate-Limit-Exceeded = "true";
+ set req.http.X-Rate-Limit = "300";
+ set req.http.X-Rate-Window = "60";
+ return (synth(429, "Too Many Requests"));
+ }
+ } else {
+ # Fallback to client.ip
+ if (vsthrottle.is_denied(client.ip, 300, 60s)) {
+ set req.http.X-Rate-Limit-Exceeded = "true";
+ set req.http.X-Rate-Limit = "300";
+ set req.http.X-Rate-Window = "60";
+ return (synth(429, "Too Many Requests"));
+ }
+ }
+ }
+ }
+
+ # Handle incoming requests
+ sub vcl_recv {
+ # Handle real IP from upstream proxy (Traefik)
+ if (req.http.X-Forwarded-For) {
+ # Get the first IP from X-Forwarded-For list (the real user IP)
+ set req.http.X-Real-IP = regsub(req.http.X-Forwarded-For, ",.*", "");
+ # Pass the real IP to the backend
+ set req.http.X-Forwarded-For = req.http.X-Real-IP;
+ }
+
+ # Redirect sparontologies.net to www.sparontologies.net
+ if (req.http.host == "sparontologies.net") {
+ return (synth(750, "Redirect to www"));
+ }
+
+ # Only accept www.sparontologies.net
+ if (req.http.host != "www.sparontologies.net") {
+ return (synth(404, "Not Found"));
+ }
+
+ # Set backend
+ set req.backend_hint = sparontologies;
+
+ # Handle CORS preflight requests
+ if (req.method == "OPTIONS") {
+ set req.http.X-CORS-Preflight = "true";
+ return (synth(200, "OK"));
+ }
+
+ # Apply rate limiting
+ call rate_limit;
+
+ # Bypass cache if preview parameter is present
+ if (req.url ~ "\?preview=true" || req.url ~ "&preview=true") {
+ set req.http.X-Cache-Skip = "bypass-preview";
+ return (pass);
+ }
+
+ # Handle GET and HEAD requests with caching
+ if (req.method == "GET" || req.method == "HEAD") {
+ # Remove cookies and other varying headers for better cache hits
+ unset req.http.Cookie;
+ return (hash);
+ } else {
+ # Don't cache POST, PUT, DELETE, etc.
+ set req.http.X-Cache-Skip = "non-get-method";
+ return (pass);
+ }
+ }
+
+ # Generate hash key for cache
+ sub vcl_hash {
+ hash_data(req.url);
+ hash_data(req.http.host);
+ return (lookup);
+ }
+
+ # Handle backend responses
+ sub vcl_backend_response {
+ # Handle 4xx and 5xx responses - short cache
+ if (beresp.status >= 400) {
+ set beresp.ttl = 60s;
+ set beresp.grace = 10s;
+ set beresp.keep = 0s;
+ return (deliver);
+ }
+
+ # Cache everything for 30 days (static content)
+ set beresp.ttl = 30d;
+ set beresp.grace = 7d;
+ set beresp.keep = 14d;
+
+ # Remove Set-Cookie from backend responses
+ unset beresp.http.Set-Cookie;
+
+ # Add informative headers
+ set beresp.http.X-Cache-TTL = "30 days";
+
+ return (deliver);
+ }
+
+ # Handle backend errors
+ sub vcl_backend_error {
+ set beresp.http.Content-Type = "text/html; charset=utf-8";
+ set beresp.http.Retry-After = "5";
+ synthetic({"
+
+
+
+ Service Temporarily Unavailable
+
+
+
+
+
⚠️
+
Service Temporarily Unavailable
+
The SPAR Ontologies service is currently experiencing technical difficulties.
+
We are working to resolve the issue as quickly as possible.
+
Error: "} + beresp.status + " " + beresp.reason + {"
+
Please try again in a few moments.
+
+
+ "});
+ return (deliver);
+ }
+
+ # Deliver responses to client
+ sub vcl_deliver {
+ # Add CORS headers for all requests
+ if (req.http.Origin) {
+ set resp.http.Access-Control-Allow-Origin = req.http.Origin;
+ set resp.http.Access-Control-Allow-Credentials = "true";
+ } else {
+ set resp.http.Access-Control-Allow-Origin = "*";
+ }
+ set resp.http.Access-Control-Allow-Methods = "GET, POST, OPTIONS, HEAD";
+ set resp.http.Access-Control-Allow-Headers = "Content-Type, Authorization, X-Requested-With, Accept, Origin, DNT, User-Agent";
+ set resp.http.Access-Control-Expose-Headers = "Content-Length, Content-Range, X-Total-Count, X-Cache";
+ set resp.http.Access-Control-Max-Age = "86400";
+
+ # Set X-Cache header based on response type
+ if (obj.hits > 0) {
+ set resp.http.X-Cache = "HIT";
+ set resp.http.X-Cache-Hits = obj.hits;
+ } elsif (req.http.X-Cache-Skip) {
+ set resp.http.X-Cache = "SKIP (" + req.http.X-Cache-Skip + ")";
+ } else {
+ set resp.http.X-Cache = "MISS";
+ }
+
+ # Add cache age
+ if (obj.hits > 0) {
+ set resp.http.Age = obj.age;
+ }
+
+ # Change Server header (keeps the existing easter egg)
+ set resp.http.Server = "Apache/2.4.10 (Win32)";
+
+ # Remove unnecessary headers for clients
+ unset resp.http.Via;
+ unset resp.http.X-Powered-By;
+ unset resp.http.X-Varnish;
+
+ return (deliver);
+ }
+
+ # Handle synthetic responses
+ sub vcl_synth {
+ # Redirect to www subdomain
+ if (resp.status == 750) {
+ set resp.http.Location = "https://www." + req.http.host + req.url;
+ set resp.status = 301;
+ return (deliver);
+ }
+
+ # Handle CORS preflight responses
+ if (req.http.X-CORS-Preflight == "true") {
+ set resp.http.Content-Type = "text/plain";
+ if (req.http.Origin) {
+ set resp.http.Access-Control-Allow-Origin = req.http.Origin;
+ set resp.http.Access-Control-Allow-Credentials = "true";
+ } else {
+ set resp.http.Access-Control-Allow-Origin = "*";
+ }
+ set resp.http.Access-Control-Allow-Methods = "GET, POST, OPTIONS, HEAD";
+ set resp.http.Access-Control-Allow-Headers = "Content-Type, Authorization, X-Requested-With, Accept, Origin, DNT, User-Agent";
+ set resp.http.Access-Control-Max-Age = "86400";
+ set resp.status = 200;
+ set resp.reason = "OK";
+ return (deliver);
+ }
+
+ # Handle 429 Rate Limit Exceeded
+ if (resp.status == 429) {
+ set resp.http.Content-Type = "text/html; charset=utf-8";
+ set resp.http.Retry-After = "60";
+ synthetic({"
+
+
+
+
+ Rate Limit Exceeded - SPAR Ontologies
+
+
+
+
+
⚠️
+
Rate Limit Exceeded
+
You have exceeded the allowed number of requests to the website.
+
+
Please wait a moment before making additional requests.
+
+
+ "});
+ return (deliver);
+ }
+
+ # Default synthetic response
+ set resp.http.Content-Type = "text/html; charset=utf-8";
+ return (deliver);
+ }
+
+---
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: varnish-external-services
+ namespace: default
+ labels:
+ app: varnish-external-services
+spec:
+ replicas: 1
+ revisionHistoryLimit: 30
+ progressDeadlineSeconds: 600
+ selector:
+ matchLabels:
+ app: varnish-external-services
+ strategy:
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ app: varnish-external-services
+ spec:
+ containers:
+ - name: varnish-external-services
+ image: varnish:7.6.0
+ imagePullPolicy: Always
+ securityContext:
+ capabilities:
+ add: ["IPC_LOCK"]
+ args:
+ - varnishd
+ - '-F'
+ - '-a'
+ - ':80'
+ - '-f'
+ - /etc/varnish/default.vcl
+ - '-s'
+ - malloc,4g
+ ports:
+ - containerPort: 80
+ protocol: TCP
+ resources:
+ limits:
+ memory: 5Gi
+ cpu: 1
+ requests:
+ memory: 4Gi
+ cpu: 1
+ volumeMounts:
+ - name: varnish-external-services-config
+ mountPath: /etc/varnish
+ volumes:
+ - name: varnish-external-services-config
+ configMap:
+ name: varnish-external-services-config
+ defaultMode: 420
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: varnish-external-services-service
+ namespace: default
+ labels:
+ app: varnish-external-services
+spec:
+ selector:
+ app: varnish-external-services
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ name: http
+ type: ClusterIP
+
+---
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: spar-ontologies
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: spar-ontologies
+ template:
+ metadata:
+ labels:
+ app: spar-ontologies
+ spec:
+ containers:
+ - name: spar-ontologies
+ image: opencitations/sparontologies:1.0.0
+ imagePullPolicy: IfNotPresent
+ ports:
+ - containerPort: 8080
+ name: http
+ protocol: TCP
+ env:
+ - name: BASE_URL
+ value: "www.sparontologies.net"
+ resources:
+ limits:
+ cpu: "500m"
+ memory: "1Gi"
+ requests:
+ memory: 1Gi
+ cpu: 200m
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: spar-ontologies-service
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ selector:
+ app: spar-ontologies
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ targetPort: 8080
+ type: ClusterIP
+---
+apiVersion: traefik.io/v1alpha1
+kind: IngressRoute
+metadata:
+ name: spar-ontologies-https
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ entryPoints:
+ - websecure
+ routes:
+ - kind: Rule
+ match: Host(`www.sparontologies.net`)
+ services:
+ - name: varnish-external-services-service
+ port: 80
+ tls:
+ certResolver: myresolver
+---
+apiVersion: traefik.io/v1alpha1
+kind: IngressRoute
+metadata:
+ name: spar-ontologies-http
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ entryPoints:
+ - web
+ routes:
+ - kind: Rule
+ match: Host(`www.sparontologies.net`)
+ middlewares:
+ - name: redirect-to-https
+ services:
+ - name: varnish-external-services-service
+ port: 80
+---
+apiVersion: traefik.io/v1alpha1
+kind: IngressRoute
+metadata:
+ name: spar-ontologies-nowww-https
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ entryPoints:
+ - websecure
+ routes:
+ - kind: Rule
+ match: Host(`sparontologies.net`)
+ services:
+ - name: varnish-external-services-service
+ port: 80
+ tls:
+ certResolver: myresolver
+---
+apiVersion: traefik.io/v1alpha1
+kind: IngressRoute
+metadata:
+ name: spar-ontologies-nowww-http
+ namespace: default
+ labels:
+ app: spar-ontologies
+spec:
+ entryPoints:
+ - web
+ routes:
+ - kind: Rule
+ match: Host(`sparontologies.net`)
+ middlewares:
+ - name: redirect-to-https
+ services:
+ - name: varnish-external-services-service
+ port: 80
\ No newline at end of file
diff --git a/gunicorn.conf.py b/gunicorn.conf.py
new file mode 100644
index 0000000..76fac9d
--- /dev/null
+++ b/gunicorn.conf.py
@@ -0,0 +1,50 @@
+
+import os
+import sys
+import subprocess
+
+# Worker configuration
+workers = 4
+worker_class = "gevent"
+worker_connections = 400
+timeout = 1200
+bind = "0.0.0.0:8080"
+
+# Logging
+accesslog = "-"
+errorlog = "-"
+loglevel = "info"
+
+def on_starting(server):
+ """
+ Called just before the master process is initialized.
+ This runs ONLY ONCE, before any workers are created.
+ """
+ print("=" * 60)
+ print("Gunicorn master process starting...")
+ print("=" * 60)
+
+ # Check if sync is enabled
+ sync_enabled = os.getenv("SYNC_ENABLED", "false").lower() == "true"
+
+ if sync_enabled:
+ print("Static sync enabled - running sync before starting workers...")
+ try:
+ subprocess.run([sys.executable, "sync_static.py", "--auto"], check=True)
+ print("Static sync completed successfully!")
+ except subprocess.CalledProcessError as e:
+ print(f"ERROR: Static sync failed: {e}")
+ except Exception as e:
+ print(f"ERROR: Unexpected error during sync: {e}")
+ else:
+ print("Static sync disabled")
+
+ print("=" * 60)
+ print("Master process initialized - spawning workers...")
+ print("=" * 60)
+
+def post_worker_init(worker):
+ """
+ Called just after a worker has been initialized.
+ """
+ print(f"Worker {worker.pid} initialized and ready")
\ No newline at end of file
diff --git a/hashformat.py b/hashformat.py
index 175ff21..dc8ca9a 100644
--- a/hashformat.py
+++ b/hashformat.py
@@ -6,7 +6,7 @@
def process_hashformat(file_path):
result = []
- with open(file_path, "rU") as f:
+ with open(file_path, "r") as f:
first_field_name = None
cur_object = None
cur_field_name = None
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..4e1423d
--- /dev/null
+++ b/main.py
@@ -0,0 +1,6 @@
+def main():
+ print("Hello from sparontologies-github-io!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..52c2556
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,12 @@
+[project]
+name = "sparontologies-github-io"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.11,<3.12"
+dependencies = [
+ "gevent>=25.9.1",
+ "gunicorn>=23.0.0",
+ "rdflib>=7.4.0",
+ "web-py>=0.62",
+]
diff --git a/spar.py b/spar.py
index ee1b097..3125187 100644
--- a/spar.py
+++ b/spar.py
@@ -24,14 +24,15 @@
mediatype_base_path = "mediatype" + os.sep
mediatype_base_url = "https://w3id.org/spar/mediatype/"
ontologies_base_url = "http://www.sparontologies.net/ontologies/"
-# ontologies_base_url = "http://localhost:8181/ontologies/"
-# mediatype_base_url = "http://localhost:8080/mediatype/"
+# ontologies_base_url = "http://localhost:8181/ontologies/"
+# mediatype_base_url = "http://localhost:8080/mediatype/"
tmp_dir_for_copying_rdf = os.path.expanduser("~")
src_fragment = "/source"
# For redirecting
urls = (
"/", "Home",
+ "/static/(.*)", "Static",
"/robots.txt", "Robots",
"/ontologies/?(.*)", "Ontologies",
"/examples/?", "Examples",
@@ -91,11 +92,46 @@
# {"REMOTE_ADDR": ["127.0.0.1"]} # uncomment this for test
)
+# Local web application
+app = web.application(urls, globals())
+
+# Gunicorn WSGI application
+application = app.wsgifunc()
class Home:
def GET(self):
web_logger.mes()
return render.home("SPAR Ontologies - Home", pages)
+
+class Static:
+ def GET(self, name):
+ """Serve static files"""
+ static_dir = "static"
+ file_path = os.path.join(static_dir, name)
+
+ if not os.path.exists(file_path):
+ raise web.notfound()
+
+ # Content types
+ ext = os.path.splitext(name)[1]
+ content_types = {
+ '.css': 'text/css',
+ '.js': 'application/javascript',
+ '.png': 'image/png',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.gif': 'image/gif',
+ '.svg': 'image/svg+xml',
+ '.ico': 'image/x-icon',
+ '.woff': 'font/woff',
+ '.woff2': 'font/woff2',
+ '.ttf': 'font/ttf',
+ }
+
+ web.header('Content-Type', content_types.get(ext, 'application/octet-stream'))
+
+ with open(file_path, 'rb') as f:
+ return f.read()
class Robots:
def GET(self):
@@ -173,18 +209,24 @@ def GET(self, onto_acronym):
if os.path.exists(ontology_path) and os.path.exists(example_path):
cur_ontology_dict = process_hashformat(ontology_path)[0]
cur_example_list = process_hashformat(example_path)
+
+ # handle empty DOI
+ doi_value = cur_ontology_dict["doi"]
+ if doi_value.lower() == "not defined":
+ doi_value = "It will be soon available"
+
web_logger.mes()
return render.ontology("SPAR Ontologies - ", pages,
- cur_ontology_dict["name"],
- cur_ontology_dict["acronym"],
- cur_ontology_dict["url"],
- cur_ontology_dict["doi"],
- cur_ontology_dict["documentation"],
- cur_ontology_dict["repository"],
- cur_ontology_dict["description"],
- cur_example_list,
- process_hashformat(publication_list_path),
- onto_acronym)
+ cur_ontology_dict["name"],
+ cur_ontology_dict["acronym"],
+ cur_ontology_dict["url"],
+ doi_value,
+ cur_ontology_dict["documentation"],
+ cur_ontology_dict["repository"],
+ cur_ontology_dict["description"],
+ cur_example_list,
+ process_hashformat(publication_list_path),
+ onto_acronym)
else:
raise web.notfound()
else: # Load home
@@ -231,5 +273,4 @@ def GET(self, file_path=None):
if __name__ == "__main__":
- app = web.application(urls, globals())
app.run()
diff --git a/spar/examples.html b/spar/examples.html
index 1abbda4..bd29856 100644
--- a/spar/examples.html
+++ b/spar/examples.html
@@ -96,7 +96,10 @@
$example["description"]
- $example["code"]
+ $if "code" in example:
+ $example["code"]
+ $else:
+ # Code example not available
$if "cite" in example:
Please cite the source above with the following reference:
diff --git a/spar/ontologies.html b/spar/ontologies.html
index 5e1b661..37ad988 100644
--- a/spar/ontologies.html
+++ b/spar/ontologies.html
@@ -107,7 +107,7 @@
Semantic Publishing and Referencing Ontologies
the Bibliometric Data Ontology (BiDO) is a modular ontology that allows the description of numerical and categorical bibliometric data (e.g., journal impact factor, author h-index, categories describing research careers) in RDF;
the Five Stars of Online Research Articles Ontology (FiveStars) is an ontology written in OWL 2 DL to enable characterization of the five attributes of an online journal article - peer review, open access, enriched content, available datasets and machine-readable metadata.
the FAIR* Reviews Ontology (FR) enables the description of reviews of scientific articles and other scholarly resources.
-
the Mention Typing Ontology (MiTO) is designed to formalize the concept of mentions and enhance interoperability with other SPAR ontologies by providing a flexible framework for describing mentions and their characteristics. Mentions are crucial for bibliometric analysis, as they allow researchers to track references to entities (e.g., software) in scholarly communication.
+
the Mention Typing Ontology (MiTO) is designed to formalize the concept of mentions and enhance interoperability with other SPAR ontologies by providing a flexible framework for describing mentions and their characteristics. Mentions are crucial for bibliometric analysis, as they allow researchers to track references to entities (e.g., software) in scholarly communication.
diff --git a/spar/ontology.html b/spar/ontology.html
index 2563774..a105675 100644
--- a/spar/ontology.html
+++ b/spar/ontology.html
@@ -74,7 +74,12 @@ $name ($acronym)
URL
$url (alternative at w3id.org)
DOI
- $doi
+
+ $if doi == "It will be soon available":
+ $doi
+ $else:
+ $doi
+
Documentation
$documentation
Source
diff --git a/spar/ontology_descriptions/mito.txt b/spar/ontology_descriptions/mito.txt
index 985cb37..ffa467c 100644
--- a/spar/ontology_descriptions/mito.txt
+++ b/spar/ontology_descriptions/mito.txt
@@ -4,6 +4,8 @@
#url http://purl.org/spar/mito
+#doi not defined
+
#source https://sparontologies.github.io/mito/current/mito.owl
#documentation http://purl.org/spar/mito.html
diff --git a/spar/ontology_examples/mito.txt b/spar/ontology_examples/mito.txt
index cd2e887..8be20d2 100644
--- a/spar/ontology_examples/mito.txt
+++ b/spar/ontology_examples/mito.txt
@@ -4,16 +4,16 @@
#description **MiTO** lets you connect two resources where one (the *mentioning entity*, e.g., a paper) mentions another (the *mentioned entity*, e.g., a software, dataset, method), optionally qualifying the mention with a **mention type** (e.g., explicit/implicit). As with CiTO, you can use a **direct** triple (`mito:mentions`) *or* a **reified** mention (`mito:Mention`) to which you can attach further metadata such as the mention type.
-## Example 1: Software Mention in Machine Learning Paper
-
-### Description
-This example demonstrates how to use **MiTO** to capture a software mention in a machine learning research paper. The paper explicitly mentions TensorFlow as the deep learning framework used for model implementation.
-
-@prefix : .
+#code @prefix : .
@prefix mito: .
@prefix fabio: .
+@prefix oa: .
@prefix dcterms: .
@prefix foaf: .
+@prefix cnt: .
+@prefix c4o: .
+@prefix per: .
+
# The research paper
:ml-paper-2024 a fabio:JournalArticle ;
@@ -31,18 +31,6 @@ This example demonstrates how to use **MiTO** to capture a software mention in a
mito:hasMentionedEntity :tensorflow ;
mito:hasMentionType mito:ExplicitMention .
-## Example 2: Dataset Mention with Open Annotation
-
-### Description
-This example shows how to use **MiTO** with **Open Annotation** to provide additional context about a dataset mention. The paper mentions ImageNet dataset but uses it only for pre-training, which is captured through an annotation.
-
-@prefix : .
-@prefix mito: .
-@prefix fabio: .
-@prefix dcterms: .
-@prefix oa: .
-@prefix cnt: .
-
# The research entities
:vision-paper a fabio:JournalArticle ;
dcterms:title "Novel Architectures for Biomedical Image Classification" .
@@ -69,18 +57,6 @@ This example shows how to use **MiTO** with **Open Annotation** to provide addit
:usage-context a cnt:ContentAsText ;
cnt:chars "ImageNet is used exclusively for pre-training the backbone network, not for final model evaluation." .
-## Example 3: Method Mention with In-Text Pointer
-
-### Description
-This example illustrates how to connect specific **in-text reference pointers** to **MiTO mentions** using **C4O** and **Open Annotation**. A paper mentions BERT methodology through a specific textual pointer "[BERT]" in the methods section.
-
-@prefix : .
-@prefix mito: .
-@prefix fabio: .
-@prefix dcterms: .
-@prefix c4o: .
-@prefix oa: .
-@prefix per: .
# Research entities
:nlp-paper a fabio:JournalArticle ;
@@ -109,17 +85,6 @@ This example illustrates how to connect specific **in-text reference pointers**
:nlp-paper mito:mentions :bert-method .
:bert-method mito:isMentionedBy :nlp-paper .
-## Example 4: Implicit Theory Mention
-
-### Description
-This example demonstrates capturing an **implicit mention** where a paper references a theoretical framework without explicitly naming it. The mention is inferred from the methodology description rather than direct citation.
-
-@prefix : .
-@prefix mito: .
-@prefix fabio: .
-@prefix dcterms: .
-@prefix oa: .
-@prefix cnt: .
# Research entities
:stats-paper a fabio:JournalArticle ;
@@ -145,4 +110,6 @@ This example demonstrates capturing an **implicit mention** where a paper refere
oa:hasBody :implicit-explanation .
:implicit-explanation a cnt:ContentAsText ;
- cnt:chars "Bayesian theory is applied throughout the methodology but never explicitly referenced by name." .
\ No newline at end of file
+ cnt:chars "Bayesian theory is applied throughout the methodology but never explicitly referenced by name." .
+
+#cite It will soon be available
\ No newline at end of file
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..b56b158
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,205 @@
+version = 1
+revision = 3
+requires-python = "==3.11.*"
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
+ { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+]
+
+[[package]]
+name = "cheroot"
+version = "11.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jaraco-functools" },
+ { name = "more-itertools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/09/01/30c083f66da622b747891680e3131cd6e207903d002185ac4e72c65a03b1/cheroot-11.1.1.tar.gz", hash = "sha256:b93428476884c802ea817497633259254bb3fcf2450934bbb8f1a7a099738a31", size = 185168, upload-time = "2025-11-03T22:16:03.946Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/2f/d88695c553bed1ef318a4214ec8ae7518027d3aa5a54d756fc5729e17ad4/cheroot-11.1.1-py3-none-any.whl", hash = "sha256:39c95d162681ed938679f645bca33abfa523b862a2393d630e3376b7426c7a36", size = 108470, upload-time = "2025-11-03T22:16:02.681Z" },
+]
+
+[[package]]
+name = "gevent"
+version = "25.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" },
+ { name = "greenlet", marker = "platform_python_implementation == 'CPython'" },
+ { name = "zope-event" },
+ { name = "zope-interface" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/48/b3ef2673ffb940f980966694e40d6d32560f3ffa284ecaeb5ea3a90a6d3f/gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd", size = 5059025, upload-time = "2025-09-17T16:15:34.528Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/86/03f8db0704fed41b0fa830425845f1eb4e20c92efa3f18751ee17809e9c6/gevent-25.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5aff9e8342dc954adb9c9c524db56c2f3557999463445ba3d9cbe3dada7b7", size = 1792418, upload-time = "2025-09-17T15:41:24.384Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/35/f6b3a31f0849a62cfa2c64574bcc68a781d5499c3195e296e892a121a3cf/gevent-25.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1cdf6db28f050ee103441caa8b0448ace545364f775059d5e2de089da975c457", size = 1875700, upload-time = "2025-09-17T15:48:59.652Z" },
+ { url = "https://files.pythonhosted.org/packages/66/1e/75055950aa9b48f553e061afa9e3728061b5ccecca358cef19166e4ab74a/gevent-25.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:812debe235a8295be3b2a63b136c2474241fa5c58af55e6a0f8cfc29d4936235", size = 1831365, upload-time = "2025-09-17T15:49:19.426Z" },
+ { url = "https://files.pythonhosted.org/packages/31/e8/5c1f6968e5547e501cfa03dcb0239dff55e44c3660a37ec534e32a0c008f/gevent-25.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b28b61ff9216a3d73fe8f35669eefcafa957f143ac534faf77e8a19eb9e6883a", size = 2122087, upload-time = "2025-09-17T15:15:12.329Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/2c/ebc5d38a7542af9fb7657bfe10932a558bb98c8a94e4748e827d3823fced/gevent-25.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e4b6278b37373306fc6b1e5f0f1cf56339a1377f67c35972775143d8d7776ff", size = 1808776, upload-time = "2025-09-17T15:52:40.16Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/26/e1d7d6c8ffbf76fe1fbb4e77bdb7f47d419206adc391ec40a8ace6ebbbf0/gevent-25.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d99f0cb2ce43c2e8305bf75bee61a8bde06619d21b9d0316ea190fc7a0620a56", size = 2179141, upload-time = "2025-09-17T15:24:09.895Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/6c/bb21fd9c095506aeeaa616579a356aa50935165cc0f1e250e1e0575620a7/gevent-25.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:72152517ecf548e2f838c61b4be76637d99279dbaa7e01b3924df040aa996586", size = 1677941, upload-time = "2025-09-17T19:59:50.185Z" },
+]
+
+[[package]]
+name = "greenlet"
+version = "3.2.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" },
+ { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" },
+ { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" },
+ { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "23.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
+]
+
+[[package]]
+name = "jaraco-functools"
+version = "4.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "more-itertools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" },
+]
+
+[[package]]
+name = "more-itertools"
+version = "10.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.23"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.2.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" },
+]
+
+[[package]]
+name = "rdflib"
+version = "7.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyparsing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/ea/30bd9eb0d4a25dd0ab929153ed23698c907c6124389aa72eea5b7b703ab8/rdflib-7.4.0.tar.gz", hash = "sha256:c8ee16c31848c19c174aed96185327ea139ca3d392fac7fa882ddf5687f8f533", size = 4866588, upload-time = "2025-10-30T12:55:21.568Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/52/9d03e93f2e00d2a07749ee90f358d08c07822819d084f08c387b7ade8b56/rdflib-7.4.0-py3-none-any.whl", hash = "sha256:0af003470404ff21bc0eb04077cc97ee96da581f2429bf42a8e163fc1c2797bc", size = 569019, upload-time = "2025-10-30T12:55:14.462Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
+]
+
+[[package]]
+name = "sparontologies-github-io"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "gevent" },
+ { name = "gunicorn" },
+ { name = "rdflib" },
+ { name = "web-py" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "gevent", specifier = ">=25.9.1" },
+ { name = "gunicorn", specifier = ">=23.0.0" },
+ { name = "rdflib", specifier = ">=7.4.0" },
+ { name = "web-py", specifier = ">=0.62" },
+]
+
+[[package]]
+name = "web-py"
+version = "0.62"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cheroot" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cd/6e/338a060bb5b52ee8229bdada422eaa5f71b13f8d33467f37f870ed2cae4b/web.py-0.62.tar.gz", hash = "sha256:5ce684caa240654cae5950da8b4b7bc178812031e08f990518d072bd44ab525e", size = 623196, upload-time = "2020-11-09T11:24:34.948Z" }
+
+[[package]]
+name = "zope-event"
+version = "6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "setuptools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c2/d8/9c8b0c6bb1db09725395618f68d3b8a08089fca0aed28437500caaf713ee/zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92", size = 18731, upload-time = "2025-09-12T07:10:13.551Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/b5/1abb5a8b443314c978617bf46d5d9ad648bdf21058074e817d7efbb257db/zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3", size = 6409, upload-time = "2025-09-12T07:10:12.316Z" },
+]
+
+[[package]]
+name = "zope-interface"
+version = "8.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/88/3a/7fcf02178b8fad0a51e67e32765cd039ae505d054d744d76b8c2bbcba5ba/zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1", size = 253746, upload-time = "2025-09-25T05:55:51.285Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f2/2f/c10c739bcb9b072090c97c2e08533777497190daa19d190d72b4cce9c7cb/zope_interface-8.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4bd01022d2e1bce4a4a4ed9549edb25393c92e607d7daa6deff843f1f68b479d", size = 207903, upload-time = "2025-09-25T05:58:21.671Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e1/9845ac3697f108d9a1af6912170c59a23732090bbfb35955fe77e5544955/zope_interface-8.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:29be8db8b712d94f1c05e24ea230a879271d787205ba1c9a6100d1d81f06c69a", size = 208345, upload-time = "2025-09-25T05:58:24.217Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/49/6573bc8b841cfab18e80c8e8259f1abdbbf716140011370de30231be79ad/zope_interface-8.0.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:51ae1b856565b30455b7879fdf0a56a88763b401d3f814fa9f9542d7410dbd7e", size = 255027, upload-time = "2025-09-25T05:58:19.975Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/fd/908b0fd4b1ab6e412dfac9bd2b606f2893ef9ba3dd36d643f5e5b94c57b3/zope_interface-8.0.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d2e7596149cb1acd1d4d41b9f8fe2ffc0e9e29e2e91d026311814181d0d9efaf", size = 259800, upload-time = "2025-09-25T05:58:11.487Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/78/8419a2b4e88410520ed4b7f93bbd25a6d4ae66c4e2b131320f2b90f43077/zope_interface-8.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2737c11c34fb9128816759864752d007ec4f987b571c934c30723ed881a7a4f", size = 260978, upload-time = "2025-09-25T06:26:24.483Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/90/caf68152c292f1810e2bd3acd2177badf08a740aa8a348714617d6c9ad0b/zope_interface-8.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf66e4bf731aa7e0ced855bb3670e8cda772f6515a475c6a107bad5cb6604103", size = 212155, upload-time = "2025-09-25T05:59:40.318Z" },
+]