Skip to content
Closed
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
6 changes: 3 additions & 3 deletions .github/workflows/documentation-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ jobs:
- '3.12'

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r rtd-requirements.txt
- name: Build HTML docs with Sphinx
run: |
sphinx-build docs/source docs/build/html
60 changes: 60 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
# The standard xchem Python package release process.
# Run on 'Release' and published to PyPI as a 'trusted' publisher.
#
# See https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/
# See https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
name: Release

on:
release:
types:
- published

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install build package
run: |
pip install --upgrade pip
pip install uv --user
- name: Build
run: |
uv version $GITHUB_REF_SLUG
uv build
- name: Store the distribution
uses: actions/upload-artifact@v5
with:
name: python-package-distribution
path: dist/

publish:
Comment on lines +16 to +43

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 5 months ago

In general, the fix is to explicitly restrict the GITHUB_TOKEN permissions for the workflow/jobs so they only have the minimum required access. For this workflow, the build job only needs to read repository contents and interact with artifacts; it does not need to write to the repo, issues, or PRs. The publish job already correctly grants id-token: write and otherwise inherits defaults, so we can keep that as-is or further restrict if desired; the CodeQL finding is specifically on the build job.

The best minimal fix without changing existing functionality is to add a permissions block to the build job that limits contents to read. This satisfies CodeQL’s recommendation (contents: read as a minimal starting point), matches what actions/checkout requires, and avoids unintentionally broad write permissions. Concretely, in .github/workflows/release.yaml, inside jobs.build (below runs-on: ubuntu-latest is a clear spot), add:

    permissions:
      contents: read

No new imports or methods are needed; this is purely a workflow YAML change.

Suggested changeset 1
.github/workflows/release.yaml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -15,6 +15,8 @@
   build:
     name: Build distribution
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
     - name: Checkout
       uses: actions/checkout@v6
EOF
@@ -15,6 +15,8 @@
build:
name: Build distribution
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
Copilot is powered by AI and may make mistakes. Always verify output.
name: Publish to PyPI
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/xchem-fragalysis
permissions:
id-token: write
steps:
- name: Download distribution
uses: actions/download-artifact@v6
with:
name: python-package-distribution
path: dist/
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: requirements.txt
- requirements: requirements.txt
57 changes: 47 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Fragalysis

![documentation build](https://github.com/xchem/fragalysis/workflows/documentation%20build/badge.svg)
![PyPI - Version](https://img.shields.io/pypi/v/xchem-fragalysis)

![documentation build](https://github.com/xchem/fragalysis/actions/workflows/documentation-build.yaml/badge.svg)
![release](https://github.com/xchem/fragalysis/actions/workflows/release.yaml/badge.svg)

![RTD latest build](https://readthedocs.org/projects/fragalysis/badge/?version=latest&style=plastic)

Expand All @@ -13,7 +16,7 @@ as a 'base' for all documentation relating to the application.
> This replaces the original fragalysis repository, which was responsible for the
RDKit-based Python tools for analysis of protein-ligand interactions.
The original repository has been renamed and can now be found in the
[fragalysis-package] repository.
[fragutils] repository.

The repository is compatible with ReadTheDocs and you can find the latest documentation
(built from the most recent changes on this repository's `main` branch) on [ReadTheDocs] at
Expand All @@ -22,16 +25,42 @@ https://fragalysis.readthedocs.io/en/latest/.
Stable documentation (built from the most recent tag in this repository) can be found at
https://fragalysis.readthedocs.io/en/stable/.

## XChem repositories
A significant amount of our work resides in public GitHub repositories in the
`XChem` organisation. A growing list of all the repositories that represent
our work can be found in the **Related repositories** section below. We also rely on a
number of _external_ repositories (those not managed by us directly). Importantly,
when we find that the material in such a repository becomes crucial to our work we
**SHOULD** consider *fork* it to `XChem`. Forking allows us to: -

- Preserve content
- Adopt our own development processes, which include: -
- A consistent release mechanism
- Consistent package naming (PyPI packages all begin `xchem-` for example)
- Use of linting, formatting, and test tools that we like
- Improve stability (we like working with static *tagged* references)

In `xchem` we tend to follow a trunk-based development strategy that we explain in
our [trunk-based-development] repository. It's provides us with a centrally-defined
set of policies with accompanying documentation on its [wiki] - a place
where we provide guidance, some advanced topics, and development inspiration.

**Working on codes that's not in XChem?**

If you are using code that we don't own (manage) we **MUST** consider forking the
repository into `XChem`, applying our development process, tagging it when important
changes are available, and asking others to switch to using our repository and the
packages it produces.

## Local development
To compile the documentation, which is based on [Sphinx],
start with a Python environment (ideally Python 3.12, as that's the version used by
ReadTheDocs and the GitHub CI workflow) and install the dependencies: -

python -m venv venv
source venv/bin/activate
pip install --upgrade pip

pip install -r requirements.txt
pip install uv
uv venv
source .venv/bin/activate
pip install -r rtd-requirements.txt

Then, to build the HTML documentation, run the following command: -

Expand All @@ -44,9 +73,11 @@ The Fragalysis Stack you find running in Kubernetes relies on a number of relate
(and diverse) repositories. We've tried to capture references to all of them
below, in no particular order: -

**xchem respositories**
**xchem repositories**

[fragalysis-package] : Logic that allows connection to the neo4j graph
[trunk-based-development] : A repository for development guidance

[fragutils] : Logic that allows connection to the neo4j graph

[fragalysis-backend] : Django/REST Framework application

Expand All @@ -57,7 +88,10 @@ to create the container image

[fragalysis-api] : Command-line API utilities

[fragalysis-database] : A reference PostgreSQL database image (with extras)

[fragalysis-keycloak] : A specialised build of keycloak to provide a custom login theme
deprecated

[fragalysis-ispyb-target-access-authenticator] : Code for the container image that acts
as an interface to ISPyB, yielding Target Access Strings based on username
Expand Down Expand Up @@ -136,11 +170,14 @@ typically kubernetes or slurm: -
[fragalysis-ispyb-target-access-authenticator]: https://github.com/xchem/fragalysis-ispyb-target-access-authenticator
[fragalysis-rdkit-cartridge-pgvector-debian]: https://github.com/xchem/fragalysis-rdkit-cartridge-pgvector-debian
[fragalysis-api]: https://github.com/xchem/fragalysis-api
[fragalysis-database]: https://github.com/xchem/fragalysis-database
[fragalysis-backend]: https://github.com/xchem/fragalysis-backend
[fragalysis-frontend]: https://github.com/xchem/fragalysis-frontend
[fragalysis-keycloak]: https://github.com/xchem/fragalysis-keycloak
[fragalysis-package]: https://github.com/xchem/fragalysis-package
[fragutils]: https://github.com/xchem/fragutils
[fragalysis-stack]: https://github.com/xchem/fragalysis-stack
[readthedocs]: https://app.readthedocs.org/dashboard/
[sphinx]: https://www.sphinx-doc.org/en/master
[trunk-based-development]: https://github.com/xchem/trunk-based-development
[xchem-align]: https://github.com/xchem/xchem-align
[wiki]: https://github.com/xchem/trunk-based-development/wiki
33 changes: 32 additions & 1 deletion docs/source/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,40 @@
![GitHub last commit](https://img.shields.io/github/last-commit/xchem/fragalysis-frontend?style=for-the-badge&label=last%20f%2Fe%20update)



## Release Notes

- [2026.02.1](https://github.com/xchem/fragalysis-stack/releases/tag/2026.02.1)
* 1997 [Results (former scores) invisible after unification](https://github.com/m2ms/fragalysis-frontend/issues/1997)
* 1998 [Change version mismatch message (XCAv3)](https://github.com/m2ms/fragalysis-frontend/issues/1998)
* 2003 [Incorrectly assigned shortcodes](https://github.com/m2ms/fragalysis-frontend/issues/2003)
* 2028 [Target loader no main_observation error](https://github.com/m2ms/fragalysis-frontend/issues/2028)
- [2026.01.1](https://github.com/xchem/fragalysis-stack/releases/tag/2026.01.1)
* 1908 [Mpro download fails with 403](https://github.com/m2ms/fragalysis-frontend/issues/1908)
* 1922 [Major backend dependency upgrade](https://github.com/m2ms/fragalysis-frontend/issues/1922)
* 1932 [More options to download endpoint (e.g. sqlite files)](https://github.com/m2ms/fragalysis-frontend/issues/1932)
* 1934 [Squonk file transfer fails if no proteins supplied](https://github.com/m2ms/fragalysis-frontend/issues/1934)
* 1935 [Incorrect ligand file path handling in target loader](https://github.com/m2ms/fragalysis-frontend/issues/1935)
* 1936 [Missing snapshot thumbnails](https://github.com/m2ms/fragalysis-frontend/issues/1936)
* 1938 [Assay data upload bug](https://github.com/m2ms/fragalysis-frontend/issues/1938)
* 1939 [Fix __str__ method AttributeError](https://github.com/m2ms/fragalysis-frontend/issues/1939)
* 1944 [Changes to plot_data endpoint](https://github.com/m2ms/fragalysis-frontend/issues/1944)
* 1955 [Target loader support for v3 XCA data format](https://github.com/m2ms/fragalysis-frontend/issues/1955)
* 1966 [Path file restoration for v2 data (post-v3 migration)](https://github.com/m2ms/fragalysis-frontend/issues/1966)
* 1981 [Investigate OSV scanner during build](https://github.com/m2ms/fragalysis-frontend/issues/1981)
* 1991 [Remove Sentry error transmission?](https://github.com/m2ms/fragalysis-frontend/issues/1991)
* 1992 [Fix v3 data loading](https://github.com/m2ms/fragalysis-frontend/issues/1992)
* 2002 [Make cset upload ignore superseded observations](https://github.com/m2ms/fragalysis-frontend/issues/2002)
* 1598 https://github.com/m2ms/fragalysis-frontend/issues/1598
* 1819 https://github.com/m2ms/fragalysis-frontend/issues/1819
* 1889 [Expand observation column on first target load](https://github.com/m2ms/fragalysis-frontend/issues/1989)
* 1912 https://github.com/m2ms/fragalysis-frontend/issues/1912
* 1936 https://github.com/m2ms/fragalysis-frontend/issues/1936
* 1945 https://github.com/m2ms/fragalysis-frontend/issues/1945
* 1960 https://github.com/m2ms/fragalysis-frontend/issues/1960
* 1965 https://github.com/m2ms/fragalysis-frontend/issues/1965
* 1967 https://github.com/m2ms/fragalysis-frontend/issues/1967
* 1969 https://github.com/m2ms/fragalysis-frontend/issues/1969
* 1974 https://github.com/m2ms/fragalysis-frontend/issues/1974
- [2025.11.1](https://github.com/xchem/fragalysis-stack/releases/tag/2025.10.3)
* _FEATURE_ [Filtering by geometric coordinates](https://github.com/m2ms/fragalysis-frontend/issues/1821)
* _IMPROVEMENT_ [More options to download endpoint (e.g. sqlite files)](https://github.com/m2ms/fragalysis-frontend/issues/1932)
Expand Down
32 changes: 30 additions & 2 deletions docs/source/py_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pip install --user -e .
To get a list of target dictionaries accessible with an optional authentication token:

```
from fragalysis.requests.download import target_list
from fragalysis.requests import target_list
targets = target_list(stack="production", token=token)
```

Expand All @@ -31,7 +31,7 @@ The `token` keyword can be ommitted if only accessing public targets, and `stack
To get a list of target dictionaries accessible with an optional authentication token:

```
from fragalysis.requests.download import download_target
from fragalysis.requests import download_target
download_target(name=target_name, tas=target_access_string, token=token, stack="production", destination=".")
```

Expand All @@ -47,6 +47,34 @@ The available POST request parameters to `/api/download_structures` can be seen
Relevant developer contacts on github are @mwinokan, @kaliif, and @boriskovar-m2ms.
```

## Tracking experiment uploads

For the purpose of automated scraping you can use the `target_uploads` function to see if there have been any recent uploads:

```
uploads = target_uploads(statistics_only=True)
```

This returns a dictionary keyed by (target_name, target_access_string):

```
{
('A71EV2A', 'lb32627-66'): {
'target_id': 31,
'target_name': 'A71EV2A',
'target_access_string': 'lb32627-66',
'project_id': 1,
'last_upload_index': 8,
'last_upload_timestamp': datetime.datetime(2025, 10, 8, 12, 34, 0, 693173, tzinfo=datetime.timezone.utc)
},
...
}
```

You can then check `last_upload_index` against your most recent download, or use `last_upload_timestamp` to determine if a fresh download is needed.

Then use the `download_target` function as described above.

## API reference

```{autodoc2-summary}
Expand Down
2 changes: 1 addition & 1 deletion fragalysis/requests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .download import target_list, download_target
from .download import target_list, download_target, target_uploads
from .csets import upload_sdf
from .plotly import upload_graph
from .fragmenstein import fragmenstein_place, fragmenstein_combine
Expand Down
84 changes: 83 additions & 1 deletion fragalysis/requests/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from urllib.parse import urljoin

from .session import _session
from .urls import PROJECTS_URL, TARGETS_URL, DOWNLOAD_URL
from .urls import PROJECTS_URL, TARGETS_URL, DOWNLOAD_URL, TARGET_EXPERIMENT_UPLOADS_URL


def target_list(
Expand Down Expand Up @@ -239,3 +239,85 @@ def download_target(
return None

return target_dir


def target_uploads(
stack: str = "production",
token: str | None = None,
statistics_only: bool = False,
) -> dict[(str,str), list]:
"""Request a dictionary of uploads keyed by target name and target_access_strings from a Fragalysis deployment

:param stack: shorthand or URL of Fragalysis deployment, defaults to "production"
:param token: optional authentication token
:param statistics_only: don't list individual uploads
:returns: list of target upload dictionaries with "id", "title", and "project" keys
"""

from datetime import datetime

with _session(stack, token) as session:

# get the API response

url = urljoin(session.root, TARGET_EXPERIMENT_UPLOADS_URL)

response = session.get(url)

if not response.ok:
mrich.error("Request failed", url, response.status_code)
return None

data = response.json()

# group the data by (target_name, proposal_number)

formatted = {}
for d in data["results"]:

key = (d["target_name"], d["proposal_number"])

formatted.setdefault(key, {})
formatted[key].setdefault("uploads", [])

formatted[key]["target_id"]=d["target"]
formatted[key]["target_name"]=d["target_name"]
formatted[key]["target_access_string"]=d["proposal_number"]
formatted[key]["project_id"]=d["project"]

# reformat the serialised data

formatted[key]["uploads"].append(dict(
xca_tarball_url=d["tarball"],
committer_id=d["committer"],
committer_name=d["committer_name"],
upload_index=d["upload_version"],
data_format=f"{d['data_version_major']}.{d['data_version_minor']}",
timestamp=datetime.fromisoformat(d["commit_datetime"].replace("Z", "+00:00")),
))

# sort and format the data

for key, d in formatted.items():

new_d = {}

# general information
new_d["target_id"]=d["target_id"]
new_d["target_name"]=d["target_name"]
new_d["target_access_string"]=d["target_access_string"]
new_d["project_id"]=d["project_id"]

# sort uploads
sorted_uploads = sorted(d["uploads"], key=lambda d: d["upload_index"])

# latest statistics
new_d["last_upload_index"]=sorted_uploads[-1]["upload_index"]
new_d["last_upload_timestamp"]=sorted_uploads[-1]["timestamp"]

if not statistics_only:
new_d["uploads"] = sorted_uploads

formatted[key] = new_d

return formatted
1 change: 1 addition & 0 deletions fragalysis/requests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
TASK_STATUS_URL = "/viewer/task_status/"
USER_URL = "/api/user"
PLOT_DATA_URL = "/api/plot_data/"
TARGET_EXPERIMENT_UPLOADS_URL = "/api/target_experiment_uploads/"
Loading
Loading