python-fasthtml 0.12.5__tar.gz → 0.12.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {python_fasthtml-0.12.5/python_fasthtml.egg-info → python_fasthtml-0.12.6}/PKG-INFO +1 -1
  2. python_fasthtml-0.12.6/fasthtml/__init__.py +2 -0
  3. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/_modidx.py +14 -0
  4. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/core.py +1 -1
  5. python_fasthtml-0.12.6/fasthtml/stripe_otp.py +212 -0
  6. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6/python_fasthtml.egg-info}/PKG-INFO +1 -1
  7. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/SOURCES.txt +1 -0
  8. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/settings.ini +1 -1
  9. python_fasthtml-0.12.5/fasthtml/__init__.py +0 -2
  10. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/CONTRIBUTING.md +0 -0
  11. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/LICENSE +0 -0
  12. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/MANIFEST.in +0 -0
  13. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/README.md +0 -0
  14. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/authmw.py +0 -0
  15. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/basics.py +0 -0
  16. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/cli.py +0 -0
  17. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/common.py +0 -0
  18. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/components.py +0 -0
  19. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/components.pyi +0 -0
  20. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/core.pyi +0 -0
  21. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/fastapp.py +0 -0
  22. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/ft.py +0 -0
  23. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/js.py +0 -0
  24. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/jupyter.py +0 -0
  25. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/katex.js +0 -0
  26. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/live_reload.py +0 -0
  27. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/oauth.py +0 -0
  28. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/pico.py +0 -0
  29. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/starlette.py +0 -0
  30. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/svg.py +0 -0
  31. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/toaster.py +0 -0
  32. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/xtend.py +0 -0
  33. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/fasthtml/xtend.pyi +0 -0
  34. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/pyproject.toml +0 -0
  35. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  36. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/entry_points.txt +0 -0
  37. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/not-zip-safe +0 -0
  38. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/requires.txt +0 -0
  39. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/python_fasthtml.egg-info/top_level.txt +0 -0
  40. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/setup.cfg +0 -0
  41. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/setup.py +0 -0
  42. {python_fasthtml-0.12.5 → python_fasthtml-0.12.6}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.12.5
3
+ Version: 0.12.6
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -0,0 +1,2 @@
1
+ __version__ = "0.12.6"
2
+ from .core import *
@@ -201,6 +201,20 @@ d = { 'settings': { 'branch': 'main',
201
201
  'fasthtml.pico.Search': ('api/pico.html#search', 'fasthtml/pico.py'),
202
202
  'fasthtml.pico.set_pico_cls': ('api/pico.html#set_pico_cls', 'fasthtml/pico.py')},
203
203
  'fasthtml.starlette': {},
204
+ 'fasthtml.stripe_otp': { 'fasthtml.stripe_otp.Payment': ('explains/stripe.html#payment', 'fasthtml/stripe_otp.py'),
205
+ 'fasthtml.stripe_otp._search_app': ('explains/stripe.html#_search_app', 'fasthtml/stripe_otp.py'),
206
+ 'fasthtml.stripe_otp.account_management': ( 'explains/stripe.html#account_management',
207
+ 'fasthtml/stripe_otp.py'),
208
+ 'fasthtml.stripe_otp.archive_price': ('explains/stripe.html#archive_price', 'fasthtml/stripe_otp.py'),
209
+ 'fasthtml.stripe_otp.before': ('explains/stripe.html#before', 'fasthtml/stripe_otp.py'),
210
+ 'fasthtml.stripe_otp.cancel': ('explains/stripe.html#cancel', 'fasthtml/stripe_otp.py'),
211
+ 'fasthtml.stripe_otp.create_checkout_session': ( 'explains/stripe.html#create_checkout_session',
212
+ 'fasthtml/stripe_otp.py'),
213
+ 'fasthtml.stripe_otp.create_price': ('explains/stripe.html#create_price', 'fasthtml/stripe_otp.py'),
214
+ 'fasthtml.stripe_otp.home': ('explains/stripe.html#home', 'fasthtml/stripe_otp.py'),
215
+ 'fasthtml.stripe_otp.post': ('explains/stripe.html#post', 'fasthtml/stripe_otp.py'),
216
+ 'fasthtml.stripe_otp.refund': ('explains/stripe.html#refund', 'fasthtml/stripe_otp.py'),
217
+ 'fasthtml.stripe_otp.success': ('explains/stripe.html#success', 'fasthtml/stripe_otp.py')},
204
218
  'fasthtml.svg': { 'fasthtml.svg.Circle': ('api/svg.html#circle', 'fasthtml/svg.py'),
205
219
  'fasthtml.svg.Ellipse': ('api/svg.html#ellipse', 'fasthtml/svg.py'),
206
220
  'fasthtml.svg.Line': ('api/svg.html#line', 'fasthtml/svg.py'),
@@ -131,7 +131,7 @@ def _is_body(anno): return issubclass(anno, (dict,ns)) or _annotations(anno)
131
131
  # %% ../nbs/api/00_core.ipynb
132
132
  def _formitem(form, k):
133
133
  "Return single item `k` from `form` if len 1, otherwise return list"
134
- if isinstance(form, dict): return form[k]
134
+ if isinstance(form, dict): return form.get(k)
135
135
  o = form.getlist(k)
136
136
  return o[0] if len(o) == 1 else o if o else None
137
137
 
@@ -0,0 +1,212 @@
1
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/explains/Stripe.ipynb.
2
+
3
+ # %% auto 0
4
+ __all__ = ['DOMAIN_URL', 'app_nm', 'price_list', 'price', 'bware', 'app', 'rt', 'WEBHOOK_SECRET', 'db', 'payments',
5
+ 'create_price', 'archive_price', 'before', 'home', 'create_checkout_session', 'Payment', 'post', 'success',
6
+ 'cancel', 'refund', 'account_management']
7
+
8
+ # %% ../nbs/explains/Stripe.ipynb
9
+ from .common import *
10
+ import os
11
+
12
+ # %% ../nbs/explains/Stripe.ipynb
13
+ import stripe
14
+
15
+ # %% ../nbs/explains/Stripe.ipynb
16
+ stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
17
+ DOMAIN_URL = os.environ.get("DOMAIN_URL", "http://localhost:5001")
18
+
19
+ # %% ../nbs/explains/Stripe.ipynb
20
+ def _search_app(app_nm:str, limit=1):
21
+ "Checks for product based on app_nm and returns the product if it exists"
22
+ return stripe.Product.search(query=f"name:'{app_nm}' AND active:'True'", limit=limit).data
23
+
24
+ def create_price(app_nm:str, amt:int, currency="usd") -> list[stripe.Price]:
25
+ "Create a product and bind it to a price object. If product already exist just return the price list."
26
+ existing_product = _search_app(app_nm)
27
+ if existing_product:
28
+ return stripe.Price.list(product=existing_product[0].id).data
29
+ else:
30
+ product = stripe.Product.create(name=f"{app_nm}")
31
+ return [stripe.Price.create(product=product.id, unit_amount=amt, currency=currency)]
32
+
33
+ def archive_price(app_nm:str):
34
+ "Archive a price - useful for cleanup if testing."
35
+ existing_products = _search_app(app_nm, limit=50)
36
+ for product in existing_products:
37
+ for price in stripe.Price.list(product=product.id).data:
38
+ stripe.Price.modify(price.id, active=False)
39
+ stripe.Product.modify(product.id, active=False)
40
+
41
+ # %% ../nbs/explains/Stripe.ipynb
42
+ app_nm = "[FastHTML Docs] Demo Product"
43
+ price_list = create_price(app_nm, amt=1999)
44
+ assert len(price_list) == 1, 'For this tutorial, we only have one price bound to our product.'
45
+ price = price_list[0]
46
+
47
+ # %% ../nbs/explains/Stripe.ipynb
48
+ def before(sess): sess['auth'] = 'hamel@hamel.com'
49
+ bware = Beforeware(before, skip=['/webhook'])
50
+ app, rt = fast_app(before=bware)
51
+
52
+ # %% ../nbs/explains/Stripe.ipynb
53
+ WEBHOOK_SECRET = os.getenv("STRIPE_LOCAL_TEST_WEBHOOK_SECRET")
54
+
55
+ # %% ../nbs/explains/Stripe.ipynb
56
+ @rt("/")
57
+ def home(sess):
58
+ auth = sess['auth']
59
+ return Titled(
60
+ "Buy Now",
61
+ Div(H2("Demo Product - $19.99"),
62
+ P(f"Welcome, {auth}"),
63
+ Button("Buy Now", hx_post="/create-checkout-session", hx_swap="none"),
64
+ A("View Account", href="/account")))
65
+
66
+ # %% ../nbs/explains/Stripe.ipynb
67
+ @rt("/create-checkout-session", methods=["POST"])
68
+ async def create_checkout_session(sess):
69
+ checkout_session = stripe.checkout.Session.create(
70
+ line_items=[{'price': price.id, 'quantity': 1}],
71
+ mode='payment',
72
+ payment_method_types=['card'],
73
+ customer_email=sess['auth'],
74
+ metadata={'app_name': app_nm,
75
+ 'AnyOther': 'Metadata',},
76
+ # CHECKOUT_SESSION_ID is a special variable Stripe fills in for you
77
+ success_url=DOMAIN_URL + '/success?checkout_sid={CHECKOUT_SESSION_ID}',
78
+ cancel_url=DOMAIN_URL + '/cancel')
79
+ return Redirect(checkout_session.url)
80
+
81
+ # %% ../nbs/explains/Stripe.ipynb
82
+ # Database Table
83
+ class Payment:
84
+ checkout_session_id: str # Stripe checkout session ID (primary key)
85
+ email: str
86
+ amount: int # Amount paid in cents
87
+ payment_status: str # paid, pending, failed
88
+ created_at: int # Unix timestamp
89
+ metadata: str # Additional payment metadata as JSON
90
+
91
+ # %% ../nbs/explains/Stripe.ipynb
92
+ db = Database("stripe_payments.db")
93
+ payments = db.create(Payment, pk='checkout_session_id', transform=True)
94
+
95
+ # %% ../nbs/explains/Stripe.ipynb
96
+ @rt("/webhook")
97
+ async def post(req):
98
+ payload = await req.body()
99
+ # Verify the event came from Stripe
100
+ try:
101
+ event = stripe.Webhook.construct_event(
102
+ payload, req.headers.get("stripe-signature"), WEBHOOK_SECRET)
103
+ except Exception as e:
104
+ print(f"Webhook error: {e}")
105
+ return
106
+ if event and event.type == "checkout.session.completed":
107
+ event_data = event.data.object
108
+ if event_data.metadata.get('app_name') == app_nm:
109
+ payment = Payment(
110
+ checkout_session_id=event_data.id,
111
+ email=event_data.customer_email,
112
+ amount=event_data.amount_total,
113
+ payment_status=event_data.payment_status,
114
+ created_at=event_data.created,
115
+ metadata=str(event_data.metadata))
116
+ payments.insert(payment)
117
+ print(f"Payment recorded for user: {event_data.customer_email}")
118
+
119
+ # Do not worry about refunds yet, we will cover how to do this later in the tutorial
120
+ elif event and event.type == "charge.refunded":
121
+ event_data = event.data.object
122
+ payment_intent_id = event_data.payment_intent
123
+ sessions = stripe.checkout.Session.list(payment_intent=payment_intent_id)
124
+ if sessions and sessions.data:
125
+ checkout_sid = sessions.data[0].id
126
+ payments.update(Payment(checkout_session_id= checkout_sid, payment_status="refunded"))
127
+ print(f"Refund recorded for payment: {checkout_sid}")
128
+
129
+ # %% ../nbs/explains/Stripe.ipynb
130
+ @rt("/success")
131
+ def success(sess, checkout_sid:str):
132
+ # Get payment record from database (saved in the webhook)
133
+ payment = payments[checkout_sid]
134
+
135
+ if not payment or payment.payment_status != 'paid':
136
+ return Titled("Error", P("Payment not found"))
137
+
138
+ return Titled(
139
+ "Success",
140
+ Div(H2("Payment Successful!"),
141
+ P(f"Thank you for your purchase, {sess['auth']}"),
142
+ P(f"Amount Paid: ${payment.amount / 100:.2f}"),
143
+ P(f"Status: {payment.payment_status}"),
144
+ P(f"Transaction ID: {payment.checkout_session_id}"),
145
+ A("Back to Home", href="/")))
146
+
147
+ # %% ../nbs/explains/Stripe.ipynb
148
+ @rt("/cancel")
149
+ def cancel():
150
+ return Titled(
151
+ "Cancelled",
152
+ Div(H2("Payment Cancelled"),
153
+ P("Your payment was cancelled."),
154
+ A("Back to Home", href="/")))
155
+
156
+ # %% ../nbs/explains/Stripe.ipynb
157
+ @rt("/refund", methods=["POST"])
158
+ async def refund(sess, checkout_sid:str):
159
+ # Get payment record from database
160
+ payment = payments[checkout_sid]
161
+
162
+ if not payment or payment.payment_status != 'paid':
163
+ return P("Error: Payment not found or not eligible for refund")
164
+
165
+ try:
166
+ # Get the payment intent ID from the checkout session
167
+ checkout_session = stripe.checkout.Session.retrieve(checkout_sid)
168
+
169
+ # Process the refund
170
+ refund = stripe.Refund.create(payment_intent=checkout_session.payment_intent, reason="requested_by_customer")
171
+
172
+ # Update payment status in database
173
+ payments.update(Payment(checkout_session_id= checkout_sid, payment_status="refunded"))
174
+
175
+ return Div(
176
+ P("Refund processed successfully!"),
177
+ P(f"Refund ID: {refund.id}"))
178
+
179
+ except Exception as e: return P(f"Refund failed: {str(e)}")
180
+
181
+ # %% ../nbs/explains/Stripe.ipynb
182
+ @rt("/account")
183
+ def account_management(sess):
184
+ user_email = sess['auth']
185
+ user_payments = payments("email=?", (user_email,))
186
+ # Create table rows for each payment
187
+ payment_rows = []
188
+ for payment in user_payments:
189
+ action_button = ""
190
+ if payment.payment_status == 'paid':
191
+ action_button = Button("Request Refund", hx_post=f"/refund?checkout_sid={payment.checkout_session_id}",hx_target="#refund-status")
192
+ elif payment.payment_status == 'refunded': action_button = "Refunded"
193
+
194
+ # Add row to table
195
+ payment_rows.append(
196
+ Tr(*map(Td, (payment.created_at, payment.amount, payment.payment_status, action_button))))
197
+
198
+ # Create payment history table
199
+ payment_history = Table(
200
+ Thead(Tr(*map(Th, ("Date", "Amount", "Status", "Action")))),
201
+ Tbody(*payment_rows))
202
+
203
+ return Titled(
204
+ "Account Management",
205
+ Div(H2(f"Account: {user_email}"),
206
+ H3("Payment History"),
207
+ payment_history,
208
+ Div(id="refund-status"), # Target for refund status messages
209
+ A("Back to Home", href="/")))
210
+
211
+ # %% ../nbs/explains/Stripe.ipynb
212
+ serve()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.12.5
3
+ Version: 0.12.6
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -24,6 +24,7 @@ fasthtml/live_reload.py
24
24
  fasthtml/oauth.py
25
25
  fasthtml/pico.py
26
26
  fasthtml/starlette.py
27
+ fasthtml/stripe_otp.py
27
28
  fasthtml/svg.py
28
29
  fasthtml/toaster.py
29
30
  fasthtml/xtend.py
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.12.5
4
+ version = 0.12.6
5
5
  min_python = 3.10
6
6
  license = apache2
7
7
  requirements = fastcore>=1.7.18 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.1.1 python-multipart beautifulsoup4
@@ -1,2 +0,0 @@
1
- __version__ = "0.12.5"
2
- from .core import *