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
7 changes: 5 additions & 2 deletions .github/workflows/basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ jobs:
- name: Run tests
run: |
# FIXME: The unit tests currently only work with editable installs
# Install DIRACCommon first to ensure dependencies are resolved correctly
pip install -e ./dirac-common[testing]
pip install -e .[server,testing]
${{ matrix.command }}
env:
Expand Down Expand Up @@ -135,9 +137,10 @@ jobs:
sed -i.bak 's@editable = true@editable = false@g' pixi.toml
rm pixi.toml.bak
# Add annotations to github actions
pixi add --no-install --pypi --feature diracx-core pytest-github-actions-annotate-failures
pixi add --pypi --feature diracx-core pytest-github-actions-annotate-failures
# Add the current DIRAC clone to the pixi.toml
pixi add --no-install --pypi --feature diracx-core 'DIRAC @ file://'$PWD'/../DIRAC'
pixi add --pypi --feature diracx-core 'DIRACCommon @ file://'$PWD'/../DIRAC/dirac-common'
pixi add --pypi --feature diracx-core 'DIRAC @ file://'$PWD'/../DIRAC'
# Show any changes
git diff
- uses: prefix-dev/setup-pixi@v0.8.11
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,19 @@ jobs:
fi
fi
fi
- name: Build distributions
- name: Build DIRACCommon distribution
if: steps.check-tag.outputs.create-release == 'true'
run: |
cd dirac-common
python -m build
cd ..
- name: Pin DIRACCommon version and build DIRAC distribution
run: |
# If we're making a release, pin DIRACCommon to exact version
if [[ "${{ steps.check-tag.outputs.create-release }}" == "true" ]]; then
DIRACCOMMON_VERSION=$(cd dirac-common && python -m setuptools_scm | sed 's@Guessed Version @@g' | sed -E 's@(\.dev|\+g).+@@g')
python .github/workflows/pin_diraccommon_version.py "$DIRACCOMMON_VERSION"
fi
python -m build
- name: Make release on GitHub
if: steps.check-tag.outputs.create-release == 'true'
Expand All @@ -123,7 +134,14 @@ jobs:
--version="${NEW_VERSION}" \
--rev="$(git rev-parse HEAD)" \
--release-notes-fn="release.notes.new"
- name: Publish package on PyPI
- name: Publish DIRACCommon to PyPI
if: steps.check-tag.outputs.create-release == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: dirac-common/dist/
- name: Publish DIRAC to PyPI
if: steps.check-tag.outputs.create-release == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/dirac-common.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: DIRACCommon Tests

on:
push:
branches:
- integration
- rel-*
paths:
- 'dirac-common/**'
- '.github/workflows/dirac-common.yml'
pull_request:
branches:
- integration
- rel-*
paths:
- 'dirac-common/**'
- '.github/workflows/dirac-common.yml'

jobs:
test-dirac-common:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history for setuptools_scm

- uses: prefix-dev/setup-pixi@v0.9.0
with:
run-install: false
post-cleanup: false

- name: Apply workarounds
run: |
# Workaround for https://github.com/prefix-dev/pixi/issues/3762
sed -i.bak 's@editable = true@editable = false@g' dirac-common/pyproject.toml
rm dirac-common/pyproject.toml.bak
# Show any changes
git diff

- uses: prefix-dev/setup-pixi@v0.9.0
with:
cache: false
environments: testing
manifest-path: dirac-common/pyproject.toml

- name: Run tests with pixi
run: |
cd dirac-common
pixi add --feature testing pytest-github-actions-annotate-failures
pixi run pytest
65 changes: 65 additions & 0 deletions .github/workflows/pin_diraccommon_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""
Pin DIRACCommon version in setup.cfg during deployment.

This script is used during the deployment process to ensure DIRAC
depends on the exact version of DIRACCommon being released.
"""

import re
import sys
from pathlib import Path
import subprocess


def get_diraccommon_version():
"""Get the current version of DIRACCommon from setuptools_scm."""
result = subprocess.run(
["python", "-m", "setuptools_scm"], cwd="dirac-common", capture_output=True, text=True, check=True
)
# Extract version from output like "Guessed Version 9.0.0a65.dev7+g995f95504"
version_match = re.search(r"Guessed Version (\S+)", result.stdout)
if not version_match:
# Try direct output format
version = result.stdout.strip()
else:
version = version_match.group(1)

# Clean up the version for release (remove dev and git hash parts)
version = re.sub(r"(\.dev|\+g).+", "", version)
return version


def pin_diraccommon_version(version):
"""Pin DIRACCommon to exact version in setup.cfg."""
setup_cfg = Path("setup.cfg")
content = setup_cfg.read_text()

# Replace the DIRACCommon line with exact version pin
updated_content = re.sub(r"^(\s*)DIRACCommon\s*$", f"\\1DIRACCommon=={version}", content, flags=re.MULTILINE)

if content == updated_content:
print(f"Warning: DIRACCommon line not found or already pinned in setup.cfg")
return False

setup_cfg.write_text(updated_content)
print(f"Pinned DIRACCommon to version {version} in setup.cfg")
return True


def main():
if len(sys.argv) > 1:
version = sys.argv[1]
else:
version = get_diraccommon_version()

if pin_diraccommon_version(version):
print(f"Successfully pinned DIRACCommon to {version}")
sys.exit(0)
else:
print("Failed to pin DIRACCommon version")
sys.exit(1)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ docs/source/AdministratorGuide/CommandReference
docs/source/UserGuide/CommandReference
docs/_build
docs/source/_build

# pixi environments
.pixi
*.egg-info
pixi.lock
47 changes: 47 additions & 0 deletions dirac-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# DIRACCommon

Stateless utilities extracted from DIRAC for use by DiracX and other projects without triggering DIRAC's global state initialization.

## Purpose

This package solves the circular dependency issue where DiracX needs DIRAC utilities but importing DIRAC triggers global state initialization. DIRACCommon contains only stateless utilities that can be safely imported without side effects.

## Contents

- `DIRACCommon.Utils.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
- `DIRACCommon.Utils.DErrno`: DIRAC error codes and utilities

## Installation

```bash
pip install DIRACCommon
```

## Usage

```python
from DIRACCommon.Utils.ReturnValues import S_OK, S_ERROR

def my_function():
if success:
return S_OK("Operation successful")
else:
return S_ERROR("Operation failed")
```

## Development

This package is part of the DIRAC project and shares its version number. When DIRAC is released, DIRACCommon is also released with the same version.

```bash
pixi install
pixi run pytest
```

## Guidelines for Adding Code

Code added to DIRACCommon must:
- Be completely stateless
- Not import or use any of DIRAC's global objects (`gConfig`, `gLogger`, `gMonitor`, `Operations`)
- Not establish database connections
- Not have side effects on import
133 changes: 133 additions & 0 deletions dirac-common/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "DIRACCommon"
description = "Stateless utilities extracted from DIRAC for use by DiracX and other projects"
readme = "README.md"
requires-python = ">=3.11"
license = {text = "GPL-3.0-only"}
authors = [
{name = "DIRAC Collaboration", email = "dirac-dev@cern.ch"},
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering",
"Topic :: System :: Distributed Computing",
]
dependencies = [
"typing-extensions>=4.0.0",
]
dynamic = ["version"]

[project.optional-dependencies]
testing = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]

[project.urls]
Homepage = "https://github.com/DIRACGrid/DIRAC"
Documentation = "https://dirac.readthedocs.io/"
"Source Code" = "https://github.com/DIRACGrid/DIRAC"

[tool.hatch.version]
source = "vcs"

[tool.hatch.version.raw-options]
root = ".."

[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
]

[tool.hatch.build.targets.wheel]
packages = ["src/DIRACCommon"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = ["-v", "--cov=DIRACCommon", "--cov-report=term-missing"]

[tool.coverage.run]
source = ["src/DIRACCommon"]
omit = ["*/tests/*"]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]

[tool.mypy]
python_version = "3.11"
files = ["src/DIRACCommon"]
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true

[tool.ruff]
line-length = 120
target-version = "py311"
select = [
"E", # pycodestyle errors
"F", # pyflakes
"B", # flake8-bugbear
"I", # isort
"PLE", # pylint errors
"UP", # pyupgrade
]
ignore = [
"B905", # zip without explicit strict parameter
"B008", # do not perform function calls in argument defaults
"B006", # do not use mutable data structures for argument defaults
]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
# This ensures DIRACCommon never imports from DIRAC
"DIRAC" = {msg = "DIRACCommon must not import from DIRAC to avoid global state initialization"}

[tool.black]
line-length = 120
target-version = ['py311']

[tool.isort]
profile = "black"
line_length = 120

[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "linux-aarch64", "osx-arm64"]

[tool.pixi.pypi-dependencies]
DIRACCommon = { path = ".", editable = true }

[tool.pixi.feature.testing.tasks.pytest]
cmd = "pytest"

[tool.pixi.environments]
default = { solve-group = "default" }
testing = { features = ["testing"], solve-group = "default" }

[tool.pixi.tasks]
Loading
Loading