Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/emerald/36/domain value view model #12

Draft
wants to merge 59 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
15cc7db
fix(api): resolve issues where the API was not sending/receiving with…
philtweir May 27, 2024
651020c
chore: ruff fixes
philtweir May 27, 2024
9c220a2
fix: if no period in target, continue working
philtweir May 27, 2024
583ecc6
fix: spot missing/wrong graphs
philtweir May 27, 2024
c0360aa
fix: trigger loading of API on start, not request
philtweir May 28, 2024
acd3ebf
fix(graphql): make unset values none in api
philtweir May 30, 2024
f019248
fix: differences between model_class and model_class_name
philtweir May 30, 2024
a01d67d
fix: work better with (single) related models
philtweir Jun 5, 2024
f162648
fix: add model reloading
philtweir Jun 5, 2024
745e64b
feat: remove from node lists
philtweir Jun 5, 2024
3047b3c
fix: ensure ghost children are working correctly
philtweir Jun 5, 2024
86f1d1e
fix: the django group coming back was a tuple with (group, bool) I've…
taylorn01 Jun 6, 2024
46561e9
chore: remove logs
taylorn01 Jun 6, 2024
632622f
wip: explicit traceback
philtweir Jun 7, 2024
f9ba571
fix: ensure that the tile-saving happens in a transaction so we do no…
philtweir Jun 7, 2024
2435720
fix: remapping broken if against a root field
philtweir Jun 7, 2024
6a648ab
Merge pull request #6 from flaxandteal/fix/group-returning-as-tuple
taylorn01 Jun 7, 2024
3ffeb96
fix: removes all DB calls on class init by calling _build_nodes only …
philtweir Jun 8, 2024
17b13b8
chore: fix ruff
philtweir Jun 8, 2024
514d8bf
Merge pull request #7 from flaxandteal/fix/do-not-hit-db-on-import
philtweir Jun 8, 2024
d41537d
feat: add alpha-level endpoints for concept and resource models, requ…
philtweir Jun 9, 2024
27595ba
chore: fix ruff
philtweir Jun 9, 2024
229050b
Merge pull request #8 from flaxandteal/feat/add-other-endpoints
philtweir Jun 9, 2024
51519ab
fix: do not raise an exception if the user or group is missing, but d…
philtweir Jun 9, 2024
4e918e4
feat: add geojson datatype
philtweir Jun 9, 2024
afd64bc
fix: better debug for missing related resources
philtweir Jun 15, 2024
a04cafc
fix: better context-free debugging
philtweir Jun 10, 2024
5e93551
chore: fix ruff
philtweir Jun 10, 2024
571d3cb
fix: extra protections against ghost resources
philtweir Nov 14, 2024
c01d13e
fix: stop str(...) raising DescriptorsNotYetSet
philtweir Nov 22, 2024
264f5d2
fix: ensure reindexing occurs on save
philtweir Dec 8, 2024
da0cc59
chore: fix CI
philtweir Jun 29, 2024
f3fe10a
chore: fix CI
philtweir Jun 29, 2024
96ac30c
feat(rdm): basic rdm
philtweir Jul 13, 2024
c9cbfaf
feat: support 3.9
philtweir Jul 13, 2024
40ce2a9
fix(concepts): take care of none case
philtweir Jul 6, 2024
71b93ce
fix(concepts): align arches_django with static
philtweir Jul 6, 2024
ae34c90
fix(rdm): ensure semantic nodes bind closures correctly
philtweir Jul 13, 2024
d4b570e
fix: improve debug output
philtweir Jul 13, 2024
ecceb82
fix: ensure right child nodes loaded
philtweir Jul 13, 2024
c2156bb
fix(tests): ensure User is cardinality 1
philtweir Jul 13, 2024
d36f271
feat(rdm): add static concepts
philtweir Jul 13, 2024
73a6b08
feat: support 3.9 for static
philtweir Jul 13, 2024
277db20
chore: tidy ruff
philtweir Jul 6, 2024
d12c948
fix(rdm): ensure compatibility with arches_graphql_client 0.0.11
philtweir Jul 13, 2024
df39e0d
fix: tests restored and made more efficient by reusing seeded data af…
philtweir Sep 13, 2024
e3d0586
fix: skip concepts that have no identifier
philtweir Oct 3, 2024
82788c6
fix(docs): tidy up doc generation
philtweir Oct 26, 2024
18072dc
fix(docs): tidy up doc generation
philtweir Oct 26, 2024
f30b8b7
fix(docs): basic quickstart
philtweir Oct 26, 2024
9934b97
fix(docs): basic quickstart
philtweir Oct 26, 2024
3c8e421
feat(rdm): test creating and returning a concept is consistent
philtweir Nov 2, 2024
4d06a32
feat(rdm): make it possible to create new collections from old
philtweir Nov 4, 2024
ef93090
fix: only attempt to load an identifier if we do not have a working UUID
philtweir Nov 4, 2024
3da26cf
fix: documentation correction to ensure ORM is pulled in
philtweir Nov 23, 2024
559188c
fix: add test for retrieving concept id
philtweir Dec 17, 2024
3576f92
feat: add method to get all collections on a model
philtweir Dec 17, 2024
7fae3dc
fix(arches_django): use a sentinel type for missing groups
philtweir Jan 12, 2025
77e081c
fix(groups): django group view model
philtweir Jan 20, 2025
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
54 changes: 54 additions & 0 deletions .github/workflows/_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from portray import config, render # type: ignore
from typing import Any
import yaml

# Note that this does not work for reloads.
mkdocs = config.mkdocs


def _mkdocs(directory: str, **overrides) -> dict: # type: ignore
superfences = yaml.unsafe_load("""
preserve_tabs: true
custom_fences:
# Mermaid diagrams
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
""")
overrides.setdefault("markdown_extensions", [])
for n, ext in enumerate(overrides["markdown_extensions"]):
if ext == "pymdownx.superfences":
ext = {"pymdownx.superfences": {}}
elif isinstance(ext, dict) and len(ext) == 1 and "pymdownx.superfences" in ext:
...
else:
continue
ext["pymdownx.superfences"].update(superfences)
overrides["markdown_extensions"][n] = ext
res: dict[Any, Any] = mkdocs(directory, **overrides)
return res


config.mkdocs = _mkdocs

mkdocs_render = render.mkdocs


def _mkdocs_render(config: dict[str, Any]) -> Any:
"""Ensures the original config can be reused."""
original_config = list(config.get("markdown_extensions", []))
if original_config and "pymdownx.superfences" in original_config[0]:
original_config = original_config[0]["pymdownx.superfences"]
result = mkdocs_render(config)
config["markdown_extensions"][0]["pymdownx.superfences"] = original_config
else:
result = mkdocs_render(config)
return result


render.mkdocs = _mkdocs_render

if __name__ == "__main__":
from portray.cli import cli # type: ignore

cli()
18 changes: 18 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Docs
on:
push:
tags:
- 'release/v*'
pull_request:
jobs:
static:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: install
run: python -m pip install .\[docs\]
- name: portray
run: python .github/workflows/_build.py on-github-pages -f
8 changes: 5 additions & 3 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ permissions:
jobs:
deploy:

name: upload release to PyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write

steps:
- uses: actions/checkout@v3
Expand All @@ -27,8 +31,6 @@ jobs:
run: |
python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: dist/
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@ This provides simple (server-side) access to Arches resources from Python
as Python objects. It makes no guarantees about efficiency or type-accuracy
but such issues raised will be addressed as far as possible.

## Installation

Basic installation can then happen as follows, _without_ Arches backend support:

```
pip install .
```

To run tests, make sure you have `libsqlite3-mod-spatialite`, or your distribution's equivalent
package for enabling Spatialite in Python. Instead of using a real Arches PostgreSQL database, we spin
a fresh test database up in memory.

**WARNING:** The mock DB behaviour for Python testing is not identical to a
real Arches database, but is adequately close for now, is fast and has no server dependency.

There are several sets of optional dependencies.

### GraphQL

Turns Arches ORM into an API server for Arches.

```
pip install .[graphql]
```

### Arches

Allows Arches ORM to directly manipulate the Arches database. Note that this is only
necessary if you do not already have Arches installed in the current environment.

```
pip install .[arches]
```

### Test

Runs tests across the various backends.

```
pip install .[tests]
python -m pytest
```

## Well-known Resource Models

To provide a partial boundary, this package expects a settings object called
Expand Down Expand Up @@ -39,11 +82,7 @@ extension loading:

## Documentation

Documentation is generated using [pdocs](https://github.com/timothycrosley/pdocs) but,
as the `arches_django` subpackage expects a running Arches instance to be importable
(a side-effect of Django), we add an initialization routine.

python docs/make_doc.py
Documentation is available on [https://flaxandteal.github.io/arches-orm/](https://flaxandteal.github.io/arches-orm/).

## Thanks

Expand Down
61 changes: 58 additions & 3 deletions arches_orm/adapter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
from __future__ import annotations

import logging
from uuid import UUID
from enum import Enum
from typing import Any, Generator, Callable, Literal
from inspect import isgenerator, isgeneratorfunction
from functools import partial, wraps
from contextlib import contextmanager
from contextvars import ContextVar
from abc import ABC, abstractmethod

from .view_models._base import ResourceInstanceViewModel
from .view_models.concepts import ConceptValueViewModel

class Adapter:
logger = logging.getLogger(__name__)

class Adapter(ABC):
config: dict[str, Any]
_context: ContextVar[dict[str, Any] | None]

Expand All @@ -15,9 +26,45 @@ def __init__(self, key):
def __init_subclass__(cls):
ADAPTER_MANAGER.register_adapter(cls)

def __str__(self):
return self.key

def __repr__(self):
return f"<AORA:{self.key}>"

@property
@abstractmethod
def key(self):
...

def set_context_free(self):
self._context.set(None)

def get_rdm(self):
from .collection import ReferenceDataManager
return ReferenceDataManager(self)

@abstractmethod
def retrieve_concept(self, concept_id: str | UUID) -> ConceptValueViewModel:
...

@abstractmethod
def make_concept(self, concept_id: str | UUID, values: dict[UUID, tuple[str, str]], children: list[UUID] | None) -> ConceptValueViewModel:
...

@abstractmethod
def get_collection(self, collection_id: str | UUID) -> type[Enum]:
...

@abstractmethod
def derive_collection(self, collection_id: str | UUID, include: list[UUID] | None, exclude: list[UUID] | None, language: str | None=None) -> type[Enum]:
"""Note that include and exclude should be lists of concept, not value, IDs."""
...

@abstractmethod
def load_from_id(self, resource_id: str, from_prefetch: Callable[[str], Any] | None=None, lazy: bool=False) -> ResourceInstanceViewModel:
...

def get_context(self):
return self._context

Expand Down Expand Up @@ -113,13 +160,13 @@ def wrapper(adapter_key: str | None, f: Callable[[Any], Any]) -> Callable[[Any],
@wraps(f)
def _g(*args, **kwargs):
adapter = get_adapter(adapter_key)
with adapter.context(_ctx=ctx, _override=True) as cvar:
with adapter.context(_ctx=ctx, _override=True) as _:
yield from f(*args, **kwargs)

@wraps(f)
def _f(*args, **kwargs):
adapter = get_adapter(adapter_key)
with adapter.context(_ctx=ctx, _override=True) as cvar:
with adapter.context(_ctx=ctx, _override=True) as _:
return f(*args, **kwargs)
return _g if isgenerator(f) or isgeneratorfunction(f) else _f

Expand All @@ -130,5 +177,13 @@ def admin(adapter_key: str | None=None):
with get_adapter(adapter_key).context(None, _override=True) as cvar:
yield cvar

def admin_everywhere(key=None):
get_adapter(key=key).set_context_free()
logger.warning(
"ARCHES ORM ADMINISTRATION MODE ON: use for debugging only, "
"otherwise use the `context_free` or `context` decorator/with statement to "
"achieve this result safely."
)

ADAPTER_MANAGER = AdapterManager()
get_adapter = ADAPTER_MANAGER.get_adapter
5 changes: 5 additions & 0 deletions arches_orm/arches_django/adapter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from enum import Enum
from arches_orm.adapter import Adapter

logger = logging.getLogger(__name__)
Expand All @@ -21,6 +22,10 @@ def get_wrapper(self):

return ArchesDjangoResourceWrapper

def get_collection(self, collection_id: str) -> type[Enum]:
from .datatypes.concepts import retrieve_collection
return retrieve_collection(collection_id)

def load_from_id(self, resource_id, from_prefetch=None, lazy=False):
from arches_orm.wkrm import get_resource_models_for_adapter
from arches.app.models.resource import Resource
Expand Down
3 changes: 2 additions & 1 deletion arches_orm/arches_django/datatypes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ._register import get_view_model_for_datatype
from . import concepts, semantic, resource_instances, string, user, django_group
from . import concepts, semantic, resource_instances, string, user, django_group, geojson_feature_collection

__all__ = [
"get_view_model_for_datatype",
Expand All @@ -9,4 +9,5 @@
"string",
"user",
"django_group",
"geojson_feature_collection"
]
Loading
Loading