作者 lemon

增加SqlAlchemy版本管理功能

@@ -2,8 +2,11 @@ @@ -2,8 +2,11 @@
2 __pycache__/ 2 __pycache__/
3 *.py[cod] 3 *.py[cod]
4 *$py.class 4 *$py.class
  5 +
5 test*.py 6 test*.py
6 -db/ 7 +db/*.db
  8 +db/versions/*
  9 +
7 filecache/ 10 filecache/
8 # C extensions 11 # C extensions
9 *.so 12 *.so
  1 +# A generic, single database configuration.
  2 +
  3 +[alembic]
  4 +# path to migration scripts
  5 +script_location = db
  6 +
  7 +# template used to generate migration files
  8 +# file_template = %%(rev)s_%%(slug)s
  9 +
  10 +# timezone to use when rendering the date
  11 +# within the migration file as well as the filename.
  12 +# string value is passed to dateutil.tz.gettz()
  13 +# leave blank for localtime
  14 +# timezone =
  15 +
  16 +# max length of characters to apply to the
  17 +# "slug" field
  18 +# truncate_slug_length = 40
  19 +
  20 +# set to 'true' to run the environment during
  21 +# the 'revision' command, regardless of autogenerate
  22 +# revision_environment = false
  23 +
  24 +# set to 'true' to allow .pyc and .pyo files without
  25 +# a source .py file to be detected as revisions in the
  26 +# versions/ directory
  27 +# sourceless = false
  28 +
  29 +# version location specification; this defaults
  30 +# to db/versions. When using multiple version
  31 +# directories, initial revisions must be specified with --version-path
  32 +# version_locations = %(here)s/bar %(here)s/bat db/versions
  33 +
  34 +# the output encoding used when revision files
  35 +# are written from script.py.mako
  36 +# output_encoding = utf-8
  37 +# sqlalchemy.url = driver://user:[email protected]/dbname
  38 +sqlalchemy.url = sqlite:///db/userlist.db
  39 +
  40 +[post_write_hooks]
  41 +# post_write_hooks defines scripts or Python functions that are run
  42 +# on newly generated revision scripts. See the documentation for further
  43 +# detail and examples
  44 +
  45 +# format using "black" - use the console_scripts runner, against the "black" entrypoint
  46 +# hooks=black
  47 +# black.type=console_scripts
  48 +# black.entrypoint=black
  49 +# black.options=-l 79
  50 +
  51 +# Logging configuration
  52 +[loggers]
  53 +keys = root,sqlalchemy,alembic
  54 +
  55 +[handlers]
  56 +keys = console
  57 +
  58 +[formatters]
  59 +keys = generic
  60 +
  61 +[logger_root]
  62 +level = WARN
  63 +handlers = console
  64 +qualname =
  65 +
  66 +[logger_sqlalchemy]
  67 +level = WARN
  68 +handlers =
  69 +qualname = sqlalchemy.engine
  70 +
  71 +[logger_alembic]
  72 +level = INFO
  73 +handlers =
  74 +qualname = alembic
  75 +
  76 +[handler_console]
  77 +class = StreamHandler
  78 +args = (sys.stderr,)
  79 +level = NOTSET
  80 +formatter = generic
  81 +
  82 +[formatter_generic]
  83 +format = %(levelname)-5.5s [%(name)s] %(message)s
  84 +datefmt = %H:%M:%S
@@ -10,9 +10,9 @@ from threading import Thread @@ -10,9 +10,9 @@ from threading import Thread
10 from fbchat import ThreadType, Message, \ 10 from fbchat import ThreadType, Message, \
11 ShareAttachment, FileAttachment, AudioAttachment, VideoAttachment, ImageAttachment, Sticker, LocationAttachment 11 ShareAttachment, FileAttachment, AudioAttachment, VideoAttachment, ImageAttachment, Sticker, LocationAttachment
12 12
  13 +from db import models
13 from lib import common 14 from lib import common
14 from lib.facebook import FacebookClient 15 from lib.facebook import FacebookClient
15 -from lib.sqlhelper import UserList, Status  
16 from munch import Munch 16 from munch import Munch
17 17
18 log = logging.getLogger(__name__) 18 log = logging.getLogger(__name__)
@@ -27,19 +27,19 @@ class CallBack(): @@ -27,19 +27,19 @@ class CallBack():
27 print('【%s】' % type_, code, msg) 27 print('【%s】' % type_, code, msg)
28 28
29 def onLoggingIn(self, email, password, cookie, user_agent=None): 29 def onLoggingIn(self, email, password, cookie, user_agent=None):
30 - user_obj = UserList.get(email=email) 30 + user_obj = models.UserList.get(email=email)
31 if not user_obj: 31 if not user_obj:
32 - user_obj = UserList.insert(email=email, password=password, status=Status.LOGGINE, cookie=cookie, 32 + user_obj = models.UserList.insert(email=email, password=password, status=common.Status.LOGGINE, cookie=cookie,
33 user_agent=user_agent) 33 user_agent=user_agent)
34 else: 34 else:
35 - user_obj.set(status=Status.LOGGINE) 35 + user_obj.set(status=common.Status.LOGGINE)
36 36
37 return user_obj 37 return user_obj
38 38
39 def onLoggedIn(self, client: FacebookClient): 39 def onLoggedIn(self, client: FacebookClient):
40 client.user_obj.set( 40 client.user_obj.set(
41 fbid=client.uid, 41 fbid=client.uid,
42 - status=Status.ONLINE, 42 + status=common.Status.ONLINE,
43 cookie=client.get_cookie(), 43 cookie=client.get_cookie(),
44 user_agent=client.get_user_agent() 44 user_agent=client.get_user_agent()
45 ) 45 )
@@ -55,8 +55,8 @@ class CallBack(): @@ -55,8 +55,8 @@ class CallBack():
55 55
56 def onLoggingError(self, email, reason, taskid=0): 56 def onLoggingError(self, email, reason, taskid=0):
57 57
58 - u = UserList.get(email=email)  
59 - if u: u.set(status=Status.FAILED) 58 + u = models.UserList.get(email=email)
  59 + if u: u.set(status=common.Status.FAILED)
60 60
61 client = Munch(email=email) 61 client = Munch(email=email)
62 self._notify_( 62 self._notify_(
@@ -66,7 +66,7 @@ class CallBack(): @@ -66,7 +66,7 @@ class CallBack():
66 ) 66 )
67 67
68 def onLogout(self, client): 68 def onLogout(self, client):
69 - client.user_obj.set(status=Status.OFFLINE) 69 + client.user_obj.set(status=common.Status.OFFLINE)
70 70
71 self._notify_( 71 self._notify_(
72 type_="account", 72 type_="account",
@@ -17,10 +17,10 @@ from munch import Munch @@ -17,10 +17,10 @@ from munch import Munch
17 17
18 from conf import settings 18 from conf import settings
19 from core import callback, command 19 from core import callback, command
20 -from lib import control_server 20 +from db import models
  21 +from lib import control_server, common
21 from lib.common import TaskStatus 22 from lib.common import TaskStatus
22 from lib.facebook import FacebookClient 23 from lib.facebook import FacebookClient
23 -from lib.sqlhelper import UserList, Status, Config  
24 from utils import parameter 24 from utils import parameter
25 25
26 log = logging.getLogger(__name__) 26 log = logging.getLogger(__name__)
@@ -35,9 +35,9 @@ class Monitor(callback.CallBack): @@ -35,9 +35,9 @@ class Monitor(callback.CallBack):
35 super().__init__() 35 super().__init__()
36 self._socket = self._temp_socket = None 36 self._socket = self._temp_socket = None
37 self._listenlist = dict() 37 self._listenlist = dict()
38 - self._imei = Config.get('imei', lambda: uuid.uuid1().hex)  
39 - self._name = Config.get('name', control_server.get_init_name)  
40 - self._host = Config.get('host', lambda: settings.get_server()['url']) 38 + self._imei = models.Config.get('imei', lambda: uuid.uuid1().hex)
  39 + self._name = models.Config.get('name', control_server.get_init_name)
  40 + self._host = models.Config.get('host', lambda: settings.get_server()['url'])
41 self.version = None 41 self.version = None
42 self.executor = ThreadPoolExecutor(50, 'task_thread') 42 self.executor = ThreadPoolExecutor(50, 'task_thread')
43 self.init_config = {} 43 self.init_config = {}
@@ -45,13 +45,13 @@ class Monitor(callback.CallBack): @@ -45,13 +45,13 @@ class Monitor(callback.CallBack):
45 45
46 def bind(self, socket): 46 def bind(self, socket):
47 self._socket = socket 47 self._socket = socket
48 - Config.set('host', self._socket.ws_url) 48 + models.Config.set('host', self._socket.ws_url)
49 49
50 def go_login(self): 50 def go_login(self):
51 threading.Thread(target=self._auto_login, args=(), name='auto_login_thread').start() 51 threading.Thread(target=self._auto_login, args=(), name='auto_login_thread').start()
52 52
53 def _auto_login(self): 53 def _auto_login(self):
54 - user_list = UserList.query(status=Status.ONLINE) 54 + user_list = models.UserList.query(status=common.Status.ONLINE)
55 for user in user_list: 55 for user in user_list:
56 print("自动登录->", user) 56 print("自动登录->", user)
57 self.login(user.email, user.password, user.format_cookie(), user.user_agent) 57 self.login(user.email, user.password, user.format_cookie(), user.user_agent)
  1 +Generic single-database configuration.
  1 +from logging.config import fileConfig
  2 +
  3 +from sqlalchemy import engine_from_config
  4 +from sqlalchemy import pool
  5 +
  6 +from alembic import context
  7 +
  8 +# this is the Alembic Config object, which provides
  9 +# access to the values within the .ini file in use.
  10 +config = context.config
  11 +
  12 +# Interpret the config file for Python logging.
  13 +# This line sets up loggers basically.
  14 +fileConfig(config.config_file_name)
  15 +
  16 +# add your model's MetaData object here
  17 +# for 'autogenerate' support
  18 +# from myapp import mymodel
  19 +# target_metadata = mymodel.Base.metadata
  20 +
  21 +import os, sys
  22 +
  23 +sys.path.append(os.getcwd())
  24 +from db.models import Base
  25 +target_metadata = Base.metadata
  26 +
  27 +
  28 +# other values from the config, defined by the needs of env.py,
  29 +# can be acquired:
  30 +# my_important_option = config.get_main_option("my_important_option")
  31 +# ... etc.
  32 +
  33 +
  34 +def run_migrations_offline():
  35 + """Run migrations in 'offline' mode.
  36 +
  37 + This configures the context with just a URL
  38 + and not an Engine, though an Engine is acceptable
  39 + here as well. By skipping the Engine creation
  40 + we don't even need a DBAPI to be available.
  41 +
  42 + Calls to context.execute() here emit the given string to the
  43 + script output.
  44 +
  45 + """
  46 + url = config.get_main_option("sqlalchemy.url")
  47 + context.configure(
  48 + url=url,
  49 + target_metadata=target_metadata,
  50 + literal_binds=True,
  51 + dialect_opts={"paramstyle": "named"},
  52 + )
  53 +
  54 + with context.begin_transaction():
  55 + context.run_migrations()
  56 +
  57 +
  58 +def run_migrations_online():
  59 + """Run migrations in 'online' mode.
  60 +
  61 + In this scenario we need to create an Engine
  62 + and associate a connection with the context.
  63 +
  64 + """
  65 + connectable = engine_from_config(
  66 + config.get_section(config.config_ini_section),
  67 + prefix="sqlalchemy.",
  68 + poolclass=pool.NullPool,
  69 + )
  70 +
  71 + with connectable.connect() as connection:
  72 + context.configure(
  73 + connection=connection, target_metadata=target_metadata
  74 + )
  75 +
  76 + with context.begin_transaction():
  77 + context.run_migrations()
  78 +
  79 +
  80 +if context.is_offline_mode():
  81 + run_migrations_offline()
  82 +else:
  83 + run_migrations_online()
  1 +#!/usr/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +import os
  4 +
  5 +from sqlalchemy import Column, Integer, String, create_engine
  6 +from sqlalchemy.ext.declarative import declarative_base
  7 +from sqlalchemy.orm import sessionmaker
  8 +from sqlalchemy.pool import SingletonThreadPool
  9 +
  10 +from conf.settings import user_db_path
  11 +
  12 +Base = declarative_base()
  13 +metadata = Base.metadata
  14 +
  15 +engine = create_engine('sqlite:///{}'.format(os.path.join(user_db_path, "userlist.db")),
  16 + poolclass=SingletonThreadPool,
  17 + connect_args={'check_same_thread': False})
  18 +
  19 +Session = sessionmaker(bind=engine)
  20 +session = Session()
  21 +
  22 +
  23 +class Config(Base):
  24 + __tablename__ = 'config'
  25 + id = Column(Integer, primary_key=True, autoincrement=True)
  26 + key = Column(String(50), index=True, nullable=False, unique=True)
  27 + value = Column(String(256))
  28 +
  29 + @staticmethod
  30 + def get(key, value_func=None):
  31 + conf = session.query(Config).filter_by(key=key).first()
  32 + if not conf:
  33 + if value_func:
  34 + conf = Config(key=key, value=value_func())
  35 + session.add(conf)
  36 + session.commit()
  37 + return conf.value
  38 + return None
  39 + return conf.value
  40 +
  41 + @staticmethod
  42 + def set(key, value):
  43 + conf = session.query(Config).filter_by(key=key).first()
  44 + if not conf:
  45 + conf = Config(key=key, value=value)
  46 + session.add(conf)
  47 + session.commit()
  48 + return conf
  49 + else:
  50 + conf.value = value
  51 + session.commit()
  52 + return conf
  53 +
  54 + def __repr__(self):
  55 + return "Config(key={}, value={})".format(self.key, self.value)
  56 +
  57 +
  58 +class UserList(Base):
  59 + __tablename__ = 'users'
  60 +
  61 + id = Column(Integer, primary_key=True, autoincrement=True)
  62 + email = Column(String(50), index=True, nullable=False, unique=True)
  63 + password = Column(String(50), nullable=False)
  64 + cookie = Column(String(512))
  65 + user_agent = Column(String(256))
  66 + fbid = Column(String(20), index=True)
  67 + status = Column(Integer, default=0, nullable=False, index=True)
  68 + proxy = Column(String(256))
  69 +
  70 + def __repr__(self):
  71 + return "User(id={}, email={}, password={}, cookie={}, fbid={}, status={})" \
  72 + .format(self.id, self.email, self.password, len(self.cookie) if self.cookie else None, self.fbid,
  73 + self.status)
  74 +
  75 + def format_cookie(self):
  76 + if self.cookie:
  77 + return dict([tuple(sub.split("=")) for sub in self.cookie.split('; ')])
  78 + else:
  79 + return {}
  80 +
  81 + def set(self, **kwargs):
  82 + for k, v in kwargs.items():
  83 + setattr(self, k, v)
  84 + session.commit()
  85 + return self
  86 +
  87 + @staticmethod
  88 + def update(**kwargs):
  89 + unique = tuple(kwargs)[0]
  90 + if unique == 'email':
  91 + user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()
  92 + elif unique == 'fbid':
  93 + user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()
  94 + else:
  95 + raise BaseException("条件不对")
  96 + kwargs.pop(unique)
  97 + for k, v in kwargs.items():
  98 + setattr(user_, k, v)
  99 + session.commit()
  100 + return user_
  101 +
  102 + @staticmethod
  103 + def insert(**kwargs):
  104 + u = UserList(**kwargs)
  105 + session.add(u)
  106 + session.commit()
  107 + return u
  108 +
  109 + @staticmethod
  110 + def get(**kwargs):
  111 + unique = tuple(kwargs)[0]
  112 + if unique == 'email':
  113 + user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()
  114 + elif unique == 'fbid':
  115 + user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()
  116 + else:
  117 + raise BaseException("条件不对")
  118 +
  119 + return user_
  120 +
  121 + @staticmethod
  122 + def remove(**kwargs):
  123 + unique = tuple(kwargs)[0]
  124 + if unique == 'email':
  125 + user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()
  126 + elif unique == 'fbid':
  127 + user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()
  128 + else:
  129 + raise BaseException("条件不对")
  130 + if user_:
  131 + session.delete(user_)
  132 + session.commit()
  133 + else:
  134 + raise BaseException("用户不存在")
  135 +
  136 + @staticmethod
  137 + def all() -> list:
  138 + users = session.query(UserList).all()
  139 + return users
  140 +
  141 + @staticmethod
  142 + def query(**kwargs) -> list:
  143 + users = session.query(UserList).filter_by(**kwargs).all()
  144 + return users
  1 +"""${message}
  2 +
  3 +Revision ID: ${up_revision}
  4 +Revises: ${down_revision | comma,n}
  5 +Create Date: ${create_date}
  6 +
  7 +"""
  8 +from alembic import op
  9 +import sqlalchemy as sa
  10 +${imports if imports else ""}
  11 +
  12 +# revision identifiers, used by Alembic.
  13 +revision = ${repr(up_revision)}
  14 +down_revision = ${repr(down_revision)}
  15 +branch_labels = ${repr(branch_labels)}
  16 +depends_on = ${repr(depends_on)}
  17 +
  18 +
  19 +def upgrade():
  20 + ${upgrades if upgrades else "pass"}
  21 +
  22 +
  23 +def downgrade():
  24 + ${downgrades if downgrades else "pass"}
@@ -156,6 +156,13 @@ class Exchange(Enum): @@ -156,6 +156,13 @@ class Exchange(Enum):
156 return None 156 return None
157 157
158 158
  159 +class Status():
  160 + OFFLINE = 0
  161 + ONLINE = 1
  162 + LOGGINE = 2
  163 + FAILED = 3
  164 +
  165 +
159 def todict(obj, include: list = None): 166 def todict(obj, include: list = None):
160 keys = dir(obj) 167 keys = dir(obj)
161 res = {} 168 res = {}
1 -import asyncio  
2 -import os  
3 -import threading  
4 -  
5 -from sqlalchemy import Column, Integer, String  
6 -from sqlalchemy import create_engine  
7 -from sqlalchemy.ext.declarative import declarative_base  
8 -from sqlalchemy.orm import sessionmaker  
9 -from sqlalchemy.pool import SingletonThreadPool  
10 -  
11 -from conf.settings import user_db_path  
12 -  
13 -Base = declarative_base()  
14 -  
15 -engine = create_engine('sqlite:///{}'.format(os.path.join(user_db_path, "userlist.db")),  
16 - poolclass=SingletonThreadPool,  
17 - connect_args={'check_same_thread': False})  
18 -  
19 -Session = sessionmaker(bind=engine)  
20 -session = Session()  
21 -  
22 -  
23 -class Status():  
24 - OFFLINE = 0  
25 - ONLINE = 1  
26 - LOGGINE = 2  
27 - FAILED = 3  
28 -  
29 -  
30 -class Config(Base):  
31 - __tablename__ = 'config'  
32 - id = Column(Integer, primary_key=True, autoincrement=True)  
33 - key = Column(String(50), index=True, nullable=False, unique=True)  
34 - value = Column(String(256))  
35 -  
36 - @staticmethod  
37 - def get(key, value_func=None):  
38 - conf = session.query(Config).filter_by(key=key).first()  
39 - if not conf:  
40 - if value_func:  
41 - conf = Config(key=key, value=value_func())  
42 - session.add(conf)  
43 - session.commit()  
44 - return conf.value  
45 - return None  
46 - return conf.value  
47 -  
48 - @staticmethod  
49 - def set(key, value):  
50 - conf = session.query(Config).filter_by(key=key).first()  
51 - if not conf:  
52 - conf = Config(key=key, value=value)  
53 - session.add(conf)  
54 - session.commit()  
55 - return conf  
56 - else:  
57 - conf.value = value  
58 - session.commit()  
59 - return conf  
60 -  
61 - def __repr__(self):  
62 - return "Config(key={}, value={})".format(self.key, self.value)  
63 -  
64 -  
65 -class UserList(Base):  
66 - __tablename__ = 'users'  
67 -  
68 - id = Column(Integer, primary_key=True, autoincrement=True)  
69 - email = Column(String(50), index=True, nullable=False, unique=True)  
70 - password = Column(String(50), nullable=False)  
71 - cookie = Column(String(512))  
72 - user_agent = Column(String(256))  
73 - fbid = Column(String(20), index=True)  
74 - status = Column(Integer, default=0, nullable=False, index=True)  
75 -  
76 - # proxy = Column(String(256))  
77 -  
78 - def __repr__(self):  
79 - return "User(id={}, email={}, password={}, cookie={}, fbid={}, status={})" \  
80 - .format(self.id, self.email, self.password, len(self.cookie) if self.cookie else None, self.fbid,  
81 - self.status)  
82 -  
83 - def format_cookie(self):  
84 - if self.cookie:  
85 - return dict([tuple(sub.split("=")) for sub in self.cookie.split('; ')])  
86 - else:  
87 - return {}  
88 -  
89 - def set(self, **kwargs):  
90 - for k, v in kwargs.items():  
91 - setattr(self, k, v)  
92 - session.commit()  
93 - return self  
94 -  
95 - @staticmethod  
96 - def update(**kwargs):  
97 - unique = tuple(kwargs)[0]  
98 - if unique == 'email':  
99 - user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()  
100 - elif unique == 'fbid':  
101 - user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()  
102 - else:  
103 - raise BaseException("条件不对")  
104 - kwargs.pop(unique)  
105 - for k, v in kwargs.items():  
106 - setattr(user_, k, v)  
107 - session.commit()  
108 - return user_  
109 -  
110 - @staticmethod  
111 - def insert(**kwargs):  
112 - u = UserList(**kwargs)  
113 - session.add(u)  
114 - session.commit()  
115 - return u  
116 -  
117 - @staticmethod  
118 - def get(**kwargs):  
119 - unique = tuple(kwargs)[0]  
120 - if unique == 'email':  
121 - user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()  
122 - elif unique == 'fbid':  
123 - user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()  
124 - else:  
125 - raise BaseException("条件不对")  
126 -  
127 - return user_  
128 -  
129 - @staticmethod  
130 - def remove(**kwargs):  
131 - unique = tuple(kwargs)[0]  
132 - if unique == 'email':  
133 - user_ = session.query(UserList).filter_by(email=kwargs.get(unique)).first()  
134 - elif unique == 'fbid':  
135 - user_ = session.query(UserList).filter_by(fbid=kwargs.get(unique)).first()  
136 - else:  
137 - raise BaseException("条件不对")  
138 - if user_:  
139 - session.delete(user_)  
140 - session.commit()  
141 - else:  
142 - raise BaseException("用户不存在")  
143 -  
144 - @staticmethod  
145 - def all() -> list:  
146 - users = session.query(UserList).all()  
147 - return users  
148 -  
149 - @staticmethod  
150 - def query(**kwargs) -> list:  
151 - users = session.query(UserList).filter_by(**kwargs).all()  
152 - return users  
153 -  
154 -  
155 -# Base.metadata.drop_all(engine)  
156 -Base.metadata.create_all(engine)  
157 -  
158 -if __name__ == '__main__':  
159 - print(UserList.all())  
160 - # def tes(email):  
161 - # u = UserList.get(email=email)  
162 - # u.set(status=3)  
163 - # print(u)  
164 - # threading.Thread(target=tes, args=('[email protected]',)).start()  
165 - # threading.Thread(target=tes, args=('[email protected]',)).start()  
166 - pass  
@@ -9,4 +9,5 @@ sqlalchemy==1.3.12 @@ -9,4 +9,5 @@ sqlalchemy==1.3.12
9 psutil==5.6.7 9 psutil==5.6.7
10 demjson==2.2.4 10 demjson==2.2.4
11 apscheduler==3.6.3 11 apscheduler==3.6.3
12 -pysocks==1.7.1  
  12 +pysocks==1.7.1
  13 +alembic==1.4.0