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" }, +]