cloudoll 2.3.1__tar.gz → 2.4.0__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 (47) hide show
  1. {cloudoll-2.3.1 → cloudoll-2.4.0}/PKG-INFO +1 -2
  2. cloudoll-2.4.0/cloudoll/__init__.py +1 -0
  3. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/__init__.py +20 -3
  4. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/base.py +3 -0
  5. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/model.py +117 -113
  6. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/common.py +9 -3
  7. cloudoll-2.4.0/cloudoll/web/requests.py +161 -0
  8. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/PKG-INFO +1 -2
  9. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/SOURCES.txt +1 -5
  10. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/requires.txt +0 -1
  11. {cloudoll-2.3.1 → cloudoll-2.4.0}/setup.py +0 -1
  12. cloudoll-2.3.1/cloudoll/__init__.py +0 -1
  13. cloudoll-2.3.1/cloudoll/robot/dingtalk.py +0 -209
  14. cloudoll-2.3.1/cloudoll/robot/feishu.py +0 -102
  15. cloudoll-2.3.1/cloudoll/robot/slack.py +0 -115
  16. cloudoll-2.3.1/cloudoll/utils/__init__.py +0 -0
  17. cloudoll-2.3.1/cloudoll/web/http.py +0 -79
  18. {cloudoll-2.3.1 → cloudoll-2.4.0}/LICENSE +0 -0
  19. {cloudoll-2.3.1 → cloudoll-2.4.0}/README.md +0 -0
  20. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/__main__.py +0 -0
  21. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/cli.py +0 -0
  22. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/error/__init__.py +0 -0
  23. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/error/errors.py +0 -0
  24. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/logging/__init__.py +0 -0
  25. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/mail/__init__.py +0 -0
  26. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/mail/smtp.py +0 -0
  27. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/err.py +0 -0
  28. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/field.py +0 -0
  29. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/mysql.py +0 -0
  30. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/parse.py +0 -0
  31. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/postgres.py +0 -0
  32. {cloudoll-2.3.1/cloudoll/robot → cloudoll-2.4.0/cloudoll/utils}/__init__.py +0 -0
  33. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/cli_main.py +0 -0
  34. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/m2d.py +0 -0
  35. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/patch_loop.py +0 -0
  36. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/snowflake.py +0 -0
  37. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/watch.py +0 -0
  38. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/__init__.py +0 -0
  39. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/core.py +0 -0
  40. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/exception.py +0 -0
  41. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/html.py +0 -0
  42. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/jwt.py +0 -0
  43. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/settings.py +0 -0
  44. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/dependency_links.txt +0 -0
  45. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/entry_points.txt +0 -0
  46. {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/top_level.txt +0 -0
  47. {cloudoll-2.3.1 → cloudoll-2.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudoll
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -16,7 +16,6 @@ Requires-Python: >=3.6.0
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: watchfiles
19
- Requires-Dist: curl_cffi
20
19
  Requires-Dist: colorlog
21
20
  Requires-Dist: aiomysql
22
21
  Requires-Dist: aiopg
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0"
@@ -84,7 +84,6 @@ class Postgres(MeteBase):
84
84
  elif query_type == QueryTypes.ONE:
85
85
  columns = [desc[0] for desc in cursor.description]
86
86
  row = await cursor.fetchone()
87
- # print("rows_reuslt", rows)
88
87
  result = dict(zip(columns, row)) if row else {}
89
88
  return result
90
89
  elif query_type == QueryTypes.MANY:
@@ -93,7 +92,16 @@ class Postgres(MeteBase):
93
92
  result = [dict(zip(columns, row)) for row in rows]
94
93
  return result
95
94
  elif query_type == QueryTypes.COUNT:
96
- return cursor.rowcount
95
+ result = await cursor.fetchone()
96
+ count = 0
97
+ if rows is None:
98
+ return count
99
+ for row in rows:
100
+ count = rows[row]
101
+ return count
102
+ elif query_type == QueryTypes.GROUP_COUNT:
103
+ result = await cursor.fetchall()
104
+ return 0 if not result else len(result)
97
105
  elif query_type == QueryTypes.CREATE:
98
106
  result = cursor.rowcount > 0
99
107
  id = cursor.lastrowid
@@ -192,7 +200,16 @@ class Mysql(MeteBase):
192
200
  elif query_type == QueryTypes.MANY:
193
201
  return await cursor.fetchmany(size)
194
202
  elif query_type == QueryTypes.COUNT:
195
- return cursor.rowcount
203
+ rows = await cursor.fetchone()
204
+ count = 0
205
+ if rows is None:
206
+ return count
207
+ for row in rows:
208
+ count = rows[row]
209
+ return count
210
+ elif query_type == QueryTypes.GROUP_COUNT:
211
+ result = await cursor.fetchall()
212
+ return 0 if not result else len(result)
196
213
  elif query_type == QueryTypes.CREATE:
197
214
  result = cursor.rowcount > 0
198
215
  id = cursor.lastrowid
@@ -12,6 +12,7 @@ class QueryTypes(Enum):
12
12
  DELETE = 7
13
13
  CREATEBATCH = 8
14
14
  UPDATEBATCH = 9
15
+ GROUP_COUNT = 10
15
16
 
16
17
 
17
18
  class MeteBase:
@@ -60,6 +61,8 @@ class MeteBase:
60
61
 
61
62
  async def count(self, sql, params):
62
63
  return await self.query(sql, params, QueryTypes.COUNT)
64
+ async def group_count(self, sql, params):
65
+ return await self.query(sql, params, QueryTypes.GROUP_COUNT)
63
66
 
64
67
  async def update(self, sql, params):
65
68
  return await self.query(sql, params, QueryTypes.UPDATE)
@@ -228,7 +228,9 @@ class Model(metaclass=ModelMetaclass):
228
228
  for item in args:
229
229
  if isinstance(item, Model):
230
230
  for k in item.__dict__:
231
- if (action == "u" and item[k].value is not None) or action == "i":
231
+ if (
232
+ action == "u" and item[k].value is not None
233
+ ) or action == "i":
232
234
  data[k] = item[k]
233
235
 
234
236
  else: # for object
@@ -463,20 +465,22 @@ class Model(metaclass=ModelMetaclass):
463
465
  sql = f"SELECT COUNT(*) FROM {cls.__table__} {aft}"
464
466
  args = cls.__params__
465
467
  sql = self._exchange_sql(sql)
468
+ if GROUPBY is not None and GROUPBY != "":
469
+ return await cls.__pool__.group_count(sql, args)
466
470
  return await cls.__pool__.count(sql, args)
467
471
 
468
472
 
469
473
  class Models(object):
470
474
  class CharField(Field):
471
475
  def __init__(
472
- self,
473
- name=None,
474
- primary_key=False,
475
- default=None,
476
- charset=None,
477
- max_length=None,
478
- not_null=False,
479
- comment=None,
476
+ self,
477
+ name=None,
478
+ primary_key=False,
479
+ default=None,
480
+ charset=None,
481
+ max_length=None,
482
+ not_null=False,
483
+ comment=None,
480
484
  ):
481
485
  super().__init__(
482
486
  name,
@@ -491,14 +495,14 @@ class Models(object):
491
495
 
492
496
  class VarCharField(Field):
493
497
  def __init__(
494
- self,
495
- name=None,
496
- primary_key=False,
497
- default=None,
498
- charset=None,
499
- max_length=None,
500
- not_null=False,
501
- comment=None,
498
+ self,
499
+ name=None,
500
+ primary_key=False,
501
+ default=None,
502
+ charset=None,
503
+ max_length=None,
504
+ not_null=False,
505
+ comment=None,
502
506
  ):
503
507
  super().__init__(
504
508
  name,
@@ -513,7 +517,7 @@ class Models(object):
513
517
 
514
518
  class BooleanField(Field):
515
519
  def __init__(
516
- self, name=None, default=False, not_null=False, comment=None, unsigned=False
520
+ self, name=None, default=False, not_null=False, comment=None, unsigned=False
517
521
  ):
518
522
  super().__init__(
519
523
  name,
@@ -526,15 +530,15 @@ class Models(object):
526
530
 
527
531
  class IntegerField(Field):
528
532
  def __init__(
529
- self,
530
- name=None,
531
- primary_key=False,
532
- default=None,
533
- auto_increment=False,
534
- not_null=False,
535
- unsigned=False,
536
- comment=None,
537
- max_length=None,
533
+ self,
534
+ name=None,
535
+ primary_key=False,
536
+ default=None,
537
+ auto_increment=False,
538
+ not_null=False,
539
+ unsigned=False,
540
+ comment=None,
541
+ max_length=None,
538
542
  ):
539
543
  super().__init__(
540
544
  name,
@@ -550,15 +554,15 @@ class Models(object):
550
554
 
551
555
  class BigIntegerField(Field):
552
556
  def __init__(
553
- self,
554
- name=None,
555
- primary_key=False,
556
- default=None,
557
- auto_increment=False,
558
- not_null=False,
559
- unsigned=False,
560
- comment=None,
561
- max_length=None,
557
+ self,
558
+ name=None,
559
+ primary_key=False,
560
+ default=None,
561
+ auto_increment=False,
562
+ not_null=False,
563
+ unsigned=False,
564
+ comment=None,
565
+ max_length=None,
562
566
  ):
563
567
  super().__init__(
564
568
  name,
@@ -574,13 +578,13 @@ class Models(object):
574
578
 
575
579
  class DoubleField(Field):
576
580
  def __init__(
577
- self,
578
- name=None,
579
- default=None,
580
- not_null=False,
581
- max_length=None,
582
- unsigned=False,
583
- comment=None,
581
+ self,
582
+ name=None,
583
+ default=None,
584
+ not_null=False,
585
+ max_length=None,
586
+ unsigned=False,
587
+ comment=None,
584
588
  ):
585
589
  super().__init__(
586
590
  name,
@@ -594,14 +598,14 @@ class Models(object):
594
598
 
595
599
  class FloatField(Field):
596
600
  def __init__(
597
- self,
598
- name=None,
599
- default=None,
600
- not_null=False,
601
- max_length=None,
602
- scale_length=None,
603
- unsigned=False,
604
- comment=None,
601
+ self,
602
+ name=None,
603
+ default=None,
604
+ not_null=False,
605
+ max_length=None,
606
+ scale_length=None,
607
+ unsigned=False,
608
+ comment=None,
605
609
  ):
606
610
  super().__init__(
607
611
  name,
@@ -616,14 +620,14 @@ class Models(object):
616
620
 
617
621
  class NumericField(Field):
618
622
  def __init__(
619
- self,
620
- name=None,
621
- default=0.0,
622
- not_null=False,
623
- max_length=None,
624
- scale_length=None,
625
- unsigned=False,
626
- comment=None,
623
+ self,
624
+ name=None,
625
+ default=0.0,
626
+ not_null=False,
627
+ max_length=None,
628
+ scale_length=None,
629
+ unsigned=False,
630
+ comment=None,
627
631
  ):
628
632
  super().__init__(
629
633
  name,
@@ -638,14 +642,14 @@ class Models(object):
638
642
 
639
643
  class DecimalField(Field):
640
644
  def __init__(
641
- self,
642
- name=None,
643
- default=0.0,
644
- not_null=False,
645
- max_length=None,
646
- scale_length=None,
647
- unsigned=False,
648
- comment=None,
645
+ self,
646
+ name=None,
647
+ default=0.0,
648
+ not_null=False,
649
+ max_length=None,
650
+ scale_length=None,
651
+ unsigned=False,
652
+ comment=None,
649
653
  ):
650
654
  super().__init__(
651
655
  name,
@@ -660,13 +664,13 @@ class Models(object):
660
664
 
661
665
  class TextField(Field):
662
666
  def __init__(
663
- self,
664
- name=None,
665
- default=None,
666
- charset=None,
667
- max_length=None,
668
- not_null=False,
669
- comment=None,
667
+ self,
668
+ name=None,
669
+ default=None,
670
+ charset=None,
671
+ max_length=None,
672
+ not_null=False,
673
+ comment=None,
670
674
  ):
671
675
  super().__init__(
672
676
  name,
@@ -680,13 +684,13 @@ class Models(object):
680
684
 
681
685
  class LongTextField(Field):
682
686
  def __init__(
683
- self,
684
- name=None,
685
- default=None,
686
- charset=None,
687
- max_length=None,
688
- not_null=False,
689
- comment=None,
687
+ self,
688
+ name=None,
689
+ default=None,
690
+ charset=None,
691
+ max_length=None,
692
+ not_null=False,
693
+ comment=None,
690
694
  ):
691
695
  super().__init__(
692
696
  name,
@@ -700,13 +704,13 @@ class Models(object):
700
704
 
701
705
  class MediumTextField(Field):
702
706
  def __init__(
703
- self,
704
- name=None,
705
- default=None,
706
- charset=None,
707
- max_length=None,
708
- not_null=False,
709
- comment=None,
707
+ self,
708
+ name=None,
709
+ default=None,
710
+ charset=None,
711
+ max_length=None,
712
+ not_null=False,
713
+ comment=None,
710
714
  ):
711
715
  super().__init__(
712
716
  name,
@@ -720,14 +724,14 @@ class Models(object):
720
724
 
721
725
  class DatetimeField(Field):
722
726
  def __init__(
723
- self,
724
- name=None,
725
- default=None,
726
- max_length=None,
727
- not_null=False,
728
- created_generated=False,
729
- update_generated=False,
730
- comment=None,
727
+ self,
728
+ name=None,
729
+ default=None,
730
+ max_length=None,
731
+ not_null=False,
732
+ created_generated=False,
733
+ update_generated=False,
734
+ comment=None,
731
735
  ):
732
736
  super().__init__(
733
737
  name,
@@ -742,14 +746,14 @@ class Models(object):
742
746
 
743
747
  class DateField(Field):
744
748
  def __init__(
745
- self,
746
- name=None,
747
- default=None,
748
- max_length=None,
749
- not_null=False,
750
- created_generated=False,
751
- update_generated=False,
752
- comment=None,
749
+ self,
750
+ name=None,
751
+ default=None,
752
+ max_length=None,
753
+ not_null=False,
754
+ created_generated=False,
755
+ update_generated=False,
756
+ comment=None,
753
757
  ):
754
758
  super().__init__(
755
759
  name,
@@ -764,14 +768,14 @@ class Models(object):
764
768
 
765
769
  class TimestampField(Field):
766
770
  def __init__(
767
- self,
768
- name=None,
769
- default=None,
770
- max_length=None,
771
- not_null=False,
772
- created_generated=False,
773
- update_generated=False,
774
- comment=None,
771
+ self,
772
+ name=None,
773
+ default=None,
774
+ max_length=None,
775
+ not_null=False,
776
+ created_generated=False,
777
+ update_generated=False,
778
+ comment=None,
775
779
  ):
776
780
  super().__init__(
777
781
  name,
@@ -787,7 +791,7 @@ class Models(object):
787
791
 
788
792
  class JsonField(Field):
789
793
  def __init__(
790
- self, name=None, default=None, charset=None, not_null=False, comment=None
794
+ self, name=None, default=None, charset=None, not_null=False, comment=None
791
795
  ):
792
796
  super().__init__(
793
797
  name,
@@ -1,4 +1,9 @@
1
1
  import asyncio
2
+ from ..logging import warning
3
+
4
+ EADDRINUSE: int
5
+
6
+
2
7
  class Object(dict):
3
8
  # __setattr__ = dict.__setitem__
4
9
  # __getattr__ = dict.__getitem__
@@ -7,20 +12,21 @@ class Object(dict):
7
12
 
8
13
  def __getattr__(self, key):
9
14
  return self.get(key, None)
10
-
15
+
11
16
  def __delattr__(self, key):
12
17
  try:
13
18
  del self[key]
14
19
  except KeyError:
15
20
  raise AttributeError(f"'DotDict' object has no attribute '{key}'")
16
-
21
+
17
22
  def __getstate__(self):
18
- '''to fix pickle.dumps '''
23
+ """to fix pickle.dumps"""
19
24
  return dict(self)
20
25
 
21
26
  def __setstate__(self, state):
22
27
  self.update(state)
23
28
 
29
+
24
30
  def chainMap(*dicts):
25
31
  merged_dict = Object()
26
32
  for d in dicts:
@@ -0,0 +1,161 @@
1
+ import aiohttp
2
+ import asyncio
3
+ from typing import Optional, Dict, Any, Union
4
+
5
+
6
+ class AsyncRequest:
7
+ def __init__(
8
+ self,
9
+ timeout: int = 30,
10
+ max_retries: int = 3,
11
+ retry_delay: float = 1.0,
12
+ session: Optional[aiohttp.ClientSession] = None,
13
+ ):
14
+ """
15
+ 异步HTTP请求客户端
16
+
17
+ :param timeout: 请求超时时间(秒)
18
+ :param max_retries: 最大重试次数
19
+ :param retry_delay: 重试延迟(秒)
20
+ :param session: 可复用的aiohttp会话
21
+ """
22
+ self.timeout = aiohttp.ClientTimeout(total=timeout)
23
+ self.max_retries = max_retries
24
+ self.retry_delay = retry_delay
25
+ self._session = session
26
+
27
+ async def request(
28
+ self,
29
+ method: str,
30
+ url: str,
31
+ params: Optional[Dict[str, Any]] = None,
32
+ data: Optional[Union[Dict[str, Any], str, bytes]] = None,
33
+ json_data: Optional[Any] = None,
34
+ headers: Optional[Dict[str, str]] = None,
35
+ cookies: Optional[Dict[str, str]] = None,
36
+ allow_redirects: bool = True,
37
+ raise_for_status: bool = True,
38
+ ) -> aiohttp.ClientResponse:
39
+ """
40
+ 执行异步HTTP请求
41
+
42
+ :param method: HTTP方法 (GET, POST, PUT, DELETE等)
43
+ :param url: 请求URL
44
+ :param params: URL查询参数
45
+ :param data: 请求体数据 (表单或原始数据)
46
+ :param json_data: JSON格式的请求体数据
47
+ :param headers: 请求头
48
+ :param cookies: cookies
49
+ :param allow_redirects: 是否允许重定向
50
+ :param raise_for_status: 是否在HTTP错误状态时抛出异常
51
+ :return: aiohttp.ClientResponse
52
+ """
53
+ session = self._session or aiohttp.ClientSession(timeout=self.timeout)
54
+ last_exception = None
55
+
56
+ for attempt in range(self.max_retries):
57
+ try:
58
+ async with session.request(
59
+ method=method,
60
+ url=url,
61
+ params=params,
62
+ data=data,
63
+ json=json_data,
64
+ headers=headers,
65
+ cookies=cookies,
66
+ allow_redirects=allow_redirects,
67
+ ) as response:
68
+ if raise_for_status:
69
+ response.raise_for_status()
70
+ return response
71
+
72
+ except (aiohttp.ClientError, asyncio.TimeoutError) as e:
73
+ last_exception = e
74
+ if attempt < self.max_retries - 1:
75
+ await asyncio.sleep(self.retry_delay * (attempt + 1))
76
+ continue
77
+
78
+ if self._session is None:
79
+ await session.close()
80
+
81
+ raise last_exception or RuntimeError("Request failed")
82
+
83
+ async def get(
84
+ self,
85
+ url: str,
86
+ params: Optional[Dict[str, Any]] = None,
87
+ headers: Optional[Dict[str, str]] = None,
88
+ **kwargs
89
+ ) -> aiohttp.ClientResponse:
90
+ """发送GET请求"""
91
+ return await self.request("GET", url, params=params, headers=headers, **kwargs)
92
+
93
+ async def post(
94
+ self,
95
+ url: str,
96
+ data: Optional[Union[Dict[str, Any], str, bytes]] = None,
97
+ json_data: Optional[Any] = None,
98
+ headers: Optional[Dict[str, str]] = None,
99
+ **kwargs
100
+ ) -> aiohttp.ClientResponse:
101
+ """发送POST请求"""
102
+ return await self.request(
103
+ "POST", url, data=data, json_data=json_data, headers=headers, **kwargs
104
+ )
105
+
106
+ async def put(
107
+ self,
108
+ url: str,
109
+ data: Optional[Union[Dict[str, Any], str, bytes]] = None,
110
+ json_data: Optional[Any] = None,
111
+ headers: Optional[Dict[str, str]] = None,
112
+ **kwargs
113
+ ) -> aiohttp.ClientResponse:
114
+ """发送PUT请求"""
115
+ return await self.request(
116
+ "PUT", url, data=data, json_data=json_data, headers=headers, **kwargs
117
+ )
118
+
119
+ async def delete(
120
+ self, url: str, headers: Optional[Dict[str, str]] = None, **kwargs
121
+ ) -> aiohttp.ClientResponse:
122
+ """发送DELETE请求"""
123
+ return await self.request("DELETE", url, headers=headers, **kwargs)
124
+
125
+ async def close(self) -> None:
126
+ """关闭客户端会话"""
127
+ if self._session is not None:
128
+ await self._session.close()
129
+ self._session = None
130
+
131
+ async def __aenter__(self):
132
+ return self
133
+
134
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
135
+ await self.close()
136
+
137
+
138
+ # 使用示例
139
+ """
140
+ async def test():
141
+ async with AsyncRequest() as client:
142
+ try:
143
+ # GET请求示例
144
+ response = await client.get(
145
+ "https://httpbin.org/get",
146
+ params={"key": "value"},
147
+ headers={"User-Agent": "AsyncRequest/1.0"},
148
+ )
149
+ print(await response.json())
150
+
151
+ # POST请求示例
152
+ response = await client.post(
153
+ "https://httpbin.org/post",
154
+ json_data={"key": "value"},
155
+ headers={"Content-Type": "application/json"},
156
+ )
157
+ print(await response.json())
158
+
159
+ except Exception as e:
160
+ print(f"Request failed: {e}")
161
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudoll
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -16,7 +16,6 @@ Requires-Python: >=3.6.0
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: watchfiles
19
- Requires-Dist: curl_cffi
20
19
  Requires-Dist: colorlog
21
20
  Requires-Dist: aiomysql
22
21
  Requires-Dist: aiopg
@@ -23,10 +23,6 @@ cloudoll/orm/model.py
23
23
  cloudoll/orm/mysql.py
24
24
  cloudoll/orm/parse.py
25
25
  cloudoll/orm/postgres.py
26
- cloudoll/robot/__init__.py
27
- cloudoll/robot/dingtalk.py
28
- cloudoll/robot/feishu.py
29
- cloudoll/robot/slack.py
30
26
  cloudoll/utils/__init__.py
31
27
  cloudoll/utils/cli_main.py
32
28
  cloudoll/utils/common.py
@@ -38,6 +34,6 @@ cloudoll/web/__init__.py
38
34
  cloudoll/web/core.py
39
35
  cloudoll/web/exception.py
40
36
  cloudoll/web/html.py
41
- cloudoll/web/http.py
42
37
  cloudoll/web/jwt.py
38
+ cloudoll/web/requests.py
43
39
  cloudoll/web/settings.py
@@ -1,5 +1,4 @@
1
1
  watchfiles
2
- curl_cffi
3
2
  colorlog
4
3
  aiomysql
5
4
  aiopg
@@ -25,7 +25,6 @@ VERSION = version.__version__
25
25
  # What packages are required for this module to be executed?
26
26
  REQUIRED = [
27
27
  "watchfiles",
28
- "curl_cffi",
29
28
  "colorlog",
30
29
  "aiomysql",
31
30
  "aiopg",
@@ -1 +0,0 @@
1
- __version__ = "2.3.1"
@@ -1,209 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- # docs: https://open.dingtalk.com/document/group/message-types-and-data-format
5
- __author__ = "chuchur/chuchur.com"
6
-
7
- import base64
8
- import hashlib
9
- import hmac
10
- from ..logging import print_error
11
- from curl_cffi import requests as http
12
- import time
13
- from urllib import parse
14
-
15
-
16
- class Client(object):
17
- def __init__(self, **kw):
18
- self._webhook = kw.get("webhook")
19
- self._secret = kw.get("secret")
20
- self._access_token = kw.get("access_token")
21
-
22
- def _get_sign(self):
23
- secret = self._secret
24
- if not secret:
25
- print_error("缺少secret")
26
- return None, None
27
-
28
- # 拼接timestamp和secret
29
- timestamp = str(round(time.time() * 1000))
30
- string_to_sign = "{}\n{}".format(timestamp, secret)
31
- hmac_code = hmac.new(
32
- secret.encode("utf-8"),
33
- string_to_sign.encode("utf-8"),
34
- digestmod=hashlib.sha256,
35
- ).digest()
36
-
37
- # 对结果进行base64处理
38
- sign = base64.b64encode(hmac_code).decode("utf-8")
39
- sign = parse.quote(sign)
40
- return timestamp, sign
41
-
42
- def upload(self, type: str, filepath: str):
43
- """
44
- 上传媒体
45
- :params access_token
46
- :params type 文件类型 image|voice|video|file
47
- :params filepath 要上传的文件路径
48
- """
49
- access_token = self._access_token
50
- if not access_token:
51
- print_error("请配置access_token")
52
- return False
53
- url = "https://oapi.dingtalk.com/media/upload?access_token=%s" % access_token
54
- with open(filepath, "rb") as f:
55
- files = {"media": f}
56
- result = http.post(url=url, files=files, data={"type": type})
57
- res = result.json()
58
- if res["errcode"] == 0:
59
- return res
60
- else:
61
- print_error(res["errmsg"])
62
-
63
- def send(self, data: dict):
64
- """
65
- 发送除签名外的自定义消息结构体 https://open.dingtalk.com/document/orgapp-server/message-types-and-data-format
66
- :params data 自定义消息结构体
67
- """
68
- webhook = self._webhook
69
- if not webhook:
70
- print_error("缺少webhook")
71
- return False
72
- timestamp, sign = self._get_sign()
73
- if not timestamp or not sign:
74
- return False
75
- url = webhook + "&timestamp=%s&sign=%s" % (timestamp, sign)
76
- result = http.post(
77
- url=url,
78
- json={**data},
79
- # data=data
80
- # data=json.dumps(data)
81
- )
82
- if result:
83
- res = result.json()
84
- if res["errcode"] == 0:
85
- print_error("发送成功!")
86
- return True
87
- else:
88
- print_error("发送失败")
89
- print_error(res["errmsg"])
90
- return False
91
-
92
- def send_text(self, text: str):
93
- """
94
- 发送文本消息
95
- :params text 消息内容
96
- """
97
-
98
- return self.send(
99
- data={
100
- "msgtype": "text",
101
- "text": {"content": text},
102
- }
103
- )
104
-
105
- def _send_media(self, type, media_id, **kw):
106
- """
107
- 发送媒体
108
- :params type 媒体类型 image|voice|video|file
109
- :params media_id 媒体ID
110
- """
111
- data = {"msgtype": type}
112
- data[type] = {"media_id": media_id}
113
- if type == "voice":
114
- data["voice"]["duration"] = kw.get("duration")
115
- return self.send(data)
116
-
117
- def send_markdown(self, title, text):
118
- """
119
- 发送Markdown消息
120
- :params title 标题
121
- :params text markdown结构体
122
- """
123
- return self.send(
124
- data={"msgtype": "markdown", "markdown": {"title": title, "text": text}}
125
- )
126
-
127
- def send_image(self, media_id: str):
128
- """
129
- 发送图片消息
130
- :params media_id 媒体ID
131
- """
132
- return self._send_media("image", media_id)
133
-
134
- def send_voice(self, media_id: str, duration: str):
135
- """
136
- 发送语音消息
137
- :params media_id 媒体ID
138
- :params duration 正整数,小于60,表示音频时长。
139
- """
140
- return self._send_media("voice", media_id, duration=duration)
141
-
142
- def send_file(self, media_id: str):
143
- """
144
- 发送文件消息
145
- :params media_id 媒体ID
146
- """
147
- return self._send_media("file", media_id)
148
-
149
- def send_link(self, messageUrl, picUrl, title, text):
150
- """
151
- 发送链接消息
152
- :params messageUrl 链接地址
153
- :params picUrl 链接的小图
154
- :params title 链接标题 100字内
155
- :params text 链接副标题 500字内
156
- """
157
- return self.send(
158
- data={
159
- "msgtype": "link",
160
- "link": {
161
- "picUrl": picUrl,
162
- "messageUrl": messageUrl,
163
- "title": title,
164
- "text": text,
165
- },
166
- }
167
- )
168
-
169
- def send_card(self, **kw):
170
- """
171
- 发送卡片消息
172
- :params title 标题
173
- :params text 消息内容 必填
174
- :params single_title 查看详情(按钮文字) btns 二选一
175
- :params single_url 查看详情(链接) btns 二选一
176
- :params btn_orientation 按钮排列顺序。0|1
177
- :params btns 按钮 [{title:'',actionURL:''}]
178
- """
179
- title = kw.get("title", "")
180
- text = kw.get("text", "")
181
- single_title = kw.get("single_title", "")
182
- single_url = kw.get("single_url", "")
183
- btn_orientation = kw.get("btn_orientation", 0)
184
- btns = kw.get("btns", []) or []
185
- return self.send(
186
- data={
187
- "msgtype": "actionCard",
188
- "actionCard": {
189
- "title": title,
190
- "text": text,
191
- "singleTitle": single_title,
192
- "singleUrl": single_url,
193
- "btnOrientation": btn_orientation,
194
- "btns": btns,
195
- },
196
- }
197
- )
198
-
199
- def send_freecard(self, links: list):
200
- """
201
- 发送卡片消息
202
- :params links [{ title:'',messageURL:'',picURL:''}]
203
- """
204
- return self.send(
205
- data={
206
- "msgtype": "feedCard",
207
- "feedCard": {"links": links},
208
- }
209
- )
@@ -1,102 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- __author__ = "chuchur/chuchur.com"
5
-
6
- import hashlib, hmac, time, base64
7
- from curl_cffi import requests as http
8
- from ..logging import print_error, print_info
9
-
10
-
11
- class Client(object):
12
- def __init__(self, **kw):
13
- self._webhook = kw.get("webhook")
14
- self._secret = kw.get("secret")
15
-
16
- def _get_sign(self):
17
- secret = self._secret
18
- if not secret:
19
- print_error("缺少secret")
20
- return None, None
21
- # 拼接timestamp和secret
22
- timestamp = int(time.time())
23
- string_to_sign = "{}\n{}".format(timestamp, secret)
24
- hmac_code = hmac.new(
25
- string_to_sign.encode("utf-8"), digestmod=hashlib.sha256
26
- ).digest()
27
-
28
- # 对结果进行base64处理
29
- sign = base64.b64encode(hmac_code).decode("utf-8")
30
-
31
- return timestamp, sign
32
-
33
- def send(self, data: dict):
34
- """
35
- 发送除签名外的自定义消息结构体
36
- :params data 自定义消息结构体
37
- """
38
- webhook = self._webhook
39
- if not webhook:
40
- print_error("缺少webhook")
41
- return False
42
- timestamp, sign = self._get_sign()
43
- if not timestamp or not sign:
44
- return False
45
- result = http.post(
46
- url=webhook,
47
- json={"timestamp": timestamp, "sign": sign, **data},
48
- )
49
- if result:
50
- res = result.json()
51
- if res["StatusCode"] == 0:
52
- print_info("发送成功")
53
- return False
54
- else:
55
- print_error("发送失败!")
56
- return True
57
-
58
- def send_text(self, text: str):
59
- """
60
- 发送文本消息
61
- :params text 消息内容
62
- """
63
- return self.send(
64
- data={
65
- "msg_type": "text",
66
- "content": {"text": text},
67
- }
68
- )
69
-
70
- def send_card(self, **kw):
71
- """
72
- 发送卡片消息
73
- :params content 卡片内容,str
74
- :params elements 参见 :https://open.feishu.cn/document/ukTMukTMukTM/uMjNwUjLzYDM14yM2ATN
75
- :params template 标题背景颜色 blue|wathet|truquoise|green|yellow|orange|red|carmine|violet|purple|indigo|grey
76
- :params i18n 多语言 {'zh_cn':'...'}
77
- """
78
- content = kw.get("content", "")
79
- i18n = kw.get("i18n", {})
80
- elements = kw.get("elements", [])
81
- template = kw.get("template", "blue")
82
- return self.send(
83
- data={
84
- "msg_type": "interactive",
85
- "card": {
86
- "config": {
87
- "wide_screen_mode": True,
88
- "enable_forward": True,
89
- "update_multi": True,
90
- },
91
- "header": {
92
- "title": {
93
- "tag": "plain_text",
94
- "content": content,
95
- "i18n": i18n,
96
- },
97
- "template": template,
98
- },
99
- "elements": elements,
100
- },
101
- }
102
- )
@@ -1,115 +0,0 @@
1
- from cloudoll.web import http
2
-
3
- """
4
- web_hook: https://infloww.slack.com/apps/A0F7XDUAZ-incoming-webhook?tab=settings&next_id=0
5
- blocks_info: https://api.slack.com/messaging/composing/layouts
6
- """
7
-
8
-
9
- class Header:
10
- def __init__(self, text: str):
11
- self._text = text
12
-
13
- def get_json(self):
14
- return {"type": "header", "text": {"type": "plain_text", "text": self._text}}
15
-
16
-
17
- class Text:
18
- def __init__(self, text: str | int):
19
- self._text = text
20
-
21
- def get_json(self):
22
- return {"type": "section", "text": {"type": "mrkdwn", "text": self._text}}
23
-
24
-
25
- class Field:
26
- def __init__(self, title: str | int, text: str | int):
27
- self._title = title
28
- self._text = text
29
-
30
- def get_json(self):
31
- return {"type": "mrkdwn", "text": f"{self._title}\n{self._text}"}
32
-
33
-
34
- class Link:
35
- def __init__(self, title: str | int, url: str | int):
36
- self._title = title
37
- self._url = url
38
-
39
- def get_json(self):
40
- return {
41
- "type": "context",
42
- "elements": [{"type": "mrkdwn", "text": f"<{self._url}|_{self._title}_>"}],
43
- }
44
-
45
-
46
- class Section:
47
- def __init__(self):
48
- self._fileds: list[Field] = []
49
-
50
- def add(self, field: Field):
51
- self._fileds.append(field.get_json())
52
- return self
53
-
54
- def get_json(self):
55
- return {"type": "section", "fileds": self._fileds}
56
-
57
-
58
- class Elements:
59
- def __init__(self):
60
- self._elements: list[Field] = []
61
-
62
- def add(self, field: Field):
63
- self._elements.append(field.get_json())
64
- return self
65
-
66
- def get_json(self):
67
- return {"type": "context", "elements": self._elements}
68
-
69
-
70
- class Divider:
71
- def __init__(self):
72
- self._fileds: list[Field] = []
73
-
74
- def get_json(self):
75
- return {"type": "divider"}
76
-
77
-
78
- class Blocks:
79
- def __init__(self):
80
- self._blocks = []
81
-
82
- def add(self, block: Section | Header | Text | Divider | Link):
83
- self._blocks.append(block)
84
- return self
85
-
86
- def add_header(self, text):
87
- self._blocks.append(Header(text))
88
- return self
89
-
90
- def add_link(self, title, url: str):
91
- self._blocks.append(Link(title, url))
92
- return self
93
-
94
- def add_text(self, text):
95
- self._blocks.append(Text(text))
96
- return self
97
-
98
- def add_divider(self):
99
- self._blocks.append(Divider())
100
- return self
101
-
102
- def get_json(self):
103
- blocks = []
104
- for block in self._blocks:
105
- blocks.append(block.get_json())
106
- return blocks
107
-
108
-
109
- class Slack:
110
- def __init__(self, url: str):
111
- self._url = url
112
-
113
- def send_notify(self, blocks: Blocks):
114
- res = http.post(url=self._url, json={"blocks": blocks})
115
- return res
File without changes
@@ -1,79 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- __author__ = "chuchur/chuchur.com"
5
- # from aiohttp.web import middleware
6
- from curl_cffi import requests
7
-
8
-
9
- class Client(object):
10
- def __init__(self):
11
- self.session = requests.Session()
12
-
13
- def requests(self, method, url, **kw) -> requests.Response:
14
- impersonate = kw.get("impersonate", None)
15
- if not impersonate:
16
- kw["impersonate"] = "chrome133a"
17
- fun = getattr(self.session, method, None)
18
- result: requests.Response = fun(url, **kw)
19
- return result
20
-
21
-
22
- http = Client()
23
-
24
-
25
- def get(url, **kw):
26
- """
27
- get 请求
28
- :params params=dict() 传参
29
- """
30
- return http.requests("get", url, **kw)
31
-
32
-
33
- def post(url, **kw):
34
- """
35
- post 请求
36
- :params data=dict() or json=dict() 传参
37
- """
38
- return http.requests("post", url, **kw)
39
-
40
-
41
- def put(url, **kw):
42
- """
43
- post 请求
44
- data=dict()传参
45
- """
46
- return http.requests("put", url, **kw)
47
-
48
-
49
- def delete(url, **kw):
50
- return http.requests("delete", url, **kw)
51
-
52
-
53
- def head(url, **kw):
54
- return http.requests("head", url, **kw)
55
-
56
-
57
- def option(url, **kw):
58
- return http.requests("option", url, **kw)
59
-
60
-
61
- def download(url, savepath=None, **kw):
62
- """
63
- 下载文件
64
- :params url 文件的地址
65
- :params savepath 保存路径
66
- """
67
- response = http.requests("get", url, **kw)
68
- if response.status_code == 200:
69
- # 如果传入了保存路径,保存文件
70
- if savepath:
71
- with open(savepath, "wb") as file:
72
- file.write(response.content)
73
- print(f"File saved to {savepath}")
74
- else:
75
- # 如果没有传入保存路径,返回文件内容
76
- return response.content
77
- else:
78
- print(f"Failed to download the file. Status code: {response.status_code}")
79
- return None
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