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
29 changes: 20 additions & 9 deletions neat/aggregations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
and code for adding new user-defined ones.
"""

import inspect
import types
import warnings
from functools import reduce
Expand Down Expand Up @@ -48,15 +49,25 @@ class InvalidAggregationFunction(TypeError):
pass


def validate_aggregation(function): # TODO: Recognize when need `reduce`
if not isinstance(function,
(types.BuiltinFunctionType,
types.FunctionType,
types.LambdaType)):
raise InvalidAggregationFunction("A function object is required.")

if not (function.__code__.co_argcount >= 1):
raise InvalidAggregationFunction("A function taking at least one argument is required")
def validate_aggregation(function):
if not callable(function):
raise InvalidAggregationFunction("A callable object is required.")

try:
signature = inspect.signature(function)
except (TypeError, ValueError) as exc:
# CPython builtins (e.g. max, sum) often lack introspectable signatures.
# Skip signature validation for these; they are assumed correct.
if isinstance(function, types.BuiltinFunctionType):
return
raise InvalidAggregationFunction("Unable to inspect aggregation callable signature.") from exc

try:
signature.bind(object())
except TypeError as exc:
raise InvalidAggregationFunction(
"A callable with exactly one required positional argument is required"
) from exc


class AggregationFunctionSet:
Expand Down
49 changes: 49 additions & 0 deletions tests/test_aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,29 @@
assert config.genome_config.aggregation_function_defs.is_valid('minabs')


def test_add_builtin_max():
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'test_configuration')
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_path)

config.genome_config.add_aggregation('builtin_max', max)
assert config.genome_config.aggregation_function_defs.get('builtin_max') is max


def dud_function():
return 0.0


def keyword_only_function(*, items):
return sum(items)


def two_argument_function(items, scale):
return sum(items) * scale


def test_function_set():
s = aggregations.AggregationFunctionSet()
assert s.get('sum') is not None
Expand Down Expand Up @@ -135,6 +154,36 @@
raise Exception("Should have had a TypeError/derived for dud_function")


def test_bad_add3():
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'test_configuration')
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_path)

try:
config.genome_config.add_aggregation('keyword_only_function', keyword_only_function)
except TypeError:
pass
else:
raise Exception("Should have had a TypeError/derived for keyword_only_function")

Check warning on line 169 in tests/test_aggregation.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this generic exception class with a more specific one.

See more on https://sonarcloud.io/project/issues?id=CodeReclaimers_neat-python&issues=AZzu9tStGQUKvkf3nFpG&open=AZzu9tStGQUKvkf3nFpG&pullRequest=290


def test_bad_add4():
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'test_configuration')
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_path)

try:
config.genome_config.add_aggregation('two_argument_function', two_argument_function)
except TypeError:
pass
else:
raise Exception("Should have had a TypeError/derived for two_argument_function")

Check warning on line 184 in tests/test_aggregation.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this generic exception class with a more specific one.

See more on https://sonarcloud.io/project/issues?id=CodeReclaimers_neat-python&issues=AZzu9tStGQUKvkf3nFpH&open=AZzu9tStGQUKvkf3nFpH&pullRequest=290


if __name__ == '__main__':
test_sum()
test_product()
Expand Down