class Cryptocurrency(Currency): __tablename__ = 'cryptocurrency' __mapper_args__ = { 'polymorphic_identity': __tablename__, } symbol = Field( Unicode(10), ForeignKey(Currency.symbol), min_length=1, max_length=10, pattern=r'^[A-Z0-9]{1,10}$', primary_key=True ) # A reference to an external cryptocurrency wallet wallet_id = Field(Unicode(32)) wallet_latest_sync = Field(Integer(), default=0) withdraw_min = Field(DECIMAL(18, 8), default=Decimal('0.00001000')) withdraw_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) withdraw_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) withdraw_commission_rate = Field(Unicode(10), default="0.005") withdraw_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) deposit_min = Field(DECIMAL(18, 8), default=Decimal('0.00000001')) deposit_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) deposit_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) deposit_commission_rate = Field(Unicode(10), default="0.000") deposit_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) def calculate_withdraw_commission(self, amount: Decimal): commission = self.withdraw_static_commission if self.withdraw_commission_rate != Decimal(0): commission += amount * Decimal(self.withdraw_commission_rate) return min( commission, self.withdraw_max_commission ) if self.withdraw_max_commission != Decimal(0) else commission def calculate_deposit_commission(self, amount: Decimal): commission = self.deposit_static_commission if self.deposit_commission_rate != Decimal(0): commission += amount * Decimal(self.deposit_commission_rate) return min( commission, self.deposit_max_commission ) if self.deposit_max_commission != Decimal(0) else commission def to_dict(self): result = super().to_dict() # TODO: Get the current user's wallet_tier_policy about this currency # result['tirePolicy'] = {} result['withdrawMin'] = self.normalized_to_output(self.withdraw_min) result['withdrawMax'] = self.normalized_to_output(self.withdraw_max) result['withdrawStaticCommission'] = self.normalized_to_output(self.withdraw_static_commission) result['withdrawMaxCommission'] = self.normalized_to_output(self.withdraw_max_commission) result['depositMin'] = self.normalized_to_output(self.deposit_min) result['depositMax'] = self.normalized_to_output(self.deposit_max) result['depositStaticCommission'] = self.normalized_to_output(self.deposit_static_commission) result['depositMaxCommission'] = self.normalized_to_output(self.deposit_max_commission) return result
class BankAccount(BankingId): __tablename__ = 'bank_account' __mapper_args__ = { 'polymorphic_identity': __tablename__, } id = Field(Integer(), ForeignKey(BankingId.id), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) iban = Field(Unicode(50), pattern=r'^[A-Z]{2}[A-Z0-9]{4,10}[0-9]{5,40}$') # TODO: Should be unique if is_valid owner = Field(Unicode(100)) bic = Field(Unicode(20), pattern=r'^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$', nullable=True)
class BankCard(BankingId): __tablename__ = 'bank_card' __mapper_args__ = { 'polymorphic_identity': __tablename__, } id = Field(Integer(), ForeignKey(BankingId.id), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) pan = Field(Unicode(30), pattern=r'^([0-9]{4}-){3}[0-9]{4}$') # TODO: Should be unique if is_valid holder = Field(Unicode(100)) expiration = Field(Unicode(7), pattern=r'^[0-1]{1}/[0-9]{2,4}$', nullable=True, protected=True) # mm/yy or mm/yyyy
class PaymentGateway(DeclarativeBase): __tablename__ = 'payment_gateway' name = Field(Unicode(30), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) # # TODO: Will be deprecated and replaced by tiers cashin_min = Field(DECIMAL(18, 8), default=Decimal('0.00001000')) cashin_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) cashin_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) cashin_commission_rate = Field(Unicode(10), default="0.000") cashin_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) # # TODO: Will be deprecated and replaced by tiers cashout_min = Field(DECIMAL(18, 8), default=Decimal('0.00010000')) cashout_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) cashout_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) cashout_commission_rate = Field(Unicode(10), default="0.005") cashout_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) fiat = relationship('Fiat') def to_dict(self): result = super().to_dict() # TODO: Get the current user's fiat_tier_policy about this currency # result['tirePolicy'] = {} result['cashoutMin'] = self.fiat.normalized_to_output(self.cashout_min) result['cashoutMax'] = self.fiat.normalized_to_output(self.cashout_max) result['cashoutStaticCommission'] = self.fiat.normalized_to_output(self.cashout_static_commission) result['cashoutMaxCommission'] = self.fiat.normalized_to_output(self.cashout_max_commission) result['cashinMin'] = self.fiat.normalized_to_output(self.cashin_min) result['cashinMax'] = self.fiat.normalized_to_output(self.cashin_max) result['cashinStaticCommission'] = self.fiat.normalized_to_output(self.cashin_static_commission) result['cashinMaxCommission'] = self.fiat.normalized_to_output(self.cashin_max_commission) return result def calculate_cashout_commission(self, amount: Decimal) -> Decimal: commission = self.cashout_static_commission if self.cashout_commission_rate != Decimal(0): commission += amount * Decimal(self.cashout_commission_rate) return min( commission, self.cashout_max_commission ) if self.cashout_max_commission != Decimal(0) else commission def calculate_cashin_commission(self, amount: Decimal) -> Decimal: commission = self.cashin_static_commission if self.cashin_commission_rate != Decimal(0): commission += amount * Decimal(self.cashin_commission_rate) return min( commission, self.cashin_max_commission ) if self.cashin_max_commission != Decimal(0) else commission
class Invitation(ActivationMixin, DeclarativeBase): __tablename__ = 'invitation' code = Field(Unicode(), primary_key=True) total_sits = Field(Integer()) filled_sits = Field(Integer(), default=0) __table_args__ = (CheckConstraint( 'total_sits >= filled_sits', name='cc_invitation_total_sits_grater_that_filled'), ) @property def unfilled_sits(self): return self.total_sits - self.filled_sits @classmethod def ensure_code(cls, code): invitation = Invitation.query.filter( Invitation.code == code).with_for_update().one_or_none() if invitation is None: raise HttpNotFound() return invitation
class Board(Versioned, Base): """Model class for board. This model serve as a category to topic and also holds settings regarding how posts are created and displayed. It should always be accessed using :attr:`slug`. """ __tablename__ = "board" id = Column(Integer, primary_key=True) created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) slug = Column(String(64), unique=True, nullable=False) title = Column(Unicode(255), nullable=False) _settings = Column("settings", JSON, nullable=False, default={}) agreements = Column(Text, nullable=True) description = Column(Text, nullable=True) status = Column(BoardStatusEnum, default="open", nullable=False) def get_settings(self): if self._settings is None: return DEFAULT_BOARD_CONFIG settings = DEFAULT_BOARD_CONFIG.copy() settings.update(self._settings) return settings def set_settings(self, value): self._settings = value @declared_attr def settings(self): return synonym("_settings", descriptor=property(self.get_settings, self.set_settings))
class TicketMessage(ModifiedMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'ticket_message' id = Field(Integer(), primary_key=True) ticket_id = Field(Integer(), ForeignKey(Ticket.id)) member_id = Field(Integer(), ForeignKey('member.id')) text = Field(Unicode()) is_answer = Field(Boolean(), default=False) _attachment = Field(TicketAttachment.as_mutable(JSON), nullable=True, protected=True) ticket = relationship(Ticket, lazy='select', uselist=False, protected=True) @property def attachment(self): return self._attachment.locate() if self._attachment else None @attachment.setter def attachment(self, value): if value is not None: self._attachment = TicketAttachment.create_from(value) else: self._attachment = None def to_dict(self): result = super().to_dict() result['attachment'] = self.attachment return result
class Cashin(BankingTransaction): __tablename__ = 'cashin' __mapper_args__ = { 'polymorphic_identity': __tablename__, } id = Field(Integer(), ForeignKey(BankingTransaction.id), primary_key=True) # TODO: Add some salt to prevent man in the middle (Extra field to send on creation and check on verification) transaction_id = Field(Unicode())
class Fiat(Currency): __tablename__ = 'fiat' __mapper_args__ = { 'polymorphic_identity': __tablename__, } symbol = Field( Unicode(10), ForeignKey(Currency.symbol), min_length=1, max_length=10, pattern=r'^[A-Z0-9]{1,10}$', primary_key=True )
class BankingTransaction(ModifiedMixin, OrderingMixin, FilteringMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'banking_transaction' id = Field(Integer(), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) member_id = Field(Integer(), ForeignKey('member.id')) # FIXME: Change the name to `member_id` payment_gateway_name = Field(Unicode(30), ForeignKey('payment_gateway.name')) amount = Field(DECIMAL(18, 8)) # Value without commission commission = Field(DECIMAL(18, 8), default=Decimal(0)) error = Field(Unicode(), nullable=True) reference_id = Field(Unicode(260), nullable=True) banking_id_id = Field(Integer(), ForeignKey('banking_id.id'), protected=True) payment_gateway = relationship('PaymentGateway') banking_id = relationship('BankingId') type = Field(Unicode(50)) member = relationship('Member') __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type }
class BankingId(TimestampMixin, DeclarativeBase): __tablename__ = 'banking_id' id = Field(Integer(), primary_key=True) client_id = Field(Integer(), ForeignKey('client.id')) is_verified = Field(Boolean(), default=False) error = Field(Unicode(), nullable=True) client = relationship('Client', lazy='select', protected=True) type = Field(Enum('bank_account', 'bank_card', name='banking_id_type')) __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type }
class Ticket(ModifiedMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'ticket' id = Field(Integer(), primary_key=True) title = Field(Unicode()) member_id = Field(Integer(), ForeignKey('member.id')) department_id = Field(Integer(), ForeignKey('ticket_department.id')) closed_at = Field(DateTime(), nullable=True) department = relationship('TicketDepartment', uselist=False) @hybrid_property def is_closed(self): return self.closed_at is not None @is_closed.setter def is_closed(self, value): self.closed_at = datetime.now() if value else None @is_closed.expression def is_closed(self): # noinspection PyUnresolvedReferences return self.closed_at.isnot(None) @classmethod def import_value(cls, column, v): # noinspection PyUnresolvedReferences if column.key == cls.is_closed.key and not isinstance(v, bool): return str(v).lower() == 'true' # noinspection PyUnresolvedReferences return super().import_value(column, v) def to_dict(self): result = super().to_dict() first_message = DBSession.query(TicketMessage) \ .filter(TicketMessage.ticket_id == self.id) \ .order_by(TicketMessage.created_at) \ .first() result['firstMessage'] = first_message.to_dict() if (first_message is not None) else None return result
class Board(Versioned, Base): """Model class for board. This model serve as a category to topic and also holds settings regarding how posts are created and displayed. It should always be accessed using :attr:`slug`. """ __tablename__ = 'board' id = Column(Integer, primary_key=True) created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) slug = Column(String(64), unique=True, nullable=False) title = Column(Unicode(255), nullable=False) _settings = Column('settings', JsonType, nullable=False, default={}) agreements = Column(Text, nullable=True) description = Column(Text, nullable=True) status = Column(Enum('open', 'restricted', 'locked', 'archived', name='board_status'), default='open', nullable=False) def get_settings(self): settings = DEFAULT_BOARD_CONFIG.copy() settings.update(self._settings) return settings def set_settings(self, value): self._settings = value @declared_attr def settings(cls): return synonym('_settings', descriptor=property(cls.get_settings, cls.set_settings))
class Topic(Versioned, Base): """Model class for topic. This model only holds topic metadata such as title or its associated board. The actual content of a topic belongs to :class:`Post`. """ __tablename__ = "topic" id = Column(Integer, primary_key=True) created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) board_id = Column(Integer, ForeignKey("board.id"), nullable=False) title = Column(Unicode(255), nullable=False) status = Column(TopicStatusEnum, default="open", nullable=False) board = relationship( "Board", backref=backref( "topics", lazy="dynamic", cascade="all,delete", order_by=desc( func.coalesce( select([TopicMeta.bumped_at ]).where(TopicMeta.topic_id == id).as_scalar(), created_at, )), ), ) QUERY = ( ("single_post", re.compile("^(\d+)$")), ("ranged_posts", re.compile("^(\d+)?\-(\d+)?$")), ("recent_posts", re.compile("^l(\d+)$")), ("recent_posts", re.compile("^recent$")), ) def scoped_posts(self, query=None): """Return single post or multiple posts according to `query`. If `query` is not given, this method is an equivalent of calling :attr:`posts` directly. This method always returns an iterator. Single numeric (e.g. "253") Returns a single post that matches the number. For example if "253" is given, then an iterator containing post number "253" is returned. Ranged query (e.g. "100-150") Returns all posts within range. If start number is missing ("-150") or end number is missing ("100-") then the first post and last post are automatically assumed. Recent query (e.g. "l30", "recent") Returns the n last posts where n is the number after "l" letter. If named "recent" is given, then a default value of last 20 posts is used instead. """ if query is None: return self.posts.all() else: for handler, matcher in self.QUERY: match = matcher.match(str(query)) if match: fn = getattr(self, handler) return fn(*match.groups()) return [] def single_post(self, number=None): """Returns an iterator that contains a single post that matches `number`. If post with such number could not be found, an empty iterator is returned. """ if not number: number = -1 return self.posts.filter_by(number=int(number)).all() def ranged_posts(self, start=None, end=None): """Returns a range of post between `start` and `end`. When `start` or `end` is empty, the first and last posts are assumed respectively. """ if start is None: start = 1 if end is None: query = Post.number >= start else: query = Post.number.between(start, end) return self.posts.filter(query).all() def recent_posts(self, count=30): """Returns recent `count` number of posts associated with this topic. Defaults to 30 posts if `count` is not given. """ return (self.posts.order_by(False).order_by(desc( Post.number)).limit(count).all()[::-1])
class Client(Member): __tablename__ = 'client' __mapper_args__ = { 'polymorphic_identity': __tablename__, } # noinspection PyArgumentList def __init__(self): # TODO: Move it to somewhere better self.evidence = ClientEvidence() id = Field(Integer, ForeignKey(Member.id), primary_key=True) has_second_factor = Field(Boolean(), default=False) invitation_code = Field(Unicode(), ForeignKey('invitation.code'), nullable=True) email_verified_at = Field(DateTime(), nullable=True, protected=True) evidence_verified_at = Field(DateTime(), nullable=True, protected=True) # FIXME: is `select` the best choice? evidence = relationship('ClientEvidence', lazy='select', uselist=False, protected=True) @hybrid_property def is_email_verified(self): return self.email_verified_at is not None @is_email_verified.setter def is_email_verified(self, value): self.email_verified_at = datetime.now() if value else None @is_email_verified.expression def is_email_verified(self): # noinspection PyUnresolvedReferences return self.email_verified_at.isnot(None) @hybrid_property def is_evidence_verified(self): return self.evidence_verified_at is not None @is_evidence_verified.setter def is_evidence_verified(self, value): self.evidence_verified_at = datetime.now() if value else None @is_evidence_verified.expression def is_evidence_verified(self): # noinspection PyUnresolvedReferences return self.evidence_verified_at.isnot(None) @classmethod def import_value(cls, column, v): if column.key == cls.is_email_verified.key and not isinstance(v, bool): return str(v).lower() == 'true' if column.key == cls.is_evidence_verified.key and not isinstance( v, bool): return str(v).lower() == 'true' return super().import_value(column, v) @property def roles(self): roles = ['client'] if self.is_evidence_verified: return roles + ['trusted_client'] elif self.is_email_verified: return roles + ['semitrusted_client'] else: return roles + ['distrusted_client'] @classmethod def is_exists(cls, client_id): return cls.query.filter(cls.id == client_id).count() > 0
class Currency(OrderingMixin, FilteringMixin, DeclarativeBase): """ For exp. for 'BTC' we'll use: * smallest_unit_scale = -8 * normalization_scale = 0 For exp. for 'ETH' we'll use: * smallest_unit_scale = -18 * normalization_scale = -1 For exp. for 'IRR' we'll use: * smallest_unit_scale = 0 * normalization_scale = -8 """ __tablename__ = 'currency' symbol = Field(Unicode(10), min_length=1, max_length=10, pattern=r'^[A-Z0-9]{1,10}$', primary_key=True) name = Field(Unicode(25), min_length=1, max_length=25) normalization_scale = Field(Integer(), default=0) smallest_unit_scale = Field(Integer(), default=0) type = Field(Enum('fiat', 'cryptocurrency', name='currency_type')) __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type } def smallest_unit_to_normalized(self, number): """ :return: """ if number is None: return None return Decimal(number).scaleb(self.smallest_unit_scale + self.normalization_scale) def normalized_to_output(self, number: Decimal): """ :return: """ if number is None: return None if not isinstance(number, Decimal): number = Decimal(number) return ('{:.' + str(max(0, -self.smallest_unit_scale)) + 'f}') \ .format(number.scaleb(- self.normalization_scale)) def normalized_smallest_unit(self, number: Decimal): """ :return: """ if number is None: return None if not isinstance(number, Decimal): number = Decimal(number) return '{:.0f}'.format(number.scaleb(-self.smallest_unit_scale - self.normalization_scale)) def input_to_normalized(self, number: str, strict=True): """ :return: """ if number is None: return None if strict and \ ( (not (self.smallest_unit_scale >= 0 and len(number.split('.')) == 1)) and (len(number.split('.')) == 1 or len(number.split('.')[1]) != -self.smallest_unit_scale) ): raise HttpBadRequest(f'This number is in {self.symbol} unit and this currency should be presented with ' f'the exact ${-self.smallest_unit_scale} digits behind the floating-point', 'bad-number-format') number = Decimal(number) return number.scaleb(-self.normalization_scale) def smallest_unit_to_output(self, number): if number is None: return None return self.normalized_to_output(self.smallest_unit_to_normalized(number)) # def normalized_to_smallest_unit(self, number): # if number is None: # return None # if not isinstance(number, Decimal): # number = Decimal(number) # return self.normalized_to_output(self.smallest_unit_to_normalized(number)) def to_dict(self): result = super().to_dict() # TODO: Get the current user's wallet_tier_policy about this currency # result['tirePolicy'] = {} return result @classmethod def format_normalized_string(cls, number: Decimal): return '{:.8f}'.format(number)
class ClientEvidence(ModifiedMixin, DeclarativeBase): __tablename__ = 'client_evidence' client_id = Field(Integer(), ForeignKey('member.id'), primary_key=True) first_name = Field(Unicode(50), nullable=True) last_name = Field(Unicode(50), nullable=True) mobile_phone = Field(Unicode(50), min_length=10, pattern=r'^\+[1-9]{1}[0-9]{3,14}$', watermark='Mobile Phone', example='+989876543210', nullable=True, unique=True) fixed_phone = Field(Unicode(50), min_length=10, pattern=r'^\+[1-9]{1}[0-9]{3,14}$', watermark='Fixed Phone', example='+982167653945', nullable=True, unique=True) gender = Field(Enum('male', 'female', name='gender'), nullable=True) birthday = Field(DateTime(), nullable=True) city_id = Field(Integer, ForeignKey('city.id'), nullable=True) city = relationship('City', uselist=False) national_code = Field(Unicode(50), nullable=True) address = Field(Unicode(), nullable=True) _id_card = Field(ClientIdCard.as_mutable(JSON), nullable=True, protected=True) # Attachment _id_card_secondary = Field(ClientIdCard.as_mutable(JSON), nullable=True, protected=True) # Attachment error = Field(Unicode(), nullable=True) @property def id_card(self): return self._id_card.locate() if self._id_card else None @id_card.setter def id_card(self, value): if value is not None: self._id_card = ClientIdCard.create_from(value) else: self._id_card = None @property def id_card_secondary(self): return self._id_card_secondary.locate( ) if self._id_card_secondary else None @id_card_secondary.setter def id_card_secondary(self, value): if value is not None: self._id_card_secondary = ClientIdCard.create_from(value) else: self._id_card_secondary = None def to_dict(self): result = super().to_dict() result['idCard'] = self.id_card result['idCardSecondary'] = self.id_card_secondary return result
class Market(OrderingMixin, FilteringMixin, DeclarativeBase): """ Currency pairs are sometimes then written by concatenating the ISO currency codes (ISO 4217) of the base currency and the counter currency, separating them with a slash character. Often the slash character is omitted, alternatively the slash may be replaced by and etc. A widely traded currency pair is the relation of the euro against the US dollar, designated as EUR/USD. The quotation EUR/USD 1.2500 means that one euro is exchanged for 1.2500 US dollars. Here, EUR is the base currency and USD is the quote currency(counter currency). This means that 1 Euro can be exchangeable to 1.25 US Dollars. Reference: https://en.wikipedia.org/wiki/Currency_pair """ __tablename__ = 'market' name = Field(Unicode(20), pattern=r'^[A-Z0-9]{1,10}_[A-Z0-9]{1,10}$', primary_key=True) # e.g. btc_usd base_currency_symbol = Field(Unicode(), ForeignKey('currency.symbol'), protected=True) quote_currency_symbol = Field(Unicode(), ForeignKey('currency.symbol'), protected=True) base_currency = relationship('Currency', foreign_keys=[base_currency_symbol]) quote_currency = relationship('Currency', foreign_keys=[quote_currency_symbol]) buy_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100')) buy_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) sell_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100')) sell_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) taker_commission_rate = Field(Unicode(10), default='0.4') maker_commission_rate = Field(Unicode(10), default='0.1') # taker_static_commission = Field(BigInteger(), default=0) # taker_permille_commission = Field(Integer(), default=0) # taker_max_commission = Field(BigInteger(), default=0) # maker_static_commission = Field(BigInteger(), default=0) # maker_permille_commission = Field(Integer(), default=0) # maker_max_commission = Field(BigInteger(), default=0) def to_dict(self): result = super().to_dict() # TODO: Get the current user's wallet_tier_policy about this currency # result['tirePolicy'] = {} result['buyAmountMin'] = self.base_currency.normalized_to_output( self.buy_amount_min) result['buyAmountMax'] = self.base_currency.normalized_to_output( self.buy_amount_max) result['sellAmountMin'] = self.base_currency.normalized_to_output( self.sell_amount_min) result['sellAmountMax'] = self.base_currency.normalized_to_output( self.sell_amount_max) return result def get_last_price(self): try: return Decimal(stexchange_client.market_last(self.name)) except StexchangeException as e: raise stexchange_http_exception_handler(e) def validate_ranges(self, type_, total_amount, price=None): # TODO: Review and rewrite the price threshold validator # threshold = Decimal(settings.trader.price_threshold_permille) # price_rate = Decimal(1000 * (price or self.get_last_price()) / self.get_last_price()) - Decimal(1000) if type_ == 'buy': # if price_rate > threshold: # raise HttpBadRequest('Price not in valid range', 'price-not-in-range') if total_amount < self.buy_amount_min or \ (self.buy_amount_max != 0 and total_amount > self.buy_amount_max): raise HttpBadRequest('Amount not in range', 'amount-not-in-range') elif type_ == 'sell': # if price_rate < -threshold: # raise HttpBadRequest('Price not in valid range', 'price-not-in-range') if total_amount < self.sell_amount_min or \ (self.sell_amount_max != 0 and total_amount > self.sell_amount_max): raise HttpBadRequest('Amount not in range', 'amount-not-in-range')
user='******', pwd='root', host='localhost', port='3306', db_name='iaasms' ) # pool_recycle=3600 连接超时参数 engine = create_engine(url) table = Table( 'tom_test', MetaData(), Column('id', INTEGER, primary_key=True), Column('start_time', INTEGER, index=False), Column('last_time', INTEGER, nullable=False), Column('count', INTEGER, nullable=False), Column('region', Unicode(20, _warn_on_bytestring=False)) ) # 创建表 table.create(engine, True) def _formatter_data(res): """ sqlalchemy.engine.result.ResultProxy 对象数据提取 res.cursor._rows # 数据 res._metadata.keys 或 res.cursor.description # 数据库表字段名 :param res: :return: list """
class TicketDepartment(DeclarativeBase): __tablename__ = 'ticket_department' id = Field(Integer(), primary_key=True) title = Field(Unicode(50))
class Member(ModifiedMixin, ActivationMixin, OrderingMixin, FilteringMixin, DeclarativeBase): __tablename__ = 'member' id = Field(Integer, primary_key=True) _password = Field('password', Unicode(), index=True, json='password', protected=True, min_length=6) email = Field( Unicode(), unique=True, index=True, pattern=r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)') type = Field(Unicode(50)) @classmethod def _hash_password(cls, password): salt = hashlib.sha256() salt.update(os.urandom(60)) salt = salt.hexdigest() hashed_pass = hashlib.sha256() # Make sure password is a str because we cannot hash unicode objects hashed_pass.update((password + salt).encode('utf-8')) hashed_pass = hashed_pass.hexdigest() password = salt + hashed_pass return password def _set_password(self, password): """Hash ``password`` on the fly and store its hashed version.""" min_length = self.__class__.password.info['min_length'] if len(password) < min_length: raise HttpBadRequest( 'Please enter at least %d characters for password field.' % min_length) self._password = self._hash_password(password) def _get_password(self): """Return the hashed version of the password.""" return self._password password = synonym('_password', descriptor=property(_get_password, _set_password), info=dict(protected=True)) def validate_password(self, password): """ Check the password against existing credentials. :param password: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the hashed one in the database. :type password: unicode object. :return: Whether the password is valid. :rtype: bool """ hashed_pass = hashlib.sha256() hashed_pass.update((password + self.password[:64]).encode('utf-8')) return self.password[64:] == hashed_pass.hexdigest() def create_jwt_principal(self, session_id=None): # FIXME: IMPORTANT Include user password as salt in signature if session_id is None: session_id = str(uuid.uuid4()) return JwtPrincipal( dict(id=self.id, roles=self.roles, email=self.email, sessionId=session_id)) @classmethod def current(cls): if context.identity is None: raise HttpUnauthorized() return cls.query.filter(cls.email == context.identity.email).one() def change_password(self, current_password, new_password): if not self.validate_password(current_password): raise HttpBadRequest() self.password = new_password def create_refresh_principal(self): return JwtRefreshToken(dict(id=self.id)) @property def sessions(self): return context.application.__authenticator__.get_member_sessions_info( self.id) __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type }
# Local copy of SQLAlchemy's private type map # Copyright (C) 2005-2021 the SQLAlchemy authors and contributors _type_map = { int: Integer(), float: Float(), bool: BOOLEANTYPE, decimal.Decimal: Numeric(), dt.date: Date(), dt.datetime: DateTime(), dt.time: Time(), dt.timedelta: Interval(), type(None): NULLTYPE, bytes: LargeBinary(), str: Unicode(), } def _to_sql( func: Callable[..., Any], argtypes: Optional[Sequence[Type_]] = None, rettype: Type_ = "", ) -> text: class ListAppender(list): def __call__(self, *args: Any) -> None: [self.append(arg) for arg in args] SQL_INDENT = " " # 2 spaces PY_INDENT = " " # 4 spaces SPACE = " " # Single Space