From daf750e89d76dad57845135127aaefa4396119e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 9 Mar 2026 09:51:56 +0400 Subject: [PATCH] feat: notes-python CRUD example + runtime path/query forwarding - invoke.go: forward sub-path and query string to function pods - server.js v0.1.2: add _path, _query, _method to event - server.py v0.1.1: add _path, _query, _method to event - upload.go: bump runtime versions (nodejs20:v0.1.2, python3.11:v0.1.1) - examples/notes-python: CRUD notes via sub-path routing - sql-runner: generic SQL executor for DDL jobs - notes: CRUD router (/add, /update, /delete) - notes-list: SELECT all notes - init.tf: create TABLE + INDEX on apply --- notes-python/code/notes-list/handler.py | 22 ++++++ notes-python/code/notes-list/requirements.txt | 1 + notes-python/code/notes/handler.py | 74 ++++++++++++++++++ notes-python/code/notes/requirements.txt | 1 + notes-python/code/sql-runner/handler.py | 26 ++++++ notes-python/code/sql-runner/requirements.txt | 1 + notes-python/dist/notes-list.zip | Bin 0 -> 746 bytes notes-python/dist/notes.zip | Bin 0 -> 1226 bytes notes-python/dist/sql-runner.zip | Bin 0 -> 796 bytes notes-python/init.tf | 34 ++++++++ notes-python/main.tf | 21 +++++ notes-python/notes-list.tf | 32 ++++++++ notes-python/notes.tf | 35 +++++++++ notes-python/outputs.tf | 12 +++ notes-python/sql-runner.tf | 26 ++++++ notes-python/variables.tf | 9 +++ 16 files changed, 294 insertions(+) create mode 100644 notes-python/code/notes-list/handler.py create mode 100644 notes-python/code/notes-list/requirements.txt create mode 100644 notes-python/code/notes/handler.py create mode 100644 notes-python/code/notes/requirements.txt create mode 100644 notes-python/code/sql-runner/handler.py create mode 100644 notes-python/code/sql-runner/requirements.txt create mode 100644 notes-python/dist/notes-list.zip create mode 100644 notes-python/dist/notes.zip create mode 100644 notes-python/dist/sql-runner.zip create mode 100644 notes-python/init.tf create mode 100644 notes-python/main.tf create mode 100644 notes-python/notes-list.tf create mode 100644 notes-python/notes.tf create mode 100644 notes-python/outputs.tf create mode 100644 notes-python/sql-runner.tf create mode 100644 notes-python/variables.tf diff --git a/notes-python/code/notes-list/handler.py b/notes-python/code/notes-list/handler.py new file mode 100644 index 0000000..84a0cf4 --- /dev/null +++ b/notes-python/code/notes-list/handler.py @@ -0,0 +1,22 @@ +# 2026-03-09 +# handler.py — возвращает все записи из таблицы notes. +# GET/POST /fn/default/notes-list → JSON массив записей, сортировка по created_at DESC. +import os +import psycopg2 +import psycopg2.extras + + +def handle(event): + dsn = os.environ['PG_DSN'] + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + "SELECT id, title, body, created_at::text FROM notes ORDER BY created_at DESC" + ) + rows = cur.fetchall() + return [dict(r) for r in rows] + except Exception as e: + return {'error': str(e)} + finally: + conn.close() diff --git a/notes-python/code/notes-list/requirements.txt b/notes-python/code/notes-list/requirements.txt new file mode 100644 index 0000000..37ec460 --- /dev/null +++ b/notes-python/code/notes-list/requirements.txt @@ -0,0 +1 @@ +psycopg2-binary diff --git a/notes-python/code/notes/handler.py b/notes-python/code/notes/handler.py new file mode 100644 index 0000000..cce026f --- /dev/null +++ b/notes-python/code/notes/handler.py @@ -0,0 +1,74 @@ +# 2026-03-09 +# handler.py — CRUD роутер для таблицы notes. +# Роутинг по event._path (sub-path URL): +# POST /fn/default/notes/add?title=...&body=... → INSERT +# POST /fn/default/notes/update?id=1&title=... → UPDATE +# POST /fn/default/notes/delete?id=1 → DELETE +# _path и _query добавляет runtime из HTTP запроса (server.py). +import os +import psycopg2 +import psycopg2.extras + + +def handle(event): + dsn = os.environ['PG_DSN'] + # sub-path без ведущего слэша: "add", "update", "delete" + action = event.get('_path', '/').strip('/') + q = event.get('_query', {}) + + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + + if action == 'add': + title = q.get('title') or event.get('title', '') + body = q.get('body') or event.get('body', '') + if not title: + return {'error': 'title is required'} + cur.execute( + "INSERT INTO notes (title, body) VALUES (%s, %s)" + " RETURNING id, title, body, created_at::text", + (title, body) + ) + row = cur.fetchone() + conn.commit() + return dict(row) + + elif action == 'update': + id_ = q.get('id') or event.get('id') + if not id_: + return {'error': 'id is required'} + title = q.get('title') or event.get('title', '') + body = q.get('body') or event.get('body', '') + cur.execute( + "UPDATE notes SET title=%s, body=%s WHERE id=%s" + " RETURNING id, title, body, created_at::text", + (title, body, int(id_)) + ) + row = cur.fetchone() + conn.commit() + return dict(row) if row else {'error': 'not found'} + + elif action == 'delete': + id_ = q.get('id') or event.get('id') + if not id_: + return {'error': 'id is required'} + cur.execute( + "DELETE FROM notes WHERE id=%s RETURNING id", + (int(id_),) + ) + row = cur.fetchone() + conn.commit() + return {'deleted': row['id']} if row else {'error': 'not found'} + + else: + return { + 'error': f'unknown action: /{action}', + 'hint': 'use /add?title=...&body=..., /update?id=X&title=...&body=..., /delete?id=X' + } + + except Exception as e: + conn.rollback() + return {'error': str(e)} + finally: + conn.close() diff --git a/notes-python/code/notes/requirements.txt b/notes-python/code/notes/requirements.txt new file mode 100644 index 0000000..37ec460 --- /dev/null +++ b/notes-python/code/notes/requirements.txt @@ -0,0 +1 @@ +psycopg2-binary diff --git a/notes-python/code/sql-runner/handler.py b/notes-python/code/sql-runner/handler.py new file mode 100644 index 0000000..2b39304 --- /dev/null +++ b/notes-python/code/sql-runner/handler.py @@ -0,0 +1,26 @@ +# 2026-03-09 +# handler.py — универсальный исполнитель SQL запросов. +# Принимает event.statements — массив SQL строк, выполняет последовательно. +# Используется sless_job для DDL операций (CREATE TABLE, миграции и т.д.) +import os +import psycopg2 + + +def handle(event): + dsn = os.environ['PG_DSN'] + statements = event.get('statements', []) + if not statements: + return {'error': 'no statements provided'} + + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor() + for sql in statements: + cur.execute(sql) + conn.commit() + return {'ok': True, 'executed': len(statements)} + except Exception as e: + conn.rollback() + return {'error': str(e)} + finally: + conn.close() diff --git a/notes-python/code/sql-runner/requirements.txt b/notes-python/code/sql-runner/requirements.txt new file mode 100644 index 0000000..37ec460 --- /dev/null +++ b/notes-python/code/sql-runner/requirements.txt @@ -0,0 +1 @@ +psycopg2-binary diff --git a/notes-python/dist/notes-list.zip b/notes-python/dist/notes-list.zip new file mode 100644 index 0000000000000000000000000000000000000000..1e3b0367130cc3e103e500a5a5cd44a4521569bb GIT binary patch literal 746 zcmWIWW@Zs#-~d7f2E{HQ0S8<_Rz_l8N=|B#UO{Ed#QR;h{6vnw|18q}^$?3mm;Db% z*8Ej3oieYx?$z|3SWyzF51 z1K#O>4n*&Mp!;E(dBXIVPv-wlMpx(CH;1&SebFkA?3KFy_aTSW${dv$$6Wn*mtXsQ zM!@D?edF`P|3p9N>%C3le{xl`{_Oqi1o1tai+4X+JNI*6_@8F)2kiG6FGvWhJ)W^Z zoOk!a>3e5u_%1kee|zUlPSeWUR_~qXOzP`>Udg$ve%kkse5$Ou{0s_)LRJfWIzH)L z_PQ6cUjO*shoN7$PHvr4{kP-xca0PNJv?(7zr0~6dgtVP{A|mj3vQYJ1CC|tc)W~! z{wCrf!{a^EIbT^t7jSVEb}dr)(l7u0{857{!BY1Ls-_Eiiq;&Io_Z!rU2yB|mf)uJ z=6|OT^E%dF=w|!y&gY!IhxsMn zQ)j$&Ry|>2fth(duHmN`PzOj?fHxzP2s0vmA=?K^Uofzw5yV2IHgtW+W`fLxfhCPx ZK$oMtEx?)ux=gUPlCxO>w}=g=GNC!VLy ztVvn>q~n|Wtuyjx-`uNycc=JVUmB~Kx!L~6`#-8`AKyO3^~POf=?wN}#TuLc%yL_+ zeNUXfn)}acf578EGFPfgC;w;r*($ZtyL9oo&*ne%9cCU;TCt|#*XEABVf8D^7uccIB|0;IeFoAj`p9 za?Z}Sj0``1JS)GEReGqbJhINrVd4>fj;xxkW=9)6>+-k%+#s!Y+TG%)tKF3LbNOwz z7oOZ+yE6NQeA*0tQ^)PEf_kq+`%aO1E`GpR>m(yv>DqMx&XYL`ZHCf&Zp?{i|Kakt4@>^)ch~8k*WD-kpV_YrlwBq0 z6e_=JTDa|Io>1Nh5${7KmaD&U>%<-pc>LqR6&ZbjNs}8_FIW3lpm$}$^B)CO&q941 z&a7ygZMOQl#WX#kD@q=ATXJW;=y5*$gr)Ep>*IBQU#bV{NXnZ%^WeT^;Wb-jTE9<3 zU@-qApX$s8VZ|qRLJd>5PF=v*UiLWcWyOIXIo97+^7~zUo01`Z|LLo%POB<+%{Wm zyYar+4z>&CvK1c=AATEmgRwmH#|EuEuaua!9L`&R_g0oITT{)9liw7dSxl1nsvNoA zyGCjCKc~8tF)wo~kKWvP&GxZlQ|g}c8fB*$uL-?*&~Rd>Ics{ykxNhCR2*Zn(4Bq2 zs_q;+%h&f)R5Bdh|Guze<$U#D_%BD4(!Kvr4nCc?Q9Ubhy5EhhyK^$SXB@5$J<#ue zsEmWfH0x1zI20^hAAZNe$8d;aPvP_zU-^>qQIS#>>}S zzxQ)rK(*WTNgdx;ng|~Izvt8(u{BD|*Ik^Kxo_V5zuq&C+zGTk822KpYl?+rt>;Zc z-7jmM-(ER)ZjP(boLZ-)>*jOv%-`|hhe7e=%3Wpo2kwSGT^XzuRMPC9WHUivmi*gY z9tV7~+kDPlD$(zXKX+m011ZIw-L|i%wp`HgZ7WfkfOEZg7b5rw5iuFn=O0+cA`JB`DFu&w`>WsI}swYe=Ff*^m zHT)C<>Hz5q@MdHZVMdf6$o7HC4;Wa|2x1{h6LfvZW`fLxfhCQvfUZY(TYxt!8%PBc N5S9UHJD}Sb7yv%a8SMZ7 literal 0 HcmV?d00001 diff --git a/notes-python/dist/sql-runner.zip b/notes-python/dist/sql-runner.zip new file mode 100644 index 0000000000000000000000000000000000000000..b684fdd65a10ad88487bf655a57212f82318e1ce GIT binary patch literal 796 zcmWIWW@Zs#-~d7f2E{HQ0S8<_Rz_l8N=|B#UO{Ef1jnq~4kB&et&TS9s5B}!akonC z@yNd1tC{;amgC^WV=JX9-DXP8o*}eB=7kz#{;n76!VT8>PT93>{fB#^Rg;X8PnUUb zndtWa&;RfH%Nd?7{&f2AV)1tO^>dhiTzM3DC}qO8hvy$iUy#)^IL>)4!$GpI(2sw| zg6bNkGn$7*^$PRWT@XljYnjZ?d%#`haTnW;B|OtDIOi}QkT*GSXW8FlhUYRk)*Ro# zHzhCgT4Id0w~1Ej6vB;Fql z`Vjp6@9odGPFjCIbE^9E%Diu-C$~I*_iwkVh3)6JPj~Rfys$kll_++#Xl`}99O+c#b-qWlHQmI__^yge$~ zBj7sckv}(+q^l3)MclZ0CiwcvbG!XS=K8JqF0_4fhxT?R-)Wy|Uv8*vmi-(y>C1m_PT_?!o?F*5 zGcf%BAK=Z-@#pAeXBH*~hDe}D0M4`{08BeYsfDGPMX9-|c_qbqB^4!F8tZ({>3f)8 z@;!CNTW8f1CKi~P*W(&~iUD