{"id":476457,"date":"2026-04-18T13:15:33","date_gmt":"2026-04-18T13:15:33","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=476457"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=476457","title":{"rendered":"\u0414\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439 Data Pipeline"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0410\u0432\u0442\u043e\u0440 \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0434\u0430\u0442\u0430-\u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0438 \u0438\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u0431\u0435\u0437 \u043b\u043e\u0433\u0438\u043a\u0438 \u0438 \u0431\u0435\u0437 \u043e\u0431\u0449\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0446\u0435\u043b\u044c \u0441\u0442\u0430\u0442\u044c\u0438 &#8212; \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043e\u0431\u0449\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0438 \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u043e\u0443\u043f\u0440\u0430\u0436\u043d\u044f\u0442\u044c\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u043c \u0432 \u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438.<\/p>\n<h3>\u041f\u043e\u0434\u0432\u043e\u0434\u044f\u0449\u0438\u0435 \u0438\u0434\u0435\u0438<\/h3>\n<p>\u0412 Enterprise \u043c\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u044d\u0442\u043e\u043c\u0443:\u00a0<\/p>\n<ol>\n<li>\n<p>\u041a\u0440\u0430\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043c\u0435\u043d\u044c &#8212; \u044d\u0442\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u043d\u0435\u043a\u0438\u0439 <strong>Flow<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u043e\u0434\u043d\u0443 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0430\u0431\u043b\u0438\u0446.\u00a0<\/p>\n<\/li>\n<li>\n<p>\u0422\u0430\u0431\u043b\u0438\u0446\u0430 &#8212; \u044d\u0442\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c, \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u044e\u0449\u0430\u044f \u0431\u0438\u0437\u043d\u0435\u0441. \u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0440\u0430\u0437\u043c\u044b\u0432\u0430\u043d\u0438\u044e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p> \u041f\u043e\u0442\u043e\u043a\u0438 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0443\u0434\u043e\u0431\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043a\u043b\u0430\u0441\u0441\u044b.  <\/p>\n<\/li>\n<li>\n<p> \u0422\u0430\u043a \u043a\u0430\u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439, \u0435\u0451 \u0440\u0430\u0437\u0443\u043c\u043d\u043e \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u043d\u0430 <strong>\u0448\u0430\u0433\u0438 (steps)<\/strong> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.  <\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0430\u0436\u043d\u043e: \u0437\u0430 \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u043e\u044f\u0442\u044c \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Spark DataFrame. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u043c \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 Spark.  <\/p>\n<h3>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0438\u0434\u0435\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u0430<\/h3>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 <strong>Flow<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 <\/p>\n<\/li>\n<li>\n<p>\u041e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u044b \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435:<\/p>\n<pre><code class=\"python\">class MyFlow(Flow):      ...<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/li>\n<li>\n<p> \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430 \u0448\u0430\u0433\u0438 \u2014 \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043b\u0430\u0441\u0441\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c<\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u043b\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0448\u0430\u0433\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b <\/p>\n<\/li>\n<\/ol>\n<h3>\u041f\u043e\u0434\u0445\u043e\u0434 &#8212; Class-based pipeline orchestration \u043d\u0430 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0430\u0445<\/h3>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f. \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u043e\u0442\u043e\u043a <strong>MyFlow<\/strong>, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442 <strong>Flow<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043e\u043f\u0438\u0448\u0435\u043c \u0434\u0430\u043b\u0435\u0435, \u043f\u043e\u043a\u0430 \u044d\u0442\u043e \u043d\u0435 \u0432\u0430\u0436\u043d\u043e. \u0418 \u0435\u0441\u0442\u044c \u043e\u0434\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e: \u0448\u0430\u0433 \u043f\u043e\u0442\u043e\u043a\u0430, \u0447\u0442\u043e \u043e\u043d\u0430 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 <strong>MyTable<\/strong>, \u0438 \u0432\u044b\u0445\u043e\u0434\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 <strong>MyTable2<\/strong>:<\/p>\n<pre><code class=\"python\">class MyFlow(Flow):    @classmethod    @Flow.step()    @Flow.input([MyTable])    @Flow.output(MyTable2)    def step_one(cls, context: Context) -&gt; DataFrame:        print(f\" Step 1: \u0441\u043e\u0437\u0434\u0430\u0451\u043c\/\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c MyTable,{context.id}\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u0442\u043e\u0433\u043e: \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043e\u0434\u0438\u043d \u0448\u0430\u0433 <code>step_one<\/code>. \u0427\u0442\u043e \u0434\u0435\u043b\u0430\u044e\u0442 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b:<\/p>\n<ul>\n<li>\n<p><code>@Flow.step()<\/code> \u2014 \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u043a\u0430\u043a \u0448\u0430\u0433 pipeline <\/p>\n<\/li>\n<li>\n<p><code>@Flow.input([MyTable])<\/code> \u2014 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 <\/p>\n<\/li>\n<li>\n<p><code>@Flow.output(MyTable2)<\/code> \u2014 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 <\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0430\u0436\u043d\u043e: \u0441\u0430\u043c\u0438 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b <strong>\u043d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u043b\u043e\u0433\u0438\u043a\u0443<\/strong>, \u043e\u043d\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442 \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e.<\/p>\n<h3>Context \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f  <\/h3>\n<p>\u041d\u0430\u0448\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f-\u0448\u0430\u0433 \u0438\u043c\u0435\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435\u00a0 \u043d\u0435\u043a\u0438\u0439\u00a0 <strong>context:Context<\/strong> \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 DataFrame &#8212; \u044d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0442\u0438\u043f \u0441\u043f\u0430\u0440\u043a\u0430 \u0434\u043b\u044f \u0442\u0430\u0431\u043b\u0438\u0447\u0435\u043a. \u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 <strong>Context<\/strong>?<\/p>\n<p>\u0422\u0443\u0442 \u043d\u0430\u0434\u043e \u0440\u0435\u0448\u0438\u0442\u044c &#8212; \u043a\u0430\u043a \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438. \u041b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u043a\u0438\u0439 \u043e\u0431\u043e\u0431\u0449\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0434, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043d\u0430\u0434\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u0435 \u0441\u043f\u0438\u0441\u043a\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432. \u042d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u043c\u0438\u043d\u0443\u0441\u044b, \u0440\u0435\u0448\u0430\u0442\u044c \u0432\u0430\u043c. \u041d\u043e \u0434\u043b\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u044d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b.<\/p>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f:<\/p>\n<pre><code class=\"python\">from pydantic import BaseModelclass Context(BaseModel):    # \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u00a0\u00a0\u00a0\u00a0config: Dict[Type[Config], Config] = {}    # \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c\u00a0\u00a0\u00a0\u00a0data: Dict[Union[str, Type[Table]], DataFrame] = {}\u00a0\u00a0\u00a0\u00a0# \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435    diff: Dict[Any, Any] = {}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u044e\u0441\u044b \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430:<\/p>\n<ul>\n<li>\n<p> \u043c\u0435\u043d\u044c\u0448\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445 <\/p>\n<\/li>\n<li>\n<p> \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c <\/p>\n<\/li>\n<li>\n<p> \u0435\u0434\u0438\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f <\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u0438\u043d\u0443\u0441\u044b:<\/p>\n<ul>\n<li>\n<p> \u043c\u0435\u043d\u0435\u0435 \u044f\u0432\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/p>\n<\/li>\n<\/ul>\n<h3>\u0422\u0430\u0431\u043b\u0438\u0446\u044b<\/h3>\n<p>\u0412 Spark \u0441\u0445\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>StructType<\/code>:  <\/p>\n<pre><code class=\"python\">from pyspark.sql.types import StructType, StructField, IntegerType, StringTypeschema = StructType([\u00a0\u00a0\u00a0\u00a0StructField(\"item\", StringType(), nullable=False),\u00a0\u00a0\u00a0\u00a0StructField(\"loc\", StringType(), nullable=False),\u00a0\u00a0\u00a0\u00a0StructField(\"qty\", IntegerType(), nullable=True)])df = spark.createDataFrame([], schema=schema)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0430\u0441\u0442\u043e \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0447\u0435\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f SQLAlchemy, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0438\u0434\u0435\u0435\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u0435\u0433\u043e, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435. \u0411\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <strong>\u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e <\/strong>\u043d\u043e\u0442\u0430\u0446\u0438\u044e \u0432\u0435\u0440\u0441\u0438\u0439 1.x,\u00a0<\/p>\n<pre><code class=\"python\">from sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()class Table(Base):\u00a0\u00a0\u00a0\u00a0__abstract__  = True\u00a0 # \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430class MyTable(Table):\u00a0\u00a0\u00a0\u00a0__tablename__  = \"my_table\"\u00a0\u00a0\u00a0\u00a0id = Column(Integer, primary_key=True, nullable=True)\u00a0\u00a0\u00a0\u00a0name = Column(String(50), nullable=True)\u00a0\u00a0\u00a0\u00a0age = Column(Integer, nullable=True)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u043c \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0443 \u0441 3 \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c\u0438, \u043f\u0435\u0440\u0432\u0430\u044f &#8212; primary key<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0434\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u043b\u0430\u0441\u0441 <strong>Table, <\/strong>\u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432 spark-\u0441\u0442\u0438\u043b\u0435, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440<\/p>\n<pre><code class=\"python\">def _convert_type(sqlalchemy_type) -&gt; T.DataType:    # \u0423\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0430\u044f \u043a\u0430\u0440\u0442\u0430 \u0442\u0438\u043f\u043e\u0432       if isinstance(sqlalchemy_type, Integer):        return T.IntegerType()    if isinstance(sqlalchemy_type, String):        return T.StringType()    if isinstance(sqlalchemy_type, Float):        return T.FloatType()    if isinstance(sqlalchemy_type, Boolean):        return T.BooleanType()    if isinstance(sqlalchemy_type, Date):        return T.DateType()    if isinstance(sqlalchemy_type, DateTime):        return T.TimestampType()    if isinstance(sqlalchemy_type, Numeric):        return T.DoubleType()    raise ValueError(f\"Unsupported type: {sqlalchemy_type}\")class Table(Base):    __abstract__ = True  # \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430    @classmethod    def get_schema(cls) -&gt; T.StructType:        fields = []        for column in cls.__table__.columns:            spark_type = _convert_type(column.type)            fields.append(                T.StructField(column.name, spark_type, column.nullable)            )        return T.StructType(fields)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u0432\u0438\u0434\u0430 <strong>MyTable.get_schema()<\/strong>, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0441\u043f\u0430\u0440\u043a-\u0441\u0442\u0438\u043b\u0435. \u0415\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0438\u043c\u043e\u0440\u0444\u0438\u0437\u043c \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0438<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e\u00a0\u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"python\">class MyTable(Table):    __tablename__ = \"my_table\"    id = Column(Integer)    name = Column(String)    @classmethod    def get_schema(cls) -&gt; T.StructType:        return T.StructType([            T.StructField(\"id\", T.LongType(), False),            T.StructField(\"name\", T.StringType(), True),            T.StructField(\"extra_col\", T.StringType(), True),        ])<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u00a0\u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c StructType. \u0410 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\/\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u044f \u043f\u043e\u0432\u0435\u0440\u0445 \u043d\u0435\u0451.<\/p>\n<pre><code class=\"python\">class MyExtendedTable(Table):    __tablename__ = \"ext\"    id = Column(Integer)    name = Column(String)    @classmethod    def get_schema(cls):              # \u0432\u043e\u0437\u044c\u043c\u0435\u043c SQLAlchemy-\u0432\u0435\u0440\u0441\u0438\u044e        schema = super().get_schema()           # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u0443\u044e \u043a\u043e\u043b\u043e\u043d\u043a\u0443        fields = schema.fields + [            T.StructField(\"sys_load_ts\", T.TimestampType(), False)        ]        return T.StructType(fields)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0440\u0443\u043f\u043d\u043e\u0431\u043b\u043e\u0447\u043d\u043e \u043c\u044b \u043e\u043f\u0438\u0441\u0430\u043b\u0438,\u00a0\u0442\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041e\u0441\u043d\u043e\u0432\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 &#8212; \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043c \u0442\u0435\u043e\u0440\u0438\u044e.\u00a0\u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f:<\/p>\n<pre><code class=\"python\">@classmethod@Flow.step(order=1)def step_one(cls, context):\u00a0\u00a0\u00a0\u00a0pass<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0420\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p><code>Flow.step(order=1)<\/code> \u2192 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 <\/p>\n<\/li>\n<li>\n<p> \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <\/p>\n<\/li>\n<li>\n<p> \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 <code>classmethod<\/code> <\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0430\u0436\u043d\u043e: \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u2014 <strong>\u0441\u043d\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445<\/strong><\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0438\u0434\u0435\u0438:<\/p>\n<ol>\n<li>\n<p>\u0421\u0430\u043c\u0438 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0432\u0435\u0448\u0438\u0432\u0430\u044e\u0442 \u043d\u0435\u043a\u0443\u044e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e <strong>_step_meta <\/strong>\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043f\u043e\u0440\u044f\u0434\u043e\u043a, \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0431\u043e\u0440\u043a\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430 \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0435 <strong>__init_subclass__<\/strong>, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442, \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0438\u0434\u0435\u044f \u2014 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0448\u0430\u0433\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043b\u0430\u0441\u0441\u0430.  <\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e <strong>__init_subclass__ <\/strong>\u044d\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 Python 3.6 (PEP 487). \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u043e\u043c\u0443 \u043a\u043b\u0430\u0441\u0441\u0443 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0432\u043e\u0435\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430.<\/p>\n<pre><code class=\"python\">from functools import wrapsclass Flow:    def __init_subclass__(cls):        # 1) \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u0430\u0441\u0441\u0430, \u0432\u0430\u0436\u043d\u043e!        cls.steps = []        cls.step_meta = {}        # 2)  \u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0432\u0441\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043a\u043b\u0430\u0441\u0441\u0430, \u0438\u0449\u0435\u043c \u043d\u0430\u0448\u0438 \u0448\u0430\u0433\u0438        for name, attr in cls.__dict__.items():            func = None            if isinstance(attr, classmethod):                func = attr.__func__            elif callable(attr):                func = attr            # 3) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u0446\u0430\u0446\u0438\u044e            if func and hasattr(func, \"_step_meta\"):                cls.steps.append(name)                cls.step_meta[name] = func._step_meta        # 4) \u0438 \u043d\u0430\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e order        cls.steps.sort(key=lambda n: cls.step_meta[n][\"order\"])<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u044e\u0441:<\/p>\n<ul>\n<li>\n<p> \u043a\u0430\u0436\u0434\u044b\u0439 Flow \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <strong>\u0441\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0448\u0430\u0433\u043e\u0432<\/strong> <\/p>\n<\/li>\n<li>\n<p> \u043d\u0435\u0442 \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0430\u0441\u0441\u0430\u043c\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043d\u0430\u0448 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0432 \u0448\u0430\u0433. \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043d\u0430\u0448\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 &#8212; \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e<\/p>\n<pre><code class=\"python\">@classmethoddef step(cls, order):    def decorator(func):        func._step_meta = getattr(func, \"_step_meta\", {})        func._step_meta[\"order\"] = order        return func    return decorator<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p> \u0418 \u0437\u0430\u043f\u0443\u0441\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0442\u0430\u043a &#8212; \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c \u0448\u0430\u0433\u0430\u043c, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0448\u0430\u0433\u0430, \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0435\u0435:<\/p>\n<pre><code class=\"python\">@classmethoddef run(cls, context):    print(f\"\\n=== RUN FLOW {cls.__name__} ===\")    for step in cls.steps:        print(\"META:\", step, cls.step_meta[step])        # \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0435 \u0441 context        getattr(cls, step)(context)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>\u0424\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438<\/h3>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c pydantic. \u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 , \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0432\u0441\u044e \u043c\u0430\u0433\u0438\u044e.<\/p>\n<pre><code class=\"python\">from pydantic import BaseModelclass Config(BaseModel):    param1: str = \"A\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u044e\u0441\u044b:<\/p>\n<ul>\n<li>\n<p> \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0442\u0438\u043f\u043e\u0432 <\/p>\n<\/li>\n<li>\n<p> \u0430\u0432\u0442\u043e\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432 IDE <\/p>\n<\/li>\n<li>\n<p> \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u0423\u0434\u043e\u0431\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c yaml-\u0444\u043e\u0440\u043c\u0430\u0442 \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e, \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 yaml \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430:\u00a0<\/p>\n<pre><code class=\"python\">def save_config(config: Config, filepath: str):    yaml.dump(config.dict(), open(filepath, 'w'))def load_config(filepath: str) -&gt; Config:    return Config(**yaml.safe_load(open(filepath)))<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>SQL \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0439  <\/h3>\n<p>\u0418\u0434\u0435\u044f:<\/p>\n<ul>\n<li>\n<p> \u0441\u043d\u0430\u0447\u0430\u043b\u0430 SQL <\/p>\n<\/li>\n<li>\n<p> \u0435\u0441\u043b\u0438 \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u2014 DataFrame API <\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043b\u044e\u0441\u044b:<\/p>\n<ul>\n<li>\n<p> \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043f\u0438\u0441\u0430\u0442\u044c <\/p>\n<\/li>\n<li>\n<p> \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430\u043c<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0441\u043f\u0430\u0440\u043a\u0430, \u043a\u0430\u043a \u043c\u044b \u0437\u043d\u0430\u0435\u043c, \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e SQL<\/p>\n<pre><code class=\"python\">df = spark.range(10)df.createOrReplaceTempView(\"my_view\")df = spark.sql(\"select id, 1 as asd, 2 as ewq from my_view\")df.show()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f &#8212; \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0448\u0430\u0433, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043b\u0438\u0431\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u043b\u0438\u0431\u043e \u043c\u044b \u0441\u0430\u043c\u0438 \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 SQL, \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442.<\/p>\n<pre><code class=\"python\">class MyFlow(Flow):    @classmethod    @Flow.step(order=3)    @Flow.input([MyTable])    @Flow.output(AnotherTable)    # \u041b\u0438\u0431\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430    @Flow.sql(\"step_three.sql\")    def step_three(cls, context: Context):        context.data[MyTable].show()        # \u043b\u0438\u0431\u043e \u0441\u0430\u043c\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c         df = cls.execute_sql(context, \"step_three.sql\",vars={\"id\":1})        df.show()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0441\u0430\u043c \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f <strong>step_three.sql<\/strong>:<\/p>\n<pre><code class=\"sql\">select * from my_tablewhere id={{id}}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e  &#8212; \u044d\u0442\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e  <\/p>\n<pre><code class=\"python\">@classmethoddef sql(cls, file: str, vars: Dict[str, Any] = None):    \"\"\"    \u0412\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0448\u0430\u0433:    @Flow.sql(\"step1.sql\", vars={\"x\":1})    \"\"\"    def decorator(func):        func._step_meta = getattr(func, \"_step_meta\", {})        func._step_meta[\"sql\"] = {            \"file\": file,            \"vars\": vars        }        return func    return decorator<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 view:<\/p>\n<pre><code class=\"python\">def camel_to_snake(name: ...) -&gt; str:    \"\"\"    \u041f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 \u0432\u0438\u0434.        \u041f\u0440\u0438\u043c\u0435\u0440:        Abc -&gt; abc    \"\"\"    @classmethoddef create_temp_views(cls, context: Context, tables: List[Any]):    \"\"\"    \u0421\u043e\u0437\u0434\u0430\u0451\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 spark view  \u0434\u043b\u044f SQL    \"\"\"    for table_class in tables:        df = context.data.get(table_class, None)        table_name = table_class if isinstance(table_class, str) else camel_to_snake(table_class.__name__)        if df is not None:                df.createOrReplaceTempView(table_name)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f sql-\u043a\u043e\u0434\u0430 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u043e\u0434\u0445\u043e\u0434 <strong>jinja2 <\/strong>\u0434\u043b\u044f \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445<\/p>\n<pre><code class=\"python\">from jinja2 import Template@classmethoddef execute_sql(cls, context: Context, *, file: str, vars: Dict[str, Any]):    # \u0447\u0438\u0442\u0430\u0435\u043c \u0444\u0430\u0439\u043b     sql_query = cls.read_sql(file)    # \u041f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435    if vars:        sql_query = Template(sql_query).render(vars)    result_df = context.spark.sql(sql_query)    return result_df<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0431\u043b\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">@classmethod    def run(cls, context:Context):        print(f\"\\n=== RUN FLOW {cls.__name__} ===\")        for i, step in enumerate(cls.steps):            print(\"META:\", step, cls.step_meta[step], i)            cls.current_step = i                       meta = cls.step_meta[step]            # ---------- SQL ----------            if \"sql\" in meta:                sql_meta = meta[\"sql\"]                 cls.create_temp_views(context, meta[\"input\"])                        result = cls.execute_sql(                    context,                    file=sql_meta[\"file\"],                    vars=sql_meta.get(\"vars\")                )                result.show()                # \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442                result_table =  meta[\"output\"][0]                context.data[result_table] = result            # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0448\u0430\u0433            getattr(cls, step)(context)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u041c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a:<\/p>\n<ul>\n<li>\n<p> Flow = \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441 <\/p>\n<\/li>\n<li>\n<p> Step = \u0448\u0430\u0433 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 <\/p>\n<\/li>\n<li>\n<p> Context = \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 <\/p>\n<\/li>\n<li>\n<p> \u0422\u0430\u0431\u043b\u0438\u0446\u044b = \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 <\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c:<\/p>\n<ul>\n<li>\n<p> \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0435\u0434\u0438\u043d\u043e\u043e\u0431\u0440\u0430\u0437\u043d\u0430\u044f <\/p>\n<\/li>\n<li>\n<p> \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c <\/p>\n<\/li>\n<\/ul>\n<h3>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435<\/h3>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u044b \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0438\u0434\u0435\u0438. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f:<\/p>\n<ul>\n<li>\n<p> dependency graph \u0432\u043c\u0435\u0441\u0442\u043e order <\/p>\n<\/li>\n<li>\n<p> \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0432\u0445\u043e\u0434\u043e\u0432\/\u0432\u044b\u0445\u043e\u0434\u043e\u0432 <\/p>\n<\/li>\n<li>\n<p> retry \u0438 error handling <\/p>\n<\/li>\n<li>\n<p> execution backend (Spark \/ SQL \/ Pandas) <\/p>\n<\/li>\n<li>\n<p> \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0440\u044b \u0432\u043c\u0435\u0441\u0442\u043e <code>__init_subclass__<\/code><\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e dependency graph<\/strong><\/p>\n<p>\u0422\u0435\u043a\u0443\u0449\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u044f\u0432\u043d\u044b\u0439 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0448\u0430\u0433\u043e\u0432 (<code>order<\/code>). \u041e\u0434\u043d\u0430\u043a\u043e \u0432 \u0431\u043e\u043b\u0435\u0435 \u0437\u0440\u0435\u043b\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0445 orchestration \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f dependency graph (DAG), \u0433\u0434\u0435 \u0448\u0430\u0433\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442:<\/p>\n<ul>\n<li>\n<p> \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0442\u044c \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f <\/p>\n<\/li>\n<li>\n<p> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0448\u0430\u0433\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e <\/p>\n<\/li>\n<li>\n<p> \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430<\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">@Flow.step(depends_on=[\"step_one\", \"step_two\"])def step_three(cls, context):    ...<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1025014\/\">https:\/\/habr.com\/ru\/articles\/1025014\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0410\u0432\u0442\u043e\u0440 \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0434\u0430\u0442\u0430-\u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0438 \u0438\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 \u0431\u0435\u0437 \u043b\u043e\u0433\u0438\u043a\u0438 \u0438 \u0431\u0435\u0437 \u043e\u0431\u0449\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0446\u0435\u043b\u044c \u0441\u0442\u0430\u0442\u044c\u0438 &#8212; \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043e\u0431\u0449\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0438 \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u043e\u0443\u043f\u0440\u0430\u0436\u043d\u044f\u0442\u044c\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u043c \u0432 \u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438.\u041f\u043e\u0434\u0432\u043e\u0434\u044f\u0449\u0438\u0435 \u0438\u0434\u0435\u0438\u0412 Enterprise \u043c\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u044d\u0442\u043e\u043c\u0443:\u00a0\u041a\u0440\u0430\u0435\u0443\u0433\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043c\u0435\u043d\u044c &#8212; \u044d\u0442\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u043d\u0435\u043a\u0438\u0439 Flow, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u043e\u0434\u043d\u0443 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0430\u0431\u043b\u0438\u0446.\u00a0\u0422\u0430\u0431\u043b\u0438\u0446\u0430 &#8212; \u044d\u0442\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c, \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u044e\u0449\u0430\u044f \u0431\u0438\u0437\u043d\u0435\u0441. \u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0440\u0430\u0437\u043c\u044b\u0432\u0430\u043d\u0438\u044e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438. \u041f\u043e\u0442\u043e\u043a\u0438 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0443\u0434\u043e\u0431\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043a\u043b\u0430\u0441\u0441\u044b.   \u0422\u0430\u043a \u043a\u0430\u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439, \u0435\u0451 \u0440\u0430\u0437\u0443\u043c\u043d\u043e \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u043d\u0430 \u0448\u0430\u0433\u0438 (steps) \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.  \u0412\u0430\u0436\u043d\u043e: \u0437\u0430 \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u043e\u044f\u0442\u044c \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Spark DataFrame. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u043c \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 Spark.  \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0438\u0434\u0435\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u0430\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 Flow, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u043b\u043e\u0433\u0438\u043a\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u041e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u044b \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435:class MyFlow(Flow):      &#8230; \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430 \u0448\u0430\u0433\u0438 \u2014 \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043b\u0430\u0441\u0441\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0414\u0435\u043b\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0448\u0430\u0433\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b \u041f\u043e\u0434\u0445\u043e\u0434 &#8212; Class-based pipeline orchestration \u043d\u0430 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0430\u0445\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f. \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u043e\u0442\u043e\u043a MyFlow, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442 Flow, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043e\u043f\u0438\u0448\u0435\u043c \u0434\u0430\u043b\u0435\u0435, \u043f\u043e\u043a\u0430 \u044d\u0442\u043e \u043d\u0435 \u0432\u0430\u0436\u043d\u043e. \u0418 \u0435\u0441\u0442\u044c \u043e\u0434\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e: \u0448\u0430\u0433 \u043f\u043e\u0442\u043e\u043a\u0430, \u0447\u0442\u043e \u043e\u043d\u0430 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 MyTable, \u0438 \u0432\u044b\u0445\u043e\u0434\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 MyTable2:class MyFlow(Flow):    @classmethod    @Flow.step()    @Flow.input([MyTable])    @Flow.output(MyTable2)    def step_one(cls, context: Context) -&gt; DataFrame:        print(f&#187; Step 1: \u0441\u043e\u0437\u0434\u0430\u0451\u043c\/\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c MyTable,{context.id}&#187;)\u0418\u0442\u043e\u0433\u043e: \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043e\u0434\u0438\u043d \u0448\u0430\u0433 step_one. \u0427\u0442\u043e \u0434\u0435\u043b\u0430\u044e\u0442 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b:@Flow.step() \u2014 \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u043a\u0430\u043a \u0448\u0430\u0433 pipeline @Flow.input([MyTable]) \u2014 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 @Flow.output(MyTable2) \u2014 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0412\u0430\u0436\u043d\u043e: \u0441\u0430\u043c\u0438 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b \u043d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u043b\u043e\u0433\u0438\u043a\u0443, \u043e\u043d\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442 \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e.Context \u2014 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f  \u041d\u0430\u0448\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f-\u0448\u0430\u0433 \u0438\u043c\u0435\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435\u00a0 \u043d\u0435\u043a\u0438\u0439\u00a0 context:Context \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 DataFrame &#8212; \u044d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0442\u0438\u043f \u0441\u043f\u0430\u0440\u043a\u0430 \u0434\u043b\u044f \u0442\u0430\u0431\u043b\u0438\u0447\u0435\u043a. \u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 Context?\u0422\u0443\u0442 \u043d\u0430\u0434\u043e \u0440\u0435\u0448\u0438\u0442\u044c &#8212; \u043a\u0430\u043a \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438. \u041b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u043a\u0438\u0439 \u043e\u0431\u043e\u0431\u0449\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0434, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043d\u0430\u0434\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u0435 \u0441\u043f\u0438\u0441\u043a\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432. \u042d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0438 \u043f\u043b\u044e\u0441\u044b \u0438 \u043c\u0438\u043d\u0443\u0441\u044b, \u0440\u0435\u0448\u0430\u0442\u044c \u0432\u0430\u043c. \u041d\u043e \u0434\u043b\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u044d\u0442\u043e \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b.\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f:from pydantic import BaseModelclass Context(BaseModel):    # \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u00a0\u00a0\u00a0\u00a0config: Dict[Type[Config], Config] = {}    # \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c\u00a0\u00a0\u00a0\u00a0data: Dict[Union[str, Type[Table]], DataFrame] = {}\u00a0\u00a0\u00a0\u00a0# \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435    diff: Dict[Any, Any] = {}\u041f\u043b\u044e\u0441\u044b \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430: \u043c\u0435\u043d\u044c\u0448\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445  \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0442\u044c  \u0435\u0434\u0438\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u041c\u0438\u043d\u0443\u0441\u044b: \u043c\u0435\u043d\u0435\u0435 \u044f\u0432\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438\u0422\u0430\u0431\u043b\u0438\u0446\u044b\u0412 Spark \u0441\u0445\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 StructType:  from pyspark.sql.types import StructType, StructField, IntegerType, StringTypeschema = StructType([\u00a0\u00a0\u00a0\u00a0StructField(&#171;item&#187;, StringType(), nullable=False),\u00a0\u00a0\u00a0\u00a0StructField(&#171;loc&#187;, StringType(), nullable=False),\u00a0\u00a0\u00a0\u00a0StructField(&#171;qty&#187;, IntegerType(), nullable=True)])df = spark.createDataFrame([], schema=schema)\u0427\u0430\u0441\u0442\u043e \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0447\u0435\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f SQLAlchemy, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0438\u0434\u0435\u0435\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u0435\u0433\u043e, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435. \u0411\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u043d\u043e\u0442\u0430\u0446\u0438\u044e \u0432\u0435\u0440\u0441\u0438\u0439 1.x,\u00a0from sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()class Table(Base):\u00a0\u00a0\u00a0\u00a0__abstract__  = True\u00a0 # \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430class MyTable(Table):\u00a0\u00a0\u00a0\u00a0__tablename__  = &#171;my_table&#187;\u00a0\u00a0\u00a0\u00a0id = Column(Integer, primary_key=True, nullable=True)\u00a0\u00a0\u00a0\u00a0name = Column(String(50), nullable=True)\u00a0\u00a0\u00a0\u00a0age = Column(Integer, nullable=True)\u041e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u043c \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0443 \u0441 3 \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c\u0438, \u043f\u0435\u0440\u0432\u0430\u044f &#8212; primary key\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0434\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u043b\u0430\u0441\u0441 Table, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432 spark-\u0441\u0442\u0438\u043b\u0435, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440def _convert_type(sqlalchemy_type) -&gt; T.DataType:    # \u0423\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0430\u044f \u043a\u0430\u0440\u0442\u0430 \u0442\u0438\u043f\u043e\u0432       if isinstance(sqlalchemy_type, Integer):        return T.IntegerType()    if isinstance(sqlalchemy_type, String):        return T.StringType()    if isinstance(sqlalchemy_type, Float):        return T.FloatType()    if isinstance(sqlalchemy_type, Boolean):        return T.BooleanType()    if isinstance(sqlalchemy_type, Date):        return T.DateType()    if isinstance(sqlalchemy_type, DateTime):        return T.TimestampType()    if isinstance(sqlalchemy_type, Numeric):        return T.DoubleType()    raise ValueError(f&#187;Unsupported type: {sqlalchemy_type}&#187;)class Table(Base):    __abstract__ = True  # \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430    @classmethod    def get_schema(cls) -&gt; T.StructType:        fields = []        for column in cls.__table__.columns:            spark_type = _convert_type(column.type)            fields.append(                T.StructField(column.name, spark_type, column.nullable)            )        return T.StructType(fields)\u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u0432\u0438\u0434\u0430 MyTable.get_schema(), \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0441\u0442\u0430\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0441\u043f\u0430\u0440\u043a-\u0441\u0442\u0438\u043b\u0435. \u0415\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0438\u043c\u043e\u0440\u0444\u0438\u0437\u043c \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0438\u041c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e\u00a0\u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434:class MyTable(Table):    __tablename__ = &#171;my_table&#187;    id = Column(Integer)    name = Column(String)    @classmethod    def get_schema(cls) -&gt; T.StructType:        return T.StructType([            T.StructField(&#171;id&#187;, T.LongType(), False),            T.StructField(&#171;name&#187;, T.StringType(), True),            T.StructField(&#171;extra_col&#187;, T.StringType(), True),        ])\u00a0\u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c StructType. \u0410 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\/\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u044f \u043f\u043e\u0432\u0435\u0440\u0445 \u043d\u0435\u0451.class MyExtendedTable(Table):    __tablename__ = &#171;ext&#187;    id = Column(Integer)    name = Column(String)    @classmethod    def get_schema(cls):              # \u0432\u043e\u0437\u044c\u043c\u0435\u043c SQLAlchemy-\u0432\u0435\u0440\u0441\u0438\u044e        schema = super().get_schema()           # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u0443\u044e \u043a\u043e\u043b\u043e\u043d\u043a\u0443        fields = schema.fields + [            T.StructField(&#171;sys_load_ts&#187;, T.TimestampType(), False)        ]        return T.StructType(fields)\u041a\u0440\u0443\u043f\u043d\u043e\u0431\u043b\u043e\u0447\u043d\u043e \u043c\u044b \u043e\u043f\u0438\u0441\u0430\u043b\u0438,\u00a0\u0442\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041e\u0441\u043d\u043e\u0432\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 &#8212; \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043c \u0442\u0435\u043e\u0440\u0438\u044e.\u00a0\u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f:@classmethod@Flow.step(order=1)def step_one(cls, context):\u00a0\u00a0\u00a0\u00a0pass\u0420\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a:Flow.step(order=1) \u2192 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440  \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438  \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 classmethod \u0412\u0430\u0436\u043d\u043e: \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u2014 \u0441\u043d\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0438\u0434\u0435\u0438:\u0421\u0430\u043c\u0438 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0432\u0435\u0448\u0438\u0432\u0430\u044e\u0442 \u043d\u0435\u043a\u0443\u044e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e _step_meta \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043f\u043e\u0440\u044f\u0434\u043e\u043a, \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0442.\u0434.\u0421\u0431\u043e\u0440\u043a\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430 \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0435 __init_subclass__, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442, \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u043e\u0432.\u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0438\u0434\u0435\u044f \u2014 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0448\u0430\u0433\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043b\u0430\u0441\u0441\u0430.  \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e __init_subclass__ \u044d\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 Python 3.6 (PEP 487). \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u043e\u043c\u0443 \u043a\u043b\u0430\u0441\u0441\u0443 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0432\u043e\u0435\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430.from functools import wrapsclass Flow:    def __init_subclass__(cls):        # 1) \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u0430\u0441\u0441\u0430, \u0432\u0430\u0436\u043d\u043e!        cls.steps = []        cls.step_meta = {}        # 2)  \u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0432\u0441\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043a\u043b\u0430\u0441\u0441\u0430, \u0438\u0449\u0435\u043c \u043d\u0430\u0448\u0438 \u0448\u0430\u0433\u0438        for name, attr in cls.__dict__.items():            func = None            if isinstance(attr, classmethod):                func = attr.__func__            elif callable(attr):                func = attr            # 3) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u0446\u0430\u0446\u0438\u044e            if func and hasattr(func, &#171;_step_meta&#187;):                cls.steps.append(name)                cls.step_meta[name] = func._step_meta        # 4) \u0438 \u043d\u0430\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e order        cls.steps.sort(key=lambda n: cls.step_meta[n][&#171;order&#187;])\u041f\u043b\u044e\u0441: \u043a\u0430\u0436\u0434\u044b\u0439 Flow \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0448\u0430\u0433\u043e\u0432  \u043d\u0435\u0442 \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043a\u043b\u0430\u0441\u0441\u0430\u043c\u0438\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043d\u0430\u0448 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0432 \u0448\u0430\u0433. \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043d\u0430\u0448\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 &#8212; \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0442\u0430-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044e@classmethoddef step(cls, order):    def decorator(func):        func._step_meta = getattr(func, &#171;_step_meta&#187;, {})        func._step_meta[&#171;order&#187;] = order        return func    return decorator \u0418 \u0437\u0430\u043f\u0443\u0441\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0442\u0430\u043a &#8212; \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c \u0448\u0430\u0433\u0430\u043c, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0448\u0430\u0433\u0430, \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0435\u0435:@classmethoddef run(cls, context):    print(f&#187;\\n=== RUN FLOW {cls.__name__} ===&#187;)    for step in cls.steps:        print(&#171;META:&#187;, step, cls.step_meta[step])        # \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0435 \u0441 context        getattr(cls, step)(context)\u0424\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c pydantic. \u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 , \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0432\u0441\u044e \u043c\u0430\u0433\u0438\u044e.from pydantic import BaseModelclass Config(BaseModel):    param1: str = &#171;A&#187;\u041f\u043b\u044e\u0441\u044b: \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0442\u0438\u043f\u043e\u0432  \u0430\u0432\u0442\u043e\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432 IDE  \u0443\u0434\u043e\u0431\u043d\u0430\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u0423\u0434\u043e\u0431\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c yaml-\u0444\u043e\u0440\u043c\u0430\u0442 \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e, \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 yaml \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430:\u00a0def save_config(config: Config, filepath: str):    yaml.dump(config.dict(), open(filepath, &#8216;w&#8217;))def load_config(filepath: str) -&gt; Config:    return Config(**yaml.safe_load(open(filepath)))SQL \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0439  \u0418\u0434\u0435\u044f: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 SQL  \u0435\u0441\u043b\u0438 \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u2014 DataFrame API \u041f\u043b\u044e\u0441\u044b: \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043f\u0438\u0441\u0430\u0442\u044c  \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0414\u043b\u044f \u0441\u043f\u0430\u0440\u043a\u0430, \u043a\u0430\u043a \u043c\u044b \u0437\u043d\u0430\u0435\u043c, \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e SQLdf = spark.range(10)df.createOrReplaceTempView(&#171;my_view&#187;)df = spark.sql(&#171;select id, 1 as asd, 2 as ewq from my_view&#187;)df.show()\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f &#8212; \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0448\u0430\u0433, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043b\u0438\u0431\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u043b\u0438\u0431\u043e \u043c\u044b \u0441\u0430\u043c\u0438 \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 SQL, \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442.class MyFlow(Flow):    @classmethod    @Flow.step(order=3)    @Flow.input([MyTable])    @Flow.output(AnotherTable)    # \u041b\u0438\u0431\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430    @Flow.sql(&#171;step_three.sql&#187;)    def step_three(cls, context: Context):        context.data[MyTable].show()        # \u043b\u0438\u0431\u043e \u0441\u0430\u043c\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c         df = cls.execute_sql(context, &#171;step_three.sql&#187;,vars={&#171;id&#187;:1})        df.show()\u0418 \u0441\u0430\u043c \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f step_three.sql:select * from my_tablewhere id={{id}}\u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e  &#8212; \u044d\u0442\u043e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0442\u043e\u043b\u044c\u043a\u043e&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-476457","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/476457","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=476457"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/476457\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=476457"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=476457"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=476457"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}