renus 2.7.2__tar.gz → 2.7.4__tar.gz

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 (54) hide show
  1. {renus-2.7.2 → renus-2.7.4}/LICENSE +1 -1
  2. {renus-2.7.2/renus.egg-info → renus-2.7.4}/PKG-INFO +2 -2
  3. renus-2.7.4/README.md +135 -0
  4. {renus-2.7.2 → renus-2.7.4}/renus/core/cache.py +85 -32
  5. {renus-2.7.2 → renus-2.7.4}/renus/core/model.py +4 -2
  6. {renus-2.7.2 → renus-2.7.4}/renus/core/serialize.py +2 -4
  7. {renus-2.7.2 → renus-2.7.4/renus.egg-info}/PKG-INFO +2 -2
  8. {renus-2.7.2 → renus-2.7.4}/setup.py +2 -2
  9. renus-2.7.2/README.md +0 -1
  10. {renus-2.7.2 → renus-2.7.4}/MANIFEST.in +0 -0
  11. {renus-2.7.2 → renus-2.7.4}/renus/__init__.py +0 -0
  12. {renus-2.7.2 → renus-2.7.4}/renus/__main__.py +0 -0
  13. {renus-2.7.2 → renus-2.7.4}/renus/app.py +0 -0
  14. {renus-2.7.2 → renus-2.7.4}/renus/commands/__init__.py +0 -0
  15. {renus-2.7.2 → renus-2.7.4}/renus/commands/app/__init__.py +0 -0
  16. {renus-2.7.2 → renus-2.7.4}/renus/commands/app/run.py +0 -0
  17. {renus-2.7.2 → renus-2.7.4}/renus/commands/app/temp.py +0 -0
  18. {renus-2.7.2 → renus-2.7.4}/renus/commands/copy/__init__.py +0 -0
  19. {renus-2.7.2 → renus-2.7.4}/renus/commands/copy/run.py +0 -0
  20. {renus-2.7.2 → renus-2.7.4}/renus/commands/db/__init__.py +0 -0
  21. {renus-2.7.2 → renus-2.7.4}/renus/commands/db/run.py +0 -0
  22. {renus-2.7.2 → renus-2.7.4}/renus/commands/extension/__init__.py +0 -0
  23. {renus-2.7.2 → renus-2.7.4}/renus/commands/extension/build.py +0 -0
  24. {renus-2.7.2 → renus-2.7.4}/renus/commands/extension/remove.py +0 -0
  25. {renus-2.7.2 → renus-2.7.4}/renus/commands/extension/run.py +0 -0
  26. {renus-2.7.2 → renus-2.7.4}/renus/commands/help.py +0 -0
  27. {renus-2.7.2 → renus-2.7.4}/renus/commands/run.py +0 -0
  28. {renus-2.7.2 → renus-2.7.4}/renus/core/__init__.py +0 -0
  29. {renus-2.7.2 → renus-2.7.4}/renus/core/concurrency.py +0 -0
  30. {renus-2.7.2 → renus-2.7.4}/renus/core/config.py +0 -0
  31. {renus-2.7.2 → renus-2.7.4}/renus/core/cprint.py +0 -0
  32. {renus-2.7.2 → renus-2.7.4}/renus/core/datastructures.py +0 -0
  33. {renus-2.7.2 → renus-2.7.4}/renus/core/exception.py +0 -0
  34. {renus-2.7.2 → renus-2.7.4}/renus/core/formparsers.py +0 -0
  35. {renus-2.7.2 → renus-2.7.4}/renus/core/injection.py +0 -0
  36. {renus-2.7.2 → renus-2.7.4}/renus/core/log.py +0 -0
  37. {renus-2.7.2 → renus-2.7.4}/renus/core/middleware.py +0 -0
  38. {renus-2.7.2 → renus-2.7.4}/renus/core/request.py +0 -0
  39. {renus-2.7.2 → renus-2.7.4}/renus/core/response.py +0 -0
  40. {renus-2.7.2 → renus-2.7.4}/renus/core/routing.py +0 -0
  41. {renus-2.7.2 → renus-2.7.4}/renus/core/schedule.py +0 -0
  42. {renus-2.7.2 → renus-2.7.4}/renus/core/status.py +0 -0
  43. {renus-2.7.2 → renus-2.7.4}/renus/core/validation/__init__.py +0 -0
  44. {renus-2.7.2 → renus-2.7.4}/renus/core/validation/rules.py +0 -0
  45. {renus-2.7.2 → renus-2.7.4}/renus/core/validation/validate.py +0 -0
  46. {renus-2.7.2 → renus-2.7.4}/renus/core/websockets.py +0 -0
  47. {renus-2.7.2 → renus-2.7.4}/renus/util/__init__.py +0 -0
  48. {renus-2.7.2 → renus-2.7.4}/renus/util/helper.py +0 -0
  49. {renus-2.7.2 → renus-2.7.4}/renus.egg-info/SOURCES.txt +0 -0
  50. {renus-2.7.2 → renus-2.7.4}/renus.egg-info/dependency_links.txt +0 -0
  51. {renus-2.7.2 → renus-2.7.4}/renus.egg-info/entry_points.txt +0 -0
  52. {renus-2.7.2 → renus-2.7.4}/renus.egg-info/requires.txt +0 -0
  53. {renus-2.7.2 → renus-2.7.4}/renus.egg-info/top_level.txt +0 -0
  54. {renus-2.7.2 → renus-2.7.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2022, https://github.com/smkoBa/renus
3
+ Copyright (c) 2022, https://github.com/kak-smko/renus
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: renus
3
- Version: 2.7.2
3
+ Version: 2.7.4
4
4
  Summary: Renus Core Framework
5
- Home-page: https://github.com/smkoBa/renus
5
+ Home-page: https://github.com/kak-smko/renus
6
6
  Author: Smko Bayazidi
7
7
  Author-email: ba.smko@gmail.com
8
8
  License: BSD
renus-2.7.4/README.md ADDED
@@ -0,0 +1,135 @@
1
+
2
+ # Renus
3
+
4
+ **Renus** is a lightweight ASGI framework/toolkit, which is ideal for building async web services in Python. It is production-ready and designed for large-scale projects while maintaining a lightweight footprint and high performance.
5
+
6
+ ## ✨ Features
7
+
8
+ - 🚀 **High Performance**: Great overall performance against independent benchmarks.
9
+ - 🪶 **Lightweight**: Low-complexity HTTP web framework with few hard dependencies.
10
+ - 🔄 **Async Support**: Fully compatible with `asyncio` and `trio` backends.
11
+ - 🔌 **WebSocket Support**: Built-in support for WebSockets.
12
+ - 🛠️ **Background Tasks**: Handle in-process background tasks easily.
13
+ - 🔄 **Lifespan Events**: Manage startup and shutdown events for resource management.
14
+ - 📦 **Built-in Utilities**: CORS, GZip, Static Files, Streaming responses, Caching, Cryptography, Scheduling, and more.
15
+ - ✅ **Reliable**: 100% test coverage.
16
+ - 📝 **Type Safe**: 100% type annotated codebase.
17
+
18
+ ## 📦 Installation
19
+
20
+ Install Renus using pip:
21
+
22
+ ```bash
23
+ pip install renus
24
+ ```
25
+
26
+ ## 🚀 Quick Start
27
+
28
+ Here is a simple example to get you started with Renus:
29
+
30
+ ```python
31
+ from renus.app import App
32
+ from renus.core.response import TextResponse, JsonResponse
33
+ from renus.core.routing import Router
34
+
35
+ # 1. Define route handlers
36
+ def home():
37
+ return TextResponse('Hello, world!')
38
+
39
+ def user(username):
40
+ return JsonResponse({'name': username})
41
+
42
+ async def websocket(ws):
43
+ await ws.accept()
44
+ await ws.send_text('Hello, world!')
45
+ await ws.close()
46
+
47
+ # 2. Define lifespan events (startup and shutdown)
48
+ async def lifespan(app):
49
+ print('Startup', app)
50
+ yield
51
+ print('Shutdown')
52
+
53
+ # 3. Setup routing
54
+ r = Router()
55
+ r.get('/', func=home)
56
+ r.get('/user/{username}', func=user)
57
+ r.ws('/ws', func=websocket)
58
+
59
+ # 4. Initialize the app
60
+ app = App(lifespan=lifespan)
61
+ ```
62
+
63
+ ### Running the Application
64
+
65
+ You can run your application using an ASGI server like `uvicorn`:
66
+
67
+ ```python
68
+ # main.py
69
+ import uvicorn
70
+ from renus.app import App
71
+ import routes.index # Import your routes module
72
+
73
+ app = App()
74
+
75
+ if __name__ == "__main__":
76
+ uvicorn.run(
77
+ "main:app",
78
+ host="127.0.0.1",
79
+ port=4000,
80
+ reload=True,
81
+ log_level="info",
82
+ headers=[
83
+ ("X-Frame-Options", 'DENY'),
84
+ ("Access-Control-Allow-Headers", '*'),
85
+ ("Access-Control-Allow-Origin", '*')
86
+ ]
87
+ )
88
+ ```
89
+
90
+ Run it with:
91
+ ```bash
92
+ python main.py
93
+ ```
94
+
95
+ ## 📚 Documentation
96
+
97
+ For full documentation, advanced features, and API references, please visit the official documentation:
98
+ 👉 **[Renus Documentation](https://codenus.com/en/renus)**
99
+
100
+ ### Advanced Features
101
+ Renus comes packed with advanced features out of the box to help you build complex applications:
102
+ - **Caching**: Efficient data caching mechanisms.
103
+ - **Lifespan**: Manage application lifecycle events.
104
+ - **Cryptography**: Built-in cryptographic utilities.
105
+ - **Background Tasks**: Handle long-running tasks in the background.
106
+ - **Dependency Injection**: Powerful injection system.
107
+ - **Logging**: Integrated logging system.
108
+ - **Scheduling**: Schedule tasks to run at specific intervals.
109
+ - **Thread Pool**: Manage concurrent operations efficiently.
110
+
111
+ ## 🤝 Contributing
112
+
113
+ Thank you for your interest in contributing to the Renus framework! We welcome all contributions from the community, whether they're bug reports, feature requests, documentation improvements, or code contributions.
114
+
115
+ ### Coding Standards
116
+ - Follow **PEP 8** style guidelines.
117
+ - Use **type hints** for all new code (PEP 484).
118
+ - Keep lines under **88 characters** (Black's default line length).
119
+
120
+ ### Testing
121
+ - Write unit tests for all new features using `pytest`.
122
+ - Include docstrings for all public methods and classes.
123
+
124
+ ### Issue Reporting
125
+ When reporting bugs or requesting features:
126
+ - Check existing issues to avoid duplicates.
127
+ - Provide detailed reproduction steps for bugs.
128
+ - Include Python version, OS, and framework version.
129
+ - For feature requests, explain the use case and benefits.
130
+
131
+ Please read our [Contributing Guidelines](https://codenus.com/en/renus/3) for more details.
132
+
133
+ ## 📄 License
134
+
135
+ *BSD 3-Clause License*
@@ -1,12 +1,23 @@
1
1
  import os
2
+ import threading
2
3
  import pickle
3
- from datetime import datetime, timedelta,timezone
4
+ from datetime import UTC, datetime, timedelta
4
5
  from hashlib import sha3_256
5
6
 
6
7
  from renus.core.config import Config
7
8
  from renus.core.log import Log
8
9
  from renus.util.helper import encode64
9
10
 
11
+ try:
12
+ import fcntl
13
+ except ImportError: # Windows has no fcntl module
14
+ fcntl = None
15
+
16
+ try:
17
+ import msvcrt
18
+ except ImportError: # non-Windows has no msvcrt module
19
+ msvcrt = None
20
+
10
21
  try:
11
22
  import redis
12
23
  rds = redis.Redis()
@@ -17,11 +28,32 @@ class Cache:
17
28
  depth = 1
18
29
  folder_path = 'cache'
19
30
  typ = Config('app').get('cacheDriver', 'file')
31
+ _lock = threading.RLock()
20
32
  if typ=='redis':
21
33
  assert rds is not None, "'redis' must be installed for Cache"
22
34
  rd=rds
23
35
  else:
24
36
  rd=None
37
+
38
+ def _lock_file(self, file):
39
+ """Acquire an exclusive lock on an open file (cross-platform)."""
40
+ if fcntl is not None:
41
+ fcntl.flock(file.fileno(), fcntl.LOCK_EX)
42
+ else: # Windows
43
+ file.seek(0)
44
+ msvcrt.locking(file.fileno(), msvcrt.LK_LOCK, 1)
45
+
46
+ def _unlock_file(self, file):
47
+ """Release the lock on an open file (cross-platform)."""
48
+ if fcntl is not None:
49
+ fcntl.flock(file.fileno(), fcntl.LOCK_UN)
50
+ else: # Windows
51
+ file.seek(0)
52
+ try:
53
+ msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1)
54
+ except OSError:
55
+ pass
56
+
25
57
  def __init__(self, prefix: str = '', use_hash=False) -> None:
26
58
  self._prefix = prefix
27
59
  self._use_hash = use_hash
@@ -33,36 +65,52 @@ class Cache:
33
65
  :param value: value that save by key
34
66
  :param expire: seconds to expire from now
35
67
  """
36
- if self.rd:
37
- v=pickle.dumps(value,pickle.HIGHEST_PROTOCOL)
38
- self.rd.set(self._prefix+key,v,expire)
39
- return
40
- expire = int((datetime.now(timezone.utc) + timedelta(seconds=expire)).timestamp())
41
- path = self._build_name(key, self.depth)
42
- self._create_if_not_exist(path, value, expire)
68
+ with self._lock:
69
+ if self.rd:
70
+ v=pickle.dumps(value,pickle.HIGHEST_PROTOCOL)
71
+ self.rd.set(self._prefix+key,v,expire)
72
+ return
73
+ expire = datetime.now(UTC) + timedelta(seconds=expire)
74
+ path = self._build_name(key, self.depth)
75
+ self._create_if_not_exist(path, value, expire)
76
+
77
+ def increment(self, key: str, value, expire: int = 60):
78
+ with self._lock:
79
+ if self.rd:
80
+ r=self.rd.get(self._prefix+key)
81
+ value= value if r is None else (pickle.loads(r)+value)
82
+ v=pickle.dumps(value,pickle.HIGHEST_PROTOCOL)
83
+ self.rd.set(self._prefix+key,v,expire)
84
+ return
85
+ expire = datetime.now(UTC) + timedelta(seconds=expire)
86
+ value=self.get(key,value-1)+1
87
+ path = self._build_name(key, self.depth)
88
+ self._create_if_not_exist(path, value, expire)
43
89
 
44
90
  def get(self, key: str, default=None):
45
- if self.rd:
46
- r=self.rd.get(self._prefix+key)
47
- return default if r is None else pickle.loads(r)
48
- path = self._build_name(key, self.depth)
49
- return self._read_key(path, default)['value']
91
+ with self._lock:
92
+ if self.rd:
93
+ r=self.rd.get(self._prefix+key)
94
+ return default if r is None else pickle.loads(r)
95
+ path = self._build_name(key, self.depth)
96
+ return self._read_key(path, default)['value']
50
97
 
51
98
  def expire(self, key, default=None):
52
- if self.rd:
53
- return max(-1,self.rd.ttl(self._prefix+key))
54
- path = self._build_name(key, self.depth)
55
- t= self._read_key(path, default)['expire']
56
- if t==-1:
57
- return -1
58
- return max(-1,int(t- datetime.now(timezone.utc).timestamp()))
99
+ with self._lock:
100
+ if self.rd:
101
+ return max(-1,self.rd.ttl(self._prefix+key))
102
+ path = self._build_name(key, self.depth)
103
+ t= self._read_key(path, default)['expire']
104
+ if t==-1:
105
+ return -1
106
+ return max(-1,int(t.timestamp()-datetime.now(UTC).timestamp()))
59
107
 
60
108
  def delete(self, key):
61
- if self.rd:
62
- return self.rd.delete(self._prefix+key)
63
-
64
- path = self._build_name(key, self.depth)
65
- return self._delete_file(f"storage/{self.folder_path}{path}")
109
+ with self._lock:
110
+ if self.rd:
111
+ return self.rd.delete(self._prefix+key)
112
+ path = self._build_name(key, self.depth)
113
+ return self._delete_file(f"storage/{self.folder_path}{path}")
66
114
 
67
115
  def delete_expired(self):
68
116
  filename = f'./storage/{self.folder_path}/'
@@ -86,7 +134,8 @@ class Cache:
86
134
  except Exception as exc:
87
135
  if n < 3:
88
136
  return self._delete_file(path, n + 1)
89
- Log().info(f'cache delete_file: {exc}')
137
+ else:
138
+ raise exc
90
139
  return True
91
140
 
92
141
  def _check_pass(self, line):
@@ -94,7 +143,7 @@ class Cache:
94
143
  if len(res) < 2:
95
144
  return True
96
145
  expired, path = res
97
- if (datetime.strptime(expired, '%Y-%m-%d %H:%M:%S').timestamp() < datetime.utcnow().timestamp()):
146
+ if (datetime.strptime(expired, '%Y-%m-%d %H:%M:%S').timestamp() < datetime.now(UTC).timestamp()):
98
147
  self._delete_file(f'storage/{self.folder_path}{path}')
99
148
  return False
100
149
  return True
@@ -105,11 +154,15 @@ class Cache:
105
154
  try:
106
155
  os.makedirs(os.path.dirname(filename), exist_ok=True)
107
156
  with open(filename, 'wb') as output:
157
+ self._lock_file(output)
108
158
  pickle.dump(expire, output, pickle.HIGHEST_PROTOCOL)
109
159
  pickle.dump(value, output, pickle.HIGHEST_PROTOCOL)
110
- except:
160
+ self._unlock_file(output)
161
+ except Exception as e:
111
162
  if n < 3:
112
163
  self._create_if_not_exist(path, value, expire, n + 1)
164
+ else:
165
+ raise e
113
166
 
114
167
  def _read_key(self, path, default=None, n=0):
115
168
  filename = f'storage/{self.folder_path}{path}'
@@ -120,13 +173,13 @@ class Cache:
120
173
  with open(filename, 'rb') as input:
121
174
  expire = pickle.load(input)
122
175
  value = pickle.load(input)
123
- except Exception:
176
+ except Exception as e:
124
177
  if n < 3:
125
178
  return self._read_key(path, default, n + 1)
126
179
  else:
127
- return {'value': default, 'expire': -1}
180
+ raise e
128
181
 
129
- if expire < datetime.now(timezone.utc).timestamp():
182
+ if expire.timestamp() < datetime.now(UTC).timestamp():
130
183
  self._delete_file(filename)
131
184
  return {'value': default, 'expire': -1}
132
185
  return {'value': value, 'expire': expire}
@@ -177,4 +230,4 @@ def cache_func(time: int, prefix=''):
177
230
 
178
231
  return wrapper
179
232
 
180
- return dec
233
+ return dec
@@ -25,6 +25,9 @@ class ReModel:
25
25
  elif isinstance(v, list):
26
26
  v = self._process_list_items(v, field_type)
27
27
 
28
+ elif isinstance(v, datetime):
29
+ v = v.replace(tzinfo=timezone.utc)
30
+
28
31
  setattr(self, k, v)
29
32
 
30
33
  def _process_list_items(self, items, field_type):
@@ -366,8 +369,7 @@ class ModelBase(Generic[M]):
366
369
  self.visible_fields = fields
367
370
  return self
368
371
 
369
- @staticmethod
370
- def boot_event(typ: str, old, new, session):
372
+ def boot_event(self,typ: str, old, new, session):
371
373
  pass
372
374
 
373
375
  @staticmethod
@@ -19,10 +19,8 @@ _DECIMAL_RE = re.compile(r'^-?\d+\.\d+$')
19
19
 
20
20
  class jsonEncoder(json.JSONEncoder):
21
21
  def default(self, o):
22
- if isinstance(o, datetime.datetime):
23
- return o.isoformat()+'Z'
24
- elif isinstance(o, datetime.date):
25
- return o.isoformat() + 'T00:00:00Z'
22
+ if isinstance(o, (datetime.datetime, datetime.date)):
23
+ return o.isoformat()
26
24
  elif isinstance(o, datetime.time):
27
25
  r = o.isoformat()
28
26
  if o.microsecond:
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: renus
3
- Version: 2.7.2
3
+ Version: 2.7.4
4
4
  Summary: Renus Core Framework
5
- Home-page: https://github.com/smkoBa/renus
5
+ Home-page: https://github.com/kak-smko/renus
6
6
  Author: Smko Bayazidi
7
7
  Author-email: ba.smko@gmail.com
8
8
  License: BSD
@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='renus',
5
- version='2.7.2',
5
+ version='2.7.4',
6
6
  description='Renus Core Framework',
7
- url='https://github.com/smkoBa/renus',
7
+ url='https://github.com/kak-smko/renus',
8
8
  author='Smko Bayazidi',
9
9
  author_email='ba.smko@gmail.com',
10
10
  license='BSD',
renus-2.7.2/README.md DELETED
@@ -1 +0,0 @@
1
- # renus
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes