I have recently been trying to improve the quality of my code. Towards this goal I want to start writing unit tests and I also am trying to implement things i have read as best practices. I have read that its better to have a class raise exceptions over returning error codes. In the below class I have an init method that has dependency requirements based on other arguments being passed in.
Extra details... The class is from a contracts module that has 3 classes. Contract, ContractLine, ContractCustomer. Contracts can be of two types ('item','vendor') and a contract can also be a group contract. Group contracts are one contract shared amount many customers. A "normal" or non group contract would only have one customer these are all sqlalchemy declarative classes. TimeUserMixin is a class that provides create_time,modify_time, create_user,modify_user to classes where i want to track who made changes and when the changes were made.
My Questions ...
- Are exceptions the right way to go or is there a "better" way to do what I am trying to do.
- How would I write a tests for this init method?
Is it a bad idea to pass in other objects to my classes verse passing in the objects id(db id)?
class Contract(DeclarativeBase,TimeUserMixin): """ Contract definition. This is the customer contract definition. Valid contract_type's = 'item'|'vendor' """ __tablename__ = 'contracts' contract_id = Column(Integer, primary_key=True,autoincrement=True) code = Column(Unicode(25),nullable=False) description = Column(Unicode(100),nullable=False) notes = Column(Unicode()) is_group_contract = Column(Boolean,nullable=False,default=False) contract_type = Column(Unicode(20)) start_date = Column(Date,nullable=False,default=datetime.date.today) end_date = Column(Date,nullable=False) reminder_date = Column(Date,nullable=False) discount_perc = Column(Numeric(precision=5,scale=4),nullable=False,default=Decimal('0.0')) vendor_id = Column(Integer,ForeignKey('vendors.vendor_id')) vendor = relation('Vendor',backref=backref('contracts'), primaryjoin='Contract.vendor_id == Vendor.vendor_id') _CONTRACT_TYPES = ('item','vendor') @property def customer(self): """ Returs Contracts Customer Returns the customer object if contract is not a group contract. If contract is a group contract raise exception AttributeError """ if self.is_group_contract: raise AttributeError('customer property not avaible on group contracts.') return self.customers[0] def add_line(self,item,price,min_qty=None): """Adds a new contract line to the contract.""" new_line = ContractLine(self,item,price,min_qty) DBSession.add(new_line) def add_customer(self,customer): """ Adds a customer to a contract. customer -- Customer object Raises TypeError if not a group contract and a cutomer is already assigned. """ if not self.is_group_contract and \ len(self.customers) > 1: raise TypeError('Contract is not a group contract, and already has a customer assigned.') new_customer = ContractCustomer(self,customer) DBSession.add(new_customer) def __init__(self,code,description,contract_type, start_date,end_date,reminder_date, customer=None,isgroup=False,vendor=None, discount_perc=None): """ Create new contract Requires 'contract_type' ['item','vendor'] if not a group contract requires customer object if vendor contract requires vendor object and discount perc """ self.code = code self.description = description self.contract_type = contract_type self.start_date = start_date self.end_date = end_date self.reminder_date = reminder_date if contract_type not in self._CONTRACT_TYPES: raise AttributeError("Valid contract types are 'item' & 'vendor'") if isgroup: if customer: raise AttributeError("Group contracts should not have 'customer' passed in") self.is_group_contract = True else: if customer: self.add_customer(customer) else: raise AttributeError('Customer required for non group contracts.') if contract_type == 'vendor': if vendor and discount_perc: self.vendor = vendor self.discount_perc = discount_perc else: if not vendor: raise AttributeError('Vendor contracts require vendor to be passed in') if not discount_perc: raise AttributeError('Vendor contracts reqire discount_perc(Decimal)') class ContractLine(DeclarativeBase,TimeUserMixin): """Contract Line Definition""" __tablename__ = 'contract_lines' line_id = Column(Integer, primary_key=True,autoincrement=True) min_qty = Column(Integer,default=0,nullable=False) contract_price = Column(Numeric(precision=13,scale=2)) contract_id = Column(Integer,ForeignKey('contracts.contract_id'),nullable=False) contract = relation(Contract,backref=backref('lines')) item_id = Column(Integer,ForeignKey('items.item_id'),nullable=False) item = relation('Item',backref=backref('contract_lines')) def __init__(self,contract,item,price,min_qty=None): """Create new contract line""" self.contract = contract self.item = item self.contract_price = price if min_qty: self.min_qty = min_qty class ContractCustomer(DeclarativeBase): """ Contract customer definition Private class should only be accessed from Contract class """ __tablename__ = 'contract_customers' contract_customer_id = Column(Integer, primary_key=True,autoincrement=True) contract_id = Column(Integer,ForeignKey('contracts.contract_id'),nullable=False) contract = relation(Contract,backref=backref('customers'), primaryjoin="ContractCustomer.contract_id == Contract.contract_id") customer_id = Column(Integer,ForeignKey('customers.customer_id')) customer = relation(m_ar.Customer,backref=backref('contracts'), primaryjoin='Customer.customer_id==ContractCustomer.customer_id') def __init__(self,contract,customer): """ Assigns customer to contract contract -- contract object customer -- customer object """ self.contract = contract self.customer = customer