hypern 0.2.0__cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl → 0.2.1__cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.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.
hypern/application.py CHANGED
@@ -2,23 +2,21 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import asyncio
5
- import socket
6
5
  from typing import Any, Callable, List, TypeVar
7
6
 
8
7
  import orjson
9
8
  from typing_extensions import Annotated, Doc
10
9
 
11
10
  from hypern.datastructures import Contact, HTTPMethod, Info, License
12
- from hypern.exceptions import InvalidPortNumber
13
11
  from hypern.hypern import FunctionInfo, Router
14
12
  from hypern.hypern import Route as InternalRoute
15
- from hypern.logging import logger
16
13
  from hypern.openapi import SchemaGenerator, SwaggerUI
17
14
  from hypern.processpool import run_processes
18
15
  from hypern.response import HTMLResponse, JSONResponse
19
16
  from hypern.routing import Route
20
17
  from hypern.scheduler import Scheduler
21
18
  from hypern.middleware import Middleware
19
+ from hypern.args_parser import ArgsConfig
22
20
 
23
21
  AppType = TypeVar("AppType", bound="Hypern")
24
22
 
@@ -193,6 +191,7 @@ class Hypern:
193
191
  self.middleware_before_request = []
194
192
  self.middleware_after_request = []
195
193
  self.response_headers = {}
194
+ self.args = ArgsConfig()
196
195
 
197
196
  for route in routes:
198
197
  self.router.extend_route(route(app=self).routes)
@@ -340,60 +339,30 @@ class Hypern:
340
339
  self.after_request()(after_request)
341
340
  return self
342
341
 
343
- def is_port_in_use(self, port: int) -> bool:
344
- try:
345
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
346
- return s.connect_ex(("localhost", port)) == 0
347
- except Exception:
348
- raise InvalidPortNumber(f"Invalid port number: {port}")
349
-
350
342
  def start(
351
343
  self,
352
- host: Annotated[str, Doc("The host to run the server on. Defaults to `127.0.0.1`")] = "127.0.0.1",
353
- port: Annotated[int, Doc("The port to run the server on. Defaults to `8080`")] = 8080,
354
- workers: Annotated[int, Doc("The number of workers to run. Defaults to `1`")] = 1,
355
- processes: Annotated[int, Doc("The number of processes to run. Defaults to `1`")] = 1,
356
- max_blocking_threads: Annotated[int, Doc("The maximum number of blocking threads. Defaults to `100`")] = 1,
357
- check_port: Annotated[bool, Doc("Check if the port is already in use. Defaults to `True`")] = False,
358
344
  ):
359
345
  """
360
346
  Starts the server with the specified configuration.
361
-
362
- Args:
363
- host (str): The host to run the server on. Defaults to `127.0.0.1`.
364
- port (int): The port to run the server on. Defaults to `8080`.
365
- workers (int): The number of workers to run. Defaults to `1`.
366
- processes (int): The number of processes to run. Defaults to `1`.
367
- max_blocking_threads (int): The maximum number of blocking threads. Defaults to `100`.
368
- check_port (bool): Check if the port is already in use. Defaults to `True`.
369
-
370
347
  Raises:
371
348
  ValueError: If an invalid port number is entered when prompted.
372
349
 
373
350
  """
374
- if check_port:
375
- while self.is_port_in_use(port):
376
- logger.error("Port %s is already in use. Please use a different port.", port)
377
- try:
378
- port = int(input("Enter a different port: "))
379
- except Exception:
380
- logger.error("Invalid port number. Please enter a valid port number.")
381
- continue
382
-
383
351
  if self.scheduler:
384
352
  self.scheduler.start()
385
353
 
386
354
  run_processes(
387
- host=host,
388
- port=port,
389
- workers=workers,
390
- processes=processes,
391
- max_blocking_threads=max_blocking_threads,
355
+ host=self.args.host,
356
+ port=self.args.port,
357
+ workers=self.args.workers,
358
+ processes=self.args.processes,
359
+ max_blocking_threads=self.args.max_blocking_threads,
392
360
  router=self.router,
393
361
  injectables=self.injectables,
394
362
  before_request=self.middleware_before_request,
395
363
  after_request=self.middleware_after_request,
396
364
  response_headers=self.response_headers,
365
+ reload=self.args.reload,
397
366
  )
398
367
 
399
368
  def add_route(self, method: HTTPMethod, endpoint: str, handler: Callable[..., Any]):
hypern/args_parser.py ADDED
@@ -0,0 +1,59 @@
1
+ import argparse
2
+
3
+
4
+ class ArgsConfig:
5
+ def __init__(self) -> None:
6
+ parser = argparse.ArgumentParser(description="Hypern: A Versatile Python and Rust Framework")
7
+ self.parser = parser
8
+ parser.add_argument(
9
+ "--host",
10
+ type=str,
11
+ default=None,
12
+ required=False,
13
+ help="Choose the host. [Defaults to `127.0.0.1`]",
14
+ )
15
+
16
+ parser.add_argument(
17
+ "--port",
18
+ type=int,
19
+ default=None,
20
+ required=False,
21
+ help="Choose the port. [Defaults to `5000`]",
22
+ )
23
+
24
+ parser.add_argument(
25
+ "--processes",
26
+ type=int,
27
+ default=None,
28
+ required=False,
29
+ help="Choose the number of processes. [Default: 1]",
30
+ )
31
+ parser.add_argument(
32
+ "--workers",
33
+ type=int,
34
+ default=None,
35
+ required=False,
36
+ help="Choose the number of workers. [Default: 1]",
37
+ )
38
+
39
+ parser.add_argument(
40
+ "--max-blocking-threads",
41
+ type=int,
42
+ default=None,
43
+ required=False,
44
+ help="Choose the maximum number of blocking threads. [Default: 100]",
45
+ )
46
+
47
+ parser.add_argument(
48
+ "--reload",
49
+ action="store_true",
50
+ help="It restarts the server based on file changes.",
51
+ )
52
+ args, _ = parser.parse_known_args()
53
+
54
+ self.host = args.host or "127.0.0.1"
55
+ self.port = args.port or 5000
56
+ self.max_blocking_threads = args.max_blocking_threads or 100
57
+ self.processes = args.processes or 1
58
+ self.workers = args.workers or 1
59
+ self.reload = args.reload or False
Binary file
hypern/logging/logger.py CHANGED
@@ -59,7 +59,7 @@ class ColourizedFormatter(logging.Formatter):
59
59
  recordcopy.__dict__["message"] = recordcopy.getMessage()
60
60
  recordcopy.__dict__["levelprefix"] = levelname + separator
61
61
  recordcopy.__dict__["process"] = click.style(str(process), fg="blue")
62
- recordcopy.__dict__["asctime"] = click.style(datetime.fromtimestamp(created, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%fZ"), fg=(101, 111, 104))
62
+ recordcopy.__dict__["asctime"] = click.style(datetime.fromtimestamp(created, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"), fg=(101, 111, 104))
63
63
  recordcopy.__dict__["filename"] = click.style(f"{module}/{filename}:{lineno}:", fg=(101, 111, 104))
64
64
  return super().formatMessage(recordcopy)
65
65
 
hypern/processpool.py CHANGED
@@ -1,12 +1,15 @@
1
1
  import asyncio
2
+ import os
2
3
  import signal
3
4
  import sys
4
5
  from typing import Any, Dict, List
5
6
 
6
7
  from multiprocess import Process
8
+ from watchdog.observers import Observer
7
9
 
8
10
  from .hypern import FunctionInfo, Router, Server, SocketHeld
9
11
  from .logging import logger
12
+ from .reload import EventHandler
10
13
 
11
14
 
12
15
  def run_processes(
@@ -20,6 +23,7 @@ def run_processes(
20
23
  before_request: List[FunctionInfo],
21
24
  after_request: List[FunctionInfo],
22
25
  response_headers: Dict[str, str],
26
+ reload: bool = True,
23
27
  ) -> List[Process]:
24
28
  socket = SocketHeld(host, port)
25
29
 
@@ -33,9 +37,30 @@ def run_processes(
33
37
  signal.signal(signal.SIGINT, terminating_signal_handler)
34
38
  signal.signal(signal.SIGTERM, terminating_signal_handler)
35
39
 
36
- logger.info("Press Ctrl + C to stop \n")
37
- for process in process_pool:
38
- process.join()
40
+ if reload:
41
+ # Set up file system watcher for auto-reload
42
+ watch_dirs = [os.getcwd()]
43
+ observer = Observer()
44
+ reload_handler = EventHandler(file_path=sys.argv[0], directory_path=os.getcwd())
45
+
46
+ for directory in watch_dirs:
47
+ observer.schedule(reload_handler, directory, recursive=True)
48
+
49
+ observer.start()
50
+
51
+ logger.info(f"Server started at http://{host}:{port}")
52
+ logger.info("Press Ctrl + C to stop")
53
+
54
+ try:
55
+ for process in process_pool:
56
+ logger.debug(f"Process {process.pid} started")
57
+ process.join()
58
+ except KeyboardInterrupt:
59
+ pass
60
+ finally:
61
+ if reload:
62
+ observer.stop()
63
+ observer.join()
39
64
 
40
65
  return process_pool
41
66
 
hypern/reload.py ADDED
@@ -0,0 +1,60 @@
1
+ import sys
2
+ import time
3
+ import subprocess
4
+ from watchdog.events import FileSystemEventHandler
5
+
6
+ from .logging import logger
7
+
8
+
9
+ class EventHandler(FileSystemEventHandler):
10
+ def __init__(self, file_path: str, directory_path: str) -> None:
11
+ self.file_path = file_path
12
+ self.directory_path = directory_path
13
+ self.process = None # Keep track of the subprocess
14
+ self.last_reload = time.time() # Keep track of the last reload. EventHandler is initialized with the process.
15
+
16
+ def stop_server(self):
17
+ if self.process:
18
+ try:
19
+ # Check if the process is still alive
20
+ if self.process.poll() is None: # None means the process is still running
21
+ self.process.terminate() # Gracefully terminate the process
22
+ self.process.wait(timeout=5) # Wait for the process to exit
23
+ else:
24
+ logger.error("Process is not running.")
25
+ except subprocess.TimeoutExpired:
26
+ logger.error("Process did not terminate in time. Forcing termination.")
27
+ self.process.kill() # Forcefully kill the process if it doesn't stop
28
+ except ProcessLookupError:
29
+ logger.error("Process does not exist.")
30
+ except Exception as e:
31
+ logger.error(f"An error occurred while stopping the server: {e}")
32
+ else:
33
+ logger.debug("No process to stop.")
34
+
35
+ def reload(self):
36
+ self.stop_server()
37
+ logger.debug("Reloading the server")
38
+ prev_process = self.process
39
+ if prev_process:
40
+ prev_process.kill()
41
+
42
+ self.process = subprocess.Popen(
43
+ [sys.executable, *sys.argv],
44
+ )
45
+
46
+ self.last_reload = time.time()
47
+
48
+ def on_modified(self, event) -> None:
49
+ """
50
+ This function is a callback that will start a new server on every even change
51
+
52
+ :param event FSEvent: a data structure with info about the events
53
+ """
54
+
55
+ # Avoid reloading multiple times when watchdog detects multiple events
56
+ if time.time() - self.last_reload < 0.5:
57
+ return
58
+
59
+ time.sleep(0.2) # Wait for the file to be fully written
60
+ self.reload()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hypern
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -22,6 +22,7 @@ Requires-Dist: orjson ==3.10.11
22
22
  Requires-Dist: multiprocess ==0.70.17
23
23
  Requires-Dist: uvloop ==0.21.0 ; sys_platform != 'win32' and platform_python_implementation == 'CPython' and platform_machine != 'armv7l'
24
24
  Requires-Dist: cryptography ==43.0.3
25
+ Requires-Dist: watchdog ==6.0.0
25
26
  License-File: LICENSE
26
27
  Summary: A Fast Async Python backend with a Rust runtime.
27
28
  Author-email: Martin Dang <vannghiem848@gmail.com>
@@ -1,7 +1,7 @@
1
- hypern-0.2.0.dist-info/METADATA,sha256=vr_oCiPq5bDdIyTt0w6S_phtY8IvEX-oRW407K-Wd-o,3626
2
- hypern-0.2.0.dist-info/WHEEL,sha256=3dYz42ztAymIiwEQC7LmGpGwjFphClb96av9UKeGuu0,125
3
- hypern-0.2.0.dist-info/licenses/LICENSE,sha256=VdbaK2hSaaD-LUjtDIlEbeZVmvLGK7BEQvltP3mv-cY,1304
4
- hypern/application.py,sha256=fZ4pXSLHuJO7hyIq3THFnNDfpXJt3p5IxYYYgmOBNXU,14771
1
+ hypern-0.2.1.dist-info/METADATA,sha256=XnBEky_cZRe5MMTijuguzD2xMsbXI8Jc0mLvXmlO1Zo,3658
2
+ hypern-0.2.1.dist-info/WHEEL,sha256=3dYz42ztAymIiwEQC7LmGpGwjFphClb96av9UKeGuu0,125
3
+ hypern-0.2.1.dist-info/licenses/LICENSE,sha256=VdbaK2hSaaD-LUjtDIlEbeZVmvLGK7BEQvltP3mv-cY,1304
4
+ hypern/application.py,sha256=DE9tQjkB4Pa6YQmmKJ7QwdaHDnhW3iw99zsyLJ0uFLI,13018
5
5
  hypern/middleware/__init__.py,sha256=YpgxL7GQkzZM91VCNxHT2xmTa1R1b_BUS8n3tZ2b1Ys,268
6
6
  hypern/middleware/cors.py,sha256=SfG-3vAS-4MPXqsIsegNwDx9mqC9lvgUc3RuYzN6HNg,1643
7
7
  hypern/middleware/base.py,sha256=Llcg9wglcumvY4BqaTfrX1OOZDqns4wb34wGF55EXcI,523
@@ -15,11 +15,12 @@ hypern/routing/parser.py,sha256=4BFn8MAmSX1QplwBXEEgbabYiNUAllYf2svPZoPPD5k,3454
15
15
  hypern/response/__init__.py,sha256=9z99BDgASpG404GK8LGkOsXgac0wFwH_cQOTI5Ju-1U,223
16
16
  hypern/response/response.py,sha256=s6KqscjA7jl8RaZh5gZQgVksPtHKzsRrQywDcEjVSR4,4448
17
17
  hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- hypern/processpool.py,sha256=q985ILIzI7CSiusRL614PwOAT4RCczon7e-CAn1U5Vc,2925
18
+ hypern/processpool.py,sha256=Wrh_l87-3w4Z9znc6P22hi8kZfBA8cQ1C0Oaq8ysLVs,3657
19
19
  hypern/security.py,sha256=dOWdNHA8SoUhlbu6Z2MapOwT9tAhlsierrTBBiEV5-A,1729
20
20
  hypern/hypern.pyi,sha256=eSqxlY7q6KboRjP3bpRlGSn2sEjxrIHFpxXh1o-qQJ4,6649
21
- hypern/logging/logger.py,sha256=hYOLjx_UVdnaDLFnmCZMANx3rgs4TIGsvtBFJ-YlYxg,3174
21
+ hypern/logging/logger.py,sha256=62Qg4YAi_JDGV72Rd6R58jixqZk7anRqHbtnuBlkrwA,3174
22
22
  hypern/logging/__init__.py,sha256=lzYSz0382eIM3CvP0sZ6RbEEwYZwfeJEJh9cxQA6Rws,49
23
+ hypern/reload.py,sha256=oRwDyxze69ERiVT41eJI_qpg6eJFDbIszGPVAoS5DHs,2233
23
24
  hypern/exceptions.py,sha256=wpTSTzw32Sb6bY9YxCDM7W_-Ww6u6pB1GKNbFf-1oj0,2331
24
25
  hypern/openapi/schemas.py,sha256=nmcmNYvKmjNkwFqi_3qpXVi1ukanNxMVay68bOLTrx8,1624
25
26
  hypern/openapi/__init__.py,sha256=oJ0HM9yAgSN00mBC_fRgV2irlGugrhvIpiveuDMv8PM,136
@@ -39,6 +40,7 @@ hypern/caching/redis_backend.py,sha256=3FYzKCW0_OvoIMl-e9pARRGOUvRYGG7hGlaXEB18v
39
40
  hypern/caching/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  hypern/caching/custom_key_maker.py,sha256=DxJv1RV--5IdFCCFYawVExwMQ097hZ5V6_nHDYIQIZI,383
41
42
  hypern/caching/cache_manager.py,sha256=EBx89xNj38bYpQ9jf4MoQ3zNkfoCGYInxvvPtzsW5Xo,1997
43
+ hypern/args_parser.py,sha256=kJQtzw2xZrumDBzLQaZyEejAT02rUetPYCrmOpjzxWY,1731
42
44
  hypern/enum.py,sha256=-StRU0cWboP-y5fNuhB4Q7yMk8Zm_h1Eua9KzOtIyI8,347
43
45
  hypern/worker.py,sha256=kN93QGx4bst0inHRO_fsTzMmhDA0N3pdzaqNXwyu3N0,894
44
46
  hypern/db/nosql/__init__.py,sha256=LCVBhcJCQmFt5dCLiSf7C_TsjsEILeZe8JqiFEKuD2s,619
@@ -62,5 +64,5 @@ hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
64
  hypern/datastructures.py,sha256=7Nb_fOxmfO8CT7_v_-RhmXg54IhioXGZSp405IzJLh4,857
63
65
  hypern/config.py,sha256=v9KLL6snReAETKiVb8x3KOFrXpYd8-Io5tM7eruR85U,4781
64
66
  hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- hypern/hypern.cpython-310-i386-linux-gnu.so,sha256=Ce5YxMj85K_FFCsM3QOdKZXjVwCyg5giBCm7u0uUP8I,6250364
66
- hypern-0.2.0.dist-info/RECORD,,
67
+ hypern/hypern.cpython-310-i386-linux-gnu.so,sha256=oIDe8_eR63Ep5hRTsu20T58RMegTkoeOn1WsrdwkzoI,6250364
68
+ hypern-0.2.1.dist-info/RECORD,,
File without changes