Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This document provides guidance for GitHub Copilot when working on the Whyis project.

> **Keep instructions in sync.** This file and [`CLAUDE.md`](../CLAUDE.md) (in the repository root) are companion instruction files — one for GitHub Copilot, one for Claude — and share the same project guidance. Whenever you change the guidance in this file, apply the equivalent change to `CLAUDE.md` so both assistants stay aligned. Only the tool-specific framing (title, intro, and this sync note) should differ between them.

## Project Overview

Whyis is a nano-scale knowledge graph publishing, management, and analysis framework built with Python and Flask. It manages knowledge as nanopublications, which are the smallest publishable units of knowledge graphs with associated provenance and publication information.
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ jobs:
- name: Install Python dependencies
timeout-minutes: 15
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade pip wheel
pip install "setuptools<80"
# Install test dependencies first (lightweight)
pip install -r requirements-test.txt
# Install core dependencies needed for unit tests (without full whyis package)
# Use --no-deps where possible to avoid dependency resolution loops
pip install rdflib rdflib-jsonld Flask Flask-Security-Too Flask-Script Flask-PluginEngine
pip install filedepot Markdown
pip install filedepot Markdown pytz
# Optional dependencies - skip if they cause issues
pip install celery eventlet redislite nltk || true
pip install sadi setlr sdd2rdf oxrdflib || true
Expand Down
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ def run(self):
'requests'
],
install_requires = [
# Python 3.12 no longer bundles setuptools, and setuptools>=81 has
# removed pkg_resources. Several pinned dependencies (e.g.
# Flask-Security 3.0.0) still import pkg_resources, so pin to a
# setuptools that still ships it. Whyis's own code uses whyis._resources
# instead and does not need pkg_resources.
'setuptools<81',
'beautifulsoup4==4.7.1',
'bibtexparser==1.1.0',
'celery<6.0.0',
Expand Down
66 changes: 66 additions & 0 deletions whyis/_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Dependency-free replacements for the ``pkg_resources`` resource helpers.

``pkg_resources`` (shipped with setuptools) is deprecated and is no longer
guaranteed to be importable on Python 3.12+, where setuptools is not part of a
default environment. Whyis is always installed as a regular directory on disk
(never as a zipped egg), so we can resolve package resources directly from the
filesystem via :mod:`importlib`. These functions mirror the small subset of the
``pkg_resources`` API that Whyis relies on.
"""

import importlib.util
import os

__all__ = [
"resource_filename",
"resource_listdir",
"resource_string",
"resource_exists",
"resource_stream",
]


def _base_dir(package):
"""Return the on-disk directory associated with a package or module name.

For a package (e.g. ``"whyis"``) this is the package directory. For a
module (e.g. ``"whyis.fuseki.fuseki"``) it is the directory containing the
module file, matching ``pkg_resources`` resource-resolution semantics.
"""
spec = importlib.util.find_spec(package)
if spec is None:
raise ModuleNotFoundError(f"No module named {package!r}")
if spec.submodule_search_locations:
return list(spec.submodule_search_locations)[0]
if spec.origin and spec.origin not in ("built-in", "frozen"):
return os.path.dirname(spec.origin)
raise ValueError(f"Cannot determine a resource directory for {package!r}")


def resource_filename(package, resource=""):
"""Return the filesystem path to ``resource`` within ``package``."""
base = _base_dir(package)
if not resource:
return base
return os.path.join(base, *resource.split("/"))


def resource_listdir(package, resource):
"""List the contents of a directory ``resource`` within ``package``."""
return os.listdir(resource_filename(package, resource))


def resource_string(package, resource):
"""Return the contents of ``resource`` within ``package`` as bytes."""
with open(resource_filename(package, resource), "rb") as handle:
return handle.read()


def resource_exists(package, resource):
"""Return whether ``resource`` exists within ``package``."""
return os.path.exists(resource_filename(package, resource))


def resource_stream(package, resource):
"""Open ``resource`` within ``package`` as a binary stream."""
return open(resource_filename(package, resource), "rb")
2 changes: 1 addition & 1 deletion whyis/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from whyis.nanopub import NanopublicationManager
from whyis.authenticator import SingleUserAuthenticator
# from flask_login.config import EXEMPT_METHODS
from pkg_resources import resource_filename
from whyis._resources import resource_filename

rdflib.plugin.register('sparql', Result,
'rdflib.plugins.sparql.processor', 'SPARQLResult')
Expand Down
2 changes: 1 addition & 1 deletion whyis/commands/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from base64 import b64encode
import os
from cookiecutter.main import cookiecutter
from pkg_resources import resource_filename, resource_listdir
from whyis._resources import resource_filename, resource_listdir

try:
from pip._internal.operations import freeze
Expand Down
2 changes: 1 addition & 1 deletion whyis/fuseki/fuseki.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pkg_resources import resource_filename, resource_listdir, resource_string
from whyis._resources import resource_filename, resource_listdir, resource_string
import subprocess
import os
import sys
Expand Down
2 changes: 1 addition & 1 deletion whyis/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import sys
import os
from cookiecutter.main import cookiecutter
from pkg_resources import resource_filename, resource_listdir
from whyis._resources import resource_filename, resource_listdir

from whyis.config.utils import import_config_module, UnconfiguredAppException
import json
Expand Down
2 changes: 1 addition & 1 deletion whyis/plugin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import render_template
from flask_pluginengine import Plugin as PluginBase
from flask_pluginengine import PluginBlueprint, current_plugin
from pkg_resources import resource_exists, resource_stream, resource_filename
from whyis._resources import resource_exists, resource_stream, resource_filename
import rdflib

class Listener:
Expand Down