1class Material:
2 """Represents a base class with all the shared functionalities between the base materials."""
3
4 DENSITY = None
5
6 def __init__(self, mass: int):
7 self.mass = mass
8
9 @property
10 def volume(self) -> float:
11 return self.mass / self.DENSITY
12
13
14class Concrete(Material):
15 DENSITY = 2500
16
17
18class Brick(Material):
19 DENSITY = 2000
20
21
22class Stone(Material):
23 DENSITY = 1600
24
25
26class Wood(Material):
27 DENSITY = 600
28
29
30class Steel(Material):
31 DENSITY = 7700
32
33
34class Factory:
35 """Factory that creates dynamic classes - alloys, and materials."""
36
37 BASE_MATERIALS = {
38 "Concrete": Concrete,
39 "Brick": Brick,
40 "Stone": Stone,
41 "Wood": Wood,
42 "Steel": Steel
43 }
44
45 # A dictionary to store by keys the names of all created alloys
46 all_created_alloys = {}
47
48 # A list of all materials used by any factory
49 used_materials = []
50
51 # A list of all instances of the Factory class
52 all_factories = []
53
54 def __init__(self):
55 self.materials = []
56 Factory.all_factories.append(self)
57
58 def _process_named_arguments(self, kwargs):
59 """Processes named arguments."""
60 material_instances = []
61 for name, mass in kwargs.items():
62 if name in Factory.BASE_MATERIALS or name in Factory.all_created_alloys:
63 material_class = Factory.BASE_MATERIALS[name] or Factory.all_created_alloys[name]
64 new_material = material_class(mass)
65 material_instances.append(new_material)
66 self.materials.append(new_material)
67 else:
68 raise ValueError("Incorrect material type: {name}.")
69 return tuple(material_instances)
70
71 def _process_positional_arguments(self, args):
72 """Processes positional arguments."""
73 for arg in args:
74 for used_material in Factory.used_materials:
75 if arg is used_material:
76 raise AssertionError("Material has already been used.")
77 # In case materials are valid, enlists them as used.
78 for arg in args:
79 Factory.used_materials.append(arg)
80
81 # Retrieves all base material types for the alloy.
82 alloy_materials = []
83 for arg in args:
84 material_type = type(arg)
85 if material_type in Factory.BASE_MATERIALS.values():
86 alloy_materials.append(material_type)
87 elif material_type in Factory.all_created_alloys.values():
88 alloy_materials.extend(arg.base_materials)
89
90 alloy_name = "_".join(sorted(cls.__name__ for cls in alloy_materials))
91 if alloy_name not in Factory.all_created_alloys:
92 alloy_density = sum(cls.DENSITY for cls in alloy_materials) / len(alloy_materials)
93
94 class Alloy(Material):
95
96 DENSITY = alloy_density
97 base_materials = alloy_materials
98
99 Alloy.__name__ = alloy_name
100 Factory.all_created_alloys[alloy_name] = Alloy
101
102 # Retrieves information and create an alloy instance with it.
103 alloy_class = Factory.all_created_alloys[alloy_name]
104 alloy_mass = sum(arg.mass for arg in args)
105 alloy_instance = alloy_class(alloy_mass)
106 self.materials.append(alloy_instance)
107 return alloy_instance
108
109 def __call__(self, *args, **kwargs):
110 """Handles the calling of the factory class."""
111 if (args and kwargs) or (not args and not kwargs):
112 raise ValueError("Incorrect calling of object.")
113 elif kwargs:
114 return self._process_named_arguments(kwargs)
115 else:
116 return self._process_positional_arguments(args)
117
118 def can_build(self, to_build: int):
119 """Checks if the current instance has enough materials to build a specific amount."""
120 total_volume = sum(
121 material.volume for material in self.materials
122 if material not in self.used_materials
123 )
124 return total_volume >= to_build
125
126 @classmethod
127 def can_build_together(cls, to_build: int):
128 """Checks if all factories have enough materials to build a specific amount."""
129 total_volume = sum(
130 instance.material.volume for instance in cls.all_factories
131 if instance not in cls.used_materials
132 )
133 return total_volume >= to_build
.EE.E..F..
======================================================================
ERROR: test_can_build (test.TestFactory.test_can_build)
Test can_build methods.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 177, in test_can_build
self.assertFalse(solution.Factory.can_build_together(6.0001))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 129, in can_build_together
total_volume = sum(
^^^^
File "/tmp/solution.py", line 130, in <genexpr>
instance.material.volume for instance in cls.all_factories
^^^^^^^^^^^^^^^^^
AttributeError: 'Factory' object has no attribute 'material'. Did you mean: 'materials'?
======================================================================
ERROR: test_materials_between_factories (test.TestFactory.test_materials_between_factories)
Test materials sharing.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 156, in test_materials_between_factories
result2, = self.factory2(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 114, in __call__
return self._process_named_arguments(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 63, in _process_named_arguments
material_class = Factory.BASE_MATERIALS[name] or Factory.all_created_alloys[name]
~~~~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'Brick_Concrete_Steel_Stone_Wood'
======================================================================
ERROR: test_named_arguments_with_dynamically_created_classes (test.TestFactory.test_named_arguments_with_dynamically_created_classes)
Test dynamically created classes uniqueness.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 145, in test_named_arguments_with_dynamically_created_classes
result2, = self.factory1(Brick_Concrete_Steel_Stone_Wood=2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 114, in __call__
return self._process_named_arguments(kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 63, in _process_named_arguments
material_class = Factory.BASE_MATERIALS[name] or Factory.all_created_alloys[name]
~~~~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'Brick_Concrete_Steel_Stone_Wood'
======================================================================
FAIL: test_positional_arguments_single_argument (test.TestFactory.test_positional_arguments_single_argument)
Test calling a factory using a sigle positional argument.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 80, in test_positional_arguments_single_argument
self.assertIs(type(concrete2), solution.Concrete)
AssertionError: <class 'solution.Factory._process_positional_arguments.<locals>.Alloy'> is not <class 'solution.Concrete'>
----------------------------------------------------------------------
Ran 10 tests in 0.011s
FAILED (failures=1, errors=3)
26.11.2024 17:48
26.11.2024 17:50