ddss 0.0.6__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.
- ddss-0.0.6/PKG-INFO +15 -0
- ddss-0.0.6/README.md +1 -0
- ddss-0.0.6/ddss/ds.py +49 -0
- ddss-0.0.6/ddss/egg.py +47 -0
- ddss-0.0.6/ddss/egraph.py +83 -0
- ddss-0.0.6/ddss/input.py +32 -0
- ddss-0.0.6/ddss/main.py +34 -0
- ddss-0.0.6/ddss/orm.py +45 -0
- ddss-0.0.6/ddss/output.py +39 -0
- ddss-0.0.6/ddss/poly.py +28 -0
- ddss-0.0.6/ddss.egg-info/PKG-INFO +15 -0
- ddss-0.0.6/ddss.egg-info/SOURCES.txt +16 -0
- ddss-0.0.6/ddss.egg-info/dependency_links.txt +1 -0
- ddss-0.0.6/ddss.egg-info/entry_points.txt +2 -0
- ddss-0.0.6/ddss.egg-info/requires.txt +7 -0
- ddss-0.0.6/ddss.egg-info/top_level.txt +1 -0
- ddss-0.0.6/pyproject.toml +21 -0
- ddss-0.0.6/setup.cfg +4 -0
ddss-0.0.6/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddss
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Distributed Deductive System Sorts
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: aiomysql>=0.3.2
|
|
8
|
+
Requires-Dist: aiosqlite>=0.22.0
|
|
9
|
+
Requires-Dist: apyds>=0.0.7
|
|
10
|
+
Requires-Dist: apyds-bnf>=0.0.7
|
|
11
|
+
Requires-Dist: cloudpickle>=3.1.2
|
|
12
|
+
Requires-Dist: egglog>=12.0.0
|
|
13
|
+
Requires-Dist: sqlalchemy>=2.0.45
|
|
14
|
+
|
|
15
|
+
# Distributed Deductive System Sorts
|
ddss-0.0.6/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Distributed Deductive System Sorts
|
ddss-0.0.6/ddss/ds.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
from sqlalchemy import select
|
|
4
|
+
from apyds import Search
|
|
5
|
+
from .orm import initialize_database, insert_or_ignore, Facts, Ideas
|
|
6
|
+
from .poly import Poly
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def main(addr, engine=None, session=None):
|
|
10
|
+
if engine is None or session is None:
|
|
11
|
+
engine, session = await initialize_database(addr)
|
|
12
|
+
|
|
13
|
+
search = Search()
|
|
14
|
+
max_fact = -1
|
|
15
|
+
|
|
16
|
+
while True:
|
|
17
|
+
begin = asyncio.get_running_loop().time()
|
|
18
|
+
|
|
19
|
+
async with session() as sess:
|
|
20
|
+
for i in await sess.scalars(select(Facts).where(Facts.id > max_fact)):
|
|
21
|
+
max_fact = max(max_fact, i.id)
|
|
22
|
+
search.add(Poly(dsp=i.data).ds)
|
|
23
|
+
tasks = []
|
|
24
|
+
|
|
25
|
+
def handler(rule):
|
|
26
|
+
poly = Poly(rule=rule)
|
|
27
|
+
tasks.append(asyncio.create_task(insert_or_ignore(sess, Facts, poly.dsp)))
|
|
28
|
+
if idea := poly.idea:
|
|
29
|
+
tasks.append(asyncio.create_task(insert_or_ignore(sess, Ideas, idea.dsp)))
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
count = search.execute(handler)
|
|
33
|
+
await asyncio.gather(*tasks)
|
|
34
|
+
await sess.commit()
|
|
35
|
+
|
|
36
|
+
end = asyncio.get_running_loop().time()
|
|
37
|
+
duration = end - begin
|
|
38
|
+
if count == 0:
|
|
39
|
+
delay = max(0, 0.1 - duration)
|
|
40
|
+
await asyncio.sleep(delay)
|
|
41
|
+
|
|
42
|
+
await engine.dispose()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
if len(sys.argv) != 2:
|
|
47
|
+
print(f"Usage: {sys.argv[0]} <database-addr>")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
asyncio.run(main(sys.argv[1]))
|
ddss-0.0.6/ddss/egg.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
from sqlalchemy import select
|
|
4
|
+
from .orm import initialize_database, insert_or_ignore, Facts, Ideas
|
|
5
|
+
from .egraph import Search
|
|
6
|
+
from .poly import Poly
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def main(addr, engine=None, session=None):
|
|
10
|
+
if engine is None or session is None:
|
|
11
|
+
engine, session = await initialize_database(addr)
|
|
12
|
+
|
|
13
|
+
search = Search()
|
|
14
|
+
pool = []
|
|
15
|
+
max_fact = -1
|
|
16
|
+
max_idea = -1
|
|
17
|
+
|
|
18
|
+
while True:
|
|
19
|
+
count = 0
|
|
20
|
+
begin = asyncio.get_running_loop().time()
|
|
21
|
+
|
|
22
|
+
async with session() as sess:
|
|
23
|
+
for i in await sess.scalars(select(Facts).where(Facts.id > max_fact)):
|
|
24
|
+
max_fact = max(max_fact, i.id)
|
|
25
|
+
search.add(Poly(dsp=i.data))
|
|
26
|
+
for i in await sess.scalars(select(Ideas).where(Ideas.id > max_idea)):
|
|
27
|
+
max_idea = max(max_idea, i.id)
|
|
28
|
+
pool.append(Poly(dsp=i.data))
|
|
29
|
+
tasks = []
|
|
30
|
+
for i in pool:
|
|
31
|
+
for o in search.execute(i):
|
|
32
|
+
tasks.append(asyncio.create_task(insert_or_ignore(sess, Facts, o.dsp)))
|
|
33
|
+
await asyncio.gather(*tasks)
|
|
34
|
+
await sess.commit()
|
|
35
|
+
|
|
36
|
+
end = asyncio.get_running_loop().time()
|
|
37
|
+
duration = end - begin
|
|
38
|
+
if count == 0:
|
|
39
|
+
delay = max(0, 0.1 - duration)
|
|
40
|
+
await asyncio.sleep(delay)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
if len(sys.argv) != 2:
|
|
45
|
+
print(f"Usage: {sys.argv[0]} <database-addr>")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
asyncio.run(main(sys.argv[1]))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import typing
|
|
3
|
+
import egglog
|
|
4
|
+
from apyds import Term, List
|
|
5
|
+
from .poly import Poly
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EGraphTerm(egglog.Expr):
|
|
9
|
+
def __init__(self, name: egglog.StringLike) -> None: ...
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def begin(cls) -> EGraphTerm: ...
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def pair(cls, lhs: EGraphTerm, rhs: EGraphTerm) -> EGraphTerm: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ds_to_egraph(data: Term) -> EGraphTerm:
|
|
19
|
+
term = data.term
|
|
20
|
+
if isinstance(term, List):
|
|
21
|
+
result = EGraphTerm.begin()
|
|
22
|
+
for i in range(len(term)):
|
|
23
|
+
child = _ds_to_egraph(term[i])
|
|
24
|
+
result = EGraphTerm.pair(result, child)
|
|
25
|
+
return result
|
|
26
|
+
else:
|
|
27
|
+
return EGraphTerm(str(term))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Search:
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self.egraph = egglog.EGraph()
|
|
33
|
+
self.terms = set()
|
|
34
|
+
|
|
35
|
+
def _is_equality(self, data: Poly) -> bool:
|
|
36
|
+
return data.ds.startswith("----\n(binary == ")
|
|
37
|
+
|
|
38
|
+
def _extract_lhs_rhs(self, data: Poly) -> tuple[str, str]:
|
|
39
|
+
term = data.rule.conclusion
|
|
40
|
+
lhs = str(term.term[2])
|
|
41
|
+
rhs = str(term.term[3])
|
|
42
|
+
return lhs, rhs
|
|
43
|
+
|
|
44
|
+
def _ast(self, data: str) -> EGraphTerm:
|
|
45
|
+
result = _ds_to_egraph(Term(data))
|
|
46
|
+
self.egraph.register(result)
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
def _set_equality(self, lhs: str, rhs: str) -> None:
|
|
50
|
+
self.egraph.register(egglog.union(self._ast(lhs)).with_(self._ast(rhs)))
|
|
51
|
+
|
|
52
|
+
def _get_equality(self, lhs: str, rhs: str) -> None:
|
|
53
|
+
return self.egraph.check_bool(self._ast(lhs) == self._ast(rhs))
|
|
54
|
+
|
|
55
|
+
def _search_equality(self, data: str) -> typing.Iterator[str]:
|
|
56
|
+
for result in self.terms:
|
|
57
|
+
if self._get_equality(data, result):
|
|
58
|
+
yield result
|
|
59
|
+
|
|
60
|
+
def _build_equality(self, lhs: str, rhs: str) -> Poly:
|
|
61
|
+
return Poly(ds=f"----\n(binary == {lhs} {rhs})")
|
|
62
|
+
|
|
63
|
+
def add(self, data: Poly) -> None:
|
|
64
|
+
if not self._is_equality(data):
|
|
65
|
+
return
|
|
66
|
+
lhs, rhs = self._extract_lhs_rhs(data)
|
|
67
|
+
self.terms.add(lhs)
|
|
68
|
+
self.terms.add(rhs)
|
|
69
|
+
self._set_equality(lhs, rhs)
|
|
70
|
+
|
|
71
|
+
def execute(self, data: Poly) -> typing.Iterator[Poly]:
|
|
72
|
+
if not self._is_equality(data):
|
|
73
|
+
return
|
|
74
|
+
lhs, rhs = self._extract_lhs_rhs(data)
|
|
75
|
+
if self._get_equality(lhs, rhs):
|
|
76
|
+
yield self._build_equality(lhs, rhs)
|
|
77
|
+
return
|
|
78
|
+
if lhs.startswith("`"):
|
|
79
|
+
for result in self._search_equality(rhs):
|
|
80
|
+
yield self._build_equality(result, rhs)
|
|
81
|
+
if rhs.startswith("`"):
|
|
82
|
+
for result in self._search_equality(lhs):
|
|
83
|
+
yield self._build_equality(lhs, result)
|
ddss-0.0.6/ddss/input.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
from .orm import initialize_database, insert_or_ignore, Facts, Ideas
|
|
4
|
+
from .poly import Poly
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def main(addr, engine=None, session=None):
|
|
8
|
+
if engine is None or session is None:
|
|
9
|
+
engine, session = await initialize_database(addr)
|
|
10
|
+
|
|
11
|
+
while True:
|
|
12
|
+
try:
|
|
13
|
+
data = await asyncio.get_running_loop().run_in_executor(None, input)
|
|
14
|
+
if data.strip() == "":
|
|
15
|
+
continue
|
|
16
|
+
except EOFError:
|
|
17
|
+
break
|
|
18
|
+
async with session() as sess:
|
|
19
|
+
poly = Poly(dsp=data)
|
|
20
|
+
await insert_or_ignore(sess, Facts, poly.dsp)
|
|
21
|
+
if idea := poly.idea:
|
|
22
|
+
await insert_or_ignore(sess, Ideas, idea.dsp)
|
|
23
|
+
await sess.commit()
|
|
24
|
+
|
|
25
|
+
await engine.dispose()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
if len(sys.argv) != 2:
|
|
30
|
+
print(f"Usage: {sys.argv[0]} <database-addr>")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
asyncio.run(main(sys.argv[1]))
|
ddss-0.0.6/ddss/main.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
from .orm import initialize_database
|
|
4
|
+
from .ds import main as ds
|
|
5
|
+
from .egg import main as egg
|
|
6
|
+
from .input import main as input
|
|
7
|
+
from .output import main as output
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def main(addr, recreate=False):
|
|
11
|
+
engine, session = await initialize_database(addr, recreate)
|
|
12
|
+
await asyncio.gather(
|
|
13
|
+
ds(addr, engine, session),
|
|
14
|
+
egg(addr, engine, session),
|
|
15
|
+
input(addr, engine, session),
|
|
16
|
+
output(addr, engine, session),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cli():
|
|
21
|
+
if len(sys.argv) == 1:
|
|
22
|
+
addr = "sqlite+aiosqlite:////tmp/ddss.db"
|
|
23
|
+
recreate = True
|
|
24
|
+
elif len(sys.argv) == 2:
|
|
25
|
+
addr = sys.argv[1]
|
|
26
|
+
recreate = False
|
|
27
|
+
else:
|
|
28
|
+
print(f"Usage: {sys.argv[0]} [<database-addr>]")
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
asyncio.run(main(addr, recreate))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
cli()
|
ddss-0.0.6/ddss/orm.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
|
5
|
+
from sqlalchemy.exc import IntegrityError
|
|
6
|
+
from sqlalchemy import Integer, Text
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Base(DeclarativeBase):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Facts(Base):
|
|
15
|
+
__tablename__ = "facts"
|
|
16
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
17
|
+
data: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Ideas(Base):
|
|
21
|
+
__tablename__ = "ideas"
|
|
22
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
23
|
+
data: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def initialize_database(
|
|
27
|
+
addr: str, recreate: bool = False
|
|
28
|
+
) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]:
|
|
29
|
+
engine = create_async_engine(addr)
|
|
30
|
+
session = async_sessionmaker(engine)
|
|
31
|
+
async with engine.begin() as conn:
|
|
32
|
+
if recreate:
|
|
33
|
+
await conn.run_sync(Base.metadata.drop_all)
|
|
34
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
35
|
+
return engine, session
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def insert_or_ignore(sess: AsyncSession, model, data, locks=defaultdict(lambda: asyncio.Lock())) -> None:
|
|
39
|
+
async with locks[id(sess.bind)]:
|
|
40
|
+
try:
|
|
41
|
+
async with sess.begin_nested():
|
|
42
|
+
sess.add(model(data=data))
|
|
43
|
+
await sess.flush()
|
|
44
|
+
except IntegrityError:
|
|
45
|
+
pass
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
from sqlalchemy import select
|
|
4
|
+
from .orm import initialize_database, Facts, Ideas
|
|
5
|
+
from .poly import Poly
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def main(addr, engine=None, session=None):
|
|
9
|
+
if engine is None or session is None:
|
|
10
|
+
engine, session = await initialize_database(addr)
|
|
11
|
+
|
|
12
|
+
max_fact = -1
|
|
13
|
+
max_idea = -1
|
|
14
|
+
|
|
15
|
+
while True:
|
|
16
|
+
count = 0
|
|
17
|
+
begin = asyncio.get_running_loop().time()
|
|
18
|
+
|
|
19
|
+
async with session() as sess:
|
|
20
|
+
for i in await sess.scalars(select(Facts).where(Facts.id > max_fact)):
|
|
21
|
+
max_fact = max(max_fact, i.id)
|
|
22
|
+
print("fact:", Poly(dsp=i.data).dsp)
|
|
23
|
+
for i in await sess.scalars(select(Ideas).where(Ideas.id > max_idea)):
|
|
24
|
+
max_idea = max(max_idea, i.id)
|
|
25
|
+
print("idea:", Poly(dsp=i.data).dsp)
|
|
26
|
+
await sess.commit()
|
|
27
|
+
|
|
28
|
+
end = asyncio.get_running_loop().time()
|
|
29
|
+
duration = end - begin
|
|
30
|
+
if count == 0:
|
|
31
|
+
delay = max(0, 0.1 - duration)
|
|
32
|
+
await asyncio.sleep(delay)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
if len(sys.argv) != 2:
|
|
37
|
+
print(f"Usage: {sys.argv[0]} <database-addr>")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
asyncio.run(main(sys.argv[1]))
|
ddss-0.0.6/ddss/poly.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from apyds import Rule
|
|
2
|
+
from apyds_bnf import parse, unparse
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Poly:
|
|
6
|
+
def __init__(self, **kwargs):
|
|
7
|
+
((key, value),) = kwargs.items()
|
|
8
|
+
match key:
|
|
9
|
+
case "dsp":
|
|
10
|
+
self.ds = parse(value)
|
|
11
|
+
self.dsp = unparse(self.ds)
|
|
12
|
+
self.rule = Rule(self.ds)
|
|
13
|
+
case "ds":
|
|
14
|
+
self.dsp = unparse(value)
|
|
15
|
+
self.ds = parse(self.dsp)
|
|
16
|
+
self.rule = Rule(self.ds)
|
|
17
|
+
case "rule":
|
|
18
|
+
self.rule = value
|
|
19
|
+
self.ds = str(self.rule)
|
|
20
|
+
self.dsp = unparse(self.ds)
|
|
21
|
+
case _:
|
|
22
|
+
raise ValueError("Invalid argument for Poly")
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def idea(self):
|
|
26
|
+
if len(self.rule) != 0:
|
|
27
|
+
return Poly(ds=f"--\n{self.rule[0]}")
|
|
28
|
+
return None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddss
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Distributed Deductive System Sorts
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: aiomysql>=0.3.2
|
|
8
|
+
Requires-Dist: aiosqlite>=0.22.0
|
|
9
|
+
Requires-Dist: apyds>=0.0.7
|
|
10
|
+
Requires-Dist: apyds-bnf>=0.0.7
|
|
11
|
+
Requires-Dist: cloudpickle>=3.1.2
|
|
12
|
+
Requires-Dist: egglog>=12.0.0
|
|
13
|
+
Requires-Dist: sqlalchemy>=2.0.45
|
|
14
|
+
|
|
15
|
+
# Distributed Deductive System Sorts
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
ddss/ds.py
|
|
4
|
+
ddss/egg.py
|
|
5
|
+
ddss/egraph.py
|
|
6
|
+
ddss/input.py
|
|
7
|
+
ddss/main.py
|
|
8
|
+
ddss/orm.py
|
|
9
|
+
ddss/output.py
|
|
10
|
+
ddss/poly.py
|
|
11
|
+
ddss.egg-info/PKG-INFO
|
|
12
|
+
ddss.egg-info/SOURCES.txt
|
|
13
|
+
ddss.egg-info/dependency_links.txt
|
|
14
|
+
ddss.egg-info/entry_points.txt
|
|
15
|
+
ddss.egg-info/requires.txt
|
|
16
|
+
ddss.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ddss
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ddss"
|
|
3
|
+
version = "0.0.6"
|
|
4
|
+
description = "Distributed Deductive System Sorts"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"aiomysql>=0.3.2",
|
|
9
|
+
"aiosqlite>=0.22.0",
|
|
10
|
+
"apyds>=0.0.7",
|
|
11
|
+
"apyds-bnf>=0.0.7",
|
|
12
|
+
"cloudpickle>=3.1.2",
|
|
13
|
+
"egglog>=12.0.0",
|
|
14
|
+
"sqlalchemy>=2.0.45",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
ddss = "ddss.main:cli"
|
|
19
|
+
|
|
20
|
+
[tool.ruff]
|
|
21
|
+
line-length = 120
|
ddss-0.0.6/setup.cfg
ADDED