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.
- {cloudoll-2.3.1 → cloudoll-2.4.0}/PKG-INFO +1 -2
- cloudoll-2.4.0/cloudoll/__init__.py +1 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/__init__.py +20 -3
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/base.py +3 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/model.py +117 -113
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/common.py +9 -3
- cloudoll-2.4.0/cloudoll/web/requests.py +161 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/PKG-INFO +1 -2
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/SOURCES.txt +1 -5
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/requires.txt +0 -1
- {cloudoll-2.3.1 → cloudoll-2.4.0}/setup.py +0 -1
- cloudoll-2.3.1/cloudoll/__init__.py +0 -1
- cloudoll-2.3.1/cloudoll/robot/dingtalk.py +0 -209
- cloudoll-2.3.1/cloudoll/robot/feishu.py +0 -102
- cloudoll-2.3.1/cloudoll/robot/slack.py +0 -115
- cloudoll-2.3.1/cloudoll/utils/__init__.py +0 -0
- cloudoll-2.3.1/cloudoll/web/http.py +0 -79
- {cloudoll-2.3.1 → cloudoll-2.4.0}/LICENSE +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/README.md +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/__main__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/cli.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/error/__init__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/error/errors.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/logging/__init__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/mail/__init__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/mail/smtp.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/err.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/field.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/mysql.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/parse.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/orm/postgres.py +0 -0
- {cloudoll-2.3.1/cloudoll/robot → cloudoll-2.4.0/cloudoll/utils}/__init__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/cli_main.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/m2d.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/patch_loop.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/snowflake.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/utils/watch.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/__init__.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/core.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/exception.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/html.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/jwt.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll/web/settings.py +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/dependency_links.txt +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/entry_points.txt +0 -0
- {cloudoll-2.3.1 → cloudoll-2.4.0}/cloudoll.egg-info/top_level.txt +0 -0
- {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
|
+
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
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 +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 + "×tamp=%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
|
|
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
|