Skip to content
Merged
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
9 changes: 5 additions & 4 deletions doc/build_authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import requests
from pathlib import Path


def full_name(au):
# contributors may give a pseudonym (alternateName) rather than their given and family names
return " ".join(filter(None, (au.get("givenName"), au.get("familyName")))) or au["alternateName"]
Expand Down Expand Up @@ -65,17 +66,17 @@ def get_affiliation_text(affil):


def affiliation_number(au, affiliations):
index = list(affiliations.keys()).index(affiliation_key(au["affiliation"][0])) # to do: handle people with multiple affiliations
index = list(affiliations.keys()).index(
affiliation_key(au["affiliation"][0])
) # to do: handle people with multiple affiliations
return index + 1


affiliations_text = "\n".join(
f"{i}. {get_affiliation_text(affil)}" for i, affil in enumerate(affiliations.values(), start=1)
)

authors_text = "\n".join(
[f"- {full_name(au)} [{affiliation_number(au, affiliations)}]" for au in author_data]
)
authors_text = "\n".join([f"- {full_name(au)} [{affiliation_number(au, affiliations)}]" for au in author_data])

with open("authors.rst", "w") as fp:
fp.write(template.format(authors=authors_text, affiliations=affiliations_text))
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def full_name(au):

# The full version, including alpha/beta/rc tags
with open("../pyproject.toml", "rb") as fp:
release = tomllib.load(fp)['project']["version"]
release = tomllib.load(fp)["project"]["version"]

# The short X.Y version
version = ".".join(release.split(".")[:2])
Expand All @@ -64,7 +64,7 @@ def full_name(au):
templates_path = ["_templates"]

# The suffix(es) of source filenames.
source_suffix = {'.rst': 'restructuredtext'}
source_suffix = {".rst": "restructuredtext"}

# The master toctree document.
master_doc = "index"
Expand Down
9 changes: 9 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ Changes in behaviour
is now sorted by ``family_name``. The ordering of results is now described in :doc:`queries`
(`#129 <https://github.com/HumanBrainProject/fairgraph/issues/129>`_).

- The remaining deprecated names from the renaming of "fields" to "properties" in version 0.12.0 have been removed:

- the class attributes ``fields``, ``field_names``, ``required_field_names`` and ``existence_query_fields``;
use ``properties`` (with ``reverse_properties``, or ``all_properties`` for both), ``property_names``,
``required_property_names`` and ``existence_query_properties`` instead.
Note that ``required_field_names`` returned the names of all properties, not only the required ones.
- the ``fairgraph.fields`` module, which has not worked since version 0.13.0, as importing it raised
:exc:`ModuleNotFoundError`. Use ``openminds.properties.Property`` in place of ``Field``.

Bug fixes
---------

Expand Down
4 changes: 1 addition & 3 deletions fairgraph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ def uuid(self) -> Union[str, None]:
else:
return None

def children(
self, client: KGClient, follow_links: Optional[Dict[str, Any]] = None
) -> List[Releasable]:
def children(self, client: KGClient, follow_links: Optional[Dict[str, Any]] = None) -> List[Releasable]:
pass

def is_released(self, client: KGClient, with_children: bool = False) -> bool:
Expand Down
16 changes: 11 additions & 5 deletions fairgraph/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,7 @@ def __init__(
retry_backoff: float = 5.0,
):
if openminds_version not in ("v4", "v5"):
raise ValueError(
f"openminds_version must be 'v4' or 'v5', got {openminds_version!r}"
)
raise ValueError(f"openminds_version must be 'v4' or 'v5', got {openminds_version!r}")
self.openminds_version = openminds_version
if not have_kg_core:
raise ImportError("Please install the ebrains-kg-core package")
Expand Down Expand Up @@ -447,7 +445,11 @@ def _query(release_status, from_index, size):
)
error_context = f"_query(release_status={release_status} query_id={query_id} filter={filter} instance_id={instance_id} size={size} from_index={from_index})"
return self._check_response(
response, error_context=error_context, expected_instance_id=instance_id, id_key=id_key, ignore_not_found=True
response,
error_context=error_context,
expected_instance_id=instance_id,
id_key=id_key,
ignore_not_found=True,
)

else:
Expand All @@ -463,7 +465,11 @@ def _query(release_status, from_index, size):
)
error_context = f"_query(release_status={release_status} query_id={query_id} filter={filter} instance_id={instance_id} size={size} from_index={from_index})"
return self._check_response(
response, error_context=error_context, expected_instance_id=instance_id, id_key=id_key, ignore_not_found=True
response,
error_context=error_context,
expected_instance_id=instance_id,
id_key=id_key,
ignore_not_found=True,
)

if release_status == "any":
Expand Down
4 changes: 3 additions & 1 deletion fairgraph/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ def upload(
break
except AuthenticationError as err:
if attempt < max_retries:
logger.warning("Authentication error, refreshing client (attempt %d/%d)", attempt + 1, max_retries)
logger.warning(
"Authentication error, refreshing client (attempt %d/%d)", attempt + 1, max_retries
)
client.refresh()
else:
raise
Expand Down
4 changes: 2 additions & 2 deletions fairgraph/embedded.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ def __repr__(self):
return template.format(self=self)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.to_jsonld(embed_linked_nodes=LinkedNodeEmbedding.NEVER) == other.to_jsonld(
return isinstance(other, self.__class__) and self.to_jsonld(
embed_linked_nodes=LinkedNodeEmbedding.NEVER
)
) == other.to_jsonld(embed_linked_nodes=LinkedNodeEmbedding.NEVER)

@classmethod
def from_jsonld(cls, data: JSONdict) -> Union[None, KGEmbedded]:
Expand Down
25 changes: 0 additions & 25 deletions fairgraph/fields.py

This file was deleted.

14 changes: 10 additions & 4 deletions fairgraph/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@

def list_kg_classes(module):
"""List all KG classes defined in the given module."""
return [obj for name, obj in inspect.getmembers(module)
if inspect.isclass(obj) and issubclass(obj, KGObject) and obj.__module__.startswith(module.__name__)]
return [
obj
for name, obj in inspect.getmembers(module)
if inspect.isclass(obj) and issubclass(obj, KGObject) and obj.__module__.startswith(module.__name__)
]


def list_embedded_metadata_classes(module):
"""List all embedded metadata classes defined in the given module."""
return [obj for name, obj in inspect.getmembers(module)
if inspect.isclass(obj) and issubclass(obj, KGEmbedded) and obj.__module__.startswith(module.__name__)]
return [
obj
for name, obj in inspect.getmembers(module)
if inspect.isclass(obj) and issubclass(obj, KGEmbedded) and obj.__module__.startswith(module.__name__)
]


def set_error_handling(value, module):
Expand Down
8 changes: 4 additions & 4 deletions fairgraph/kgobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,10 +1078,10 @@ def by_name(
if not name_properties:
raise AttributeError(f"{cls.__name__} doesn't have a name-like property")

# The KG has no way to apply a single filter across several properties, so we query each of them in turn and
# merge the results.
# The regular expression is only a pre-filter. It returns a superset of the matches, since the KG's regex
# matching is always case-insensitive, which is then narrowed down, using the same matching rules as the
# The KG has no way to apply a single filter across several properties, so we query each of them in turn and
# merge the results.
# The regular expression is only a pre-filter. It returns a superset of the matches, since the KG's regex
# matching is always case-insensitive, which is then narrowed down, using the same matching rules as the
# openMINDS instance-library search.
pattern = Regex(
build_name_regex(name, match=match, case_sensitive=case_sensitive, ignore_accents=ignore_accents)
Expand Down
4 changes: 1 addition & 3 deletions fairgraph/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,7 @@ def resolve(
resolved_values: List[Any] = []
for value in as_list(values):
if isinstance(value, Resolvable):
if isinstance(value, KGNode) and isinstance(
value, Releasable
):
if isinstance(value, KGNode) and isinstance(value, Releasable):
# i.e. isinstance(value, KGObject) - already resolved
resolved_values.append(value)
else:
Expand Down
7 changes: 3 additions & 4 deletions fairgraph/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,15 @@
from openminds.base import IRI, EmbeddedMetadata, LinkedMetadata, Node
from .utility import as_list, expand_uri


logger = logging.getLogger("fairgraph")


class Regex(str):
"""
A regular expression, for use as a filter value in place of a plain string.

Where a plain string filters with the KG's "CONTAINS" operator, a `Regex` filters with "REGEX".
The KG applies the pattern as a search rather than a full match,
Where a plain string filters with the KG's "CONTAINS" operator, a `Regex` filters with "REGEX".
The KG applies the pattern as a search rather than a full match,
so a pattern with no anchors matches anywhere in the property value; use "^" and "$" to anchor it.
Matching is always case-insensitive, whatever the pattern says.

Expand All @@ -57,7 +56,7 @@ class Regex(str):

def __new__(cls, pattern: str):
try:
# The KG's regular expression engine is not Python's, so this does not guarantee the KG will accept
# The KG's regular expression engine is not Python's, so this does not guarantee the KG will accept
# the pattern, but it catches common errors such as an unbalanced bracket or parenthesis.
re.compile(pattern)
except re.error as err:
Expand Down
40 changes: 1 addition & 39 deletions fairgraph/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from __future__ import annotations
from itertools import chain
from typing import TYPE_CHECKING, Union, List, Dict
from warnings import warn

from openminds.properties import Property
from openminds.registry import Registry
Expand Down Expand Up @@ -65,8 +64,7 @@ def __new__(meta, name, bases, class_dict):
else:
# Fallback for non-versioned paths or test classes
class_dict["class_name"] = ".".join(
module.replace("fairgraph.openminds", f"openminds.{OPENMINDS_VERSION}").split(".")[:3]
+ [name]
module.replace("fairgraph.openminds", f"openminds.{OPENMINDS_VERSION}").split(".")[:3] + [name]
)
class_dict["preferred_import_path"] = class_dict["class_name"]
cls = Registry.__new__(meta, name, bases, class_dict)
Expand Down Expand Up @@ -110,42 +108,6 @@ def all_property_names(cls):
def reverse_property_names(cls):
return [p.name for p in cls.reverse_properties]

@property
def fields(cls):
warn(
"Use of the 'fields' attribute is deprecated, it will be removed in a future release. "
"Use 'properties' instead",
DeprecationWarning,
)
return cls.properties + cls.reverse_properties

@property
def field_names(cls):
warn(
"Use of the 'field_names' attribute is deprecated, it will be removed in a future release. "
"Use 'property_names' instead",
DeprecationWarning,
)
return cls.property_names

@property
def required_field_names(cls):
warn(
"Use of the 'required_field_names' attribute is deprecated, it will be removed in a future release. "
"Use 'required_property_names' instead",
DeprecationWarning,
)
return cls.property_names

@property
def existence_query_fields(cls):
warn(
"Use of the 'existence_query_fields' attribute is deprecated, it will be removed in a future release. "
"Use 'existence_query_properties' instead",
DeprecationWarning,
)
return cls.existence_query_properties


# Deprecated alias
Node = NodeMeta
8 changes: 1 addition & 7 deletions fairgraph/utility/activity_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ def __repr__(self):
return f"{self.type}: {self.cls}({self.id}) in '{self.space}'"

def as_dict(self):
return {
"cls": self.cls,
"id": self.id,
"delta": self.delta,
"space": self.space,
"type_": self.type
}
return {"cls": self.cls, "id": self.id, "delta": self.delta, "space": self.space, "type_": self.type}


class ActivityLog:
Expand Down
10 changes: 5 additions & 5 deletions fairgraph/utility/name_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
uncomment the import line, and bump ``openminds>=`` in pyproject.toml.

Everything after the block is fairgraph-specific: it *calls* the helpers above rather than
restating anything they know, so the swap changes no behaviour.
restating anything they know, so the swap changes no behaviour.

While the copy is in place, ``TestParityWithOpenMINDS`` in ``test_name_matching.py``
While the copy is in place, ``TestParityWithOpenMINDS`` in ``test_name_matching.py``
guards against it drifting from the original.
"""

Expand Down Expand Up @@ -126,7 +126,7 @@ def matches_name(

def _fold_maps() -> Tuple[Dict[str, Set[str]], Dict[str, Set[str]]]:
"""
Return (single, multi): mappings from a plain-letter form to the characters that fold onto it,
Return (single, multi): mappings from a plain-letter form to the characters that fold onto it,
split by whether that form is one character ("u" <- "ù", "ü", ...) or several ("ss" <- "ß", "ae" <- "æ", ...).

Built by calling `remove_accents()`, so it cannot drift from it.
Expand Down Expand Up @@ -156,8 +156,8 @@ def _expand(text: str, case_sensitive: bool, ignore_accents: bool) -> str:
Turn a search string into a regular expression matching every spelling of it that
`matches_name()` would consider equal, under the given options.

The KG's REGEX filter is always case-insensitive, so case is not expressed here;
it is enforced by the local `matches_name()` pass instead.
The KG's REGEX filter is always case-insensitive, so case is not expressed here;
it is enforced by the local `matches_name()` pass instead.
What must be expressed is the folding of accented and special letters, which the KG does not do.
"""
single, multi = _fold_maps()
Expand Down
Loading
Loading