wappa 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl
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.
Potentially problematic release.
This version of wappa might be problematic. Click here for more details.
- wappa/__init__.py +13 -69
- wappa/cli/examples/init/.gitignore +69 -0
- wappa/cli/examples/init/pyproject.toml +7 -0
- wappa/cli/examples/json_cache_example/.gitignore +69 -0
- wappa/cli/examples/json_cache_example/README.md +190 -0
- wappa/cli/examples/openai_transcript/.gitignore +10 -0
- wappa/cli/examples/openai_transcript/README.md +0 -0
- wappa/cli/examples/redis_cache_example/.gitignore +69 -0
- wappa/cli/examples/redis_cache_example/README.md +0 -0
- wappa/cli/examples/simple_echo_example/.gitignore +69 -0
- wappa/cli/examples/simple_echo_example/.python-version +1 -0
- wappa/cli/examples/simple_echo_example/README.md +0 -0
- wappa/cli/examples/simple_echo_example/pyproject.toml +9 -0
- wappa/cli/examples/wappa_full_example/.dockerignore +171 -0
- wappa/cli/examples/wappa_full_example/.gitignore +10 -0
- wappa/cli/examples/wappa_full_example/Dockerfile +85 -0
- wappa/cli/examples/wappa_full_example/RAILWAY_DEPLOYMENT.md +366 -0
- wappa/cli/examples/wappa_full_example/README.md +322 -0
- wappa/cli/examples/wappa_full_example/docker-compose.yml +170 -0
- wappa/cli/examples/wappa_full_example/nginx.conf +177 -0
- wappa/cli/examples/wappa_full_example/railway.toml +30 -0
- wappa/cli/main.py +346 -22
- wappa/cli/templates/__init__.py.template +0 -0
- wappa/cli/templates/env.template +37 -0
- wappa/cli/templates/gitignore.template +165 -0
- wappa/cli/templates/main.py.template +8 -0
- wappa/cli/templates/master_event.py.template +8 -0
- wappa/core/__init__.py +86 -3
- wappa/core/plugins/wappa_core_plugin.py +15 -5
- wappa/database/__init__.py +16 -4
- wappa/domain/interfaces/media_interface.py +57 -3
- wappa/domain/models/media_result.py +43 -0
- wappa/messaging/__init__.py +53 -3
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +112 -4
- wappa/models/__init__.py +103 -0
- wappa/persistence/__init__.py +55 -0
- wappa/webhooks/__init__.py +53 -4
- wappa/webhooks/whatsapp/__init__.py +57 -3
- wappa/webhooks/whatsapp/status_models.py +10 -0
- {wappa-0.1.6.dist-info → wappa-0.1.7.dist-info}/METADATA +1 -1
- {wappa-0.1.6.dist-info → wappa-0.1.7.dist-info}/RECORD +44 -23
- wappa/domain/interfaces/webhooks/__init__.py +0 -1
- wappa/persistence/json/handlers/__init__.py +0 -1
- wappa/persistence/json/handlers/utils/__init__.py +0 -1
- wappa/persistence/memory/handlers/__init__.py +0 -1
- wappa/persistence/memory/handlers/utils/__init__.py +0 -1
- wappa/schemas/webhooks/__init__.py +0 -3
- {wappa-0.1.6.dist-info → wappa-0.1.7.dist-info}/WHEEL +0 -0
- {wappa-0.1.6.dist-info → wappa-0.1.7.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.6.dist-info → wappa-0.1.7.dist-info}/licenses/LICENSE +0 -0
wappa/cli/main.py
CHANGED
|
@@ -5,40 +5,192 @@ Provides clean command-line interface for development and production workflows.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import shutil
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
import typer
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
13
16
|
|
|
14
17
|
app = typer.Typer(help="Wappa WhatsApp Business Framework CLI")
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
# Example projects metadata
|
|
21
|
+
EXAMPLES = {
|
|
22
|
+
"init": {
|
|
23
|
+
"name": "Basic Project",
|
|
24
|
+
"description": "Minimal Wappa project with basic message handling",
|
|
25
|
+
"features": ["Message handling", "Mark as read", "Basic response"],
|
|
26
|
+
"complexity": "🟢 Beginner",
|
|
27
|
+
},
|
|
28
|
+
"simple_echo_example": {
|
|
29
|
+
"name": "Simple Echo Bot",
|
|
30
|
+
"description": "Echoes all incoming messages with acknowledgment",
|
|
31
|
+
"features": ["Message echo", "Media handling", "Clean architecture"],
|
|
32
|
+
"complexity": "🟢 Beginner",
|
|
33
|
+
},
|
|
34
|
+
"json_cache_example": {
|
|
35
|
+
"name": "JSON Cache Demo",
|
|
36
|
+
"description": "File-based caching with user management and state handling",
|
|
37
|
+
"features": ["JSON caching", "User management", "State handling", "Statistics"],
|
|
38
|
+
"complexity": "🟡 Intermediate",
|
|
39
|
+
},
|
|
40
|
+
"redis_cache_example": {
|
|
41
|
+
"name": "Redis Cache Demo",
|
|
42
|
+
"description": "Redis-powered caching with advanced state management",
|
|
43
|
+
"features": [
|
|
44
|
+
"Redis caching",
|
|
45
|
+
"Advanced state",
|
|
46
|
+
"Performance monitoring",
|
|
47
|
+
"Cache statistics",
|
|
48
|
+
],
|
|
49
|
+
"complexity": "🟡 Intermediate",
|
|
50
|
+
},
|
|
51
|
+
"openai_transcript": {
|
|
52
|
+
"name": "OpenAI Transcription",
|
|
53
|
+
"description": "Voice message transcription using OpenAI Whisper API",
|
|
54
|
+
"features": ["Audio processing", "OpenAI integration", "Voice transcription"],
|
|
55
|
+
"complexity": "🟡 Intermediate",
|
|
56
|
+
},
|
|
57
|
+
"wappa_full_example": {
|
|
58
|
+
"name": "Full-Featured Bot",
|
|
59
|
+
"description": "Complete WhatsApp bot with all features and deployment configs",
|
|
60
|
+
"features": [
|
|
61
|
+
"All message types",
|
|
62
|
+
"Media handling",
|
|
63
|
+
"Interactive components",
|
|
64
|
+
"Docker deployment",
|
|
65
|
+
],
|
|
66
|
+
"complexity": "🔴 Advanced",
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_template_content(template_name: str) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Load template content from the templates directory.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
template_name: Name of the template file (e.g., 'main.py.template')
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Template content as string
|
|
80
|
+
"""
|
|
81
|
+
template_dir = Path(__file__).parent / "templates"
|
|
82
|
+
template_path = template_dir / template_name
|
|
83
|
+
|
|
84
|
+
if not template_path.exists():
|
|
85
|
+
typer.echo(f"❌ Template not found: {template_name}", err=True)
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
return template_path.read_text()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _initialize_project(directory: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Initialize a new Wappa project in the specified directory.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
directory: Target directory for project initialization
|
|
97
|
+
"""
|
|
98
|
+
project_path = Path(directory).resolve()
|
|
99
|
+
|
|
100
|
+
# Check if directory exists, create if it doesn't
|
|
101
|
+
if directory != "." and not project_path.exists():
|
|
102
|
+
try:
|
|
103
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
typer.echo(f"📁 Created directory: {project_path}")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
typer.echo(f"❌ Failed to create directory {project_path}: {e}", err=True)
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
# Check if directory is empty (except for hidden files and common files like pyproject.toml)
|
|
110
|
+
existing_files = [
|
|
111
|
+
f
|
|
112
|
+
for f in project_path.iterdir()
|
|
113
|
+
if not f.name.startswith(".")
|
|
114
|
+
and f.name not in ["pyproject.toml", "uv.lock", "README.md"]
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
if existing_files:
|
|
118
|
+
typer.echo(f"⚠️ Directory {project_path} is not empty:", err=True)
|
|
119
|
+
typer.echo(f" Found: {[f.name for f in existing_files]}", err=True)
|
|
120
|
+
|
|
121
|
+
if not typer.confirm("Continue anyway?"):
|
|
122
|
+
typer.echo("❌ Project initialization cancelled")
|
|
123
|
+
raise typer.Exit(1)
|
|
124
|
+
|
|
125
|
+
typer.echo(f"🚀 Initializing Wappa project in: {project_path}")
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# Create directory structure
|
|
129
|
+
(project_path / "app").mkdir(exist_ok=True)
|
|
130
|
+
(project_path / "scores").mkdir(exist_ok=True)
|
|
131
|
+
|
|
132
|
+
typer.echo("📁 Created directory structure")
|
|
133
|
+
|
|
134
|
+
# Create files from templates
|
|
135
|
+
templates_to_create = {
|
|
136
|
+
"app/__init__.py": "__init__.py.template",
|
|
137
|
+
"app/main.py": "main.py.template",
|
|
138
|
+
"app/master_event.py": "master_event.py.template",
|
|
139
|
+
"scores/__init__.py": "__init__.py.template",
|
|
140
|
+
".gitignore": "gitignore.template",
|
|
141
|
+
".env": "env.template",
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for file_path, template_name in templates_to_create.items():
|
|
145
|
+
full_path = project_path / file_path
|
|
146
|
+
template_content = _get_template_content(template_name)
|
|
147
|
+
|
|
148
|
+
full_path.write_text(template_content)
|
|
149
|
+
typer.echo(f"📝 Created: {file_path}")
|
|
150
|
+
|
|
151
|
+
typer.echo()
|
|
152
|
+
typer.echo("✅ Wappa project initialized successfully!")
|
|
153
|
+
typer.echo()
|
|
154
|
+
typer.echo("📋 Next steps:")
|
|
155
|
+
typer.echo("1. Add your WhatsApp credentials to .env file")
|
|
156
|
+
typer.echo("2. Install dependencies: uv sync")
|
|
157
|
+
typer.echo("3. Start development: uv run wappa dev app/main.py")
|
|
158
|
+
typer.echo()
|
|
159
|
+
typer.echo("🔧 Required environment variables (.env file):")
|
|
160
|
+
typer.echo(" WP_ACCESS_TOKEN=your_access_token")
|
|
161
|
+
typer.echo(" WP_PHONE_ID=your_phone_id")
|
|
162
|
+
typer.echo(" WP_BID=your_business_id")
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
typer.echo(f"❌ Failed to initialize project: {e}", err=True)
|
|
166
|
+
raise typer.Exit(1)
|
|
15
167
|
|
|
16
168
|
|
|
17
169
|
def _resolve_module_name(file_path: str) -> tuple[str, Path]:
|
|
18
170
|
"""
|
|
19
171
|
Convert a file path to a Python module name and working directory.
|
|
20
|
-
|
|
172
|
+
|
|
21
173
|
Handles both flat and nested project structures:
|
|
22
174
|
main.py -> ("main", Path("."))
|
|
23
175
|
app/main.py -> ("app.main", Path(".")) # Use dotted import from project root
|
|
24
176
|
examples/redis_demo/main.py -> ("examples.redis_demo.main", Path("."))
|
|
25
|
-
|
|
177
|
+
|
|
26
178
|
Returns:
|
|
27
179
|
tuple[str, Path]: (module_name, working_directory)
|
|
28
180
|
"""
|
|
29
181
|
# Convert to Path object for better handling
|
|
30
182
|
path = Path(file_path)
|
|
31
|
-
|
|
183
|
+
|
|
32
184
|
# Always use current directory as working dir and create dotted module name
|
|
33
185
|
working_dir = Path(".")
|
|
34
|
-
|
|
186
|
+
|
|
35
187
|
# Convert path to dotted module name (remove .py extension)
|
|
36
188
|
if path.suffix == ".py":
|
|
37
189
|
path = path.with_suffix("")
|
|
38
|
-
|
|
190
|
+
|
|
39
191
|
# Convert path separators to dots for Python import
|
|
40
192
|
module_name = str(path).replace(os.path.sep, ".")
|
|
41
|
-
|
|
193
|
+
|
|
42
194
|
return module_name, working_dir
|
|
43
195
|
|
|
44
196
|
|
|
@@ -83,7 +235,7 @@ def dev(
|
|
|
83
235
|
str(port),
|
|
84
236
|
]
|
|
85
237
|
|
|
86
|
-
typer.echo(
|
|
238
|
+
typer.echo("🚀 Starting Wappa development server...")
|
|
87
239
|
typer.echo(f"📡 Import: {working_dir / module_name}:{app_var}.asgi")
|
|
88
240
|
typer.echo(f"🌐 Server: http://{host}:{port}")
|
|
89
241
|
typer.echo(f"📝 Docs: http://{host}:{port}/docs")
|
|
@@ -114,7 +266,7 @@ def dev(
|
|
|
114
266
|
|
|
115
267
|
|
|
116
268
|
@app.command()
|
|
117
|
-
def
|
|
269
|
+
def prod(
|
|
118
270
|
file_path: str = typer.Argument(
|
|
119
271
|
..., help="Path to your Python file (e.g., main.py)"
|
|
120
272
|
),
|
|
@@ -131,8 +283,8 @@ def run(
|
|
|
131
283
|
Run production server (no auto-reload).
|
|
132
284
|
|
|
133
285
|
Examples:
|
|
134
|
-
wappa
|
|
135
|
-
wappa
|
|
286
|
+
wappa prod main.py
|
|
287
|
+
wappa prod main.py --workers 4 --port 8080
|
|
136
288
|
"""
|
|
137
289
|
# Validate file exists
|
|
138
290
|
if not Path(file_path).exists():
|
|
@@ -157,7 +309,7 @@ def run(
|
|
|
157
309
|
str(workers),
|
|
158
310
|
]
|
|
159
311
|
|
|
160
|
-
typer.echo(
|
|
312
|
+
typer.echo("🚀 Starting Wappa production server...")
|
|
161
313
|
typer.echo(f"📡 Import: {working_dir / module_name}:{app_var}.asgi")
|
|
162
314
|
typer.echo(f"🌐 Server: http://{host}:{port}")
|
|
163
315
|
typer.echo(f"👥 Workers: {workers}")
|
|
@@ -183,23 +335,195 @@ def run(
|
|
|
183
335
|
|
|
184
336
|
@app.command()
|
|
185
337
|
def init(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"basic", "--template", "-t", help="Project template (basic, redis)"
|
|
338
|
+
directory: str = typer.Argument(
|
|
339
|
+
".", help="Directory to initialize (default: current directory)"
|
|
189
340
|
),
|
|
190
341
|
):
|
|
191
342
|
"""
|
|
192
|
-
Initialize a new Wappa project
|
|
343
|
+
Initialize a new Wappa project.
|
|
344
|
+
|
|
345
|
+
Creates a basic Wappa project structure with:
|
|
346
|
+
- app/ directory with main.py and master_event.py
|
|
347
|
+
- scores/ directory (empty)
|
|
348
|
+
- .gitignore file
|
|
349
|
+
- .env template file
|
|
193
350
|
|
|
194
351
|
Examples:
|
|
195
|
-
wappa init
|
|
196
|
-
wappa init my-bot
|
|
352
|
+
wappa init # Initialize in current directory
|
|
353
|
+
wappa init my-bot # Initialize in ./my-bot/ directory
|
|
197
354
|
"""
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
355
|
+
_initialize_project(directory)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@app.command()
|
|
359
|
+
def examples(
|
|
360
|
+
directory: str = typer.Argument(
|
|
361
|
+
".", help="Directory to copy example to (default: current directory)"
|
|
362
|
+
),
|
|
363
|
+
):
|
|
364
|
+
"""
|
|
365
|
+
Browse and copy Wappa example projects.
|
|
366
|
+
|
|
367
|
+
Interactive menu to select from various example projects:
|
|
368
|
+
- Basic project template
|
|
369
|
+
- Echo bot example
|
|
370
|
+
- JSON cache demo
|
|
371
|
+
- Redis cache demo
|
|
372
|
+
- OpenAI transcription
|
|
373
|
+
- Full-featured bot
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
wappa examples # Show examples menu
|
|
377
|
+
wappa examples my-bot # Copy to ./my-bot/ directory
|
|
378
|
+
"""
|
|
379
|
+
_show_examples_menu(directory)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _show_examples_menu(target_directory: str) -> None:
|
|
383
|
+
"""
|
|
384
|
+
Display interactive examples menu and handle selection.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
target_directory: Directory to copy the selected example to
|
|
388
|
+
"""
|
|
389
|
+
console.print("\n🚀 [bold blue]Wappa Example Projects[/bold blue]")
|
|
390
|
+
console.print("Choose an example to copy to your project:\n")
|
|
391
|
+
|
|
392
|
+
# Create examples table
|
|
393
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
394
|
+
table.add_column("#", style="dim", width=3)
|
|
395
|
+
table.add_column("Name", style="bold")
|
|
396
|
+
table.add_column("Description", style="white")
|
|
397
|
+
table.add_column("Complexity", style="green")
|
|
398
|
+
table.add_column("Key Features", style="yellow")
|
|
399
|
+
|
|
400
|
+
example_keys = list(EXAMPLES.keys())
|
|
401
|
+
for i, (key, example) in enumerate(EXAMPLES.items(), 1):
|
|
402
|
+
features_text = ", ".join(example["features"][:3]) # Show first 3 features
|
|
403
|
+
if len(example["features"]) > 3:
|
|
404
|
+
features_text += "..."
|
|
405
|
+
|
|
406
|
+
table.add_row(
|
|
407
|
+
str(i),
|
|
408
|
+
example["name"],
|
|
409
|
+
example["description"],
|
|
410
|
+
example["complexity"],
|
|
411
|
+
features_text,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
console.print(table)
|
|
415
|
+
console.print("\n")
|
|
416
|
+
|
|
417
|
+
# Get user selection
|
|
418
|
+
while True:
|
|
419
|
+
try:
|
|
420
|
+
choice = typer.prompt("Enter your choice (1-6) or 'q' to quit")
|
|
421
|
+
|
|
422
|
+
if choice.lower() == "q":
|
|
423
|
+
console.print("👋 Goodbye!")
|
|
424
|
+
raise typer.Exit(0)
|
|
425
|
+
|
|
426
|
+
choice_num = int(choice)
|
|
427
|
+
if 1 <= choice_num <= len(example_keys):
|
|
428
|
+
selected_key = example_keys[choice_num - 1]
|
|
429
|
+
selected_example = EXAMPLES[selected_key]
|
|
430
|
+
|
|
431
|
+
console.print(f"\n✨ Selected: [bold]{selected_example['name']}[/bold]")
|
|
432
|
+
console.print(f"📝 Description: {selected_example['description']}")
|
|
433
|
+
console.print(f"🎯 Features: {', '.join(selected_example['features'])}")
|
|
434
|
+
console.print(f"📊 Complexity: {selected_example['complexity']}")
|
|
435
|
+
|
|
436
|
+
if typer.confirm(f"\nCopy this example to '{target_directory}'?"):
|
|
437
|
+
_copy_example(selected_key, target_directory)
|
|
438
|
+
break
|
|
439
|
+
else:
|
|
440
|
+
console.print("Selection cancelled. Choose another example:\n")
|
|
441
|
+
continue
|
|
442
|
+
|
|
443
|
+
else:
|
|
444
|
+
console.print(
|
|
445
|
+
f"❌ Invalid choice. Please enter a number between 1 and {len(example_keys)}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
except ValueError:
|
|
449
|
+
console.print("❌ Invalid input. Please enter a number or 'q' to quit")
|
|
450
|
+
except KeyboardInterrupt:
|
|
451
|
+
console.print("\n👋 Goodbye!")
|
|
452
|
+
raise typer.Exit(0)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _copy_example(example_key: str, target_directory: str) -> None:
|
|
456
|
+
"""
|
|
457
|
+
Copy selected example to target directory.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
example_key: Key of the example to copy
|
|
461
|
+
target_directory: Directory to copy to
|
|
462
|
+
"""
|
|
463
|
+
examples_dir = Path(__file__).parent / "examples"
|
|
464
|
+
source_path = examples_dir / example_key
|
|
465
|
+
target_path = Path(target_directory).resolve()
|
|
466
|
+
|
|
467
|
+
if not source_path.exists():
|
|
468
|
+
console.print(f"❌ Example '{example_key}' not found in {examples_dir}")
|
|
469
|
+
raise typer.Exit(1)
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
# Create target directory if it doesn't exist
|
|
473
|
+
if target_directory != "." and not target_path.exists():
|
|
474
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
475
|
+
console.print(f"📁 Created directory: {target_path}")
|
|
476
|
+
|
|
477
|
+
# Check if target directory is empty (except for standard files)
|
|
478
|
+
if target_path.exists():
|
|
479
|
+
existing_files = [
|
|
480
|
+
f
|
|
481
|
+
for f in target_path.iterdir()
|
|
482
|
+
if not f.name.startswith(".")
|
|
483
|
+
and f.name not in ["pyproject.toml", "uv.lock", "README.md"]
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
if existing_files:
|
|
487
|
+
console.print(f"⚠️ Directory {target_path} is not empty:")
|
|
488
|
+
console.print(f" Found: {[f.name for f in existing_files]}")
|
|
489
|
+
|
|
490
|
+
if not typer.confirm("Continue anyway?"):
|
|
491
|
+
console.print("❌ Example copy cancelled")
|
|
492
|
+
raise typer.Exit(0)
|
|
493
|
+
|
|
494
|
+
console.print(f"🚀 Copying {EXAMPLES[example_key]['name']} to {target_path}")
|
|
495
|
+
|
|
496
|
+
# Copy all files from the example
|
|
497
|
+
for item in source_path.iterdir():
|
|
498
|
+
if item.is_file():
|
|
499
|
+
shutil.copy2(item, target_path / item.name)
|
|
500
|
+
console.print(f"📝 Copied: {item.name}")
|
|
501
|
+
elif item.is_dir():
|
|
502
|
+
shutil.copytree(item, target_path / item.name, dirs_exist_ok=True)
|
|
503
|
+
console.print(f"📁 Copied: {item.name}/")
|
|
504
|
+
|
|
505
|
+
console.print("\n✅ Example copied successfully!")
|
|
506
|
+
console.print("\n📋 Next steps:")
|
|
507
|
+
console.print("1. Navigate to the project directory")
|
|
508
|
+
console.print("2. Install dependencies: uv sync")
|
|
509
|
+
console.print("3. Add your WhatsApp credentials to .env file (if not present)")
|
|
510
|
+
console.print("4. Start development: uv run wappa dev app/main.py")
|
|
511
|
+
|
|
512
|
+
# Show example-specific instructions
|
|
513
|
+
if example_key == "redis_cache_example":
|
|
514
|
+
console.print("\n🔧 Redis-specific setup:")
|
|
515
|
+
console.print(" - Install and start Redis server")
|
|
516
|
+
console.print(" - Add REDIS_URL to .env file")
|
|
517
|
+
elif example_key == "openai_transcript":
|
|
518
|
+
console.print("\n🔧 OpenAI-specific setup:")
|
|
519
|
+
console.print(" - Add OPENAI_API_KEY to .env file")
|
|
520
|
+
console.print(" - Ensure audio file handling is configured")
|
|
521
|
+
|
|
522
|
+
console.print("\n📖 Check the README.md for detailed setup instructions")
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
console.print(f"❌ Failed to copy example: {e}")
|
|
526
|
+
raise typer.Exit(1)
|
|
203
527
|
|
|
204
528
|
|
|
205
529
|
if __name__ == "__main__":
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# WhatsApp Business API Configuration
|
|
2
|
+
|
|
3
|
+
# General Configuration
|
|
4
|
+
PORT=8000
|
|
5
|
+
TIME_ZONE=America/Bogota
|
|
6
|
+
|
|
7
|
+
# DEBUG or INFO or WARNING or ERROR or CRITICAL
|
|
8
|
+
LOG_LEVEL=INFO
|
|
9
|
+
LOG_DIR=./logs
|
|
10
|
+
|
|
11
|
+
## Environment DEV or PROD
|
|
12
|
+
ENVIRONMENT=DEV
|
|
13
|
+
|
|
14
|
+
# WhatsApp Graph API
|
|
15
|
+
BASE_URL=https://graph.facebook.com/
|
|
16
|
+
API_VERSION=v23.0
|
|
17
|
+
|
|
18
|
+
# Get these values from your WhatsApp Business API setup
|
|
19
|
+
|
|
20
|
+
# WhatsApp Access Token (required)
|
|
21
|
+
WP_ACCESS_TOKEN=
|
|
22
|
+
|
|
23
|
+
# WhatsApp Phone Number ID (required)
|
|
24
|
+
WP_PHONE_ID=
|
|
25
|
+
|
|
26
|
+
# WhatsApp Business ID (required)
|
|
27
|
+
WP_BID=
|
|
28
|
+
|
|
29
|
+
# Webhook Configuration
|
|
30
|
+
WHATSAPP_WEBHOOK_VERIFY_TOKEN=
|
|
31
|
+
|
|
32
|
+
# Redis Configuration (Optional - uncomment to enable Redis persistence)
|
|
33
|
+
REDIS_URL=redis://localhost:6379/
|
|
34
|
+
REDIS_MAX_CONNECTIONS=64
|
|
35
|
+
|
|
36
|
+
# Optional: AI Tools
|
|
37
|
+
OPENAI_API_KEY=
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
|
158
|
+
# project, it is recommended to exclude everything except .idea/modules.xml and
|
|
159
|
+
# .idea/*.iml files. A simple way to do this is with the IDEA_IGNORE=1 environment variable.
|
|
160
|
+
.idea/
|
|
161
|
+
|
|
162
|
+
# Wappa-specific
|
|
163
|
+
logs/
|
|
164
|
+
cache/
|
|
165
|
+
*.log
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from wappa import WappaEventHandler
|
|
2
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
3
|
+
|
|
4
|
+
class MasterEventHandler(WappaEventHandler):
|
|
5
|
+
|
|
6
|
+
async def process_message(self, webhook: IncomingMessageWebhook):
|
|
7
|
+
await self.messenger.mark_as_read(webhook.message.message_id, webhook.user.user_id)
|
|
8
|
+
await self.messenger.send_text("Wappa App ready", webhook.user.user_id)
|