hypern 0.2.0__cp312-cp312-macosx_10_12_x86_64.whl → 0.2.1__cp312-cp312-macosx_10_12_x86_64.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,6 +1,6 @@
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=i-50AaV3q8zthgSaPZNLbaFczY3NRyGbQDASQt3eucc,106
3
- hypern-0.2.0.dist-info/licenses/LICENSE,sha256=VdbaK2hSaaD-LUjtDIlEbeZVmvLGK7BEQvltP3mv-cY,1304
1
+ hypern-0.2.1.dist-info/METADATA,sha256=XnBEky_cZRe5MMTijuguzD2xMsbXI8Jc0mLvXmlO1Zo,3658
2
+ hypern-0.2.1.dist-info/WHEEL,sha256=i-50AaV3q8zthgSaPZNLbaFczY3NRyGbQDASQt3eucc,106
3
+ hypern-0.2.1.dist-info/licenses/LICENSE,sha256=VdbaK2hSaaD-LUjtDIlEbeZVmvLGK7BEQvltP3mv-cY,1304
4
4
  hypern/worker.py,sha256=kN93QGx4bst0inHRO_fsTzMmhDA0N3pdzaqNXwyu3N0,894
5
5
  hypern/middleware/cors.py,sha256=SfG-3vAS-4MPXqsIsegNwDx9mqC9lvgUc3RuYzN6HNg,1643
6
6
  hypern/middleware/__init__.py,sha256=YpgxL7GQkzZM91VCNxHT2xmTa1R1b_BUS8n3tZ2b1Ys,268
@@ -10,6 +10,7 @@ hypern/middleware/base.py,sha256=Llcg9wglcumvY4BqaTfrX1OOZDqns4wb34wGF55EXcI,523
10
10
  hypern/config.py,sha256=v9KLL6snReAETKiVb8x3KOFrXpYd8-Io5tM7eruR85U,4781
11
11
  hypern/response/__init__.py,sha256=9z99BDgASpG404GK8LGkOsXgac0wFwH_cQOTI5Ju-1U,223
12
12
  hypern/response/response.py,sha256=s6KqscjA7jl8RaZh5gZQgVksPtHKzsRrQywDcEjVSR4,4448
13
+ hypern/reload.py,sha256=oRwDyxze69ERiVT41eJI_qpg6eJFDbIszGPVAoS5DHs,2233
13
14
  hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  hypern/auth/authorization.py,sha256=b8N_MiBmSJBVR45SsOI2iMGmXYOlNKbsMsrMwXApxtk,30
15
16
  hypern/security.py,sha256=dOWdNHA8SoUhlbu6Z2MapOwT9tAhlsierrTBBiEV5-A,1729
@@ -20,9 +21,9 @@ hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
21
  hypern/openapi/swagger.py,sha256=E5fHYUfFa77zQsCyQGf_vnqJVpl4_KI5qsKFHdgJEdw,61
21
22
  hypern/openapi/__init__.py,sha256=oJ0HM9yAgSN00mBC_fRgV2irlGugrhvIpiveuDMv8PM,136
22
23
  hypern/openapi/schemas.py,sha256=nmcmNYvKmjNkwFqi_3qpXVi1ukanNxMVay68bOLTrx8,1624
23
- hypern/application.py,sha256=fZ4pXSLHuJO7hyIq3THFnNDfpXJt3p5IxYYYgmOBNXU,14771
24
+ hypern/application.py,sha256=DE9tQjkB4Pa6YQmmKJ7QwdaHDnhW3iw99zsyLJ0uFLI,13018
24
25
  hypern/hypern.pyi,sha256=eSqxlY7q6KboRjP3bpRlGSn2sEjxrIHFpxXh1o-qQJ4,6649
25
- hypern/processpool.py,sha256=q985ILIzI7CSiusRL614PwOAT4RCczon7e-CAn1U5Vc,2925
26
+ hypern/processpool.py,sha256=Wrh_l87-3w4Z9znc6P22hi8kZfBA8cQ1C0Oaq8ysLVs,3657
26
27
  hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
28
  hypern/db/nosql/addons/color.py,sha256=8zUBFjR19J1zy_WaOCXlS14GjgdOaHAcuSb__zFp_II,430
28
29
  hypern/db/nosql/addons/unicode.py,sha256=vKyqfzrS9SKFo4eKlvu6klST0FrjM0wTx5N3ocvSSO8,258
@@ -44,6 +45,7 @@ hypern/db/sql/repository.py,sha256=YShEl1DqRXtYwNnn1F3YVfXX-TEdoNRjDEWIO_3Lu2s,9
44
45
  hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
46
  hypern/exceptions.py,sha256=wpTSTzw32Sb6bY9YxCDM7W_-Ww6u6pB1GKNbFf-1oj0,2331
46
47
  hypern/scheduler.py,sha256=nQoWIYMRmKd30g4XwdB072hWTNHvJlLd9S6rLlTFKS0,62
48
+ hypern/args_parser.py,sha256=kJQtzw2xZrumDBzLQaZyEejAT02rUetPYCrmOpjzxWY,1731
47
49
  hypern/routing/dispatcher.py,sha256=aujogCVTz2mYtZRkEtmpdlxXA9l6X4D072qOiIg-a_Q,2301
48
50
  hypern/routing/route.py,sha256=ribSMOo0eZ2WUZ1P8PdiXIsyVA3dHrI1gEYDikRDn4o,10090
49
51
  hypern/routing/__init__.py,sha256=MtyPYRHYMWIiCReZsUjJH93PvluotCbPU3RnWFQQmrA,97
@@ -61,6 +63,6 @@ hypern/caching/base/__init__.py,sha256=8S6QSax1FVGzfve_fYIzPmhrpvxXG8WYoc7M9v25b
61
63
  hypern/caching/base/key_maker.py,sha256=FPW9L-N8NSPPgRngqMUCOdLurm1qmqe-KTe4UctoBp8,224
62
64
  hypern/caching/redis_backend.py,sha256=3FYzKCW0_OvoIMl-e9pARRGOUvRYGG7hGlaXEB18vnY,67
63
65
  hypern/logging/__init__.py,sha256=lzYSz0382eIM3CvP0sZ6RbEEwYZwfeJEJh9cxQA6Rws,49
64
- hypern/logging/logger.py,sha256=hYOLjx_UVdnaDLFnmCZMANx3rgs4TIGsvtBFJ-YlYxg,3174
65
- hypern/hypern.cpython-312-darwin.so,sha256=Jmq4KRpvCWhQVPosdgqXkBQ2aQ_NLBtJS8cfoWfMUjg,6094016
66
- hypern-0.2.0.dist-info/RECORD,,
66
+ hypern/logging/logger.py,sha256=62Qg4YAi_JDGV72Rd6R58jixqZk7anRqHbtnuBlkrwA,3174
67
+ hypern/hypern.cpython-312-darwin.so,sha256=xj9wejpm2gopjNG_OyY66_FUGk5KI-Tpwr0YiPfQ3n8,6094040
68
+ hypern-0.2.1.dist-info/RECORD,,
File without changes