slide-narrator 1.0.0__py3-none-any.whl → 1.0.1__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 slide-narrator might be problematic. Click here for more details.

narrator/__init__.py CHANGED
@@ -8,7 +8,7 @@ from .models.thread import Thread
8
8
  from .models.message import Message
9
9
  from .models.attachment import Attachment
10
10
 
11
- __version__ = "1.0.0"
11
+ __version__ = "1.0.1"
12
12
  __all__ = [
13
13
  "ThreadStore",
14
14
  "FileStore",
narrator/database/cli.py CHANGED
@@ -1,6 +1,12 @@
1
1
  """Database CLI for Tyler Stores"""
2
2
  import asyncio
3
+ import os
3
4
  import click
5
+ import functools
6
+ import subprocess
7
+ import tempfile
8
+ import time
9
+ from pathlib import Path
4
10
  from .thread_store import ThreadStore
5
11
  from ..utils.logging import get_logger
6
12
 
@@ -8,59 +14,209 @@ logger = get_logger(__name__)
8
14
 
9
15
  @click.group()
10
16
  def main():
11
- """Tyler Stores Database CLI"""
17
+ """Narrator CLI - Database management commands"""
12
18
  pass
13
19
 
14
- @click.command()
20
+ @main.command()
15
21
  @click.option('--database-url', help='Database URL for initialization')
16
- async def init(database_url):
22
+ def init(database_url):
17
23
  """Initialize database tables"""
18
- try:
19
- if database_url:
20
- store = await ThreadStore.create(database_url)
21
- else:
22
- # Use environment variables or default
23
- store = await ThreadStore.create()
24
-
25
- logger.info("Database initialized successfully")
26
- click.echo("Database initialized successfully")
27
- except Exception as e:
28
- logger.error(f"Failed to initialize database: {e}")
29
- click.echo(f"Error: Failed to initialize database: {e}")
30
- raise click.Abort()
24
+ async def _init():
25
+ try:
26
+ # Use provided URL or check environment variable
27
+ url = database_url or os.environ.get('NARRATOR_DATABASE_URL')
28
+
29
+ if url:
30
+ store = await ThreadStore.create(url)
31
+ else:
32
+ # Use in-memory storage
33
+ store = await ThreadStore.create()
34
+
35
+ logger.info("Database initialized successfully")
36
+ click.echo("Database initialized successfully")
37
+ except Exception as e:
38
+ logger.error(f"Failed to initialize database: {e}")
39
+ click.echo(f"Error: Failed to initialize database: {e}")
40
+ raise click.Abort()
41
+
42
+ asyncio.run(_init())
31
43
 
32
- @click.command()
44
+ @main.command()
33
45
  @click.option('--database-url', help='Database URL')
34
- async def status(database_url):
46
+ def status(database_url):
35
47
  """Check database status"""
36
- try:
37
- if database_url:
38
- store = await ThreadStore.create(database_url)
39
- else:
40
- store = await ThreadStore.create()
48
+ async def _status():
49
+ try:
50
+ # Use provided URL or check environment variable
51
+ url = database_url or os.environ.get('NARRATOR_DATABASE_URL')
52
+
53
+ if url:
54
+ store = await ThreadStore.create(url)
55
+ else:
56
+ store = await ThreadStore.create()
57
+
58
+ # Get some basic stats
59
+ threads = await store.list_recent(limit=5)
60
+ click.echo(f"Database connection: OK")
61
+ click.echo(f"Recent threads count: {len(threads)}")
62
+
63
+ except Exception as e:
64
+ logger.error(f"Database status check failed: {e}")
65
+ click.echo(f"Error: Database status check failed: {e}")
66
+ raise click.Abort()
67
+
68
+ asyncio.run(_status())
69
+
70
+ @main.command()
71
+ @click.option('--port', help='Port to expose PostgreSQL on (default: 5432 or NARRATOR_DB_PORT)')
72
+ @click.option('--detach/--no-detach', default=True, help='Run container in background (default: True)')
73
+ def docker_start(port, detach):
74
+ """Start a PostgreSQL container for Narrator"""
75
+ # Use environment variables with defaults matching docker-compose.yml
76
+ db_name = os.environ.get('NARRATOR_DB_NAME', 'narrator')
77
+ db_user = os.environ.get('NARRATOR_DB_USER', 'narrator')
78
+ db_password = os.environ.get('NARRATOR_DB_PASSWORD', 'narrator_dev')
79
+ db_port = port or os.environ.get('NARRATOR_DB_PORT', '5432')
80
+
81
+ docker_compose_content = f"""services:
82
+ postgres:
83
+ image: postgres:16
84
+ container_name: narrator-postgres
85
+ environment:
86
+ POSTGRES_DB: {db_name}
87
+ POSTGRES_USER: {db_user}
88
+ POSTGRES_PASSWORD: {db_password}
89
+ ports:
90
+ - "{db_port}:5432"
91
+ volumes:
92
+ - narrator_postgres_data:/var/lib/postgresql/data
93
+ healthcheck:
94
+ test: ["CMD-SHELL", "pg_isready -U {db_user}"]
95
+ interval: 5s
96
+ timeout: 5s
97
+ retries: 5
98
+
99
+ volumes:
100
+ narrator_postgres_data:
101
+ """
102
+
103
+ # Create a temporary directory for docker-compose.yml
104
+ with tempfile.TemporaryDirectory() as tmpdir:
105
+ compose_file = Path(tmpdir) / "docker-compose.yml"
106
+ compose_file.write_text(docker_compose_content)
41
107
 
42
- # Get some basic stats
43
- threads = await store.list_recent(limit=5)
44
- click.echo(f"Database connection: OK")
45
- click.echo(f"Recent threads count: {len(threads)}")
108
+ # Check if docker is available
109
+ try:
110
+ subprocess.run(["docker", "--version"], capture_output=True, check=True)
111
+ except (subprocess.CalledProcessError, FileNotFoundError):
112
+ click.echo("❌ Docker is not installed or not available in PATH")
113
+ raise click.Abort()
46
114
 
47
- except Exception as e:
48
- logger.error(f"Database status check failed: {e}")
49
- click.echo(f"Error: Database status check failed: {e}")
50
- raise click.Abort()
51
-
52
- # Add async wrapper for commands
53
- def async_command(f):
54
- def wrapper(*args, **kwargs):
55
- return asyncio.run(f(*args, **kwargs))
56
- return wrapper
115
+ # Check if docker-compose or docker compose is available
116
+ compose_cmd = None
117
+ try:
118
+ subprocess.run(["docker", "compose", "version"], capture_output=True, check=True)
119
+ compose_cmd = ["docker", "compose"]
120
+ except (subprocess.CalledProcessError, FileNotFoundError):
121
+ try:
122
+ subprocess.run(["docker-compose", "version"], capture_output=True, check=True)
123
+ compose_cmd = ["docker-compose"]
124
+ except (subprocess.CalledProcessError, FileNotFoundError):
125
+ click.echo("❌ Docker Compose is not installed")
126
+ raise click.Abort()
127
+
128
+ # Start the container
129
+ click.echo("📦 Starting PostgreSQL container...")
130
+ cmd = compose_cmd + ["up"]
131
+ if detach:
132
+ cmd.append("-d")
133
+
134
+ result = subprocess.run(cmd, cwd=tmpdir)
135
+
136
+ if result.returncode != 0:
137
+ click.echo("❌ Failed to start PostgreSQL container")
138
+ raise click.Abort()
139
+
140
+ if detach:
141
+ # Wait for PostgreSQL to be ready
142
+ click.echo("⏳ Waiting for PostgreSQL to be ready...")
143
+ for i in range(30):
144
+ result = subprocess.run(
145
+ ["docker", "exec", "narrator-postgres", "pg_isready", "-U", db_user],
146
+ capture_output=True
147
+ )
148
+ if result.returncode == 0:
149
+ click.echo("✅ PostgreSQL is ready!")
150
+ click.echo(f"\n🎉 Database available at:")
151
+ click.echo(f" postgresql+asyncpg://{db_user}:{db_password}@localhost:{db_port}/{db_name}")
152
+ return
153
+ time.sleep(1)
154
+
155
+ click.echo("❌ PostgreSQL failed to start after 30 seconds")
156
+ raise click.Abort()
57
157
 
58
- # Apply async wrapper to commands
59
- init = click.command()(async_command(init))
60
- status = click.command()(async_command(status))
158
+ @main.command()
159
+ @click.option('--remove-volumes', is_flag=True, help='Remove data volumes (destroys all data)')
160
+ def docker_stop(remove_volumes):
161
+ """Stop the PostgreSQL container"""
162
+ # Check if docker is available
163
+ try:
164
+ subprocess.run(["docker", "--version"], capture_output=True, check=True)
165
+ except (subprocess.CalledProcessError, FileNotFoundError):
166
+ click.echo("❌ Docker is not installed or not available in PATH")
167
+ raise click.Abort()
168
+
169
+ # Check if container exists
170
+ result = subprocess.run(
171
+ ["docker", "ps", "-a", "--format", "{{.Names}}"],
172
+ capture_output=True,
173
+ text=True
174
+ )
175
+
176
+ if "narrator-postgres" not in result.stdout:
177
+ click.echo("ℹ️ No Narrator PostgreSQL container found")
178
+ return
179
+
180
+ click.echo("🛑 Stopping PostgreSQL container...")
181
+
182
+ # Stop the container
183
+ subprocess.run(["docker", "stop", "narrator-postgres"], check=False)
184
+ subprocess.run(["docker", "rm", "narrator-postgres"], check=False)
185
+
186
+ if remove_volumes:
187
+ click.echo("🗑️ Removing data volume...")
188
+ subprocess.run(["docker", "volume", "rm", "narrator_postgres_data"], check=False)
189
+ click.echo("✅ Container and data removed")
190
+ else:
191
+ click.echo("✅ Container stopped (data preserved)")
61
192
 
62
- main.add_command(init)
63
- main.add_command(status)
193
+ @main.command()
194
+ @click.option('--port', help='Port to expose PostgreSQL on (default: 5432 or NARRATOR_DB_PORT)')
195
+ def docker_setup(port):
196
+ """One-command Docker setup: start PostgreSQL and initialize tables"""
197
+ # Start PostgreSQL
198
+ ctx = click.get_current_context()
199
+ ctx.invoke(docker_start, port=port, detach=True)
200
+
201
+ # Get database configuration from environment or defaults
202
+ db_name = os.environ.get('NARRATOR_DB_NAME', 'narrator')
203
+ db_user = os.environ.get('NARRATOR_DB_USER', 'narrator')
204
+ db_password = os.environ.get('NARRATOR_DB_PASSWORD', 'narrator_dev')
205
+ db_port = port or os.environ.get('NARRATOR_DB_PORT', '5432')
206
+
207
+ # Set up database URL
208
+ database_url = f"postgresql+asyncpg://{db_user}:{db_password}@localhost:{db_port}/{db_name}"
209
+ os.environ['NARRATOR_DATABASE_URL'] = database_url
210
+
211
+ # Initialize tables
212
+ click.echo("\n🔧 Initializing database tables...")
213
+ ctx.invoke(init, database_url=database_url)
214
+
215
+ click.echo("\n🎉 Setup complete! Your database is ready.")
216
+ click.echo("\nTo use in your code:")
217
+ click.echo(f'export NARRATOR_DATABASE_URL="{database_url}"')
218
+ click.echo("\nTo stop the container: narrator docker-stop")
219
+ click.echo("To remove all data: narrator docker-stop --remove-volumes")
64
220
 
65
221
  if __name__ == '__main__':
66
222
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide-narrator
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Thread and file storage components for conversational AI - the companion to Tyler AI framework
5
5
  Project-URL: Homepage, https://github.com/adamwdraper/slide
6
6
  Project-URL: Repository, https://github.com/adamwdraper/slide
@@ -74,19 +74,72 @@ pip install slide-narrator
74
74
 
75
75
  ## Setup
76
76
 
77
+ ### Docker Setup (Recommended for Development)
78
+
79
+ For local development with PostgreSQL, Narrator includes built-in Docker commands:
80
+
81
+ ```bash
82
+ # One-command setup: starts PostgreSQL and initializes tables
83
+ uv run narrator docker-setup
84
+
85
+ # This will:
86
+ # 1. Start a PostgreSQL container
87
+ # 2. Wait for it to be ready
88
+ # 3. Initialize the database tables
89
+ # 4. Show you the connection string
90
+
91
+ # The database will be available at:
92
+ # postgresql+asyncpg://narrator:narrator_dev@localhost:5432/narrator
93
+ ```
94
+
95
+ To manage the Docker container:
96
+
97
+ ```bash
98
+ # Stop container (preserves data)
99
+ uv run narrator docker-stop
100
+
101
+ # Stop and remove all data
102
+ uv run narrator docker-stop --remove-volumes
103
+
104
+ # Start container again
105
+ uv run narrator docker-start
106
+
107
+ # Check database status
108
+ uv run narrator status
109
+ ```
110
+
111
+ For custom configurations, the Docker commands respect environment variables:
112
+
113
+ ```bash
114
+ # Use a different port
115
+ uv run narrator docker-setup --port 5433
116
+
117
+ # Or set environment variables (matching docker-compose.yml)
118
+ export NARRATOR_DB_NAME=mydb
119
+ export NARRATOR_DB_USER=myuser
120
+ export NARRATOR_DB_PASSWORD=mypassword
121
+ export NARRATOR_DB_PORT=5433
122
+
123
+ # Then run docker-setup
124
+ uv run narrator docker-setup
125
+
126
+ # This will create:
127
+ # postgresql+asyncpg://myuser:mypassword@localhost:5433/mydb
128
+ ```
129
+
77
130
  ### Database Setup
78
131
 
79
132
  For production use with PostgreSQL or SQLite persistence, you'll need to initialize the database tables:
80
133
 
81
134
  ```bash
82
135
  # Initialize database tables (PostgreSQL)
83
- narrator-db init --database-url "postgresql+asyncpg://user:password@localhost/dbname"
136
+ uv run narrator init --database-url "postgresql+asyncpg://user:password@localhost/dbname"
84
137
 
85
138
  # Initialize database tables (SQLite)
86
- narrator-db init --database-url "sqlite+aiosqlite:///path/to/your/database.db"
139
+ uv run narrator init --database-url "sqlite+aiosqlite:///path/to/your/database.db"
87
140
 
88
141
  # Check database status
89
- narrator-db status --database-url "postgresql+asyncpg://user:password@localhost/dbname"
142
+ uv run narrator status --database-url "postgresql+asyncpg://user:password@localhost/dbname"
90
143
  ```
91
144
 
92
145
  You can also use environment variables instead of passing the database URL:
@@ -96,8 +149,8 @@ You can also use environment variables instead of passing the database URL:
96
149
  export NARRATOR_DATABASE_URL="postgresql+asyncpg://user:password@localhost/dbname"
97
150
 
98
151
  # Then run without --database-url flag
99
- narrator-db init
100
- narrator-db status
152
+ uv run narrator init
153
+ uv run narrator status
101
154
  ```
102
155
 
103
156
  ### Environment Variables
@@ -382,22 +435,22 @@ The Narrator includes a CLI tool for database management:
382
435
 
383
436
  ```bash
384
437
  # Initialize database tables
385
- narrator-db init --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
438
+ uv run narrator init --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
386
439
 
387
440
  # Initialize using environment variable
388
441
  export NARRATOR_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/dbname"
389
- narrator-db init
442
+ uv run narrator init
390
443
 
391
444
  # Check database status
392
- narrator-db status --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
445
+ uv run narrator status --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
393
446
 
394
447
  # Check status using environment variable
395
- narrator-db status
448
+ uv run narrator status
396
449
  ```
397
450
 
398
451
  Available commands:
399
- - `narrator-db init` - Initialize database tables
400
- - `narrator-db status` - Check database connection and basic statistics
452
+ - `uv run narrator init` - Initialize database tables
453
+ - `uv run narrator status` - Check database connection and basic statistics
401
454
 
402
455
  ## Key Design Principles
403
456
 
@@ -1,6 +1,6 @@
1
- narrator/__init__.py,sha256=3NsXekWzkpNumnCQ0AY0dHdpou_EaMJS_V4C7wZD2pg,403
1
+ narrator/__init__.py,sha256=ZitqLYs56vOVMMvnLwbE4AFC_ZK31cC97BVH8Ei-QBs,403
2
2
  narrator/database/__init__.py,sha256=UngOnFqImCeJiMZlMasm72mC4-UnJDDvfu1MNQLkRA8,189
3
- narrator/database/cli.py,sha256=Wi5kSXWFTkWBjZS1N9AuGEmTUrYjafUOTZUSO9cGfTM,1954
3
+ narrator/database/cli.py,sha256=QvET17X5kLZ7GiOTw0b80-u4FuI-tOTu4SjAqCBkiSs,8355
4
4
  narrator/database/models.py,sha256=wsG_5GrPo41hAcojjZTZmSx6bijea-Skan-DwzHs8os,2607
5
5
  narrator/database/storage_backend.py,sha256=UeMgxW8h3ZNWORZNH_f-jZuHNjHpREBaOOAFPeEPlvA,25444
6
6
  narrator/database/thread_store.py,sha256=vMIPDdwuSpTyPogEUmxGcILxM_r1wxoQBUOn8XJpdqM,11301
@@ -13,8 +13,8 @@ narrator/storage/__init__.py,sha256=K4cxGITSQoQiw32QOWZsCBm11fwDTbsyzHGeAqcL6yY,
13
13
  narrator/storage/file_store.py,sha256=-k1ZYzKYioCMiP7KfWuuCmCeAzDqRv38ndpuM75yISY,20047
14
14
  narrator/utils/__init__.py,sha256=P4BhLvBJbBvb8qha2tTZPlYbjCRXth_K97f4vNc77UI,109
15
15
  narrator/utils/logging.py,sha256=K9EWI7lP4CNQpPwggiqzMex7oF6oyL3wIVLik2iuXd4,1983
16
- slide_narrator-1.0.0.dist-info/METADATA,sha256=W5-QQVHN3pY6JF1lCCWsTse4gGlGa9eQqux6JT3Al54,15194
17
- slide_narrator-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- slide_narrator-1.0.0.dist-info/entry_points.txt,sha256=EaFuawrdPNFByqzAb17gkkDyj4FgelJqmII6ioD-i_I,59
19
- slide_narrator-1.0.0.dist-info/licenses/LICENSE,sha256=g6cGasroU9sqSOjThWg14w0BMlwZhgmOQQVTiu036ks,1068
20
- slide_narrator-1.0.0.dist-info/RECORD,,
16
+ slide_narrator-1.0.1.dist-info/METADATA,sha256=ie1ZcCU3YGEjsDrYmBqOFbCoSuTzCGtTTUNKsOiUKP4,16497
17
+ slide_narrator-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ slide_narrator-1.0.1.dist-info/entry_points.txt,sha256=5Oa53AERvPVdrEvsdWbY85xfzAGayOqq_P4KEmf1khA,56
19
+ slide_narrator-1.0.1.dist-info/licenses/LICENSE,sha256=g6cGasroU9sqSOjThWg14w0BMlwZhgmOQQVTiu036ks,1068
20
+ slide_narrator-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ narrator = narrator.database.cli:main
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- narrator-db = narrator.database.cli:main