pyfreeflow 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyfreeflow/registry.py ADDED
@@ -0,0 +1,34 @@
1
+ class ClassRegistry():
2
+ REGISTRY = {}
3
+
4
+ @classmethod
5
+ def class_register_class(cls, other):
6
+ typename = other.__typename__
7
+ version = other.__version__
8
+
9
+ if typename is None or version is None:
10
+ return
11
+
12
+ if typename not in cls.REGISTRY.keys():
13
+ cls.REGISTRY[typename] = {}
14
+
15
+ if version not in cls.REGISTRY[typename].keys():
16
+ cls.REGISTRY[typename][version] = other
17
+
18
+ @classmethod
19
+ def get_registered_class(cls, typename, version):
20
+ return cls.REGISTRY[typename][version]
21
+
22
+
23
+ class ExtRegistry(ClassRegistry):
24
+ REGISTRY = {}
25
+
26
+
27
+ class ExtRegister(type):
28
+ __typename__ = None
29
+ __version__ = None
30
+
31
+ def __new__(meta, name, bases, class_dict):
32
+ cls = type.__new__(meta, name, bases, class_dict)
33
+ ExtRegistry.class_register_class(cls)
34
+ return cls
pyfreeflow/utils.py ADDED
@@ -0,0 +1,67 @@
1
+ import copy
2
+ import asyncio
3
+ import pyparsing as pp
4
+ import datetime as dt
5
+
6
+
7
+ def deepupdate(base, other, keep=True):
8
+ assert (isinstance(base, dict) and isinstance(other, dict))
9
+
10
+ if not keep:
11
+ bk = list(base.keys())
12
+ for k in bk:
13
+ if k not in other:
14
+ del base[k]
15
+
16
+ for k, v in other.items():
17
+ if k not in base:
18
+ base[k] = copy.deepcopy(v)
19
+ else:
20
+ if isinstance(v, dict):
21
+ deepupdate(base[k], v, keep=keep)
22
+ else:
23
+ base[k] = copy.deepcopy(v)
24
+
25
+
26
+ def asyncio_run(fn, force=False):
27
+ try:
28
+ return asyncio.create_task(fn)
29
+ except RuntimeError:
30
+ if not force:
31
+ raise RuntimeError("No loop!")
32
+ else:
33
+ return asyncio.run(fn)
34
+
35
+
36
+ class DurationParser():
37
+ INTEGER = pp.Word(pp.nums).setParseAction(lambda t: int(t[0]))
38
+
39
+ SECONDS = pp.Opt(pp.Group(INTEGER + "s"))
40
+ MINUTES = pp.Opt(pp.Group(INTEGER + "m"))
41
+ HOURS = pp.Opt(pp.Group(INTEGER + "h"))
42
+ DAYS = pp.Opt(pp.Group(INTEGER + "d"))
43
+ WEEKS = pp.Opt(pp.Group(INTEGER + "w"))
44
+ YEARS = pp.Opt(pp.Group(INTEGER + "y"))
45
+
46
+ PARSER = YEARS + WEEKS + DAYS + HOURS + MINUTES + SECONDS
47
+
48
+ OP = {
49
+ "y": lambda x: ("days", x * 365),
50
+ "w": lambda x: ("weeks", x),
51
+ "d": lambda x: ("days", x),
52
+ "h": lambda x: ("hours", x),
53
+ "m": lambda x: ("minutes", x),
54
+ "s": lambda x: ("seconds", x),
55
+ }
56
+
57
+ @classmethod
58
+ def parse(cls, duration):
59
+
60
+ parsed_duration = cls.PARSER.parseString(duration)
61
+ delta = {}
62
+
63
+ for val, unit in parsed_duration:
64
+ k, v = cls.OP[unit](val)
65
+ delta[k] = delta.get(k, 0) + v
66
+
67
+ return int(dt.timedelta(**delta).total_seconds())
@@ -0,0 +1,86 @@
1
+ #!python
2
+ import sys
3
+ import argparse
4
+ import pyfreeflow
5
+ import json
6
+ import yaml
7
+ import asyncio
8
+ from platform import system
9
+
10
+ if system() == "Linux":
11
+ import uvloop
12
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
13
+
14
+
15
+ def json_formatter(x, path):
16
+ if path:
17
+ with open(path, "w") as f:
18
+ json.dump(x, f)
19
+ else:
20
+ print(json.dumps(x))
21
+
22
+
23
+ def _str_presenter(dumper, data):
24
+ if "\n" in data:
25
+ block = "\n".join([line.rstrip() for line in data.splitlines()])
26
+ if data.endswith("\n"):
27
+ block += "\n"
28
+ return dumper.represent_scalar("tag:yaml.org,2002:str", block, style="|")
29
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data)
30
+
31
+
32
+ def yaml_formatter(x, path):
33
+ yaml.add_representer(str, _str_presenter)
34
+ yaml.representer.SafeRepresenter.add_representer(str, _str_presenter)
35
+ if path:
36
+ with open(path, "w") as f:
37
+ yaml.dump(
38
+ x, f, default_flow_style=False, allow_unicode=True)
39
+ else:
40
+ print(yaml.dump(
41
+ x, default_flow_style=False, allow_unicode=True))
42
+
43
+
44
+ OUTPUT_FORMATTER = {
45
+ "json": json_formatter,
46
+ "yaml": yaml_formatter,
47
+ }
48
+
49
+
50
+ async def cli(argv):
51
+ argparser = argparse.ArgumentParser("pyfreeflow-cli")
52
+
53
+ argparser.add_argument("--config", "-c", dest="config", type=str,
54
+ action="store", default="pyfreeflow.yaml",
55
+ required=False, help="Pipeline configuration file")
56
+ argparser.add_argument("--output", "-o", dest="output", type=str,
57
+ action="store",
58
+ required=False, help="Pipeline output file")
59
+ argparser.add_argument("--format", "-f", dest="fmt", type=str,
60
+ action="store", choices=OUTPUT_FORMATTER.keys(),
61
+ default="json", required=False,
62
+ help="Pipeline output file format")
63
+
64
+ args = argparser.parse_args(argv)
65
+
66
+ with open(args.config, "r") as f:
67
+ config = yaml.safe_load(f)
68
+
69
+ for ext in config.get("ext", []):
70
+ pyfreeflow.load_extension(ext)
71
+
72
+ assert ("pipeline" in config.keys())
73
+ pipe = pyfreeflow.pipeline.Pipeline(**config.get("pipeline"))
74
+ output = await pipe.run(config.get("args", {}))
75
+
76
+ OUTPUT_FORMATTER[args.fmt](output[0], args.output)
77
+
78
+ return output[1]
79
+
80
+
81
+ def main(argv):
82
+ return asyncio.run(cli(argv))
83
+
84
+
85
+ if __name__ == "__main__":
86
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyfreeflow
3
+ Version: 0.1.0
4
+ Summary: Async service toolchain
5
+ Home-page: https://github.com/senatoreg/pyfreeflow
6
+ Author: Giovanni Senatore
7
+ Author-email:
8
+ License: AGPL-3.0-or-later
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: aiofiles>=24.1.0
16
+ Requires-Dist: aiohttp>=3.11.16
17
+ Requires-Dist: networkx>=3.2.1
18
+ Requires-Dist: pydot>=2.0.0
19
+ Requires-Dist: PyYAML>=5.4.1
20
+ Requires-Dist: tomli_w>=1.2.0
21
+ Requires-Dist: psycopg>=3.2.6
22
+ Requires-Dist: pyparsing>=3.1.2
23
+ Requires-Dist: PyJWT==2.10.1
24
+ Requires-Dist: lupa>=2.4
25
+ Requires-Dist: uvloop>=0.21.0
26
+ Requires-Dist: cryptography>=36.0.1
27
+ Dynamic: author
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # PyFreeFlow
39
+
40
+ ## Definition
41
+
42
+ This library enables the creation of services by composing elementary modules into an operation flow.
43
+ It is designed to be usable even by non-programmers: the library automatically manages parallel execution and error handling. The user only needs to describe the process to be executed.
44
+
45
+ The flow is defined as a directed graph:
46
+
47
+ it can have 1..N input nodes and 1..N output nodes;
48
+
49
+ if multiple output nodes exist, one can be selected as the final result (all nodes are still executed);
50
+
51
+ each node’s output is passed as input to the next node in the graph.
52
+
53
+ A special module type, DataTransformer, allows data transformation between nodes with different types, formats, or structures. Since each node’s output is consumed by the following node, a state object is also passed to each node. This makes it possible to store data (usually through DataTransformer) for later use within the flow.
54
+
55
+ The library is extensible through dynamically loadable modules that follow a defined specification.
56
+
57
+ To configure a pipeline (the executable unit), two elements are required:
58
+
59
+ - the list of nodes;
60
+ - the graph definition, written in dot syntax.
61
+
62
+ ## Command line tool
63
+
64
+ A command-line tool is included to execute pipelines defined in a Yaml file.
65
+ The file must contain three sections: ext, args, pipeline.
66
+
67
+ - **ext**: list of additional extensions to load.
68
+ - **args**: input parameters for the pipeline.
69
+ - **pipeline**: definition of the flow.
70
+
71
+ Example: password encryption
72
+
73
+ ```yaml
74
+ ext:
75
+ - pyfreeflow.ext.crypto_operator
76
+ args:
77
+ username: jdoe
78
+ password: password
79
+ pipeline:
80
+ digraph:
81
+ - parseArgs -> createJson
82
+ - createJson -> prepareEncrypt
83
+ - prepareEncrypt -> encrypt
84
+ - encrypt -> prepareSaveFile
85
+ - prepareSaveFile -> saveFile
86
+ name: crypto
87
+ node:
88
+ - config:
89
+ transformer: |2
90
+ data = {op = "write", data = {username = data.username, password = data.password}}
91
+ name: parseArgs
92
+ type: DataTransformer
93
+ version: '1.0'
94
+ - name: createJson
95
+ type: JsonBufferOperator
96
+ version: '1.0'
97
+ - config:
98
+ transformer: |2
99
+ data = {op = "encrypt", data = data, key = "fernet.key"}
100
+ name: prepareEncrypt
101
+ type: DataTransformer
102
+ version: '1.0'
103
+ - name: encrypt
104
+ type: FernetCryptoOperator
105
+ version: '1.0'
106
+ - config:
107
+ transformer: |2
108
+ data = {op = "write", data = data, path = "pass.enc"}
109
+ name: prepareSaveFile
110
+ type: DataTransformer
111
+ version: '1.0'
112
+ - config:
113
+ binary: false
114
+ name: saveFile
115
+ type: AnyFileOperator
116
+ version: '1.0'
117
+ ```
118
+
119
+ Pipeline configuration parameters
120
+
121
+ - **name**: pipeline name (optional)
122
+ - **digraph**: connections between nodes, identified by unique names
123
+ - **node**: node definitions used in the digraph
124
+
125
+ Node definition parameters
126
+
127
+ - **config**: configuration parameters specific to the module
128
+ - **name**: unique node name
129
+ - **type**: module type implementing the node
130
+ - **version**: version of the module
131
+
132
+ For details about individual modules, refer to their documentation.
133
+
134
+ # License
135
+
136
+ This software is available under dual licensing:
137
+
138
+ ## Non-Commercial Use - AGPL v3
139
+ For non-commercial use, this software is released under the GNU Affero General Public License v3.0.
140
+ Any modifications must be shared under the same license.
141
+
142
+ ## Commercial Use
143
+ For commercial use, please contact me.
@@ -0,0 +1,21 @@
1
+ pyfreeflow/__init__.py,sha256=v69pBbooCheyw0O8PvAR12dTI93v2boN4m6AAQy3ysk,900
2
+ pyfreeflow/pipeline.py,sha256=_9qqa9BDiOiGb2R4_1SCmdJ3u214h6pG-vTw8TdKASc,3504
3
+ pyfreeflow/registry.py,sha256=FHkQA9fjY_MRfctwFi8qCoVzC3grhNuBxgCCZmqsppA,861
4
+ pyfreeflow/utils.py,sha256=YFK1i3kfKcBqN77qagHlLgaTTG0U66OGboN4vyRQkGE,1769
5
+ pyfreeflow/ext/__init__.py,sha256=6aIHFWxOnBFB5ztIOBDfHw3uV6uqxxCkV8-dZ7NSqp8,258
6
+ pyfreeflow/ext/buffer_operator.py,sha256=dgYpPPkXVS4Hnq4qLnT4vqYQf52OVuO92LqbUhTkF9g,4970
7
+ pyfreeflow/ext/crypto_operator.py,sha256=ODtAc2RQi9eNw5dLF9W1SQ7agfeWxP5vuJSklLiJLBo,1296
8
+ pyfreeflow/ext/data_transformer.py,sha256=RxSWTBqLO6xYqrRsg4GtMurLxGwKBKuPCPinXDZLspc,6451
9
+ pyfreeflow/ext/env_operator.py,sha256=IoIpkEByrXXyBYHs4gYEPsVc3-PxfPRfxAHU-vj4SMU,605
10
+ pyfreeflow/ext/file_operator.py,sha256=-a8dtZFAOG9V-e3O8bcaEgdY1Kfd3oTDBjsiRhB7JTc,6712
11
+ pyfreeflow/ext/jwt_operator.py,sha256=gGxybcxshodRR84yR-h12AVM_kzAekzkyeBS8b9TFAU,4474
12
+ pyfreeflow/ext/pgsql_executor.py,sha256=o1_uw4W_mNy1l32cKl_isROlk38JYwvjBjlCNIuzyG0,5093
13
+ pyfreeflow/ext/rest_api_requester.py,sha256=GbV5_6-K2WVvTEhC-bo3OBs5U-M0DY6zk-nZrKoQWfA,5441
14
+ pyfreeflow/ext/sleep_operator.py,sha256=8Uy8NWdHdpsHd1AW_Ih9U7lnmYupYWxQtsEafLBCCCQ,1403
15
+ pyfreeflow/ext/types.py,sha256=Y2_7s6JBENhPoF-Ruu6ZcmBwS-iwyzJb56sdLwjL_es,1555
16
+ pyfreeflow-0.1.0.data/scripts/pyfreeflow-cli.py,sha256=Nonkc7HCvdj5z2Xo1aeqHv7YIhuhrRtc6SB51jXwEmk,2468
17
+ pyfreeflow-0.1.0.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
18
+ pyfreeflow-0.1.0.dist-info/METADATA,sha256=--L5qtGpeArUnvMBptIy3Y-SDNLcNXhXPzYj_TcX_uc,4381
19
+ pyfreeflow-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ pyfreeflow-0.1.0.dist-info/top_level.txt,sha256=gKG9ntX49i_frKkCy7JrPv5QUx8L14eD53bSDTGPspE,11
21
+ pyfreeflow-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+