/automagica/nodes.py

https://github.com/OakwoodAI/Automagica · Python · 436 lines · 311 code · 68 blank · 57 comment · 28 complexity · 001db475f2368b47fafd1929713a5da3 MD5 · raw file

  1. """Copyright 2020 Oakwood Technologies BVBA"""
  2. import random
  3. import string
  4. from automagica.config import ACTIVITIES
  5. class Node:
  6. """
  7. Node class reference implementation, all Automagica Flow
  8. nodes should ultimately inherit from this class.
  9. """
  10. def __init__(self, x=None, y=None, uid=None, label=None):
  11. """
  12. Initialize the node
  13. """
  14. if not uid:
  15. uid = self._generate_uid(4)
  16. self.uid = uid
  17. self.label = label
  18. if not x:
  19. x = 0
  20. if not y:
  21. y = 0
  22. self.x = x
  23. self.y = y
  24. def __repr__(self):
  25. """
  26. Object representation of the node
  27. """
  28. return "<{} {}>".format(self.__class__.__name__, self.uid)
  29. def __str__(self):
  30. """
  31. String representation of the node
  32. """
  33. if self.label:
  34. return self.label
  35. else:
  36. return self.__class__.__name__.replace("Node", "")
  37. def _generate_uid(self, k):
  38. """
  39. Generate UID
  40. TODO: replace with secrets module, this is not cryptographically secure
  41. and will be flagged by bandit
  42. """
  43. return "".join(
  44. random.choices(
  45. string.ascii_uppercase
  46. + string.ascii_lowercase
  47. + string.digits,
  48. k=k,
  49. )
  50. )
  51. def to_dict(self):
  52. """
  53. Placeholder method to convert the node to a dictionary
  54. """
  55. raise NotImplementedError
  56. def run(self):
  57. """
  58. Placeholder method for running the node
  59. """
  60. raise NotImplementedError
  61. class StartNode(Node):
  62. """
  63. The Start node is always the beginning of an Automagica Flow
  64. """
  65. def __init__(self, *args, next_node=None, **kwargs):
  66. """
  67. Initialize the Start node
  68. """
  69. super().__init__(*args, **kwargs)
  70. self.next_node = next_node
  71. def get_next_node(self):
  72. """
  73. Method to get the next node
  74. """
  75. return self.next_node
  76. def to_dict(self):
  77. """
  78. Method to convert the start node to a dictionary
  79. """
  80. return {
  81. "uid": self.uid,
  82. "x": self.x,
  83. "y": self.y,
  84. "type": self.__class__.__name__,
  85. "next_node": self.next_node,
  86. "label": self.label,
  87. }
  88. def run(self, bot, on_done=None, on_fail=None):
  89. """
  90. Run the Start node
  91. """
  92. if on_done:
  93. on_done(node=self.next_node)
  94. class ActivityNode(Node):
  95. def __init__(
  96. self,
  97. activity,
  98. *args,
  99. next_node=None,
  100. on_exception_node=None,
  101. class_=None,
  102. args_=None,
  103. return_=None,
  104. **kwargs,
  105. ):
  106. """
  107. Activity node
  108. """
  109. super().__init__(*args, **kwargs)
  110. self.activity = activity
  111. if not args_:
  112. args_ = {}
  113. self.args_ = args_
  114. self.next_node = next_node
  115. self.return_ = return_
  116. self.on_exception_node = on_exception_node
  117. if self.activity.split(".")[-2][0].isupper():
  118. class_ = self.activity.split(".")[-2].lower()
  119. if not self.args_.get("self"):
  120. self.args_["self"] = class_.lower()
  121. self.class_ = class_
  122. def __str__(self):
  123. """
  124. String representation for an Activity node
  125. """
  126. if self.label:
  127. return self.label
  128. else:
  129. return ACTIVITIES[self.activity]["name"]
  130. def get_next_node(self):
  131. """
  132. Method that returns the next node
  133. """
  134. return self.next_node
  135. def to_dict(self):
  136. """
  137. Convert the Activity node to a dictionary
  138. """
  139. return {
  140. "uid": self.uid,
  141. "x": self.x,
  142. "y": self.y,
  143. "type": self.__class__.__name__,
  144. "next_node": self.next_node,
  145. "label": self.label,
  146. "activity": self.activity,
  147. "args": self.args_,
  148. "class": self.class_,
  149. "return_": self.return_,
  150. "on_exception_node": self.on_exception_node,
  151. }
  152. def run(self, bot, on_done=None, on_fail=None):
  153. """
  154. Run the Activity node
  155. """
  156. args = [
  157. "{}={}".format(key, val)
  158. for key, val in self.args_.items()
  159. if key != "self" and val != None and val != ""
  160. ]
  161. command = "# {} ({})\n".format(self, self.uid)
  162. if self.class_:
  163. module_ = ".".join(self.activity.split(".")[:-2])
  164. function_ = self.activity.split(".")[-1]
  165. command += "from {} import {}\n".format(
  166. module_, self.activity.split(".")[-2]
  167. ) # from automagica.activities import Chrome
  168. if function_ == "__init__":
  169. command += "{} = {}({})\n".format(
  170. self.args_["self"],
  171. self.activity.split(".")[-2],
  172. ", ".join(args),
  173. ) # chrome = Chrome()
  174. else:
  175. if self.return_:
  176. command += "{} = {}.{}({}, {})\n".format(
  177. self.return_,
  178. self.activity.split(".")[-2],
  179. function_,
  180. self.args_["self"],
  181. ", ".join(args),
  182. ) # Chrome.get(chrome, 'https://google.com")
  183. else:
  184. command += "{}.{}({}, {})\n".format(
  185. self.activity.split(".")[-2],
  186. function_,
  187. self.args_["self"],
  188. ", ".join(args),
  189. ) # Chrome.get(chrome, 'https://google.com")
  190. else:
  191. module_ = ".".join(self.activity.split(".")[:-1])
  192. function_ = self.activity.split(".")[-1]
  193. command += "from {} import {}\n".format(module_, function_)
  194. if self.return_:
  195. command += "{} = {}({})\n".format(
  196. self.return_, function_, ", ".join(args)
  197. )
  198. else:
  199. command += "{}({})\n".format(function_, ", ".join(args))
  200. bot.run(
  201. command,
  202. on_done=lambda: on_done(node=self.next_node),
  203. on_fail=on_fail,
  204. )
  205. class IfElseNode(Node):
  206. def __init__(
  207. self, *args, condition=None, next_node=None, else_node=None, **kwargs
  208. ):
  209. super().__init__(**kwargs)
  210. self.condition = condition
  211. self.next_node = next_node # True node
  212. self.else_node = else_node
  213. def to_dict(self):
  214. return {
  215. "uid": self.uid,
  216. "x": self.x,
  217. "y": self.y,
  218. "type": self.__class__.__name__,
  219. "next_node": self.next_node,
  220. "else_node": self.else_node,
  221. "label": self.label,
  222. "condition": self.condition,
  223. }
  224. def run(self, bot, on_done=None, on_fail=None):
  225. bot._run_command(f"AUTOMAGICA_RESULT = ({self.condition})")
  226. if bot.interpreter.locals.get("AUTOMAGICA_RESULT"):
  227. on_done(node=self.next_node)
  228. else:
  229. on_done(node=self.else_node)
  230. class LoopNode(Node):
  231. def __init__(
  232. self,
  233. *args,
  234. iterable="",
  235. repeat_n_times=10,
  236. loop_variable="",
  237. next_node=None,
  238. loop_node=None,
  239. **kwargs,
  240. ):
  241. super().__init__(**kwargs)
  242. self.iterable = iterable
  243. self.loop_variable = loop_variable
  244. self.loop_node = loop_node
  245. self.repeat_n_times = repeat_n_times
  246. self.next_node = next_node
  247. def get_next_node(self):
  248. try:
  249. # While iterable, iterate
  250. return next(self.iterable)
  251. except StopIteration:
  252. # Reset iterable and go to next node
  253. self.iterable.seek(0)
  254. return self.next_node
  255. def to_dict(self):
  256. return {
  257. "uid": self.uid,
  258. "x": self.x,
  259. "y": self.y,
  260. "type": self.__class__.__name__,
  261. "iterable": self.iterable,
  262. "loop_variable": self.loop_variable,
  263. "repeat_n_times": self.repeat_n_times,
  264. "next_node": self.next_node,
  265. "loop_node": self.loop_node,
  266. "label": self.label,
  267. }
  268. def run(self, bot, on_done=None, on_fail=None):
  269. bot.run(self.iterable, on_done=on_done, return_value_when_done=True)
  270. class DotPyFileNode(Node):
  271. def __init__(
  272. self,
  273. *args,
  274. dotpyfile_path=None,
  275. next_node=None,
  276. on_exception_node=None,
  277. **kwargs,
  278. ):
  279. super().__init__(**kwargs)
  280. self.dotpyfile_path = dotpyfile_path
  281. self.next_node = next_node
  282. self.on_exception_node = on_exception_node
  283. def to_dict(self):
  284. return {
  285. "uid": self.uid,
  286. "x": self.x,
  287. "y": self.y,
  288. "type": self.__class__.__name__,
  289. "next_node": self.next_node,
  290. "dotpyfile_path": self.dotpyfile_path,
  291. "on_exception_node": self.on_exception_node,
  292. "label": self.label,
  293. }
  294. def run(self, bot, on_done=None, on_fail=None):
  295. with open(
  296. self.dotpyfile_path.replace('"', ""), "r", encoding="utf-8"
  297. ) as f:
  298. command = f.read()
  299. bot.run(command, on_done=lambda: on_done(node=self.next_node))
  300. class CommentNode(Node):
  301. def __init__(self, *args, comment=None, **kwargs):
  302. super().__init__(**kwargs)
  303. self.comment = comment
  304. def to_dict(self):
  305. return {
  306. "uid": self.uid,
  307. "x": self.x,
  308. "y": self.y,
  309. "type": self.__class__.__name__,
  310. "comment": self.comment,
  311. "label": self.label,
  312. }
  313. def run(self, bot):
  314. pass
  315. class SubFlowNode(Node):
  316. def __init__(
  317. self,
  318. *args,
  319. subflow_path=None,
  320. next_node=None,
  321. on_exception_node=None,
  322. **kwargs,
  323. ):
  324. super().__init__(**kwargs)
  325. self.subflow_path = subflow_path
  326. self.next_node = next_node
  327. self.on_exception_node = on_exception_node
  328. def to_dict(self):
  329. return {
  330. "uid": self.uid,
  331. "x": self.x,
  332. "y": self.y,
  333. "type": self.__class__.__name__,
  334. "next_node": self.next_node,
  335. "on_exception_node": self.on_exception_node,
  336. "subflow_path": self.subflow_path,
  337. "label": self.label,
  338. }
  339. class PythonCodeNode(Node):
  340. def __init__(
  341. self,
  342. *args,
  343. code=None,
  344. next_node=None,
  345. on_exception_node=None,
  346. **kwargs,
  347. ):
  348. super().__init__(*args, **kwargs)
  349. self.code = code
  350. self.next_node = next_node
  351. self.on_exception_node = on_exception_node
  352. def to_dict(self):
  353. return {
  354. "uid": self.uid,
  355. "x": self.x,
  356. "y": self.y,
  357. "type": self.__class__.__name__,
  358. "next_node": self.next_node,
  359. "code": self.code,
  360. "on_exception_node": self.on_exception_node,
  361. "label": self.label,
  362. }
  363. def run(self, bot, on_done=None, on_fail=None):
  364. bot.run(
  365. self.code,
  366. on_done=lambda: on_done(node=self.next_node),
  367. on_fail=on_fail,
  368. )