Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6bb8444
support other data type than string, add some failsafes
mkolar Feb 3, 2019
3d06232
fixing partial formatting
antirotor May 6, 2020
2d9f322
add pyproject.toml
antirotor Jan 12, 2021
8ea28e4
add setup.py shim
antirotor Jan 12, 2021
1408cec
fix toml
antirotor Jan 12, 2021
516151e
added wheel
antirotor Jan 12, 2021
428af2f
add setup.cfg
antirotor Jan 12, 2021
97afa99
Merge pull request #1 from antirotor/fix/unformatted-tokens
mkolar Jan 12, 2021
9bf1957
Merge pull request #2 from antirotor/feature/add-build-system
mkolar Jan 12, 2021
efc1b8f
fix failure to add path
antirotor Jun 11, 2021
68784b7
fix fix to avoid duplicated entries in enviornments
iLLiCiTiT Jun 22, 2021
5a812c6
skip url paths before append
iLLiCiTiT Jul 14, 2021
0b3c969
have ability to modify missing key for formattings
iLLiCiTiT Jul 14, 2021
3c13e86
do not use append after merge
iLLiCiTiT Jul 14, 2021
ee6de93
do not skip values that are converted to string
iLLiCiTiT Jul 14, 2021
bed105b
append handle empty string values
iLLiCiTiT Jul 27, 2021
55a7c33
Merge pull request #3 from pypeclub/feature/merge_without_append
iLLiCiTiT Aug 4, 2021
432ad95
fix license field in pyproject
antirotor Mar 25, 2022
9ba9e8c
update license key round 2
antirotor Mar 27, 2022
91009ce
PEP 621
antirotor Mar 27, 2022
15a820f
PEP 621 another round
antirotor Mar 27, 2022
126f7a1
missing required version
antirotor Mar 27, 2022
1f6d3df
Ignore values that don't have correct braces to e.g. allow some linux…
BigRoy Aug 24, 2023
5f55105
Merge pull request #4 from BigRoy/bugfix/linux_bash_functions
iLLiCiTiT Aug 24, 2023
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
64 changes: 42 additions & 22 deletions acre/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
PLATFORM = platform.system().lower()

logging.basicConfig()
log = logging.getLogger()
log = logging.getLogger(__name__)


class CycleError(ValueError):
Expand Down Expand Up @@ -44,14 +44,17 @@ def compute(env,
# Collect dependencies
dependencies = []
for key, value in env.items():
dependent_keys = re.findall("{(.+?)}", value)
for dependency in dependent_keys:
# Ignore direct references to itself because
# we don't format with itself anyway
if dependency == key:
continue
try:
dependent_keys = re.findall("{(.+?)}", value)
for dependency in dependent_keys:
# Ignore direct references to itself because
# we don't format with itself anyway
if dependency == key:
continue

dependencies.append((key, dependency))
dependencies.append((key, dependency))
except Exception:
dependencies.append((key, value))
Comment on lines +47 to +57
Copy link
Owner Author

@BigRoy BigRoy Mar 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be a try..except? Doesn't make much sense to me.

That'd only occur if value is not a string maybe? E.g. a number or None? But that basically means the env isn't an "environment" so the input is already invalid to begin with? This should error. No?


result = lib.topological_sort(dependencies)

Expand All @@ -66,13 +69,17 @@ def compute(env,
# Format dynamic values
for key in reversed(result.sorted):
if key in env:
if not isinstance(env[key], str):
continue
Comment on lines +72 to +73
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of values are you expected in the input env. I'd say that anything that is not a string is not an "environment value" and thus is invalid input. That should not be continued but should raise an error. It means the input value is wrong.

Unless maybe we'd want to explicitly skip None values?

data = env.copy()
data.pop(key) # format without itself
env[key] = lib.partial_format(env[key], data=data)

# Format cyclic values
for key in result.cyclic:
if key in env:
if not isinstance(env[key], str):
continue
data = env.copy()
data.pop(key) # format without itself
env[key] = lib.partial_format(env[key], data=data)
Expand All @@ -81,8 +88,12 @@ def compute(env,
if dynamic_keys:
formatted = {}
for key, value in env.items():
new_key = lib.partial_format(key, data=env)
if not isinstance(value, str):
new_key = key
formatted[key] = value
continue

new_key = lib.partial_format(key, data=env)
if new_key in formatted:
if not allow_key_clash:
raise DynamicKeyClashError("Key clashes on: {0} "
Expand All @@ -96,6 +107,8 @@ def compute(env,
if cleanup:
separator = os.pathsep
for key, value in env.items():
if not isinstance(value, str):
continue
paths = value.split(separator)

# Keep unique path entries: {A};{A};{B} -> {A};{B}
Expand Down Expand Up @@ -138,7 +151,7 @@ def parse(env, platform_name=None):

# Allow to have lists as values in the tool data
if isinstance(value, (list, tuple)):
value = ";".join(value)
value = os.pathsep.join(value)

result[variable] = value

Expand All @@ -151,12 +164,14 @@ def append(env, env_b):
# todo: this function name might also be confusing with "merge"
env = env.copy()
for variable, value in env_b.items():
for path in value.split(";"):
if not path:
continue

lib.append_path(env, variable, path)

try:
for path in value.split(os.pathsep):
if not path:
continue
lib.append_path(env, variable, path)
except Exception:
if not isinstance(value, str):
env[variable] = value
return env


Expand Down Expand Up @@ -190,7 +205,7 @@ def get_tools(tools, platform_name=None):
'"TOOL_ENV" environment variable not found. '
'Please create it and point it to a folder with your .json '
'config files.'
)
)

# Collect the tool files to load
tool_paths = []
Expand Down Expand Up @@ -219,12 +234,13 @@ def get_tools(tools, platform_name=None):
continue

tool_env = parse(tool_env, platform_name=platform_name)

environment = append(environment, tool_env)

return environment


def merge(env, current_env):
def merge(env, current_env, missing=None):
"""Merge the tools environment with the 'current_env'.

This finalizes the join with a current environment by formatting the
Expand All @@ -236,16 +252,20 @@ def merge(env, current_env):
env (dict): The dynamic environment
current_env (dict): The "current environment" to merge the dynamic
environment into.
missing (str): Argument passed to 'partial_format' during merging.
`None` should keep missing keys unchanged.

Returns:
dict: The resulting environment after the merge.

"""

result = current_env.copy()
for key, value in env.items():
value = lib.partial_format(value, data=current_env, missing="")
result[key] = value
if not isinstance(value, str):
value = str(value)

return result
value = lib.partial_format(value, data=current_env, missing=missing)

result[key] = str(value)

return result
31 changes: 23 additions & 8 deletions acre/lib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
import string

from collections import defaultdict, namedtuple
Expand Down Expand Up @@ -37,12 +38,24 @@ class FormatDict(dict):
Missing keys are replaced with the return value of __missing__.

"""

def __missing__(self, key):
return missing.format(key=key)

formatter = string.Formatter()
mapping = FormatDict(**data)
return formatter.vformat(s, (), mapping)
try:
f = formatter.vformat(s, (), mapping)
except Exception:
r_token = re.compile(r"({.*?})")
matches = re.findall(r_token, s)
f = s
for m in matches:
try:
f = re.sub(m, m.format(**data), f)
except (KeyError, ValueError):
continue
return f


def topological_sort(dependency_pairs):
Expand All @@ -68,10 +81,12 @@ def topological_sort(dependency_pairs):
return Results(ordered, cyclic)


def append_path(self, key, path):
"""Append *path* to *key* in *self*."""
try:
if path not in self[key]:
self[key] = os.pathsep.join([self[key], str(path)])
except KeyError:
self[key] = str(path)
def append_path(env, key, path):
"""Append *path* to *key* in *env*."""

orig_value = env.get(key)
if not orig_value:
env[key] = str(path)

elif path not in orig_value.split(os.pathsep):
env[key] = os.pathsep.join([orig_value, str(path)])
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[project]
version = "1.0.0"
name = "acre"
description = "Lightweight cross-platform environment management Python package that makes it trivial to launch applications in their own configurable working environment."
readme = "README.md"
authors = [
{ name = "Roy Nieterau", email ="roy_nieterau@hotmail.com" },
]
license = { file = "LICENSE" }
requires-python = ">=2.7"
keywords = ["environment", "pipeline"]

classifiers = [
"Topic :: Software Development",
"Topic :: Utilities",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules"
]

[project.urls]
homepage = "https://github.com/BigRoy/acre"
repository = "https://github.com/BigRoy/acre"
documentation = "https://github.com/BigRoy/acre"

[build-system]
requires = ["setuptools >= 35.0.2", "wheel"]
build-backend = "setuptools.build_meta"
20 changes: 20 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[metadata]
name = acre
version = 1.0.0
description = Lightweight cross-platform environment management Python package that makes it trivial to launch applications in their own configurable working environment
long_description = file: README.md, LICENSE.md
keywords = environment, pipeline
license = GNU Lesser General Public License v3 (LGPLv3)
classifiers =
Topic :: Software Development
License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
Topic :: Utilities
Intended Audience :: Developers
Topic :: Software Development :: Libraries :: Python Modules

[options]
zip_safe = True
include_package_data = True
packages = find:
install_requires =
importlib; python_version == "2.7"
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env python

import setuptools

if __name__ == "__main__":
setuptools.setup()
16 changes: 16 additions & 0 deletions tests/test_dynamic_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,22 @@ def test_compute_preserve_reference_to_self(self):
"PYTHONPATH": "x;y/{PYTHONPATH}"
})

def test_compute_reference_formats(self):
"""acre.compute() will correctly skip unresolved references."""
data = {
"A": "a",
"B": "{A},b",
"C": "{C}",
"D": "{D[x]}",
}
data = acre.compute(data)
self.assertEqual(data, {
"A": "a",
"B": "a,b",
"C": "{C}",
"D": "{D[x]}"
})

def test_append(self):
"""Append paths of two environments into one."""

Expand Down