Skip to content

Commit

Permalink
Linting
Browse files Browse the repository at this point in the history
  • Loading branch information
cmutel committed Oct 11, 2024
1 parent 8786f49 commit 22bfdb9
Show file tree
Hide file tree
Showing 17 changed files with 78 additions and 171 deletions.
16 changes: 5 additions & 11 deletions multifunctional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,19 @@
logger.disable("multifunctional")

from bw2data import labels
from bw2data.subclass_mapping import (
DATABASE_BACKEND_MAPPING,
NODE_PROCESS_CLASS_MAPPING,
)
from bw2data.subclass_mapping import DATABASE_BACKEND_MAPPING, NODE_PROCESS_CLASS_MAPPING

from .allocation import allocation_strategies, generic_allocation, property_allocation
from .database import MultifunctionalDatabase
from .node_classes import (
MaybeMultifunctionalProcess,
ReadOnlyProcessWithReferenceProduct,
)
from .node_dispatch import multifunctional_node_dispatcher
from .utils import allocation_before_writing
from .custom_allocation import (
add_custom_property_allocation_to_project,
check_property_for_allocation,
check_property_for_process_allocation,
list_available_properties,
)
from .database import MultifunctionalDatabase
from .node_classes import MaybeMultifunctionalProcess, ReadOnlyProcessWithReferenceProduct
from .node_dispatch import multifunctional_node_dispatcher
from .utils import allocation_before_writing

DATABASE_BACKEND_MAPPING["multifunctional"] = MultifunctionalDatabase
NODE_PROCESS_CLASS_MAPPING["multifunctional"] = multifunctional_node_dispatcher
Expand Down
29 changes: 6 additions & 23 deletions multifunctional/allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ def generic_allocation(
act: Union[dict, Activity],
func: Callable,
strategy_label: Optional[str] = None,
supplemental_functions: Optional[List[Callable]] = [
add_product_node_properties_to_exchange
],
supplemental_functions: Optional[List[Callable]] = [add_product_node_properties_to_exchange],
) -> List[dict]:
"""Allocation by single allocation factor generated by `func`.
Expand Down Expand Up @@ -70,14 +68,6 @@ def generic_allocation(
del original_exc["properties"][key]
del original_exc["__mf__properties_from_product"]

# if not factor:
# # Functional product of multifunctional process, but with allocation factor of zero
# # We need to link to *something*, so link to original multifunctional process to avoid
# # unlinked exchanges
# if not original_exc.get("input"):
# original_exc["input"] = (act["database"], act["code"])
# continue

logger.debug(
"Using allocation factor {f} for functional edge {e} on activity {a}",
f=factor,
Expand Down Expand Up @@ -122,9 +112,7 @@ def generic_allocation(
if original_exc["mf_manual_input_product"]:
# Get product name and unit attributes from the separate node, if available
try:
product = get_node(
database=new_exc["input"][0], code=new_exc["input"][1]
)
product = get_node(database=new_exc["input"][0], code=new_exc["input"][1])
except UnknownObject:
# Try using attributes stored on the edge
# Might not work, but better than trying to give access to whole raw database
Expand All @@ -137,21 +125,18 @@ def generic_allocation(
if "id" in allocated_process:
del allocated_process["id"]
if strategy_label:
allocated_process["mf_strategy_label"] = act["mf_strategy_label"] = (
strategy_label
)
allocated_process["mf_strategy_label"] = act["mf_strategy_label"] = strategy_label
allocated_process["code"] = process_code
allocated_process["mf_parent_key"] = (act["database"], act["code"])
# Used to filter out previous read only processes
allocated_process["type"] = "readonly_process"
allocated_process["production amount"] = original_exc["amount"]
if product:
allocated_process["reference product"] = product.get("name", "(unknown)")
allocated_process["unit"] = product.get("unit", "(unknown)")
else:
allocated_process["reference product"] = new_exc.get("name", "(unknown)")
allocated_process["unit"] = new_exc.get("unit") or act.get(
"unit", "(unknown)"
)
allocated_process["unit"] = new_exc.get("unit") or act.get("unit", "(unknown)")
allocated_process["exchanges"] = [new_exc]

for other in filter(lambda x: not x.get("functional"), act["exchanges"]):
Expand Down Expand Up @@ -205,7 +190,5 @@ def property_allocation(
"manual_allocation", normalize_by_production_amount=False
),
"mass": property_allocation("mass"),
"equal": partial(
generic_allocation, func=lambda x, y: 1.0, strategy_label="equal_allocation"
),
"equal": partial(generic_allocation, func=lambda x, y: 1.0, strategy_label="equal_allocation"),
}
21 changes: 6 additions & 15 deletions multifunctional/custom_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass
from enum import Enum
from numbers import Number
from typing import List, Union, Optional
from typing import List, Optional, Union

from blinker import signal
from bw2data import Database, databases
Expand Down Expand Up @@ -65,9 +65,7 @@ def list_available_properties(database_label: str, target_process: Optional[Node
results = []
all_properties = set()

for ds in filter(
lambda x: x["type"] == "multifunctional", Database(database_label)
):
for ds in filter(lambda x: x["type"] == "multifunctional", Database(database_label)):
for edge in filter(lambda x: x.get("functional"), ds.exchanges()):
for key in _get_unified_properties(edge):
all_properties.add(key)
Expand Down Expand Up @@ -110,15 +108,12 @@ def check_property_for_process_allocation(
if messages is None:
messages = []

if process['type'] != "multifunctional":
if process["type"] != "multifunctional":
return True

for edge in filter(lambda x: x.get("functional"), process.exchanges()):
properties = _get_unified_properties(edge)
if (
property_label not in properties
and edge.input["type"] != "readonly_process"
):
if property_label not in properties and edge.input["type"] != "readonly_process":
messages.append(
PropertyMessage(
level=logging.WARNING,
Expand Down Expand Up @@ -260,12 +255,8 @@ def update_allocation_strategies_on_project_change(
for obsolete in set(allocation_strategies).difference(DEFAULT_ALLOCATIONS):
del allocation_strategies[obsolete]

for key, value in project_dataset.data.get(
"multifunctional.custom_allocations", {}
).items():
for key, value in project_dataset.data.get("multifunctional.custom_allocations", {}).items():
allocation_strategies[key] = property_allocation(**value)


signal("bw2data.project_changed").connect(
update_allocation_strategies_on_project_change
)
signal("bw2data.project_changed").connect(update_allocation_strategies_on_project_change)
38 changes: 13 additions & 25 deletions multifunctional/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

from .edge_classes import ReadOnlyExchanges
from .errors import NoAllocationNeeded
from .utils import product_as_process_name, update_datasets_from_allocation_results
from .utils import (
product_as_process_name,
set_correct_process_type,
update_datasets_from_allocation_results,
)


class BaseMultifunctionalNode(Activity):
Expand Down Expand Up @@ -63,9 +67,7 @@ def allocate(
"Can't find `default_allocation` in input arguments, or process/database metadata."
)
if strategy_label not in allocation_strategies:
raise KeyError(
f"Given strategy label {strategy_label} not in `allocation_strategies`"
)
raise KeyError(f"Given strategy label {strategy_label} not in `allocation_strategies`")

logger.debug(
"Allocating {p} (id: {i}) with strategy {s}",
Expand Down Expand Up @@ -126,46 +128,32 @@ def delete(self):

def exchanges(self, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().exchanges(exchanges_class=ReadOnlyExchanges)

def technosphere(self, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().technosphere(exchanges_class=ReadOnlyExchanges)

def biosphere(self, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().biosphere(exchanges_class=ReadOnlyExchanges)

def production(self, include_substitution=False, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().production(
include_substitution=include_substitution, exchanges_class=ReadOnlyExchanges
)

def substitution(self, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().substitution(exchanges_class=ReadOnlyExchanges)

def upstream(
self, kinds=labels.technosphere_negative_edge_types, exchanges_class=None
):
def upstream(self, kinds=labels.technosphere_negative_edge_types, exchanges_class=None):
if exchanges_class is not None:
warnings.warn(
"`exchanges_class` argument ignored; must be `ReadOnlyExchanges`"
)
warnings.warn("`exchanges_class` argument ignored; must be `ReadOnlyExchanges`")
return super().upstream(kinds=kinds, exchanges_class=ReadOnlyExchanges)
5 changes: 1 addition & 4 deletions multifunctional/node_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
from bw2data.backends.proxies import Activity
from bw2data.backends.schema import ActivityDataset

from .node_classes import (
MaybeMultifunctionalProcess,
ReadOnlyProcessWithReferenceProduct,
)
from .node_classes import MaybeMultifunctionalProcess, ReadOnlyProcessWithReferenceProduct


def multifunctional_node_dispatcher(node_obj: Optional[ActivityDataset] = None) -> Activity:
Expand Down
4 changes: 1 addition & 3 deletions multifunctional/supplemental.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
def add_product_node_properties_to_exchange(obj: dict) -> dict:
"""Add properties from products to the exchange to make them available during allocation."""
this = (obj["database"], obj["code"])
for exc in filter(
lambda x: x.get("functional") and "input" in x, obj.get("exchanges", [])
):
for exc in filter(lambda x: x.get("functional") and "input" in x, obj.get("exchanges", [])):
if exc.get("type") == "production":
# Keep as separate because it should eventually be an output, not an input...
other = exc["input"]
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import pytest
from bw2data.tests import bw2test
from fixtures.basic import DATA as BASIC_DATA
from fixtures.errors import DATA as ERRORS_DATA
from fixtures.internal_linking import DATA as INTERNAL_LINKING_DATA
from fixtures.product_properties import DATA as PP_DATA
from fixtures.products import DATA as PRODUCT_DATA
from fixtures.errors import DATA as ERRORS_DATA

from multifunctional import allocation_before_writing, MultifunctionalDatabase
from multifunctional import MultifunctionalDatabase, allocation_before_writing


@pytest.fixture
Expand Down
12 changes: 3 additions & 9 deletions tests/test_allocation_before_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ def check_basic_allocation_results(factor_1, factor_2, database):


def test_without_allocation(allocate_then_write):
nodes = sorted(
allocate_then_write, key=lambda x: (x["name"], x.get("reference product", ""))
)
nodes = sorted(allocate_then_write, key=lambda x: (x["name"], x.get("reference product", "")))
assert len(nodes) == 4

assert isinstance(nodes[0], mf.MaybeMultifunctionalProcess)
Expand All @@ -132,9 +130,7 @@ def test_without_allocation(allocate_then_write):
def test_price_allocation_strategy_label(allocate_then_write):
allocate_then_write.metadata["default_allocation"] = "price"
bd.get_node(code="1").allocate()
nodes = sorted(
allocate_then_write, key=lambda x: (x["name"], x.get("reference product", ""))
)
nodes = sorted(allocate_then_write, key=lambda x: (x["name"], x.get("reference product", "")))

assert not nodes[0].get("mf_strategy_label")
assert nodes[1].get("mf_strategy_label") == "property allocation by 'price'"
Expand Down Expand Up @@ -177,9 +173,7 @@ def test_allocation_uses_existing(allocate_then_write):
def test_allocation_already_allocated(allocate_then_write):
allocate_then_write.metadata["default_allocation"] = "price"
bd.get_node(code="1").allocate()
node = sorted(
allocate_then_write, key=lambda x: (x["name"], x.get("reference product", ""))
)[2]
node = sorted(allocate_then_write, key=lambda x: (x["name"], x.get("reference product", "")))[2]

assert mf.generic_allocation(node, None) == []

Expand Down
8 changes: 2 additions & 6 deletions tests/test_allocation_with_product_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@


def check_products_allocation_results(factor_1, factor_2, database):
nodes = sorted(
database, key=lambda x: (x["name"], x.get("reference product", ""), x["type"])
)
nodes = sorted(database, key=lambda x: (x["name"], x.get("reference product", ""), x["type"]))

assert isinstance(nodes[0], MaybeMultifunctionalProcess)
assert nodes[0]["name"] == "first product"
Expand Down Expand Up @@ -170,9 +168,7 @@ def test_allocation_uses_existing(product_properties):
def test_allocation_already_allocated(product_properties):
product_properties.metadata["default_allocation"] = "price"
bd.get_node(code="1").allocate()
node = sorted(
product_properties, key=lambda x: (x["name"], x.get("reference product", ""))
)[3]
node = sorted(product_properties, key=lambda x: (x["name"], x.get("reference product", "")))[3]

assert generic_allocation(node, None) == []

Expand Down
12 changes: 3 additions & 9 deletions tests/test_allocation_with_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@


def check_products_allocation_results(factor_1, factor_2, database):
nodes = sorted(
database, key=lambda x: (x["name"], x.get("reference product", ""), x["type"])
)
nodes = sorted(database, key=lambda x: (x["name"], x.get("reference product", ""), x["type"]))

assert isinstance(nodes[0], MaybeMultifunctionalProcess)
assert nodes[0]["name"] == "first product"
Expand Down Expand Up @@ -107,9 +105,7 @@ def check_products_allocation_results(factor_1, factor_2, database):


def test_without_allocation(products):
nodes = sorted(
products, key=lambda x: (x["name"], x.get("reference product", ""), x["type"])
)
nodes = sorted(products, key=lambda x: (x["name"], x.get("reference product", ""), x["type"]))

assert len(nodes) == 3

Expand Down Expand Up @@ -167,9 +163,7 @@ def test_allocation_uses_existing(products):
def test_allocation_already_allocated(products):
products.metadata["default_allocation"] = "price"
bd.get_node(code="1").allocate()
node = sorted(products, key=lambda x: (x["name"], x.get("reference product", "")))[
3
]
node = sorted(products, key=lambda x: (x["name"], x.get("reference product", "")))[3]

assert generic_allocation(node, None) == []

Expand Down
5 changes: 1 addition & 4 deletions tests/test_bw2data_labels.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from bw2data import labels
from bw2data.subclass_mapping import (
DATABASE_BACKEND_MAPPING,
NODE_PROCESS_CLASS_MAPPING,
)
from bw2data.subclass_mapping import DATABASE_BACKEND_MAPPING, NODE_PROCESS_CLASS_MAPPING
from bw2data.tests import bw2test


Expand Down
7 changes: 2 additions & 5 deletions tests/test_custom_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
from bw2data.tests import bw2test

from multifunctional import (
MultifunctionalDatabase,
add_custom_property_allocation_to_project,
allocation_strategies,
check_property_for_allocation,
check_property_for_process_allocation,
list_available_properties,
MultifunctionalDatabase,
)
from multifunctional.custom_allocation import (
DEFAULT_ALLOCATIONS,
MessageType,
)
from multifunctional.custom_allocation import DEFAULT_ALLOCATIONS, MessageType


@bw2test
Expand Down
Loading

0 comments on commit 22bfdb9

Please sign in to comment.