1# type: ignore
2
3from abc import ABC, abstractmethod
4from functools import reduce
5
6
7class ErrorUtils:
8 @staticmethod
9 def raise_hw_err():
10 raise ValueError("произволен текст")
11
12
13class Mine:
14 _registry = dict()
15
16 @staticmethod
17 def get_material(material):
18 return Mine._registry.get(material)
19
20 @staticmethod
21 def add_material(name, cls, with_error=False):
22 if Mine.get_material(name):
23 if with_error:
24 ErrorUtils.raise_hw_err()
25 Mine._registry.update({name: cls})
26 return Mine.get_material(name)
27
28
29class Material:
30 density = 1
31
32 def __init__(self, mass):
33 self.mass = mass
34 self.is_usable = True
35 self.atomic_types = frozenset()
36 Mine.add_material(self.__class__.__name__, self.__class__)
37
38 @property
39 def volume(self):
40 return self.mass / self.density
41
42
43class Concrete(Material):
44 density = 2500
45
46 def __init__(self, mass):
47 super().__init__(mass)
48 self.atomic_types = frozenset([self.__class__.__name__])
49
50
51class Brick(Material):
52 density = 2000
53
54 def __init__(self, mass):
55 super().__init__(mass)
56 self.atomic_types = frozenset([self.__class__.__name__])
57
58
59class Stone(Material):
60 density = 1600
61
62 def __init__(self, mass):
63 super().__init__(mass)
64 self.atomic_types = frozenset([self.__class__.__name__])
65
66
67class Wood(Material):
68 density = 600
69
70 def __init__(self, mass):
71 super().__init__(mass)
72 self.atomic_types = frozenset([self.__class__.__name__])
73
74
75class Steel(Material):
76 density = 7700
77
78 def __init__(self, mass):
79 super().__init__(mass)
80 self.atomic_types = frozenset([self.__class__.__name__])
81
82
83Mine.add_material(Concrete.__name__, Concrete)
84Mine.add_material(Brick.__name__, Brick)
85Mine.add_material(Stone.__name__, Stone)
86Mine.add_material(Wood.__name__, Wood)
87Mine.add_material(Steel.__name__, Steel)
88
89
90class ProductionStrategy(ABC):
91 @abstractmethod
92 def _validate_input(self, *args, **kwargs):
93 pass
94
95 @abstractmethod
96 def produce_new(self, *args, **kwargs):
97 pass
98
99
100class NamedProductionStrategy(ProductionStrategy):
101 def _validate_input(fn):
102 def validated(self, kwargs):
103 for material_name in kwargs:
104
105 cls = Mine.get_material(material_name)
106
107 if not cls:
108 ErrorUtils.raise_hw_err()
109
110 return fn(self, kwargs)
111 return validated
112
113 @_validate_input
114 def produce_new(self, kwargs):
115 return tuple(
116 [Mine.get_material(material_name)(kwargs[material_name]) for material_name in kwargs]
117 )
118
119
120class PositionalProductionStrategy(ProductionStrategy):
121 def _validate_input(fn):
122 def validated(self, args):
123 for maybe_material_instance in args:
124 cls = Mine.get_material(maybe_material_instance.__class__.__name__)
125
126 if not cls:
127 ErrorUtils.raise_hw_err()
128
129 if not isinstance(maybe_material_instance, cls):
130 ErrorUtils.raise_hw_err()
131
132 assert maybe_material_instance.is_usable
133 return fn(self, args)
134 return validated
135
136 @_validate_input
137 def produce_new(self, args):
138 alloy_atomic_types = reduce(lambda aggr, material: aggr.union(material.atomic_types), [material for material in args], frozenset())
139 alloy_signature = "_".join([atomic_type for atomic_type in sorted(alloy_atomic_types, key=str.casefold)])
140
141 alloy_cls = None
142 if existing_alloy_cls := Mine.get_material(alloy_signature):
143 alloy_cls = existing_alloy_cls
144 else:
145 alloy_cls = type(
146 alloy_signature,
147 tuple([Material]),
148 {
149 "density": sum([Mine.get_material(material_cls).density for material_cls in alloy_atomic_types]) / len(alloy_atomic_types),
150 "atomic_types": alloy_atomic_types
151 }
152 )
153
154 Mine.add_material(alloy_signature, alloy_cls)
155
156 alloy = alloy_cls(sum([material.mass for material in args]))
157
158 for material in args:
159 material.is_usable = False
160
161 return alloy
162
163
164class Factory:
165 @staticmethod
166 def _validate_factory_call(fn):
167 def validated(self, *args, **kwargs):
168 if len(args) == 0 and len(kwargs) == 0:
169 ErrorUtils.raise_hw_err()
170 if len(args) > 0 and len(kwargs) > 0:
171 ErrorUtils.raise_hw_err()
172 return fn(self, *args, **kwargs)
173 return validated
174
175 _registry = dict()
176
177 def __init__(self):
178 Factory._registry.update({id(self): self})
179 self.materials = []
180
181 @_validate_factory_call
182 def __call__(self, *args, **kwargs):
183 strategy, payload = (PositionalProductionStrategy(), args) if len(args) else (NamedProductionStrategy(), kwargs)
184
185 batch = strategy.produce_new(payload)
186
187 if len(kwargs):
188 for material in batch:
189 self.materials.append(material)
190 else:
191 self.materials.append(batch)
192
193 return batch
194
195 def can_build(self, volume):
196 return reduce(lambda acc, material: acc + material.volume, [material for material in self.materials if material.is_usable], 0) >= volume
197
198 @classmethod
199 def can_build_together(self, volume):
200 return reduce(lambda acc, material: acc + material.volume, [material for factory_key in Factory._registry for material in Factory._registry[factory_key].materials if material.is_usable], 0) >= volume
......F...
======================================================================
FAIL: test_positional_arguments_multiple_argument_with_dynamics (test.TestFactory.test_positional_arguments_multiple_argument_with_dynamics)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 128, in test_positional_arguments_multiple_argument_with_dynamics
self.assertEqual(mega_material.__class__.__name__,
AssertionError: 'Steel_Wood' != 'Brick_Concrete_Steel_Stone_Wood'
- Steel_Wood
+ Brick_Concrete_Steel_Stone_Wood
----------------------------------------------------------------------
Ran 10 tests in 0.016s
FAILED (failures=1)
25.11.2024 12:50
25.11.2024 12:51
25.11.2024 12:50
25.11.2024 12:55
25.11.2024 12:56
25.11.2024 12:57
25.11.2024 12:59
25.11.2024 13:01
25.11.2024 13:02
25.11.2024 13:02