voyolib 0.0.1__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.
@@ -0,0 +1,35 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ contents: write
15
+ id-token: write
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.13"
24
+
25
+ - name: Install build dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install build
29
+
30
+ - name: Build package
31
+ run: python -m build
32
+
33
+ - name: Publish to PyPI
34
+ if: startsWith(github.ref, 'refs/tags/v')
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,39 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ venv/
12
+ env/
13
+
14
+ # Environment variables
15
+ .env
16
+ .env.*
17
+
18
+ # Test / coverage
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .coverage
23
+ htmlcov/
24
+ .tox/
25
+ noxfile.py
26
+
27
+ # IDE
28
+ .idea/
29
+ .vscode/
30
+ *.swp
31
+ *.swo
32
+ *~
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Logs
39
+ *.log
@@ -0,0 +1 @@
1
+ 3.13
voyolib-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: voyolib
3
+ Version: 0.0.1
4
+ Summary: Python library for Voyo
5
+ Requires-Python: >=3.13
File without changes
voyolib-0.0.1/main.py ADDED
@@ -0,0 +1,6 @@
1
+ def main():
2
+ print("Hello from python-lib!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "voyolib"
3
+ version = "0.0.1"
4
+ description = "Python library for Voyo"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = []
8
+
9
+ [build-system]
10
+ requires = ["hatchling"]
11
+ build-backend = "hatchling.build"
12
+
13
+ [tool.hatch.build.targets.wheel]
14
+ packages = ["voyo"]
File without changes
@@ -0,0 +1,146 @@
1
+ from abc import ABC, abstractmethod
2
+ import queue
3
+ import threading
4
+ import time
5
+ from typing import Optional
6
+
7
+ import pymysql
8
+
9
+ _default_pool: Optional["YoConnPool"] = None
10
+
11
+ def set_default_pool(pool: "YoConnPool"):
12
+ global _default_pool
13
+ _default_pool = pool
14
+
15
+ def get_default_pool() -> Optional["YoConnPool"]:
16
+ return _default_pool
17
+
18
+ class ConnPool(ABC):
19
+
20
+ @abstractmethod
21
+ def get_conn(self):
22
+ pass
23
+
24
+ @abstractmethod
25
+ def release_conn(self, conn):
26
+ pass
27
+
28
+ class PooledConnection:
29
+
30
+ def __init__(self, raw_conn, created_at: float, is_burst: bool = False):
31
+ self.raw = raw_conn
32
+ self.created_at = created_at
33
+ self.is_burst = is_burst
34
+
35
+ def is_expired(self, max_timeout: float) -> bool:
36
+ return (time.time() - self.created_at) > max_timeout
37
+
38
+ def close(self):
39
+ if self.raw:
40
+ try:
41
+ self.raw.close()
42
+ except Exception:
43
+ pass
44
+ self.raw = None
45
+
46
+ class YoConnPool(ConnPool):
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ host: str = "localhost",
52
+ port: int = 3306,
53
+ user: str = "",
54
+ password: str = "",
55
+ database: str = "",
56
+ charset: str = "utf8mb4",
57
+ pool_size: int = 5,
58
+ max_burst: int = 10,
59
+ max_timeout: float = 600,
60
+ ):
61
+ self._config = {
62
+ "host": host,
63
+ "port": port,
64
+ "user": user,
65
+ "password": password,
66
+ "database": database,
67
+ "charset": charset,
68
+ "autocommit": False,
69
+ }
70
+ self._pool_size = pool_size
71
+ self._max_burst = max_burst
72
+ self._max_timeout = max_timeout
73
+ self._pool = queue.Queue(maxsize=pool_size)
74
+ self._lock = threading.Lock()
75
+ self._burst_lock = threading.Lock()
76
+ self._burst_count = 0
77
+ self._conn_map = {}
78
+
79
+ for _ in range(pool_size):
80
+ pooled = self._create_connection()
81
+ self._pool.put(pooled)
82
+
83
+ def _create_connection(self, is_burst: bool = False) -> PooledConnection:
84
+ raw = pymysql.connect(**self._config)
85
+ return PooledConnection(raw, time.time(), is_burst=is_burst)
86
+
87
+ def _recycle(self, pooled: PooledConnection) -> PooledConnection:
88
+ pooled.close()
89
+ return self._create_connection()
90
+
91
+ def get_conn(self):
92
+ is_burst = False
93
+ try:
94
+ pooled = self._pool.get(block=False)
95
+ except queue.Empty:
96
+
97
+ with self._burst_lock:
98
+ if self._burst_count < self._max_burst:
99
+ self._burst_count += 1
100
+ is_burst = True
101
+
102
+ pooled = self._create_connection(is_burst=True)
103
+
104
+ if not pooled.is_burst and pooled.is_expired(self._max_timeout):
105
+ pooled = self._recycle(pooled)
106
+
107
+ try:
108
+ pooled.raw.ping()
109
+ except Exception:
110
+ pooled = self._recycle(pooled)
111
+
112
+ with self._lock:
113
+ self._conn_map[pooled.raw] = pooled
114
+ return pooled.raw
115
+
116
+ def release_conn(self, conn):
117
+ with self._lock:
118
+ pooled = self._conn_map.pop(conn, None)
119
+
120
+ if pooled is None:
121
+
122
+ try:
123
+ conn.close()
124
+ except Exception:
125
+ pass
126
+ return
127
+
128
+ if pooled.is_burst:
129
+ try:
130
+ conn.rollback()
131
+ except Exception:
132
+ pass
133
+ pooled.close()
134
+ with self._burst_lock:
135
+ self._burst_count = max(0, self._burst_count - 1)
136
+ return
137
+
138
+ try:
139
+ conn.rollback()
140
+ except Exception:
141
+ pass
142
+
143
+ try:
144
+ self._pool.put(pooled, block=False)
145
+ except queue.Full:
146
+ pooled.close()
@@ -0,0 +1,59 @@
1
+ import functools
2
+ import inspect
3
+ import threading
4
+
5
+ from voyo.db.mysql.conn_pool import YoConnPool, get_default_pool
6
+
7
+ _tx_local = threading.local()
8
+
9
+ def _get_tx_stack():
10
+ if not hasattr(_tx_local, "stack"):
11
+ _tx_local.stack = []
12
+ return _tx_local.stack
13
+
14
+ class Transaction:
15
+
16
+ def __init__(self, propagation="required", pool=None):
17
+ if propagation not in ("required", "new"):
18
+ raise ValueError("propagation must be 'required' or 'new'")
19
+ self.propagation = propagation
20
+ self.pool = pool or get_default_pool()
21
+ if self.pool is None:
22
+ raise RuntimeError(
23
+ "No connection pool configured for @Transaction. "
24
+ "Call yo_mysql.set_default_pool(pool) first."
25
+ )
26
+
27
+ def __call__(self, func):
28
+ sig = inspect.signature(func)
29
+ params = list(sig.parameters.values())
30
+
31
+ has_conn_param = bool(params) and params[-1].name == 'conn'
32
+
33
+ @functools.wraps(func)
34
+ def wrapper(*args, **kwargs):
35
+ stack = _get_tx_stack()
36
+
37
+ if stack and self.propagation != "new":
38
+ conn = stack[-1]["conn"]
39
+
40
+ if has_conn_param and "conn" not in kwargs:
41
+ kwargs["conn"] = conn
42
+
43
+ return func(*args, **kwargs)
44
+
45
+ conn = self.pool.get_conn()
46
+ stack.append({"conn": conn})
47
+
48
+ if has_conn_param and "conn" not in kwargs:
49
+ kwargs["conn"] = conn
50
+ try:
51
+ result = func(*args, **kwargs)
52
+ conn.commit()
53
+ return result
54
+ finally:
55
+ conn.rollback()
56
+ stack.pop()
57
+ self.pool.release_conn(conn)
58
+
59
+ return wrapper