cobweb-launcher 0.0.4__py3-none-any.whl → 0.0.6__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
cobweb/__init__.py CHANGED
@@ -1,10 +1,8 @@
1
- from .base.config import StorerInfo, SchedulerInfo, RedisInfo, SchedulerDB, StorerDB
2
- from .base.interface import StorerInterface, SchedulerInterface
1
+ from .bbb import Seed, Queue, DBItem
2
+ from .task import Task
3
+ from .log import log
4
+ from .interface import SchedulerInterface, StorerInterface
5
+ from .db.redis_db import RedisDB
3
6
  from .distributed.launcher import launcher
4
- from .distributed import models
5
- from .base.task import Task
6
- from .base.bbb import Seed
7
- from .db.base import *
8
- from .db.storer import *
9
- from .db.scheduler import *
7
+
10
8
 
@@ -2,8 +2,8 @@
2
2
  import json
3
3
  import time
4
4
  import hashlib
5
- from log import log
6
- from utils import struct_queue_name
5
+ from .log import log
6
+ from .utils import struct_queue_name
7
7
  from collections import deque, namedtuple
8
8
 
9
9
 
cobweb/db/__init__.py CHANGED
@@ -0,0 +1,2 @@
1
+ from . import oss_db, redis_db
2
+ from . import scheduler, storer
@@ -2,7 +2,7 @@ import oss2
2
2
  from typing import Union
3
3
  from oss2.models import PartInfo
4
4
  from requests import Response
5
- from base.log import log
5
+ from cobweb import log
6
6
 
7
7
 
8
8
  class OssDB:
@@ -1,6 +1,6 @@
1
1
  import time
2
2
  import redis
3
- from base.bbb import Seed
3
+ from cobweb import Seed
4
4
 
5
5
 
6
6
  class RedisDB:
@@ -9,19 +9,9 @@ class RedisDB:
9
9
  self,
10
10
  project: str,
11
11
  task_name: str,
12
- host=None,
13
- port=None,
14
- username=None,
15
- password=None,
16
- db=0
12
+ config: dict
17
13
  ):
18
- pool = redis.ConnectionPool(
19
- host=host,
20
- port=port,
21
- username=username,
22
- password=password,
23
- db=db
24
- )
14
+ pool = redis.ConnectionPool(**config)
25
15
  self.heartbeat_key = f"{project}:{task_name}:heartbeat" # redis type string
26
16
  self.spider_key = f"{project}:{task_name}:seed_info:spider" # redis type zset, .format(priority)
27
17
  self.storer_key = f"{project}:{task_name}:seed_info:storer:%s" # redis type set,
@@ -1,4 +1,4 @@
1
- from base.interface import SchedulerInterface
1
+ from cobweb import SchedulerInterface
2
2
 
3
3
 
4
4
  class Default(SchedulerInterface):
@@ -1,6 +1,4 @@
1
- from base.log import log
2
- from base.bbb import Seed
3
- from base.interface import SchedulerInterface
1
+ from cobweb import log, Seed, SchedulerInterface
4
2
 
5
3
 
6
4
  class Textfile(SchedulerInterface):
@@ -1,5 +1,4 @@
1
- from base.log import log
2
- from base.interface import StorerInterface
1
+ from cobweb import log, StorerInterface
3
2
 
4
3
 
5
4
  class Console(StorerInterface):
@@ -1,13 +1,12 @@
1
1
  import json
2
- from base.log import log
3
- from base.interface import StorerInterface
4
2
  from aliyun.log import LogClient, LogItem, PutLogsRequest
3
+ from cobweb import log, StorerInterface
5
4
 
6
5
 
7
6
  class Loghub(StorerInterface):
8
7
 
9
- def __init__(self, table, fields, length, queue, config):
10
- super().__init__(table, fields, length, queue, config)
8
+ def __init__(self, **kwargs):
9
+ super().__init__(**kwargs)
11
10
  self.client = None
12
11
 
13
12
  def init_loghub_clint(self):
cobweb/db/storer/redis.py CHANGED
@@ -1,5 +1,4 @@
1
- from base.log import log
2
- from base.interface import StorerInterface
1
+ from cobweb import log, StorerInterface
3
2
 
4
3
 
5
4
  class Redis(StorerInterface):
@@ -1,5 +1,4 @@
1
- from base.log import log
2
- from base.interface import StorerInterface
1
+ from cobweb import log, StorerInterface
3
2
 
4
3
 
5
4
  class Textfile(StorerInterface):
@@ -1,34 +1,71 @@
1
1
  import time
2
2
  import threading
3
3
  from threading import Thread
4
- from base.log import log
5
- from db.base.redis_db import RedisDB
6
- from base.bbb import Queue, Seed, DBItem
7
- from base.utils import struct_queue_name, restore_table_name
8
- from models import Scheduler, Spider, Storer
9
-
10
-
11
- def start_seeds(seeds):
12
- if not seeds:
13
- return None
14
- if any(isinstance(seeds, t) for t in (list, tuple)):
15
- return [Seed(seed) for seed in seeds]
16
- elif any(isinstance(seeds, t) for t in (str, dict)):
17
- return Seed(seeds)
18
-
19
-
20
- def parse_storer_info(storer_info):
21
- storer_data = {}
22
- storer_info_list = []
23
- if storer_info.__class__.__name__ == 'StorerInfo':
24
- storer_info_list.append(storer_info)
25
- elif any(isinstance(storer_info, t) for t in (list, tuple)):
26
- storer_info_list = storer_info
27
- for info in storer_info_list:
28
- db_name = info.DB.__name__
29
- storer_data.setdefault(db_name, {"StorerDB": info.DB, "db_args_list": []})
30
- storer_data[db_name]["db_args_list"].append(info[1:])
31
- return storer_data
4
+ from importlib import import_module
5
+
6
+ from cobweb import log, Queue, DBItem, RedisDB, StorerInterface
7
+ from cobweb.utils import struct_queue_name, restore_table_name
8
+ from .models import Scheduler, Spider, Storer
9
+ from collections import namedtuple
10
+
11
+ # def start_seeds(seeds):
12
+ # if not seeds:
13
+ # return None
14
+ # if any(isinstance(seeds, t) for t in (list, tuple)):
15
+ # return [Seed(seed) for seed in seeds]
16
+ # elif any(isinstance(seeds, t) for t in (str, dict)):
17
+ # return Seed(seeds)
18
+
19
+
20
+ # def parse_storer_info(storer_info):
21
+ # storer_data = {}
22
+ # storer_info_list = []
23
+ # if storer_info.__class__.__name__ == 'StorerInfo':
24
+ # storer_info_list.append(storer_info)
25
+ # elif any(isinstance(storer_info, t) for t in (list, tuple)):
26
+ # storer_info_list = storer_info
27
+ # for info in storer_info_list:
28
+ # db_name = info.DB.__name__
29
+ # storer_data.setdefault(db_name, {"StorerDB": info.DB, "db_args_list": []})
30
+ # storer_data[db_name]["db_args_list"].append(info[1:])
31
+ # return storer_data
32
+
33
+ def get_scheduler_db(db):
34
+ if isinstance(db, str):
35
+ if "." in db:
36
+ model_path = db.split(".")
37
+ model = import_module(db)
38
+ obj = getattr(model, db)
39
+ else:
40
+ model = import_module(f"cobweb.db.scheduler.{db.lower()}")
41
+ obj = getattr(model, db.capitalize())
42
+ return obj
43
+ # if db.lower() in dir(StorerDB):
44
+ # return getattr(StorerDB, db)
45
+ # else:
46
+ # pass
47
+ elif issubclass(db, StorerInterface):
48
+ return db
49
+ raise TypeError()
50
+
51
+
52
+ def get_storer_db(db):
53
+ if isinstance(db, str):
54
+ if "." in db:
55
+ model_path = db.split(".")
56
+ model = import_module(db)
57
+ obj = getattr(model, db)
58
+ else:
59
+ model = import_module(f"cobweb.db.storer.{db.lower()}")
60
+ obj = getattr(model, db.capitalize())
61
+ return obj, db.lower()
62
+ # if db.lower() in dir(StorerDB):
63
+ # return getattr(StorerDB, db)
64
+ # else:
65
+ # pass
66
+ elif issubclass(db, StorerInterface):
67
+ return db, db.__name__.lower()
68
+ raise TypeError()
32
69
 
33
70
 
34
71
  def check(stop, last, spider, scheduler, storer_list, ready_seed_length, spider_queue_length):
@@ -84,54 +121,96 @@ def launcher(task):
84
121
  stop = threading.Event()
85
122
 
86
123
  # 初始化redis信息
87
- redis_db = RedisDB(task.project, task.task_name, *task.redis_info)
124
+ redis_db = RedisDB(task.project, task.task_name, task.redis_info)
88
125
 
89
126
  log.info("初始化cobweb!")
90
127
 
91
128
  seed_queue = Queue()
92
129
 
130
+ if task.scheduler_info is None:
131
+ task.scheduler_info = dict()
132
+
93
133
  # 调度器动态继承
94
- SchedulerDB, table, sql, length, size, config = task.scheduler_info
95
- SchedulerTmp = type(SchedulerDB.__name__, (Scheduler, SchedulerDB), {})
134
+ sql = task.scheduler_info.get("sql")
135
+ table = task.scheduler_info.get("table")
136
+ size = task.scheduler_info.get("size")
137
+ scheduler_config = task.scheduler_info.get("config")
138
+ scheduler_db = task.scheduler_info.get("db", "default")
139
+ DB = get_scheduler_db(scheduler_db)
140
+ # SchedulerDB, table, sql, length, size, config = task.scheduler_info
141
+ SchedulerTmp = type(DB.__name__, (Scheduler, DB), {})
96
142
 
97
143
  # 初始化调度器
98
- scheduler = SchedulerTmp(table, sql, length, size, seed_queue, config)
144
+ scheduler = SchedulerTmp(
145
+ table=table, sql=sql, size=size, queue=seed_queue,
146
+ length=task.scheduler_queue_length, config=scheduler_config
147
+ )
99
148
 
100
149
  # 初始化采集器
101
150
  spider = Spider(seed_queue, task.max_retries)
102
151
 
103
152
  # 解析存储器信息
104
- storer_data = parse_storer_info(task.storer_info)
153
+ storer_info_list = task.storer_info
154
+ if not isinstance(storer_info_list, list):
155
+ storer_info_list = [storer_info_list]
105
156
 
106
157
  # new item
107
158
  item = type("Item", (object,), {"redis_client": redis_db})()
108
- for db_name in storer_data.keys():
109
- # 存储器动态继承
110
- StorerDB = storer_data[db_name]["StorerDB"]
159
+
160
+ for storer_info in storer_info_list:
161
+ storer_db = storer_info["db"]
162
+ fields = storer_info["fields"]
163
+ storer_table = storer_info.get("table", "console")
164
+ storer_config = storer_info.get("config")
165
+
166
+ StorerDB, db_name = get_storer_db(storer_db)
111
167
  StorerTmp = type(StorerDB.__name__, (Storer, StorerDB), {})
112
- db_args_list = storer_data[db_name]["db_args_list"]
113
- for storer_db_args in db_args_list:
114
- table, fields, length, config = storer_db_args
115
- if not getattr(item, db_name, None):
116
- instance = type(db_name, (DBItem,), {})
117
- setattr(item, db_name, instance)
118
- # 创建存储xxx, 创建存储队列
119
- storer_item_instance = getattr(item, db_name)
120
- storer_item_instance.init_item(table, fields)
121
- #
122
- storer_queue = struct_queue_name(db_name, table)
123
- queue = getattr(storer_item_instance, storer_queue)
124
- # 初始话存储器
125
- table_name = restore_table_name(table_name=table)
126
- storer = StorerTmp(table_name, fields, length, queue, config)
127
- storer_list.append(storer)
168
+
169
+ if not getattr(item, db_name, None):
170
+ instance = type(db_name, (DBItem,), {})
171
+ setattr(item, db_name, instance)
172
+
173
+ storer_item_instance = getattr(item, db_name)
174
+ storer_item_instance.init_item(storer_table, fields)
175
+
176
+ storer_queue = struct_queue_name(db_name, storer_table)
177
+ queue = getattr(storer_item_instance, storer_queue)
178
+ # 初始话存储器
179
+ table_name = restore_table_name(table_name=storer_table)
180
+ storer = StorerTmp(
181
+ table=table_name, fields=fields,
182
+ length=task.storer_queue_length,
183
+ queue=queue, config=storer_config
184
+ )
185
+ storer_list.append(storer)
186
+
187
+ # for db_name in storer_data.keys():
188
+ # # 存储器动态继承
189
+ # StorerDB = storer_data[db_name]["StorerDB"]
190
+ # StorerTmp = type(StorerDB.__name__, (Storer, StorerDB), {})
191
+ # db_args_list = storer_data[db_name]["db_args_list"]
192
+ # for storer_db_args in db_args_list:
193
+ # table, fields, length, config = storer_db_args
194
+ # if not getattr(item, db_name, None):
195
+ # instance = type(db_name, (DBItem,), {})
196
+ # setattr(item, db_name, instance)
197
+ # # 创建存储xxx, 创建存储队列
198
+ # storer_item_instance = getattr(item, db_name)
199
+ # storer_item_instance.init_item(table, fields)
200
+ # #
201
+ # storer_queue = struct_queue_name(db_name, table)
202
+ # queue = getattr(storer_item_instance, storer_queue)
203
+ # # 初始话存储器
204
+ # table_name = restore_table_name(table_name=table)
205
+ # storer = StorerTmp(table_name, fields, length, queue, config)
206
+ # storer_list.append(storer)
128
207
 
129
208
  Thread(target=redis_db.check_spider_queue, args=(stop, len(storer_list))).start()
130
209
  Thread(target=redis_db.set_heartbeat, args=(stop,)).start()
131
210
 
132
211
  # 推送初始种子
133
- seeds = start_seeds(task.start_seed)
134
- redis_db.add_seed(seeds)
212
+ # seeds = start_seeds(task.start_seed)
213
+ redis_db.add_seed(task.seeds)
135
214
  # 启动调度器, 调度至redis队列
136
215
  Thread(
137
216
  # name="xxxx_schedule_seeds",
@@ -190,5 +269,5 @@ def launcher(task):
190
269
  return decorator
191
270
 
192
271
 
193
-
194
-
272
+ # model = get_storer_db("console")
273
+ # print()
@@ -1,8 +1,6 @@
1
1
  import time
2
2
  from hashlib import md5
3
- from base.log import log
4
- from base.bbb import Queue, Seed
5
- from base.interface import SchedulerInterface, StorerInterface
3
+ from cobweb import log, Queue, Seed, StorerInterface, SchedulerInterface
6
4
  # from pympler import asizeof
7
5
 
8
6
 
@@ -1,15 +1,5 @@
1
- import json
2
1
  from abc import ABC, abstractmethod
3
-
4
-
5
- def parse_config(config):
6
- if not config:
7
- return None
8
- if isinstance(config, str):
9
- return json.loads(config)
10
- if isinstance(config, dict):
11
- return config
12
- raise TypeError("config type is not in [string, dict]!")
2
+ from .utils import parse_info
13
3
 
14
4
 
15
5
  class SchedulerInterface(ABC):
@@ -20,7 +10,7 @@ class SchedulerInterface(ABC):
20
10
  self.length = length
21
11
  self.size = size
22
12
  self.queue = queue
23
- self.config = parse_config(config)
13
+ self.config = parse_info(config)
24
14
  self.stop = False
25
15
 
26
16
  @abstractmethod
@@ -35,7 +25,7 @@ class StorerInterface(ABC):
35
25
  self.fields = fields
36
26
  self.length = length
37
27
  self.queue = queue
38
- self.config = parse_config(config)
28
+ self.config = parse_info(config)
39
29
  # self.redis_db = redis_db
40
30
 
41
31
  @abstractmethod
cobweb/task.py ADDED
@@ -0,0 +1,44 @@
1
+ from .utils import parse_info, struct_start_seeds
2
+
3
+
4
+ class Task:
5
+
6
+ def __init__(
7
+ self,
8
+ seeds=None,
9
+ project=None,
10
+ task_name=None,
11
+ redis_info=None,
12
+ storer_info=None,
13
+ scheduler_info=None,
14
+ spider_num=None,
15
+ max_retries=None,
16
+ storer_queue_length=None,
17
+ scheduler_queue_length=None,
18
+ ):
19
+ """
20
+
21
+ :param seeds:
22
+ :param project:
23
+ :param task_name:
24
+ :param redis_info:
25
+ :param storer_info:
26
+ :param scheduler_info: dict(DB="", table="", size="", config="")
27
+ :param spider_num:
28
+ :param max_retries:
29
+ :param storer_queue_length:
30
+ :param scheduler_queue_length:
31
+ """
32
+ self.seeds = struct_start_seeds(seeds)
33
+ self.project = project or "test"
34
+ self.task_name = task_name or "spider"
35
+
36
+ self.redis_info = parse_info(redis_info)
37
+ self.storer_info = parse_info(storer_info)
38
+ self.scheduler_info = parse_info(scheduler_info)
39
+
40
+ self.spider_num = spider_num or 1
41
+ self.max_retries = max_retries or 5
42
+ self.storer_queue_length = storer_queue_length or 100
43
+ self.scheduler_queue_length = scheduler_queue_length or 100
44
+
cobweb/utils.py ADDED
@@ -0,0 +1,85 @@
1
+ import json
2
+ import sys
3
+ from typing import Iterable
4
+
5
+ # from cobweb import Seed
6
+
7
+
8
+ def struct_table_name(table_name):
9
+ return table_name.replace(".", "__p__").replace(":", "__c__")
10
+
11
+
12
+ def restore_table_name(table_name):
13
+ return table_name.replace("__p__", ".").replace("__c__", ":")
14
+
15
+
16
+ def struct_queue_name(db_name, table_name):
17
+ return sys.intern(f"__{db_name}_{table_name}_queue__")
18
+
19
+
20
+ # class StorerDB:
21
+ #
22
+ # @staticmethod
23
+ # def console(self):
24
+ # from db.storer.console import Console
25
+ # table = struct_table_name(table)
26
+ # return StorerInfo(DB=Console, table=table, length=length, config=None)
27
+ #
28
+ # @staticmethod
29
+ # def textfile(table, length=200):
30
+ # from db.storer.textfile import Textfile
31
+ # table = struct_table_name(table)
32
+ # return StorerInfo(DB=Textfile, table=table, length=length, config=None)
33
+ #
34
+ # @staticmethod
35
+ # def loghub(table, length=200, config=None):
36
+ # from db.storer.loghub import Loghub
37
+ # table = struct_table_name(table)
38
+ # return StorerInfo(DB=Loghub, table=table, length=length, config=config)
39
+
40
+
41
+ def parse_info(info):
42
+ if not info:
43
+ return info
44
+
45
+ if isinstance(info, dict):
46
+ return info
47
+
48
+ if isinstance(info, str):
49
+ return json.loads(info)
50
+
51
+ if isinstance(info, Iterable):
52
+ result = list()
53
+ for ii in info:
54
+ if isinstance(ii, str):
55
+ result.append(json.loads(ii))
56
+ elif isinstance(ii, dict):
57
+ result.append(ii)
58
+ else:
59
+ raise TypeError("must be in [str, dict]")
60
+
61
+ return result
62
+
63
+
64
+ def struct_start_seeds(seeds):
65
+ from .bbb import Seed
66
+ if not seeds:
67
+ return None
68
+ if any(isinstance(seeds, t) for t in (list, tuple)):
69
+ return [Seed(seed) for seed in seeds]
70
+ elif any(isinstance(seeds, t) for t in (str, dict)):
71
+ return Seed(seeds)
72
+
73
+
74
+ # def get_storer_db(db):
75
+ #
76
+ # if isinstance(db, str):
77
+ # model = import_module(f" db.storer.{db.lower()}")
78
+ #
79
+ # # if db.lower() in dir(StorerDB):
80
+ # # return getattr(StorerDB, db)
81
+ # # else:
82
+ # # pass
83
+
84
+
85
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cobweb-launcher
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: spider_hole
5
5
  Home-page: https://github.com/Juannie-PP/cobweb
6
6
  Author: Juannie-PP
@@ -0,0 +1,28 @@
1
+ cobweb/__init__.py,sha256=IKQkcts73m_K-b6TGs_IxkdkncnZRU0O_N0MJct2COI,218
2
+ cobweb/bbb.py,sha256=8croagwF3xVdb1Xq3f5g22WydQ-pHHWWnA50895dH3E,5617
3
+ cobweb/interface.py,sha256=um_k2AAQl1HTOvfUlq914DjkpfZVwt2m1B65EpPKrmE,802
4
+ cobweb/log.py,sha256=Gb3_y4IzTo5pJohTggBCU9rK6-ZN3hgTOHkoXHyN6CU,2384
5
+ cobweb/task.py,sha256=awZWFwON34WAJs08TKPaYTbyRjmoNOCBkLCNf2l9C-Q,1282
6
+ cobweb/utils.py,sha256=tHSj_T1Ct7Y-QdIo5w4hCwkO59RlQiq9yGxUOjojMOg,2158
7
+ cobweb/db/__init__.py,sha256=4m9lqmxZCRbaih3Z3rl_BT0GugMd0dkOIgu_P9aeC84,63
8
+ cobweb/db/oss_db.py,sha256=l-Xbqawg1HJgedz9MumXQrr1jMK6_EePXCis11CZEkE,3810
9
+ cobweb/db/redis_db.py,sha256=keVlFpUT7spfNwZ4g_5teROo_uOsjfDWtR-WvAcqZIE,7415
10
+ cobweb/db/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ cobweb/db/scheduler/default.py,sha256=OxmFX7OvMEhKEq-NF7A8I9cA4V4qWw5vayS-yIbng0A,114
12
+ cobweb/db/scheduler/textfile.py,sha256=atRDeNT-e5toNvyGsCXAxL1FJi77uSYktdCzH_hXGo8,821
13
+ cobweb/db/storer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ cobweb/db/storer/console.py,sha256=f7yZFo4qTieaB9JxbGfrVAclAb2H_wji82dWoZp7HUw,182
15
+ cobweb/db/storer/loghub.py,sha256=4VqZacXWhidzINHXQu2_-E0HOBRCcc86f6LkKfnXD5I,1731
16
+ cobweb/db/storer/redis.py,sha256=7Q2XEQwBL6X_M1uvxzzuSBt6iw9piKw-_FWKm2INZDQ,412
17
+ cobweb/db/storer/textfile.py,sha256=3mDHMvF6Sh5fn3IHzWQxyTUd45V-zUoH8vY3EoRlMx0,415
18
+ cobweb/distributed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ cobweb/distributed/launcher.py,sha256=WEEfZDdOXpEN5ljrYEGQWzPtDM7XcVczEnDO81yCoWg,9135
20
+ cobweb/distributed/models.py,sha256=zRZfSOiP-OyvTVdI5_KScyf_jZmYmaYbxJvohf7ffDA,4390
21
+ cobweb/single/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ cobweb/single/models.py,sha256=lu8teNWnWcUwZFra8XmqyhzOAf3UyuEztwBr1Ne6pUs,2898
23
+ cobweb/single/nest.py,sha256=mL8q9a5BjtoeUyzXCIVw_vyUsNY8ltbvQpYIIpZEDFU,5012
24
+ cobweb_launcher-0.0.6.dist-info/LICENSE,sha256=z1rxSIGOyzcSb3orZxFPxzx-0C1vTocmswqBNxpKfEk,1063
25
+ cobweb_launcher-0.0.6.dist-info/METADATA,sha256=TDawOhbZk0ugagG8ZitV2rv_3iVjaKDu7S_847R6VH4,1225
26
+ cobweb_launcher-0.0.6.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
27
+ cobweb_launcher-0.0.6.dist-info/top_level.txt,sha256=4GETBGNsKqiCUezmT-mJn7tjhcDlu7nLIV5gGgHBW4I,7
28
+ cobweb_launcher-0.0.6.dist-info/RECORD,,
cobweb/base/__init__.py DELETED
File without changes