pyreact-framework 1.0.0__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.
Files changed (41) hide show
  1. pyreact/__init__.py +144 -0
  2. pyreact/cli/__init__.py +3 -0
  3. pyreact/cli/main.py +512 -0
  4. pyreact/core/__init__.py +80 -0
  5. pyreact/core/component.py +372 -0
  6. pyreact/core/context.py +173 -0
  7. pyreact/core/element.py +208 -0
  8. pyreact/core/error_boundary.py +145 -0
  9. pyreact/core/hooks.py +550 -0
  10. pyreact/core/memo.py +221 -0
  11. pyreact/core/portal.py +159 -0
  12. pyreact/core/reconciler.py +399 -0
  13. pyreact/core/refs.py +213 -0
  14. pyreact/core/renderer.py +112 -0
  15. pyreact/core/scheduler.py +304 -0
  16. pyreact/devtools/__init__.py +18 -0
  17. pyreact/devtools/debugger.py +314 -0
  18. pyreact/devtools/profiler.py +288 -0
  19. pyreact/dom/__init__.py +64 -0
  20. pyreact/dom/attributes.py +317 -0
  21. pyreact/dom/dom_operations.py +333 -0
  22. pyreact/dom/events.py +349 -0
  23. pyreact/server/__init__.py +34 -0
  24. pyreact/server/hydration.py +216 -0
  25. pyreact/server/ssr.py +344 -0
  26. pyreact/styles/__init__.py +19 -0
  27. pyreact/styles/css_module.py +231 -0
  28. pyreact/styles/styled.py +303 -0
  29. pyreact/testing/__init__.py +71 -0
  30. pyreact/testing/fire_event.py +355 -0
  31. pyreact/testing/screen.py +267 -0
  32. pyreact/testing/test_renderer.py +232 -0
  33. pyreact/utils/__init__.py +17 -0
  34. pyreact/utils/diff.py +182 -0
  35. pyreact/utils/object_pool.py +216 -0
  36. pyreact_framework-1.0.0.dist-info/METADATA +363 -0
  37. pyreact_framework-1.0.0.dist-info/RECORD +41 -0
  38. pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
  39. pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
  40. pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  41. pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
pyreact/__init__.py ADDED
@@ -0,0 +1,144 @@
1
+ """
2
+ PyReact - Framework Web Declarativo para Python
3
+ ================================================
4
+
5
+ PyReact é um framework web declarativo inspirado no React, mas construído
6
+ nativamente para Python. Permite criar interfaces de usuário reativas,
7
+ componentizadas e modernas, sem precisar aprender JavaScript/TypeScript.
8
+
9
+ Quick Start:
10
+ from pyreact import h, render, use_state
11
+
12
+ def Counter(props):
13
+ count, set_count = use_state(0)
14
+ return h('div', {'className': 'counter'},
15
+ h('span', None, f"Count: {count}"),
16
+ h('button', {'onClick': lambda _: set_count(count + 1)}, '+')
17
+ )
18
+
19
+ render(h(Counter, None), document.getElementById('root'))
20
+
21
+ Princípios:
22
+ - Declaratividade: A UI é uma função do estado
23
+ - Componentização: Tudo é um componente
24
+ - Reatividade: Mudanças de estado disparam re-renderizações
25
+ - Isomorfismo: Suporte a Server-Side Rendering
26
+ """
27
+
28
+ __version__ = '1.0.0'
29
+ __author__ = 'PyReact Team'
30
+
31
+ # Core
32
+ from .core.element import VNode, h, create_element, is_valid_element, clone_element
33
+ from .core.component import Component, PureComponent
34
+ from .core.renderer import render, hydrate, create_root
35
+ from .core.reconciler import Reconciler
36
+
37
+ # Hooks
38
+ from .core.hooks import (
39
+ use_state,
40
+ use_reducer,
41
+ use_effect,
42
+ use_layout_effect,
43
+ use_context,
44
+ use_ref,
45
+ use_memo,
46
+ use_callback,
47
+ use_imperative_handle,
48
+ use_debug_value,
49
+ use_id,
50
+ use_transition,
51
+ use_deferred_value,
52
+ )
53
+
54
+ # Context
55
+ from .core.context import create_context
56
+
57
+ # Refs
58
+ from .core.refs import create_ref, forward_ref
59
+
60
+ # Portal
61
+ from .core.portal import create_portal
62
+
63
+ # Memo
64
+ from .core.memo import memo, lazy
65
+
66
+ # Error Boundary
67
+ from .core.error_boundary import ErrorBoundary
68
+
69
+ # Scheduler
70
+ from .core.scheduler import Scheduler, Priority
71
+
72
+ # Styles
73
+ from .styles import styled, css_module
74
+
75
+ # Server
76
+ from .server.ssr import render_to_string, render_to_static_markup
77
+
78
+ # DOM
79
+ from .dom.dom_operations import document
80
+
81
+ __all__ = [
82
+ # Version
83
+ '__version__',
84
+
85
+ # Core
86
+ 'VNode',
87
+ 'h',
88
+ 'create_element',
89
+ 'is_valid_element',
90
+ 'clone_element',
91
+ 'Component',
92
+ 'PureComponent',
93
+ 'render',
94
+ 'hydrate',
95
+ 'create_root',
96
+ 'Reconciler',
97
+
98
+ # Hooks
99
+ 'use_state',
100
+ 'use_reducer',
101
+ 'use_effect',
102
+ 'use_layout_effect',
103
+ 'use_context',
104
+ 'use_ref',
105
+ 'use_memo',
106
+ 'use_callback',
107
+ 'use_imperative_handle',
108
+ 'use_debug_value',
109
+ 'use_id',
110
+ 'use_transition',
111
+ 'use_deferred_value',
112
+
113
+ # Context
114
+ 'create_context',
115
+
116
+ # Refs
117
+ 'create_ref',
118
+ 'forward_ref',
119
+
120
+ # Portal
121
+ 'create_portal',
122
+
123
+ # Memo
124
+ 'memo',
125
+ 'lazy',
126
+
127
+ # Error Boundary
128
+ 'ErrorBoundary',
129
+
130
+ # Scheduler
131
+ 'Scheduler',
132
+ 'Priority',
133
+
134
+ # Styles
135
+ 'styled',
136
+ 'css_module',
137
+
138
+ # Server
139
+ 'render_to_string',
140
+ 'render_to_static_markup',
141
+
142
+ # DOM
143
+ 'document',
144
+ ]
@@ -0,0 +1,3 @@
1
+ """
2
+ PyReact CLI Module
3
+ """
pyreact/cli/main.py ADDED
@@ -0,0 +1,512 @@
1
+ """
2
+ PyReact CLI
3
+ ===========
4
+
5
+ Command-line interface for PyReact development.
6
+ """
7
+
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def create_project(name: str) -> None:
15
+ """Create a new PyReact project"""
16
+ project_dir = Path(name)
17
+
18
+ if project_dir.exists():
19
+ print(f"Error: Directory '{name}' already exists")
20
+ sys.exit(1)
21
+
22
+ # Create directory structure
23
+ dirs = [
24
+ 'src/components',
25
+ 'src/hooks',
26
+ 'src/pages',
27
+ 'src/styles',
28
+ 'src/utils',
29
+ 'public',
30
+ 'tests',
31
+ ]
32
+
33
+ for dir_path in dirs:
34
+ (project_dir / dir_path).mkdir(parents=True, exist_ok=True)
35
+
36
+ # Create pyproject.toml
37
+ pyproject_content = '''[tool.pyreact]
38
+ entry = "src/index.py"
39
+ output = "dist"
40
+ dev_port = 3000
41
+ ssr = true
42
+ css_modules = true
43
+ source_maps = true
44
+ '''
45
+ (project_dir / 'pyproject.toml').write_text(pyproject_content, encoding='utf-8')
46
+
47
+ # Create main index file
48
+ index_content = '''"""
49
+ Main entry point for PyReact application
50
+ """
51
+
52
+ from pyreact import h, render, use_state
53
+
54
+
55
+ def App(props):
56
+ """Main application component"""
57
+ count, set_count = use_state(0)
58
+
59
+ return h('div', {'className': 'app'},
60
+ h('h1', None, f'Welcome to {props.get("name", "PyReact")}!'),
61
+ h('p', None, 'Edit src/index.py to get started.'),
62
+ h('div', {'className': 'counter'},
63
+ h('span', None, f'Count: {count}'),
64
+ h('button', {'onClick': lambda _: set_count(count + 1)}, '+'),
65
+ h('button', {'onClick': lambda _: set_count(count - 1)}, '-')
66
+ )
67
+ )
68
+
69
+
70
+ if __name__ == '__main__':
71
+ from pyreact.dom.dom_operations import document
72
+
73
+ root = document.create_element('div')
74
+ root.attributes['id'] = 'root'
75
+ document.body.append_child(root)
76
+
77
+ render(h(App, {'name': 'My App'}), root)
78
+ '''
79
+ (project_dir / 'src' / 'index.py').write_text(index_content, encoding='utf-8')
80
+
81
+ # Create __init__.py files
82
+ init_content = '"""PyReact application package"""'
83
+ (project_dir / 'src' / '__init__.py').write_text(init_content, encoding='utf-8')
84
+ (project_dir / 'src' / 'components' / '__init__.py').write_text(init_content, encoding='utf-8')
85
+ (project_dir / 'src' / 'hooks' / '__init__.py').write_text(init_content, encoding='utf-8')
86
+ (project_dir / 'src' / 'pages' / '__init__.py').write_text(init_content, encoding='utf-8')
87
+
88
+ # Create README
89
+ readme_content = f'''# {name}
90
+
91
+ A PyReact application.
92
+
93
+ ## Getting Started
94
+
95
+ ```bash
96
+ # Install dependencies
97
+ pip install pyreact
98
+
99
+ # Run development server
100
+ pyreact dev
101
+
102
+ # Build for production
103
+ pyreact build
104
+ ```
105
+
106
+ ## Project Structure
107
+
108
+ ```
109
+ {name}/
110
+ ├── src/
111
+ │ ├── components/ # Reusable components
112
+ │ ├── hooks/ # Custom hooks
113
+ │ ├── pages/ # Page components
114
+ │ ├── styles/ # CSS files
115
+ │ └── index.py # Entry point
116
+ ├── public/ # Static assets
117
+ ├── tests/ # Test files
118
+ └── pyproject.toml # Configuration
119
+ ```
120
+ '''
121
+ (project_dir / 'README.md').write_text(readme_content, encoding='utf-8')
122
+
123
+ # Create test file
124
+ test_content = '''"""
125
+ Tests for the application
126
+ """
127
+
128
+ from pyreact.testing import render, screen, fireEvent
129
+ from pyreact import h
130
+
131
+
132
+ def test_app_renders():
133
+ """Test that the app renders"""
134
+ # Add your tests here
135
+ pass
136
+
137
+
138
+ if __name__ == '__main__':
139
+ import pytest
140
+ pytest.main([__file__, '-v'])
141
+ '''
142
+ (project_dir / 'tests' / 'test_app.py').write_text(test_content, encoding='utf-8')
143
+
144
+ print(f"[OK] Created project '{name}'")
145
+ print(f"\nNext steps:")
146
+ print(f" cd {name}")
147
+ print(f" pyreact dev")
148
+
149
+
150
+ def generate_component(name: str, component_type: str = 'functional') -> None:
151
+ """Generate a new component"""
152
+ # Determine output directory
153
+ components_dir = Path('src/components')
154
+ if not components_dir.exists():
155
+ components_dir = Path('components')
156
+
157
+ if not components_dir.exists():
158
+ print("Error: Could not find components directory")
159
+ sys.exit(1)
160
+
161
+ # Create component file
162
+ if component_type == 'class':
163
+ content = f'''"""
164
+ {name} Component
165
+ """
166
+
167
+ from pyreact import h, Component
168
+
169
+
170
+ class {name}(Component):
171
+ """Class component: {name}"""
172
+
173
+ def __init__(self, props):
174
+ super().__init__(props)
175
+ self.state = {{}}
176
+
177
+ def render(self):
178
+ return h('div', {{'className': '{name.lower()}'}},
179
+ h('h2', None, '{name}'),
180
+ self.props.get('children', None)
181
+ )
182
+ '''
183
+ else:
184
+ content = f'''"""
185
+ {name} Component
186
+ """
187
+
188
+ from pyreact import h, use_state
189
+
190
+
191
+ def {name}(props):
192
+ """Functional component: {name}"""
193
+
194
+ return h('div', {{'className': '{name.lower()}'}},
195
+ h('h2', None, '{name}'),
196
+ props.get('children', None)
197
+ )
198
+ '''
199
+
200
+ file_path = components_dir / f'{name}.py'
201
+ file_path.write_text(content, encoding='utf-8')
202
+
203
+ print(f"[OK] Created component '{name}' at {file_path}")
204
+
205
+
206
+ def generate_hook(name: str) -> None:
207
+ """Generate a new custom hook"""
208
+ hooks_dir = Path('src/hooks')
209
+ if not hooks_dir.exists():
210
+ hooks_dir = Path('hooks')
211
+
212
+ if not hooks_dir.exists():
213
+ print("Error: Could not find hooks directory")
214
+ sys.exit(1)
215
+
216
+ # Remove 'use' prefix if present
217
+ if name.startswith('use_'):
218
+ name = name[4:]
219
+ elif name.startswith('use'):
220
+ name = name[3:]
221
+
222
+ hook_name = f'use_{name.lower()}'
223
+
224
+ content = f'''"""
225
+ {name} Hook
226
+ """
227
+
228
+ from pyreact import use_state, use_effect
229
+
230
+
231
+ def {hook_name}(initial_value=None):
232
+ """
233
+ Custom hook: {hook_name}
234
+
235
+ Args:
236
+ initial_value: Initial value
237
+
238
+ Returns:
239
+ tuple: (value, setter)
240
+ """
241
+ value, set_value = use_state(initial_value)
242
+
243
+ @use_effect([])
244
+ def setup():
245
+ # Setup logic here
246
+ return lambda: None # Cleanup
247
+
248
+ return value, set_value
249
+ '''
250
+
251
+ file_path = hooks_dir / f'{hook_name}.py'
252
+ file_path.write_text(content, encoding='utf-8')
253
+
254
+ print(f"[OK] Created hook '{hook_name}' at {file_path}")
255
+
256
+
257
+ def run_dev_server(port: int = 3000) -> None:
258
+ """Run development server"""
259
+ import http.server
260
+ import socketserver
261
+ import threading
262
+ import time
263
+ import webbrowser
264
+ from pathlib import Path
265
+
266
+ # Check if we're in a PyReact project
267
+ if not Path('pyproject.toml').exists():
268
+ print("Error: Not a PyReact project. Run 'pyreact create <name>' first.", flush=True)
269
+ sys.exit(1)
270
+
271
+ # Check if src/index.py exists
272
+ if not Path('src/index.py').exists():
273
+ print("Error: src/index.py not found", flush=True)
274
+ sys.exit(1)
275
+
276
+ # Create public directory if it doesn't exist
277
+ Path('public').mkdir(exist_ok=True)
278
+
279
+ # Generate HTML file
280
+ html_content = '''<!DOCTYPE html>
281
+ <html lang="en">
282
+ <head>
283
+ <meta charset="UTF-8">
284
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
285
+ <title>PyReact App</title>
286
+ <style>
287
+ body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
288
+ .app { max-width: 800px; margin: 0 auto; }
289
+ .counter { display: flex; gap: 10px; align-items: center; }
290
+ button { padding: 5px 15px; font-size: 16px; cursor: pointer; }
291
+ </style>
292
+ </head>
293
+ <body>
294
+ <div id="root"></div>
295
+ <script>
296
+ // PyReact runtime simulation
297
+ const PyReact = {
298
+ h: function(type, props, ...children) {
299
+ return { type, props: props || {}, children: children.flat() };
300
+ },
301
+ render: function(vnode, container) {
302
+ const dom = this.createDom(vnode);
303
+ container.innerHTML = '';
304
+ container.appendChild(dom);
305
+ },
306
+ createDom: function(vnode) {
307
+ if (typeof vnode === 'string') {
308
+ return document.createTextNode(vnode);
309
+ }
310
+ if (!vnode || !vnode.type) {
311
+ return document.createTextNode('');
312
+ }
313
+
314
+ const dom = document.createElement(vnode.type);
315
+
316
+ // Apply props
317
+ for (const [key, value] of Object.entries(vnode.props || {})) {
318
+ if (key === 'className') {
319
+ dom.className = value;
320
+ } else if (key.startsWith('on')) {
321
+ const event = key[2].toLowerCase() + key.slice(3);
322
+ dom.addEventListener(event, value);
323
+ } else if (key === 'style' && typeof value === 'object') {
324
+ Object.assign(dom.style, value);
325
+ } else {
326
+ dom.setAttribute(key, value);
327
+ }
328
+ }
329
+
330
+ // Render children
331
+ for (const child of vnode.children || []) {
332
+ dom.appendChild(this.createDom(child));
333
+ }
334
+
335
+ return dom;
336
+ }
337
+ };
338
+
339
+ // State management
340
+ let stateValues = [];
341
+ let stateIndex = 0;
342
+
343
+ function useState(initialValue) {
344
+ const currentIndex = stateIndex;
345
+ if (stateValues[currentIndex] === undefined) {
346
+ stateValues[currentIndex] = initialValue;
347
+ }
348
+ const setValue = (newValue) => {
349
+ if (typeof newValue === 'function') {
350
+ stateValues[currentIndex] = newValue(stateValues[currentIndex]);
351
+ } else {
352
+ stateValues[currentIndex] = newValue;
353
+ }
354
+ stateIndex = 0;
355
+ render();
356
+ };
357
+ stateIndex++;
358
+ return [stateValues[currentIndex], setValue];
359
+ }
360
+
361
+ function render() {
362
+ stateIndex = 0;
363
+ const [count, setCount] = useState(0);
364
+ const root = document.getElementById('root');
365
+ const app = PyReact.h('div', {className: 'app'},
366
+ PyReact.h('h1', null, 'Welcome to PyReact!'),
367
+ PyReact.h('p', null, 'Edit src/index.py to get started.'),
368
+ PyReact.h('div', {className: 'counter'},
369
+ PyReact.h('span', null, 'Count: ' + count),
370
+ PyReact.h('button', {onClick: () => setCount(count + 1)}, '+'),
371
+ PyReact.h('button', {onClick: () => setCount(count - 1)}, '-')
372
+ )
373
+ );
374
+ PyReact.render(app, root);
375
+ }
376
+
377
+ // Initial render
378
+ render();
379
+
380
+ // Hot reload simulation
381
+ console.log('PyReact dev server running. Edit src/index.py to see changes.');
382
+ </script>
383
+ </body>
384
+ </html>'''
385
+
386
+ html_path = Path('public/index.html')
387
+ html_path.write_text(html_content, encoding='utf-8')
388
+
389
+ # Change to public directory
390
+ original_dir = os.getcwd()
391
+ os.chdir('public')
392
+
393
+ # Create custom handler
394
+ class Handler(http.server.SimpleHTTPRequestHandler):
395
+ def log_message(self, format, *args):
396
+ # Suppress default logging
397
+ pass
398
+
399
+ # Start server
400
+ try:
401
+ # Allow address reuse
402
+ socketserver.TCPServer.allow_reuse_address = True
403
+
404
+ with socketserver.TCPServer(("", port), Handler) as httpd:
405
+ url = f"http://localhost:{port}"
406
+ print(f"[OK] Development server running at {url}", flush=True)
407
+ print(f"\nPress Ctrl+C to stop the server", flush=True)
408
+
409
+ # Open browser after a short delay
410
+ def open_browser():
411
+ time.sleep(1.5)
412
+ try:
413
+ webbrowser.open(url)
414
+ except:
415
+ pass
416
+
417
+ browser_thread = threading.Thread(target=open_browser, daemon=True)
418
+ browser_thread.start()
419
+
420
+ # Serve forever
421
+ try:
422
+ httpd.serve_forever()
423
+ except KeyboardInterrupt:
424
+ pass
425
+ finally:
426
+ os.chdir(original_dir)
427
+
428
+ except OSError as e:
429
+ os.chdir(original_dir)
430
+ if 'Address already in use' in str(e) or e.errno == 10048: # Port already in use (Windows)
431
+ print(f"Error: Port {port} is already in use", flush=True)
432
+ print(f"Try: pyreact dev --port {port + 1}", flush=True)
433
+ else:
434
+ print(f"Error: {e}", flush=True)
435
+ sys.exit(1)
436
+ except KeyboardInterrupt:
437
+ print("\n\n[OK] Server stopped", flush=True)
438
+
439
+
440
+ def build_project() -> None:
441
+ """Build project for production"""
442
+ print("Building project for production...")
443
+ print("Note: This is a placeholder. Implement actual build process.")
444
+
445
+ # Check for pyproject.toml
446
+ if not Path('pyproject.toml').exists():
447
+ print("Error: pyproject.toml not found")
448
+ sys.exit(1)
449
+
450
+ print("[OK] Build complete (placeholder)")
451
+
452
+
453
+ def run_tests() -> None:
454
+ """Run tests"""
455
+ import subprocess
456
+
457
+ print("Running tests...")
458
+ result = subprocess.run(['pytest', 'tests/', '-v'])
459
+ sys.exit(result.returncode)
460
+
461
+
462
+ def main():
463
+ """Main CLI entry point"""
464
+ parser = argparse.ArgumentParser(
465
+ description='PyReact - Framework Web Declarativo para Python'
466
+ )
467
+
468
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
469
+
470
+ # Create command
471
+ create_parser = subparsers.add_parser('create', help='Create a new project')
472
+ create_parser.add_argument('name', help='Project name')
473
+
474
+ # Dev command
475
+ dev_parser = subparsers.add_parser('dev', help='Start development server')
476
+ dev_parser.add_argument('--port', type=int, default=3000, help='Port number')
477
+
478
+ # Build command
479
+ subparsers.add_parser('build', help='Build for production')
480
+
481
+ # Test command
482
+ subparsers.add_parser('test', help='Run tests')
483
+
484
+ # Generate command
485
+ gen_parser = subparsers.add_parser('generate', help='Generate component or hook')
486
+ gen_parser.add_argument('type', choices=['component', 'hook'], help='Type to generate')
487
+ gen_parser.add_argument('name', help='Name of component or hook')
488
+ gen_parser.add_argument('--class', dest='class_type', action='store_true',
489
+ help='Generate class component')
490
+
491
+ args = parser.parse_args()
492
+
493
+ if args.command == 'create':
494
+ create_project(args.name)
495
+ elif args.command == 'dev':
496
+ run_dev_server(args.port)
497
+ elif args.command == 'build':
498
+ build_project()
499
+ elif args.command == 'test':
500
+ run_tests()
501
+ elif args.command == 'generate':
502
+ if args.type == 'component':
503
+ component_type = 'class' if args.class_type else 'functional'
504
+ generate_component(args.name, component_type)
505
+ elif args.type == 'hook':
506
+ generate_hook(args.name)
507
+ else:
508
+ parser.print_help()
509
+
510
+
511
+ if __name__ == '__main__':
512
+ main()