Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:addman2/aiida_python into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
addman2 committed Feb 20, 2024
2 parents e92e3b0 + 1889f40 commit 046aa37
Show file tree
Hide file tree
Showing 38 changed files with 1,035 additions and 406 deletions.
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Build and test

on: [push, pull_request]

jobs:

build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Setup RabbitMQ
uses: nijel/rabbitmq-action@v1.0.0
with:
rabbitmq version: 3.8.9

- name: Install code
run: |
pip install .[testing,pre-commit]
- name: Test code
run: |
pytest -s .
pre-commit:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Setup RabbitMQ
uses: nijel/rabbitmq-action@v1.0.0
with:
rabbitmq version: 3.8.9

- name: Install code
run: |
pip install .[pre-commit]
- name: Run pre-commit
run: |
pre-commit install
pre-commit run --all-files || ( git status --short ; git diff ; exit 1 )
39 changes: 39 additions & 0 deletions .github/workflows/pypi_publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish on PyPI

on:
push:
tags:
# After vMajor.Minor.Patch _anything_ is allowed (without "/") !
- v[0-9]+.[0-9]+.[0-9]+*

jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'addman2/aiida-python' && startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
url: https://pypi.org/p/aiida-python
permissions:
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Upgrade setuptools and install package
run: |
python -m pip install --upgrade pip setuptools build wheel
python -m pip install -e .
- name: Build source distribution
run: python -m build --sdist --bdist_wheel --outdir dist/ .

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
29 changes: 29 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Install pre-commit hooks via:
# pre-commit install
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: double-quote-string-fixer
- id: end-of-file-fixer
- id: fix-encoding-pragma
- id: mixed-line-ending
- id: trailing-whitespace
- id: check-json

# yapf = yet another python formatter
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.30.0
hooks:
- id: yapf
name: yapf
args: ["-i"]

#- repo: local
#hooks:

# - id: version-number
# name: Check version numbers
# entry: python ./.github/check_version.py
# language: system
# files: '^(setup.json)|(qp2/__init__.py)'
20 changes: 20 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py

python:
install:
- requirements: docs/source/requirements.txt
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include setup.json
include LICENCE
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# AiiDA Python

This package allows you to run python code as `CalcJob` on a remote computer. Usage is easy one has to inherit CalcJobPython class and instead of `prepare_for_submition` method one ahs to overload `run_python`. Parser is generated automatically one does not have to write its own.
This package is an AiiDA plugin allowing you to run python code as `CalcJob` on a remote computer. Usage is easy one has to inherit CalcJobPython class and instead of `prepare_for_submition` method one ahs to overload `run_python`. Parser is generated automatically one does not have to write its own.


```from aiida.orm import (Int, Float, Str, List, ArrayData)
from aiida.plugins import CalculationFactory
CalcJobPython = CalculationFactory("aiida_python.calc")
CalcJobPython = CalculationFactory("python.calc")
class ClassThatCannotStartWithTestExample(CalcJobPython):
Expand Down Expand Up @@ -58,4 +58,4 @@ def test_example(aiida_local_code_factory, clear_database):
result = run(calculation, **inputs)
```

THIS IS IN TESTING STAGE
For more information look at this [link](https://aiida-python.readthedocs.io/en/latest/).
3 changes: 3 additions & 0 deletions aiida_python/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# -*- coding: utf-8 -*-
from .serializers import *

__version__ = '0.0.6'
117 changes: 65 additions & 52 deletions aiida_python/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@

from aiida.common import datastructures
from aiida.engine import CalcJob
from aiida.orm import (Data,
Int,
Float,
Str)
from aiida.orm import (Data, Int, Float, Str)

INFILE = '.__data_mia.inpkl'
OUTFILE = '.__data_mia.outpkl'
ERRFILE = '.__data_mia.errpkl'

INFILE = ".__data_mia.inpkl"
OUTFILE = ".__data_mia.outpkl"
ERRFILE = ".__data_mia.errpkl"

class NoRunPythonMethod(Exception):
pass


class CalcJobPython(CalcJob):
"""
AiiDA Python calculation class
"""

@classmethod
def define(cls, spec):
super().define(spec)
Expand All @@ -33,51 +31,66 @@ def define(cls, spec):
required=False,
help='Dont leave this empty')

spec.inputs['metadata']['options']['parser_name'].default = 'aiida_python.parser'
spec.inputs['metadata']['options']['input_filename'].default = '__noone_will_ever_use_this_name.input'
spec.inputs['metadata']['options']['output_filename'].default = '__noone_will_ever_use_this_name.output'

spec.inputs['metadata']['options']['serializers'].default = ['int',
'float',
'str',
'list',
'arraydata',]
spec.output('run_code',
valid_type=Str,
help='Code that had been run')
spec.inputs['metadata']['options'][
'parser_name'].default = 'python.parser'
spec.inputs['metadata']['options'][
'input_filename'].default = '__noone_will_ever_use_this_name.input'
spec.inputs['metadata']['options'][
'output_filename'].default = '__noone_will_ever_use_this_name.output'

spec.inputs['metadata']['options']['serializers'].default = [
'int',
'float',
'str',
'list',
'arraydata',
]
spec.output('run_code', valid_type=Str, help='Code that had been run')

spec.output('error_message',
valid_type=Str,
help='Error message if exception was raised')

spec.exit_code(300, 'ERROR_MISSING_OUTPUT_VARIABLES', message='Calculation did not produce all expected output files.')
spec.exit_code(
300,
'ERROR_MISSING_OUTPUT_VARIABLES',
message='Calculation did not produce all expected output files.')

def serialize(self, fhandle):
"""
"""

import pkg_resources
from aiida.plugins import entry_point as ep

def serialize_this(obj):
for entry_point in pkg_resources.iter_entry_points('aiida_python.serializers'):
if entry_point.name in self.inputs['metadata']['options']['serializers']:
for entry_point in ep.eps().select(
group='aiida_python.serializers'):
if entry_point.name in self.inputs['metadata']['options'][
'serializers']:
obj = entry_point.load().serialize(obj)
return obj

"""
o linja mute mute ...
"""
data = { inp: serialize_this(self.inputs[inp]) for inp in self.inputs if inp not in ('metadata', 'code') }
data = {
inp: serialize_this(self.inputs[inp])
for inp in self.inputs if inp not in ('metadata', 'code')
}
"""
fixme: Make a warning if something was not serialized
"""
data = { key: val for key, val in data.items() if not isinstance(val, Data) }
data = {
key: val
for key, val in data.items() if not isinstance(val, Data)
}

import pickle
pickle.dump(data, fhandle)

def prepare_for_submission(self, folder):

run_python = getattr(self, "run_python", None)
run_python = getattr(self, 'run_python', None)
if not callable(run_python):
# None type is not callable, right?
raise NoRunPythonMethod()
Expand All @@ -100,19 +113,19 @@ def prepare_for_submission(self, folder):
"""

#source_code = [ l[4:-1] for l in source_code ]
source_code = ['import os',
'os.system("mkdir ihyh")',
'os.system("echo \'from .ihyh import IHideYouHolder\' > ihyh/__init__.py")',
'os.system("cp ihyh.py ihyh")',
'from ihyh import IHideYouHolder'] + source_code
source_code = [
'import os', 'os.system("mkdir ihyh")',
'os.system("echo \'from .ihyh import IHideYouHolder\' > ihyh/__init__.py")',
'os.system("cp ihyh.py ihyh")', 'from ihyh import IHideYouHolder',
] + source_code

arguments = f'infile="{INFILE}", outfile="{OUTFILE}"'
runline = f"run_python(IHideYouHolder({arguments}))"
trailing_code = ['try:',
f' {runline}',
'except Exception as exc:',
f' with open("{ERRFILE}", "w") as fhandle:',
' fhandle.write(str(exc))']
runline = f'run_python(IHideYouHolder({arguments}))'
trailing_code = [
'try:', f' {runline}', 'except Exception as exc:',
f' with open("{ERRFILE}", "w") as fhandle:',
' fhandle.write(str(exc))'
]
#source_code.append(f'try:\n run_python(IHideYouHolder({arguments}))\nexcept:\n os.system("echo ijo ike > {ERRFILE}")')
source_code = '\n'.join(source_code + trailing_code)

Expand All @@ -126,7 +139,8 @@ def prepare_for_submission(self, folder):
open(ihyh_file, 'r') as fhandle_source:
fhandle_destination.write(fhandle_source.read())

with folder.open(self.inputs.metadata.options.input_filename, "w") as fhandle:
with folder.open(self.inputs.metadata.options.input_filename,
'w') as fhandle:
fhandle.write(source_code)

with folder.open(INFILE, 'wb') as fhandle:
Expand All @@ -142,13 +156,14 @@ def prepare_for_submission(self, folder):
calcinfo.codes_info = [codeinfo]
calcinfo.local_copy_list = []

calcinfo.retrieve_list = [self.inputs.metadata.options.output_filename,
self.inputs.metadata.options.input_filename,
OUTFILE,
ERRFILE,
]
if "retrieve_list" in self.helper:
calcinfo.retrieve_list.extend(self.helper["retrieve_list"])
calcinfo.retrieve_list = [
self.inputs.metadata.options.output_filename,
self.inputs.metadata.options.input_filename,
OUTFILE,
ERRFILE,
]
if 'retrieve_list' in self.helper:
calcinfo.retrieve_list.extend(self.helper['retrieve_list'])

return calcinfo

Expand All @@ -159,26 +174,24 @@ def _process_docs(self, folder, docs):
import re
import os

for line in docs.split("\n"):
for line in docs.split('\n'):
m = re.match(r'.*!file\s+(.+):\s*([_a-zA-Z0-9]+)\s*$', line)
if m:
self._op_file_in(folder, m.group(1), m.group(2))
self._op_file_out(folder, m.group(2))

def _op_file_out(self, folder, filename):
if "retrieve_list" not in self.helper:
if 'retrieve_list' not in self.helper:
self.helper['retrieve_list'] = []
self.helper['retrieve_list'].append(filename)

def _op_file_in(self, folder, inputname, filename):
try:
with folder.open(filename, "wb") as fhandle_destination, \
getattr(self.inputs, inputname).open(mode="rb") as fhandle_source:
with folder.open(filename, 'wb') as fhandle_destination, \
getattr(self.inputs, inputname).open(mode='rb') as fhandle_source:
fhandle_destination.write(fhandle_source.read())
except AttributeError:
"""
If input port does not exists do nothing
"""
pass


1 change: 1 addition & 0 deletions aiida_python/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# -*- coding: utf-8 -*-
from .ihyh import IHideYouHolder

0 comments on commit 046aa37

Please sign in to comment.