<commit_before># Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

from __future__ import unicode_literals
import frappe

from frappe.utils import cint, getdate, cstr, flt, add_days
import datetime
from frappe import _, ValidationError

from erpnext.controllers.stock_controller import StockController

class SerialNoCannotCreateDirectError(ValidationError): pass
class SerialNoCannotCannotChangeError(ValidationError): pass
class SerialNoNotRequiredError(ValidationError): pass
class SerialNoRequiredError(ValidationError): pass
class SerialNoQtyError(ValidationError): pass
class SerialNoItemError(ValidationError): pass
class SerialNoWarehouseError(ValidationError): pass
class SerialNoStatusError(ValidationError): pass
class SerialNoNotExistsError(ValidationError): pass
class SerialNoDuplicateError(ValidationError): pass

class DocType(StockController):
	def __init__(self, doc, doclist=None):
		self.doc = doc
		self.doclist = doclist or []
		self.via_stock_ledger = False

	def validate(self):
		if self.doc.fields.get("__islocal") and self.doc.warehouse:
			frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be \
				set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)

		self.validate_warranty_status()
		self.validate_amc_status()
		self.validate_warehouse()
		self.validate_item()
		self.on_stock_ledger_entry()

	def validate_amc_status(self):
		if (self.doc.maintenance_status == 'Out of AMC' and self.doc.amc_expiry_date and getdate(self.doc.amc_expiry_date) >= datetime.date.today()) or (self.doc.maintenance_status == 'Under AMC' and (not self.doc.amc_expiry_date or getdate(self.doc.amc_expiry_date) < datetime.date.today())):
			frappe.throw(self.doc.name + ": " +
				_("AMC expiry date and maintenance status mismatched"))

	def validate_warranty_status(self):
		if (self.doc.maintenance_status == 'Out of Warranty' and self.doc.warranty_expiry_date and getdate(self.doc.warranty_expiry_date) >= datetime.date.today()) or (self.doc.maintenance_status == 'Under Warranty' and (not self.doc.warranty_expiry_date or getdate(self.doc.warranty_expiry_date) < datetime.date.today())):
			frappe.throw(self.doc.name + ": " +
				_("Warranty expiry date and maintenance status mismatched"))

	def validate_warehouse(self):
		if not self.doc.fields.get("__islocal"):
			item_code, warehouse = frappe.db.get_value("Serial No",
				self.doc.name, ["item_code", "warehouse"])
			if item_code != self.doc.item_code:
				frappe.throw(_("Item Code cannot be changed for Serial No."),
					SerialNoCannotCannotChangeError)
			if not self.via_stock_ledger and warehouse != self.doc.warehouse:
				frappe.throw(_("Warehouse cannot be changed for Serial No."),
					SerialNoCannotCannotChangeError)

	def validate_item(self):
		"""
			Validate whether serial no is required for this item
		"""
		item = frappe.doc("Item", self.doc.item_code)
		if item.has_serial_no!="Yes":
			frappe.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)

		self.doc.item_group = item.item_group
		self.doc.description = item.description
		self.doc.item_name = item.item_name
		self.doc.brand = item.brand
		self.doc.warranty_period = item.warranty_period

	def set_status(self, last_sle):
		if last_sle:
			if last_sle.voucher_type == "Stock Entry":
				document_type = frappe.db.get_value("Stock Entry", last_sle.voucher_no,
					"purpose")
			else:
				document_type = last_sle.voucher_type

			if last_sle.actual_qty > 0:
				if document_type == "Sales Return":
					self.doc.status = "Sales Returned"
				else:
					self.doc.status = "Available"
			else:
				if document_type == "Purchase Return":
					self.doc.status = "Purchase Returned"
				elif last_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
					self.doc.status = "Delivered"
				else:
					self.doc.status = "Not Available"
		else:
			self.doc.status = "Not Available"

	def set_purchase_details(self, purchase_sle):
		if purchase_sle:
			self.doc.purchase_document_type = purchase_sle.voucher_type
			self.doc.purchase_document_no = purchase_sle.voucher_no
			self.doc.purchase_date = purchase_sle.posting_date
			self.doc.purchase_time = purchase_sle.posting_time
			self.doc.purchase_rate = purchase_sle.incoming_rate
			if purchase_sle.voucher_type == "Purchase Receipt":
				self.doc.supplier, self.doc.supplier_name = \
					frappe.db.get_value("Purchase Receipt", purchase_sle.voucher_no,
						["supplier", "supplier_name"])
		else:
			for fieldname in ("purchase_document_type", "purchase_document_no",
				"purchase_date", "purchase_time", "purchase_rate", "supplier", "supplier_name"):
					self.doc.fields[fieldname] = None

	def set_sales_details(self, delivery_sle):
		if delivery_sle:
			self.doc.delivery_document_type = delivery_sle.voucher_type
			self.doc.delivery_document_no = delivery_sle.voucher_no
			self.doc.delivery_date = delivery_sle.posting_date
			self.doc.delivery_time = delivery_sle.posting_time
			self.doc.customer, self.doc.customer_name = \
				frappe.db.get_value(delivery_sle.voucher_type, delivery_sle.voucher_no,
					["customer", "customer_name"])
			if self.doc.warranty_period:
				self.doc.warranty_expiry_date	= add_days(cstr(delivery_sle.posting_date),
					cint(self.doc.warranty_period))
		else:
			for fieldname in ("delivery_document_type", "delivery_document_no",
				"delivery_date", "delivery_time", "customer", "customer_name",
				"warranty_expiry_date"):
					self.doc.fields[fieldname] = None

	def get_last_sle(self):
		entries = {}
		sle_dict = self.get_stock_ledger_entries()
		if sle_dict:
			if sle_dict.get("incoming", []):
				entries["purchase_sle"] = sle_dict["incoming"][0]

			if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
				entries["last_sle"] = sle_dict["incoming"][0]
			else:
				entries["last_sle"] = sle_dict["outgoing"][0]
				entries["delivery_sle"] = sle_dict["outgoing"][0]

		return entries

	def get_stock_ledger_entries(self):
		sle_dict = {}
		for sle in frappe.db.sql("""select * from `tabStock Ledger Entry`
			where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'
			order by posting_date desc, posting_time desc, name desc""",
			("%%%s%%" % self.doc.name, self.doc.item_code), as_dict=1):
				if self.doc.name.upper() in get_serial_nos(sle.serial_no):
					if sle.actual_qty > 0:
						sle_dict.setdefault("incoming", []).append(sle)
					else:
						sle_dict.setdefault("outgoing", []).append(sle)

		return sle_dict

	def on_trash(self):
		if self.doc.status == 'Delivered':
			frappe.throw(_("Delivered Serial No ") + self.doc.name + _(" can not be deleted"))
		if self.doc.warehouse:
			frappe.throw(_("Cannot delete Serial No in warehouse. \
				First remove from warehouse, then delete.") + ": " + self.doc.name)

	def before_rename(self, old, new, merge=False):
		if merge:
			frappe.throw(_("Sorry, Serial Nos cannot be merged"))

	def after_rename(self, old, new, merge=False):
		"""rename serial_no text fields"""
		for dt in frappe.db.sql("""select parent from tabDocField
			where fieldname='serial_no' and fieldtype='Text'"""):

			for item in frappe.db.sql("""select name, serial_no from `tab%s`
				where serial_no like '%%%s%%'""" % (dt[0], old)):

				serial_nos = map(lambda i: i==old and new or i, item[1].split('\n'))
				frappe.db.sql("""update `tab%s` set serial_no = %s
					where name=%s""" % (dt[0], '%s', '%s'),
					('\n'.join(serial_nos), item[0]))

	def on_stock_ledger_entry(self):
		if self.via_stock_ledger and not self.doc.fields.get("__islocal"):
			last_sle = self.get_last_sle()
			self.set_status(last_sle.get("last_sle"))
			self.set_purchase_details(last_sle.get("purchase_sle"))
			self.set_sales_details(last_sle.get("delivery_sle"))

	def on_communication(self):
		return

def process_serial_no(sle):
	item_det = get_item_details(sle.item_code)
	validate_serial_no(sle, item_det)
	update_serial_nos(sle, item_det)

def validate_serial_no(sle, item_det):
	if item_det.has_serial_no=="No":
		if sle.serial_no:
			frappe.throw(_("Serial Number should be blank for Non Serialized Item" + ": "
				+ sle.item_code), SerialNoNotRequiredError)
	else:
		if sle.serial_no:
			serial_nos = get_serial_nos(sle.serial_no)
			if cint(sle.actual_qty) != flt(sle.actual_qty):
				frappe.throw(_("Serial No qty cannot be a fraction") + \
					(": %s (%s)" % (sle.item_code, sle.actual_qty)))
			if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)):
				frappe.throw(_("Serial Nos do not match with qty") + \
					(": %s (%s)" % (sle.item_code, sle.actual_qty)), SerialNoQtyError)

			if len(serial_nos) != len(set(serial_nos)):
				frappe.throw(_("Duplicate Serial No entered against item") +
					(": %s" % sle.item_code), SerialNoDuplicateError)

			for serial_no in serial_nos:
				if frappe.db.exists("Serial No", serial_no):
					sr = frappe.bean("Serial No", serial_no)

					if sr.doc.item_code!=sle.item_code:
						frappe.throw(_("Serial No does not belong to Item") +
							(": %s (%s)" % (sle.item_code, serial_no)), SerialNoItemError)

					if sr.doc.warehouse and sle.actual_qty > 0:
						frappe.throw(_("Same Serial No") + ": " + sr.doc.name +
							_(" can not be received twice"), SerialNoDuplicateError)

					if sle.actual_qty < 0:
						if sr.doc.warehouse!=sle.warehouse:
							frappe.throw(_("Serial No") + ": " + serial_no +
								_(" does not belong to Warehouse") + ": " + sle.warehouse,
								SerialNoWarehouseError)

						if sle.voucher_type in ("Delivery Note", "Sales Invoice") \
							and sr.doc.status != "Available":
							frappe.throw(_("Serial No status must be 'Available' to Deliver")
								+ ": " + serial_no, SerialNoStatusError)
				elif sle.actual_qty < 0:
					# transfer out
					frappe.throw(_("Serial No must exist to transfer out.") + \
						": " + serial_no, SerialNoNotExistsError)
		elif sle.actual_qty < 0 or not item_det.serial_no_series:
			frappe.throw(_("Serial Number Required for Serialized Item" + ": "
				+ sle.item_code), SerialNoRequiredError)

def update_serial_nos(sle, item_det):
	if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 and item_det.serial_no_series:
		from frappe.model.doc import make_autoname
		serial_nos = []
		for i in xrange(cint(sle.actual_qty)):
			serial_nos.append(make_autoname(item_det.serial_no_series))
		frappe.db.set(sle, "serial_no", "\n".join(serial_nos))

	if sle.serial_no:
		serial_nos = get_serial_nos(sle.serial_no)
		for serial_no in serial_nos:
			if frappe.db.exists("Serial No", serial_no):
				sr = frappe.bean("Serial No", serial_no)
				sr.make_controller().via_stock_ledger = True
				sr.doc.warehouse = sle.warehouse if sle.actual_qty > 0 else None
				sr.save()
			elif sle.actual_qty > 0:
				make_serial_no(serial_no, sle)

def get_item_details(item_code):
	return frappe.db.sql("""select name, has_batch_no, docstatus,
		is_stock_item, has_serial_no, serial_no_series
		from tabItem where name=%s""", item_code, as_dict=True)[0]

def get_serial_nos(serial_no):
	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
		if s.strip()]

def make_serial_no(serial_no, sle):
	sr = frappe.new_bean("Serial No")
	sr.doc.serial_no = serial_no
	sr.doc.item_code = sle.item_code
	sr.doc.warehouse = None
	sr.make_controller().via_stock_ledger = True
	sr.insert()
	sr.doc.warehouse = sle.warehouse
	sr.doc.status = "Available"
	sr.save()
	frappe.msgprint(_("Serial No created") + ": " + sr.doc.name)
	return sr.doc.name

def update_serial_nos_after_submit(controller, parentfield):
	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no
		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
		(controller.doc.doctype, controller.doc.name), as_dict=True)

	if not stock_ledger_entries: return

	for d in controller.doclist.get({"parentfield": parentfield}):
		serial_no = None
		for sle in stock_ledger_entries:
			if sle.voucher_detail_no==d.name:
				serial_no = sle.serial_no
				break

		if d.serial_no != serial_no:
			d.serial_no = serial_no
			frappe.db.set_value(d.doctype, d.name, "serial_no", serial_no)
<commit_msg>Update serial no maintenance status based on warranty/amc expiry date<commit_after># Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

from __future__ import unicode_literals
import frappe

from frappe.utils import cint, cstr, flt, add_days, nowdate
from frappe import _, ValidationError

from erpnext.controllers.stock_controller import StockController

class SerialNoCannotCreateDirectError(ValidationError): pass
class SerialNoCannotCannotChangeError(ValidationError): pass
class SerialNoNotRequiredError(ValidationError): pass
class SerialNoRequiredError(ValidationError): pass
class SerialNoQtyError(ValidationError): pass
class SerialNoItemError(ValidationError): pass
class SerialNoWarehouseError(ValidationError): pass
class SerialNoStatusError(ValidationError): pass
class SerialNoNotExistsError(ValidationError): pass
class SerialNoDuplicateError(ValidationError): pass

class DocType(StockController):
	def __init__(self, doc, doclist=None):
		self.doc = doc
		self.doclist = doclist or []
		self.via_stock_ledger = False

	def validate(self):
		if self.doc.fields.get("__islocal") and self.doc.warehouse:
			frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be \
				set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)

		self.set_maintenance_status()
		self.validate_warehouse()
		self.validate_item()
		self.on_stock_ledger_entry()

	def set_maintenance_status(self):
		if not self.doc.warranty_expiry_date and not self.doc.amc_expiry_date:
			self.doc.maintenance_status = None

		if self.doc.warranty_expiry_date and self.doc.warranty_expiry_date < nowdate():
			self.doc.maintenance_status = "Out of Warranty"

		if self.doc.amc_expiry_date and self.doc.amc_expiry_date < nowdate():
			self.doc.maintenance_status = "Out of AMC"

		if self.doc.amc_expiry_date and self.doc.amc_expiry_date >= nowdate():
			self.doc.maintenance_status = "Under AMC"

		if self.doc.warranty_expiry_date and self.doc.warranty_expiry_date >= nowdate():
			self.doc.maintenance_status = "Under Warranty"

	def validate_warehouse(self):
		if not self.doc.fields.get("__islocal"):
			item_code, warehouse = frappe.db.get_value("Serial No",
				self.doc.name, ["item_code", "warehouse"])
			if item_code != self.doc.item_code:
				frappe.throw(_("Item Code cannot be changed for Serial No."),
					SerialNoCannotCannotChangeError)
			if not self.via_stock_ledger and warehouse != self.doc.warehouse:
				frappe.throw(_("Warehouse cannot be changed for Serial No."),
					SerialNoCannotCannotChangeError)

	def validate_item(self):
		"""
			Validate whether serial no is required for this item
		"""
		item = frappe.doc("Item", self.doc.item_code)
		if item.has_serial_no!="Yes":
			frappe.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)

		self.doc.item_group = item.item_group
		self.doc.description = item.description
		self.doc.item_name = item.item_name
		self.doc.brand = item.brand
		self.doc.warranty_period = item.warranty_period

	def set_status(self, last_sle):
		if last_sle:
			if last_sle.voucher_type == "Stock Entry":
				document_type = frappe.db.get_value("Stock Entry", last_sle.voucher_no,
					"purpose")
			else:
				document_type = last_sle.voucher_type

			if last_sle.actual_qty > 0:
				if document_type == "Sales Return":
					self.doc.status = "Sales Returned"
				else:
					self.doc.status = "Available"
			else:
				if document_type == "Purchase Return":
					self.doc.status = "Purchase Returned"
				elif last_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
					self.doc.status = "Delivered"
				else:
					self.doc.status = "Not Available"
		else:
			self.doc.status = "Not Available"

	def set_purchase_details(self, purchase_sle):
		if purchase_sle:
			self.doc.purchase_document_type = purchase_sle.voucher_type
			self.doc.purchase_document_no = purchase_sle.voucher_no
			self.doc.purchase_date = purchase_sle.posting_date
			self.doc.purchase_time = purchase_sle.posting_time
			self.doc.purchase_rate = purchase_sle.incoming_rate
			if purchase_sle.voucher_type == "Purchase Receipt":
				self.doc.supplier, self.doc.supplier_name = \
					frappe.db.get_value("Purchase Receipt", purchase_sle.voucher_no,
						["supplier", "supplier_name"])
		else:
			for fieldname in ("purchase_document_type", "purchase_document_no",
				"purchase_date", "purchase_time", "purchase_rate", "supplier", "supplier_name"):
					self.doc.fields[fieldname] = None

	def set_sales_details(self, delivery_sle):
		if delivery_sle:
			self.doc.delivery_document_type = delivery_sle.voucher_type
			self.doc.delivery_document_no = delivery_sle.voucher_no
			self.doc.delivery_date = delivery_sle.posting_date
			self.doc.delivery_time = delivery_sle.posting_time
			self.doc.customer, self.doc.customer_name = \
				frappe.db.get_value(delivery_sle.voucher_type, delivery_sle.voucher_no,
					["customer", "customer_name"])
			if self.doc.warranty_period:
				self.doc.warranty_expiry_date	= add_days(cstr(delivery_sle.posting_date),
					cint(self.doc.warranty_period))
		else:
			for fieldname in ("delivery_document_type", "delivery_document_no",
				"delivery_date", "delivery_time", "customer", "customer_name",
				"warranty_expiry_date"):
					self.doc.fields[fieldname] = None

	def get_last_sle(self):
		entries = {}
		sle_dict = self.get_stock_ledger_entries()
		if sle_dict:
			if sle_dict.get("incoming", []):
				entries["purchase_sle"] = sle_dict["incoming"][0]

			if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
				entries["last_sle"] = sle_dict["incoming"][0]
			else:
				entries["last_sle"] = sle_dict["outgoing"][0]
				entries["delivery_sle"] = sle_dict["outgoing"][0]

		return entries

	def get_stock_ledger_entries(self):
		sle_dict = {}
		for sle in frappe.db.sql("""select * from `tabStock Ledger Entry`
			where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'
			order by posting_date desc, posting_time desc, name desc""",
			("%%%s%%" % self.doc.name, self.doc.item_code), as_dict=1):
				if self.doc.name.upper() in get_serial_nos(sle.serial_no):
					if sle.actual_qty > 0:
						sle_dict.setdefault("incoming", []).append(sle)
					else:
						sle_dict.setdefault("outgoing", []).append(sle)

		return sle_dict

	def on_trash(self):
		if self.doc.status == 'Delivered':
			frappe.throw(_("Delivered Serial No ") + self.doc.name + _(" can not be deleted"))
		if self.doc.warehouse:
			frappe.throw(_("Cannot delete Serial No in warehouse. \
				First remove from warehouse, then delete.") + ": " + self.doc.name)

	def before_rename(self, old, new, merge=False):
		if merge:
			frappe.throw(_("Sorry, Serial Nos cannot be merged"))

	def after_rename(self, old, new, merge=False):
		"""rename serial_no text fields"""
		for dt in frappe.db.sql("""select parent from tabDocField
			where fieldname='serial_no' and fieldtype='Text'"""):

			for item in frappe.db.sql("""select name, serial_no from `tab%s`
				where serial_no like '%%%s%%'""" % (dt[0], old)):

				serial_nos = map(lambda i: i==old and new or i, item[1].split('\n'))
				frappe.db.sql("""update `tab%s` set serial_no = %s
					where name=%s""" % (dt[0], '%s', '%s'),
					('\n'.join(serial_nos), item[0]))

	def on_stock_ledger_entry(self):
		if self.via_stock_ledger and not self.doc.fields.get("__islocal"):
			last_sle = self.get_last_sle()
			self.set_status(last_sle.get("last_sle"))
			self.set_purchase_details(last_sle.get("purchase_sle"))
			self.set_sales_details(last_sle.get("delivery_sle"))
			self.set_maintenance_status()

	def on_communication(self):
		return

def process_serial_no(sle):
	item_det = get_item_details(sle.item_code)
	validate_serial_no(sle, item_det)
	update_serial_nos(sle, item_det)

def validate_serial_no(sle, item_det):
	if item_det.has_serial_no=="No":
		if sle.serial_no:
			frappe.throw(_("Serial Number should be blank for Non Serialized Item" + ": "
				+ sle.item_code), SerialNoNotRequiredError)
	else:
		if sle.serial_no:
			serial_nos = get_serial_nos(sle.serial_no)
			if cint(sle.actual_qty) != flt(sle.actual_qty):
				frappe.throw(_("Serial No qty cannot be a fraction") + \
					(": %s (%s)" % (sle.item_code, sle.actual_qty)))
			if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)):
				frappe.throw(_("Serial Nos do not match with qty") + \
					(": %s (%s)" % (sle.item_code, sle.actual_qty)), SerialNoQtyError)

			if len(serial_nos) != len(set(serial_nos)):
				frappe.throw(_("Duplicate Serial No entered against item") +
					(": %s" % sle.item_code), SerialNoDuplicateError)

			for serial_no in serial_nos:
				if frappe.db.exists("Serial No", serial_no):
					sr = frappe.bean("Serial No", serial_no)

					if sr.doc.item_code!=sle.item_code:
						frappe.throw(_("Serial No does not belong to Item") +
							(": %s (%s)" % (sle.item_code, serial_no)), SerialNoItemError)

					if sr.doc.warehouse and sle.actual_qty > 0:
						frappe.throw(_("Same Serial No") + ": " + sr.doc.name +
							_(" can not be received twice"), SerialNoDuplicateError)

					if sle.actual_qty < 0:
						if sr.doc.warehouse!=sle.warehouse:
							frappe.throw(_("Serial No") + ": " + serial_no +
								_(" does not belong to Warehouse") + ": " + sle.warehouse,
								SerialNoWarehouseError)

						if sle.voucher_type in ("Delivery Note", "Sales Invoice") \
							and sr.doc.status != "Available":
							frappe.throw(_("Serial No status must be 'Available' to Deliver")
								+ ": " + serial_no, SerialNoStatusError)
				elif sle.actual_qty < 0:
					# transfer out
					frappe.throw(_("Serial No must exist to transfer out.") + \
						": " + serial_no, SerialNoNotExistsError)
		elif sle.actual_qty < 0 or not item_det.serial_no_series:
			frappe.throw(_("Serial Number Required for Serialized Item" + ": "
				+ sle.item_code), SerialNoRequiredError)

def update_serial_nos(sle, item_det):
	if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 and item_det.serial_no_series:
		from frappe.model.doc import make_autoname
		serial_nos = []
		for i in xrange(cint(sle.actual_qty)):
			serial_nos.append(make_autoname(item_det.serial_no_series))
		frappe.db.set(sle, "serial_no", "\n".join(serial_nos))

	if sle.serial_no:
		serial_nos = get_serial_nos(sle.serial_no)
		for serial_no in serial_nos:
			if frappe.db.exists("Serial No", serial_no):
				sr = frappe.bean("Serial No", serial_no)
				sr.make_controller().via_stock_ledger = True
				sr.doc.warehouse = sle.warehouse if sle.actual_qty > 0 else None
				sr.save()
			elif sle.actual_qty > 0:
				make_serial_no(serial_no, sle)

def get_item_details(item_code):
	return frappe.db.sql("""select name, has_batch_no, docstatus,
		is_stock_item, has_serial_no, serial_no_series
		from tabItem where name=%s""", item_code, as_dict=True)[0]

def get_serial_nos(serial_no):
	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
		if s.strip()]

def make_serial_no(serial_no, sle):
	sr = frappe.new_bean("Serial No")
	sr.doc.serial_no = serial_no
	sr.doc.item_code = sle.item_code
	sr.doc.warehouse = None
	sr.make_controller().via_stock_ledger = True
	sr.insert()
	sr.doc.warehouse = sle.warehouse
	sr.doc.status = "Available"
	sr.save()
	frappe.msgprint(_("Serial No created") + ": " + sr.doc.name)
	return sr.doc.name

def update_serial_nos_after_submit(controller, parentfield):
	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no
		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
		(controller.doc.doctype, controller.doc.name), as_dict=True)

	if not stock_ledger_entries: return

	for d in controller.doclist.get({"parentfield": parentfield}):
		serial_no = None
		for sle in stock_ledger_entries:
			if sle.voucher_detail_no==d.name:
				serial_no = sle.serial_no
				break

		if d.serial_no != serial_no:
			d.serial_no = serial_no
			frappe.db.set_value(d.doctype, d.name, "serial_no", serial_no)
<|endoftext|>


<commit_before>__all__ = ["main", "tree", "actions"]

import sys
if sys.hexversion < 0x2000000:
  sys.stderr.write('[SKIPPED] at least Python 2.0 is required')

  # note: exitting is a bit harsh for a library module, but we really do
  # require Python 2.0. this package isn't going to work otherwise. and if
  # a user truly wants to use this package under 1.x somehow (or to clean
  # up in some way), then they can always trap the SystemExit exception

  # we're skipping this test, not failing, so exit with 0
  sys.exit(0)

# don't export this name
del sys

import main, testcase, tree, actions, wc
<commit_msg>* subversion/tests/clients/cmdline/svntest/__init__.py<commit_after>__all__ = ["main", "tree", "actions"]

import sys
if sys.hexversion < 0x2000000:
  sys.stderr.write('[SKIPPED] at least Python 2.0 is required')

  # note: exitting is a bit harsh for a library module, but we really do
  # require Python 2.0. this package isn't going to work otherwise. and if
  # a user truly wants to use this package under 1.x somehow (or to clean
  # up in some way), then they can always trap the SystemExit exception

  # we're skipping this test, not failing, so exit with 0
  sys.exit(0)

# don't export this name
del sys

import main, tree, actions, wc
<|endoftext|>


<commit_before>"""Transitland Datastore interface."""
import json
import urllib
import urllib2

import entities

class Datastore(object):
  def __init__(self, endpoint, apitoken=None, debug=False):
    self.endpoint = endpoint
    self.debug = debug
    self.apitoken = apitoken

  def postjson(self, endpoint, data):
    if self.debug:  # pragma: no cover
      print "====== POST: %s ======"%endpoint
      print data
    req = urllib2.Request(endpoint)
    req.add_header('Content-Type', 'application/json')
    if self.apitoken:
      req.add_header('Authorization', 'Token token=%s'%self.apitoken)
    response = urllib2.urlopen(req, json.dumps(data))
    ret = json.loads(response.read())
    if self.debug:  # pragma: no cover
      print "--> Response: "
      print ret
    return ret

  def getjson(self, endpoint):
    if self.debug:  # pragma: no cover
      print "====== GET: %s ======"%endpoint
    req = urllib2.Request(endpoint)
    if self.apitoken:
      req.add_header('Authorization', 'Token token=%s'%self.apitoken)
    response = urllib2.urlopen(req)
    ret = json.loads(response.read())
    if self.debug: # pragma: no cover
      print "--> Response: "
      print ret
    return ret

  def changeset(self, data, rels=True):
    onestop_types = {
      's': 'stop',
      'r': 'route',
      'o': 'operator'
    }
    return {
       "changeset": {
         "whenToApply": "instantlyIfClean",
         "payload": {
           "changes": [
             {
               "action": "createUpdate",
               onestop_types[data['onestopId'][0]]: data
             }
           ]
         }
       }
    }

  def update_entity(self, entity, rels=True):
    data = self.changeset(entity.json_datastore(rels=rels))
    endpoint = '%s/api/v1/changesets/'%(self.endpoint)
    self.postjson(endpoint, data)

  def stops(self, point=None, radius=1000):
    endpoint = '%s/api/v1/stops'%(self.endpoint)
    if point:
      endpoint = '%s/api/v1/stops?lon=%0.8f&lat=%0.8f&r=%d'%(
        self.endpoint,
        point[0],
        point[1],
        radius
      )
    response = self.getjson(endpoint)
    stops = set()
    for i in response['stops']:
      e = entities.Stop.from_json(i)
      stops.add(e)
    return stops
<commit_msg>Add identifier keyword argument to datastore stops()<commit_after>"""Transitland Datastore interface."""
import json
import urllib
import urllib2

import entities

class Datastore(object):
  def __init__(self, endpoint, apitoken=None, debug=False):
    self.endpoint = endpoint
    self.debug = debug
    self.apitoken = apitoken

  def postjson(self, endpoint, data):
    if self.debug:  # pragma: no cover
      print "====== POST: %s ======"%endpoint
      print data
    req = urllib2.Request(endpoint)
    req.add_header('Content-Type', 'application/json')
    if self.apitoken:
      req.add_header('Authorization', 'Token token=%s'%self.apitoken)
    response = urllib2.urlopen(req, json.dumps(data))
    ret = json.loads(response.read())
    if self.debug:  # pragma: no cover
      print "--> Response: "
      print ret
    return ret

  def getjson(self, endpoint):
    if self.debug:  # pragma: no cover
      print "====== GET: %s ======"%endpoint
    req = urllib2.Request(endpoint)
    if self.apitoken:
      req.add_header('Authorization', 'Token token=%s'%self.apitoken)
    response = urllib2.urlopen(req)
    ret = json.loads(response.read())
    if self.debug: # pragma: no cover
      print "--> Response: "
      print ret
    return ret

  def changeset(self, data, rels=True):
    onestop_types = {
      's': 'stop',
      'r': 'route',
      'o': 'operator'
    }
    return {
       "changeset": {
         "whenToApply": "instantlyIfClean",
         "payload": {
           "changes": [
             {
               "action": "createUpdate",
               onestop_types[data['onestopId'][0]]: data
             }
           ]
         }
       }
    }

  def update_entity(self, entity, rels=True):
    data = self.changeset(entity.json_datastore(rels=rels))
    endpoint = '%s/api/v1/changesets/'%(self.endpoint)
    self.postjson(endpoint, data)

  def stops(self, point=None, radius=1000, identifier=None):
    endpoint = '%s/api/v1/stops'%(self.endpoint)
    if identifier:
      endpoint = '%s/api/v1/stops?identifier=%s'%(
        self.endpoint,
        identifier
      )
    if point:
      endpoint = '%s/api/v1/stops?lon=%0.8f&lat=%0.8f&r=%d'%(
        self.endpoint,
        point[0],
        point[1],
        radius
      )
    response = self.getjson(endpoint)
    stops = set()
    for i in response['stops']:
      e = entities.Stop.from_json(i)
      stops.add(e)
    return stops
<|endoftext|>


<commit_before>import MOS6502
import instructions

def disasm(memory, maxLines=0, address=-1):
    index = 0
    lines = []

    while index < len(memory):
        currInst = instructions.instructions[memory[index]]

        if address > 0:
            line = format(address+index, '04x') + ": "
        else:
            line = ''
        line += currInst.mnem + " "
        line += currInst.operType + " "
        if currInst.size > 1:
            if 'ABS' in currInst.operType:
                line += hex(memory[index+1] + (memory[index+2] << 8))
            else:
                for i in range(1, currInst.size):
                    line += hex(memory[index + i]) + " "

        lines.append(line)
        index += currInst.size

        if maxLines != 0 and len(lines) == maxLines:
            return lines

    return lines
<commit_msg>Add catch for undefined opcodes<commit_after>import MOS6502
import instructions
import code

def disasm(memory, maxLines=0, address=-1):
    index = 0
    lines = []

    while index < len(memory):
        opcode = memory[index]
        if opcode not in instructions.instructions.keys():
            print "Undefined opcode: " + hex(opcode)
            code.interact(local=locals())

        currInst = instructions.instructions[memory[index]]

        if address > 0:
            line = format(address+index, '04x') + ": "
        else:
            line = ''
        line += currInst.mnem + " "
        line += currInst.operType + " "
        if currInst.size > 1:
            if 'ABS' in currInst.operType:
                line += hex(memory[index+1] + (memory[index+2] << 8))
            else:
                for i in range(1, currInst.size):
                    line += hex(memory[index + i]) + " "

        lines.append(line)
        index += currInst.size

        if maxLines != 0 and len(lines) == maxLines:
            return lines

    return lines
<|endoftext|>


<commit_before># Copyright 2013 Red Hat, Inc.
# Copyright 2013 New Dream Network, LLC (DreamHost)
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import eventlet
import greenlet

from openstack.common.messaging._executors import base

class EventletExecutor(base.ExecutorBase):

    def __init__(self, conf, listener, callback):
        super(EventletExecutor, self).__init__(conf, listener, callback)
        self._thread = None

    def start(self):
        if self._thread is not None:
            return

        def _executor_thread():
            try:
                while True:
            except greenlet.GreenletExit:
                return

        self._thread = eventlet.spawn(_executor_thread)

    def stop(self):
        if self._thread is None:
            return
        self._thread.kill()

    def wait(self):
        if self._thread is None:
            return
        try:
            self._thread.wait()
        except greenlet.GreenletExit:
            pass
        self._thread = None
<commit_msg>Add forgotten piece of eventlet executor<commit_after># Copyright 2013 Red Hat, Inc.
# Copyright 2013 New Dream Network, LLC (DreamHost)
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import eventlet
import greenlet

from openstack.common.messaging._executors import base

class EventletExecutor(base.ExecutorBase):

    def __init__(self, conf, listener, callback):
        super(EventletExecutor, self).__init__(conf, listener, callback)
        self._thread = None

    def start(self):
        if self._thread is not None:
            return

        def _executor_thread():
            try:
                while True:
                    self._process_one_message()
            except greenlet.GreenletExit:
                return

        self._thread = eventlet.spawn(_executor_thread)

    def stop(self):
        if self._thread is None:
            return
        self._thread.kill()

    def wait(self):
        if self._thread is None:
            return
        try:
            self._thread.wait()
        except greenlet.GreenletExit:
            pass
        self._thread = None
<|endoftext|>


<commit_before>source("../../shared/qtcreator.py")

projectsPath = tempDir()
project = "untitled"

def __handlerunControlFinished__(object, runControlP):
    global runControlFinished
    runControlFinished = True

def main():
    global runControlFinished
    outputQDebug = "Output from qDebug()."
    outputStdOut = "Output from std::cout."
    outputStdErr = "Output from std::cerr."
    startApplication("qtcreator" + SettingsPath)
    installLazySignalHandler("{type='ProjectExplorer::Internal::ProjectExplorerPlugin' unnamed='1'}",
                             "runControlFinished(ProjectExplorer::RunControl*)", "__handlerunControlFinished__")
    createProject_Qt_Console(projectsPath, project)

    mainEditor = waitForObject(":Qt Creator_CppEditor::Internal::CPPEditorWidget")
    replaceEditorContent(mainEditor, "")
    typeLines(mainEditor, ["#include <QDebug>",
                           "#include <iostream>",
                           "int main(int, char *argv[])",
                           "{",
                           '    std::cout << \"' + outputStdOut + '\" << std::endl;',
                           '    std::cerr << \"' + outputStdErr + '\" << std::endl;',
                           '    qDebug() << \"' + outputQDebug + '\";'])
    # Rely on code completion for closing bracket
    invokeMenuItem("File", "Save All")
    selectFromLocator(project + ".pro")
    proEditor = waitForObject("{type='Qt4ProjectManager::Internal::ProFileEditorWidget' unnamed='1' visible='1'"
                              "window=':Qt Creator_Core::Internal::MainWindow'}", 20000)
    test.verify("CONFIG   += console" in str(proEditor.plainText), "Verifying that program is configured with console")
    setRunInTerminal(1, 0, False)

    for config in iterateBuildConfigs(1, 0):
        selectBuildConfig(1, 0, config)
        test.log("Testing build configuration: " + config)

        test.log("Running application")
        runControlFinished = False
        clickButton(waitForObject("{type='Core::Internal::FancyToolButton' text='Run' visible='1'}", 20000))
        waitFor("runControlFinished==True", 20000)
        if not runControlFinished:
            test.warning("Waiting for runControlFinished timed out")
        appOutput = str(waitForObject("{type='Core::OutputWindow' unnamed='1' visible='1'}", 20000).plainText)
        verifyOutput(appOutput, outputStdOut, "std::cout", "Application Output")
        verifyOutput(appOutput, outputStdErr, "std::cerr", "Application Output")
        verifyOutput(appOutput, outputQDebug, "qDebug()", "Application Output")
        clickButton(waitForObject(":Qt Creator_CloseButton"))

        test.log("Debugging application")
        runControlFinished = False
        invokeMenuItem("Debug", "Start Debugging", "Start Debugging")
        JIRA.performWorkaroundIfStillOpen(6853, JIRA.Bug.CREATOR, config)
        handleDebuggerWarnings(config)
        waitFor("runControlFinished==True", 20000)
        if not runControlFinished:
            test.warning("Waiting for runControlFinished timed out")
        try:
            debuggerLog = takeDebuggerLog()
            if not "MSVC" in config:
                # cout works with MSVC, too, but we don't check it since it's not supported
                verifyOutput(debuggerLog, outputStdOut, "std::cout", "Debugger Log")
                verifyOutput(debuggerLog, outputStdErr, "std::cerr", "Debugger Log")
                verifyOutput(debuggerLog, outputQDebug, "qDebug()", "Debugger Log")
        except LookupError:
            # takeDebuggerLog() expects the debugger log to not be visible.
            # If debugger log showed up automatically, previous call to takeDebuggerLog() has closed it.
            debuggerLog = takeDebuggerLog()
            if "lib\qtcreatorcdbext64\qtcreatorcdbext.dll cannot be found." in debuggerLog:
                test.fatal("qtcreatorcdbext.dll is missing in lib\qtcreatorcdbext64")
            else:
                test.fatal("Debugger log did not behave as expected. Please check manually.")
        switchViewTo(ViewConstants.EDIT)
        appOutput = str(waitForObject("{type='Core::OutputWindow' unnamed='1' visible='1'}", 20000).plainText)
        if not "MSVC" in config:
            verifyOutput(appOutput, outputStdOut, "std::cout", "Application Output")
            verifyOutput(appOutput, outputStdErr, "std::cerr", "Application Output")
            verifyOutput(appOutput, outputQDebug, "qDebug()", "Application Output")
        clickButton(waitForObject(":Qt Creator_CloseButton"))

    invokeMenuItem("File", "Exit")
    waitForCleanShutdown()

def init():
    cleanup()

def cleanup():
    deleteDirIfExists(projectsPath + os.sep + project)
    deleteDirIfExists(shadowBuildDir(projectsPath, project, defaultQtVersion, 1))
<commit_msg>Squish: Skipping unstable test on Mac<commit_after>source("../../shared/qtcreator.py")

projectsPath = tempDir()
project = "untitled"

def __handlerunControlFinished__(object, runControlP):
    global runControlFinished
    runControlFinished = True

def main():
    if platform.system() == "Darwin" and JIRA.isBugStillOpen(6853, JIRA.Bug.CREATOR):
        test.xverify(False, "This test is unstable on Mac, see QTCREATORBUG-6853.")
        return
    global runControlFinished
    outputQDebug = "Output from qDebug()."
    outputStdOut = "Output from std::cout."
    outputStdErr = "Output from std::cerr."
    startApplication("qtcreator" + SettingsPath)
    installLazySignalHandler("{type='ProjectExplorer::Internal::ProjectExplorerPlugin' unnamed='1'}",
                             "runControlFinished(ProjectExplorer::RunControl*)", "__handlerunControlFinished__")
    createProject_Qt_Console(projectsPath, project)

    mainEditor = waitForObject(":Qt Creator_CppEditor::Internal::CPPEditorWidget")
    replaceEditorContent(mainEditor, "")
    typeLines(mainEditor, ["#include <QDebug>",
                           "#include <iostream>",
                           "int main(int, char *argv[])",
                           "{",
                           '    std::cout << \"' + outputStdOut + '\" << std::endl;',
                           '    std::cerr << \"' + outputStdErr + '\" << std::endl;',
                           '    qDebug() << \"' + outputQDebug + '\";'])
    # Rely on code completion for closing bracket
    invokeMenuItem("File", "Save All")
    selectFromLocator(project + ".pro")
    proEditor = waitForObject("{type='Qt4ProjectManager::Internal::ProFileEditorWidget' unnamed='1' visible='1'"
                              "window=':Qt Creator_Core::Internal::MainWindow'}", 20000)
    test.verify("CONFIG   += console" in str(proEditor.plainText), "Verifying that program is configured with console")
    setRunInTerminal(1, 0, False)

    for config in iterateBuildConfigs(1, 0):
        selectBuildConfig(1, 0, config)
        test.log("Testing build configuration: " + config)

        test.log("Running application")
        runControlFinished = False
        clickButton(waitForObject("{type='Core::Internal::FancyToolButton' text='Run' visible='1'}", 20000))
        waitFor("runControlFinished==True", 20000)
        if not runControlFinished:
            test.warning("Waiting for runControlFinished timed out")
        appOutput = str(waitForObject("{type='Core::OutputWindow' unnamed='1' visible='1'}", 20000).plainText)
        verifyOutput(appOutput, outputStdOut, "std::cout", "Application Output")
        verifyOutput(appOutput, outputStdErr, "std::cerr", "Application Output")
        verifyOutput(appOutput, outputQDebug, "qDebug()", "Application Output")
        clickButton(waitForObject(":Qt Creator_CloseButton"))

        test.log("Debugging application")
        runControlFinished = False
        invokeMenuItem("Debug", "Start Debugging", "Start Debugging")
        JIRA.performWorkaroundIfStillOpen(6853, JIRA.Bug.CREATOR, config)
        handleDebuggerWarnings(config)
        waitFor("runControlFinished==True", 20000)
        if not runControlFinished:
            test.warning("Waiting for runControlFinished timed out")
        try:
            debuggerLog = takeDebuggerLog()
            if not "MSVC" in config:
                # cout works with MSVC, too, but we don't check it since it's not supported
                verifyOutput(debuggerLog, outputStdOut, "std::cout", "Debugger Log")
                verifyOutput(debuggerLog, outputStdErr, "std::cerr", "Debugger Log")
                verifyOutput(debuggerLog, outputQDebug, "qDebug()", "Debugger Log")
        except LookupError:
            # takeDebuggerLog() expects the debugger log to not be visible.
            # If debugger log showed up automatically, previous call to takeDebuggerLog() has closed it.
            debuggerLog = takeDebuggerLog()
            if "lib\qtcreatorcdbext64\qtcreatorcdbext.dll cannot be found." in debuggerLog:
                test.fatal("qtcreatorcdbext.dll is missing in lib\qtcreatorcdbext64")
            else:
                test.fatal("Debugger log did not behave as expected. Please check manually.")
        switchViewTo(ViewConstants.EDIT)
        appOutput = str(waitForObject("{type='Core::OutputWindow' unnamed='1' visible='1'}", 20000).plainText)
        if not "MSVC" in config:
            verifyOutput(appOutput, outputStdOut, "std::cout", "Application Output")
            verifyOutput(appOutput, outputStdErr, "std::cerr", "Application Output")
            verifyOutput(appOutput, outputQDebug, "qDebug()", "Application Output")
        clickButton(waitForObject(":Qt Creator_CloseButton"))

    invokeMenuItem("File", "Exit")
    waitForCleanShutdown()

def init():
    cleanup()

def cleanup():
    deleteDirIfExists(projectsPath + os.sep + project)
    deleteDirIfExists(shadowBuildDir(projectsPath, project, defaultQtVersion, 1))
<|endoftext|>


<commit_before>import re
import json
import requests

from dzstruct import Post
from dzstruct import Thread
from bs4 import BeautifulSoup

class Discuz():
    s = ''
    url = ''
    fid = {}        # 版块名称 和 ID 的映射
    dzconfig = ''   # post & reply param
    logined = False

    def __init__(self, url):
        self.url = url
        self.s = requests.session()
        self.logined = False

        f= open('./dzconfig.json')
        self.dzconfig = json.loads(f.read())
        f.close()

    def get_fid(self):
        fid_pattern = re.compile(r'
(?u)(.+)')
        response = self.s.get(self.url + 'bbs/')
        t = fid_pattern.findall(response.text)
        if t:
            self.fid = dict((fname, fid) for fid, fname in t)
            print('[get_fid]', '{successed}')
        else:
            print('[get_fid]', '{unknown error}')

    def get_formhash(self, url):
        formhash_pattern = re.compile('formhash=([0-9a-zA-Z]+)')
        response = self.s.get(url)
        formhash_info = formhash_pattern.search(response.text)
        if formhash_info:
            return formhash_info.group(1)
        else:
            return None

    def login(self, usr):
        action = 'bbs/member.php?mod=logging&action=login&loginsubmit=yes'
        succ_pattern = re.compile(r"\('succeedlocation'\)\.innerHTML = '(?u)(.+)，现在将转入登录前页面';")
        fail_pattern = re.compile(r'
\n
(?u)(.+)
')

        arg = self.dzconfig['login']
        arg['username'] = usr['username'].encode('gb2312')
        arg['password'] = usr['password'].encode('gb2312')

        response = self.s.post(self.url + action, data = arg)
        succ_info = succ_pattern.search(response.text)
        fail_info = fail_pattern.search(response.text)

        if succ_info:
            print('[login]','{successed}', succ_info.group(1))
            self.get_fid()
            print(self.fid)
            self.logined = True
        elif fail_info:
            print('[login]', '{failed}', fail_info.group(1))
            self.logined = False
        else:
            print('[login]', '{unknown error}')
            self.logined = False
        return self.logined

    def post(self, fname, subject, message):
        if not self.logined:
            print('[post]', 'not logged in')
            return False

        tid = None
        fail_pattern = re.compile('
\n
(?u)(.+)
')
        succ_pattern = re.compile('tid=([0-9]+)')
        action = 'bbs/forum.php?mod=post&action=newthread&fid=FID&topicsubmit=yes'.replace('FID', self.fid[fname])
        fake = 'bbs/forum.php?mod=post&action=newthread&fid=FID'.replace('FID', self.fid[fname])

        arg = self.dzconfig['post']
        arg['formhash'] = self.get_formhash(self.url + fake)
        arg['message'] = message.encode('gb2312')
        arg['subject'] = subject.encode('gb2312')

        print('[post]', 'fname:', fname, 'subject:', subject)
        print('[post]', 'message:', message)

        response = self.s.post(self.url + action, data = arg)
        fail_info = fail_pattern.search(response.text)
        succ_info = succ_pattern.search(response.url)

        if fail_info:
            print('[post]', '{failed}', fail_info.group(1))
        elif succ_info:
            tid = succ_info.group(1)
            print('[post]', '{successed} tid:', succ_info.group(1))
        else:
            print('[post]', '{unknown error}')
        return tid

    def reply(self, tid, message):
        if not self.logined:
            print('[post]', 'not logged in')
            return False

        action = '/bbs/forum.php?mod=post&action=reply&tid=TID&replysubmit=yes'.replace('TID', tid)
        fake = '/bbs/forum.php?mod=viewthread&tid=865411'
        fail_pattern = re.compile("errorhandle_fastpost\('(?u)(.+?)'")
        succ_pattern = re.compile('pid=([0-9]+)')
        # TODO 这里并不能匹配到最后一次发表的 PID

        arg = self.dzconfig['post']
        arg['formhash'] = self.get_formhash(self.url + fake)
        arg['message'] = message.encode('gb2312')

        response = self.s.post(self.url + action, data = arg)
        fail_info = fail_pattern.search(response.text)
        succ_info = succ_pattern.search(response.text)

        if fail_info:
            print('[reply]', '{failed}', fail_info.group(1))
        elif succ_info:
            print('[reply]', '{successed} pid:', succ_info.group(1))
        else:
            print('[reply]', '{unknown error}')

    def get_post(self, thread, page):
        action = 'bbs/forum.php?mod=viewthread&tid=TID&page=PAGE'.replace('TID', thread.tid).replace('PAGE', str(page))

        response = self.s.get(self.url + action)
        html = BeautifulSoup(response.text, 'html.parser')

        fail_info = html.find(id = 'messagetext', class_ = 'alert_error')
        posts = html.find_all('div', id = re.compile(r'post_[0-9]+'))

        idx = len(thread.posts)

        if posts:
            for post in posts:
                idx = idx + 1

                author = post.find('a', class_='xw1')
                if author:
                    author = author.get_text()
                    uid = re.search(r'uid=([0-9]+)', post.find('a', class_='xw1')['href']).group(1)
                else:
                    author = 'anonymous'
                    uid = '0'

                pid = re.search(r'post_([0-9]+)', post['id']).group(1)

                time = post.find('em', id = re.compile(r'authorposton[0-9]+'))
                if time.span:
                    # 发表于 1 小时前
                    time = time.span['title']
                else:
                    # 发表于 2015-9-21 08:47:50
                    time = time.string[4:]

                lock_info = post.find('div', class_ = 'locked')
                if (lock_info):
                    print('[get_post]', '{warning}', 'page:', page, 'idx:', idx, 'post locked')
                    message = '作者被禁止或删除 内容自动屏蔽'
                else:
                    post.find('div', class_ = 'a_pr').decompose()   # 删除 分享栏
                    message = post.find('td', id = re.compile(r'postmessage_[0-9]+')).get_text()
                    # print(idx, end = ' ')

                thread.posts.append(Post(pid, uid, author, time, message))

            # print('')
            print('[get_post]', '{successed}', 'page:', page, 'posts_num:', len(posts))
            return True
        elif fail_info:
            print('[get_post]', '{failed}', 'page:', page, fail_info.p.get_text())
        else:
            print('[get_post]', '{unknown error}', 'page:', page)

        return False

    def get_thread(self, tid):
        action = 'bbs/forum.php?mod=viewthread&tid=TID'.replace('TID', tid)

        response = self.s.get(self.url + action)
        html = BeautifulSoup(response.text, 'html.parser')
        # html = BeautifulSoup(open('./test.html').read(), 'html.parser')

        fail_info = html.find(id = 'messagetext', class_ = 'alert_error')
        if fail_info:
            print('[get_thread]', '{failed}', fail_info.p.get_text())
            return None

        title = html.find('span', id = 'thread_subject').string
        f_tag = html.find('a', href = re.compile(r'^forum.php\?mod=forumdisplay&fid=[0-9]+'))
        fid = re.search(r'fid=([0-9]+)', f_tag['href']).group(1)
        npages = int(html.find('span', title = re.compile('共 [0-9]+ 页'))['title'][2:-2])

        thread = Thread(tid, fid, title, 0)
        for p in range(1, npages):
            if not self.get_post(thread, p):
                print('[get_thread]', '{aborted}')
                return None

        thread.nposts = len(thread.posts) - 1;

        print('[get_thread]', '{successed}', 'title:', thread.title, 'reply_num:', thread.nposts)

        thread.disp()

        return thread

<commit_msg>modify discuz.py: * bug fixed: fault when thread have only one page * add user-agent * sleep: prevent from being blocked * modify log output<commit_after># -*- encoding: UTF-8 -*-

import re
import json
import requests
import random

from time import sleep
from dzstruct import Post, Thread
from bs4 import BeautifulSoup

class Discuz():
    s = ''
    url = ''
    fid = {}        # 版块名称 和 ID 的映射
    dzconfig = ''   # post & reply param
    logined = False

    def __init__(self, url):
        self.url = url
        self.s = requests.session()
        self.s.headers = {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; Acoo Browser 1.98.744; .NET CLR 3.5.30729)'}
        self.logined = False

        f= open('./dzconfig.json')
        self.dzconfig = json.loads(f.read())
        f.close()

    def get_fid(self):
        fid_pattern = re.compile(r'
(?u)(.+)')
        response = self.s.get(self.url + 'bbs/')
        t = fid_pattern.findall(response.text)
        if t:
            self.fid = dict((fname, fid) for fid, fname in t)
            print('[discuz]', '[get_fid]', '{successed}')
        else:
            print('[discuz]', '[get_fid]', '{unknown error}')

    def get_formhash(self, url):
        formhash_pattern = re.compile('formhash=([0-9a-zA-Z]+)')
        response = self.s.get(url)
        formhash_info = formhash_pattern.search(response.text)
        if formhash_info:
            return formhash_info.group(1)
        else:
            return None

    def login(self, usr):
        action = 'bbs/member.php?mod=logging&action=login&loginsubmit=yes'
        succ_pattern = re.compile(r"\('succeedlocation'\)\.innerHTML = '(?u)(.+)，现在将转入登录前页面';")
        fail_pattern = re.compile(r'
\n
(?u)(.+)
')

        arg = self.dzconfig['login']
        arg['username'] = usr['username'].encode('gb2312')
        arg['password'] = usr['password'].encode('gb2312')

        response = self.s.post(self.url + action, data = arg)
        succ_info = succ_pattern.search(response.text)
        fail_info = fail_pattern.search(response.text)

        if succ_info:
            print('[discuz]', '[login]','{successed}', succ_info.group(1))
            self.get_fid()
            self.logined = True
        elif fail_info:
            print('[discuz]', '[login]', '{failed}', fail_info.group(1))
            self.logined = False
        else:
            print('[discuz]', '[login]', '{unknown error}')
            self.logined = False
        return self.logined

    def post(self, fname, subject, message):
        if not self.logined:
            print('[discuz]', '[post]', 'not logged in')
            return False

        tid = None
        fail_pattern = re.compile('
\n
(?u)(.+)
')
        succ_pattern = re.compile('tid=([0-9]+)')
        action = 'bbs/forum.php?mod=post&action=newthread&fid=FID&topicsubmit=yes'.replace('FID', self.fid[fname])
        fake = 'bbs/forum.php?mod=post&action=newthread&fid=FID'.replace('FID', self.fid[fname])

        arg = self.dzconfig['post']
        arg['formhash'] = self.get_formhash(self.url + fake)
        arg['message'] = message.encode('gb2312')
        arg['subject'] = subject.encode('gb2312')

        print('[discuz]', '[post]', 'fname:', fname, 'subject:', subject)
        print('[discuz]', '[post]', 'message:', message)

        response = self.s.post(self.url + action, data = arg)
        fail_info = fail_pattern.search(response.text)
        succ_info = succ_pattern.search(response.url)

        if fail_info:
            print('[discuz]', '[post]', '{failed}', fail_info.group(1))
        elif succ_info:
            tid = succ_info.group(1)
            print('[discuz]', '[post]', '{successed} tid:', succ_info.group(1))
        else:
            print('[discuz]', '[post]', '{unknown error}')
        return tid

    def reply(self, tid, message):
        if not self.logined:
            print('[discuz]', '[post]', 'not logged in')
            return False

        action = '/bbs/forum.php?mod=post&action=reply&tid=TID&replysubmit=yes'.replace('TID', tid)
        fake = '/bbs/forum.php?mod=viewthread&tid=865411'
        fail_pattern = re.compile("errorhandle_fastpost\('(?u)(.+?)'")
        succ_pattern = re.compile('pid=([0-9]+)')
        # TODO 这里并不能匹配到最后一次发表的 PID

        arg = self.dzconfig['post']
        arg['formhash'] = self.get_formhash(self.url + fake)
        arg['message'] = message.encode('gb2312')

        response = self.s.post(self.url + action, data = arg)
        fail_info = fail_pattern.search(response.text)
        succ_info = succ_pattern.search(response.text)

        if fail_info:
            print('[discuz]', '[reply]', '{failed}', fail_info.group(1))
        elif succ_info:
            print('[discuz]', '[reply]', '{successed} pid:', succ_info.group(1))
        else:
            print('[discuz]', '[reply]', '{unknown error}')

    def get_post(self, thread, page):
        action = 'bbs/forum.php?mod=viewthread&tid=TID&page=PAGE'.replace('TID', thread.tid).replace('PAGE', str(page))

        sleep(random.random()*2)    # SLEEP
        response = self.s.get(self.url + action)
        html = BeautifulSoup(response.text, 'html.parser')

        fail_info = html.find(id = 'messagetext', class_ = 'alert_error')
        posts = html.find_all('div', id = re.compile(r'post_[0-9]+'))

        idx = len(thread.posts)

        if posts:
            for post in posts:
                idx = idx + 1

                author = post.find('a', class_='xw1')
                if author:
                    author = author.get_text()
                    uid = re.search(r'uid=([0-9]+)', post.find('a', class_='xw1')['href']).group(1)
                else:
                    author = 'anonymous'
                    uid = '0'

                pid = re.search(r'post_([0-9]+)', post['id']).group(1)

                time = post.find('em', id = re.compile(r'authorposton[0-9]+'))
                if time.span:
                    # 发表于 1 小时前
                    time = time.span['title']
                else:
                    # 发表于 2015-9-21 08:47:50
                    time = time.string[4:]

                lock_info = post.find('div', class_ = 'locked')
                if (lock_info):
                    print('[discuz]', '[get_post]', '{warning}', 'page:', page, 'idx:', idx, 'post locked')
                    message = '作者被禁止或删除 内容自动屏蔽'
                else:
                    post.find('div', class_ = 'a_pr').decompose()   # 删除 分享栏
                    message = post.find('td', id = re.compile(r'postmessage_[0-9]+')).get_text()
                    # print(idx, end = ' ')

                thread.posts.append(Post(pid, uid, author, time, message))

            # print('')
            print('[discuz]', '[get_post]', '{successed}', 'page:', page, 'posts_num:', len(posts))
            return True
        elif fail_info:
            print('[discuz]', '[get_post]', '{failed}', 'page:', page, fail_info.p.get_text())
        else:
            print('[discuz]', '[get_post]', '{unknown error}', 'page:', page)

        return False

    def get_thread(self, tid):
        action = 'bbs/forum.php?mod=viewthread&tid=TID'.replace('TID', tid)

        response = self.s.get(self.url + action)
        html = BeautifulSoup(response.text, 'html.parser')
        # html = BeautifulSoup(open('./test.html').read(), 'html.parser')

        fail_info = html.find(id = 'messagetext', class_ = 'alert_error')
        if fail_info:
            print('[discuz]', '[get_thread]', '{failed}', fail_info.p.get_text())
            return None

        title = html.find('span', id = 'thread_subject').string
        f_tag = html.find('a', href = re.compile(r'^forum.php\?mod=forumdisplay&fid=[0-9]+'))
        fid = re.search(r'fid=([0-9]+)', f_tag['href']).group(1)

        npages = html.find('span', title = re.compile('共 [0-9]+ 页'))
        if npages:
            npages = int(npages['title'][2:-2])
        else:
            npages = 1

        thread = Thread(tid, fid, title, 0)
        for p in range(1, npages + 1):
            sleep(random.random()*10)   # SLEEP
            if not self.get_post(thread, p):
                print('[discuz]', '[get_thread]', '{aborted}')
                return None

        thread.nposts = len(thread.posts) - 1;

        print('[discuz]', '[get_thread]', '{successed}', 'title:', thread.title, 'reply_num:', thread.nposts)

        return thread

<|endoftext|>


<commit_before>import typing
from datetime import datetime
from math import ceil
from xml.etree.ElementTree import fromstring
from api.session import Session
from api.commands import *

class DomainAPI(Session):

    def register(self):
        pass

    def renew(self, domain: str, years: int,
              check_status_first: bool = False):
        """Domain renewal option.

        If check_status_first is set to True, get_info() will be
        called first to check if the domain is expired. If it is,
        reactivate() will be called instead.
        """
        pass

    def reactivate(self, domain: str):
        pass

    def get_info(self, domain: str) -> dict:
        """Get domain information.

        Domain must be listed in your Namecheap account.

        Arguments:
            domain -- domain name (e.g. 'google.com')

        Returns:
            Dict with domain information
        """
        xml = self._call(DOMAINS_GET_INFO, {'DomainName': domain}).find(
            self._tag('DomainGetInfoResult'))

        # Basic information
        result = {
            'Domain': xml.attrib['DomainName'],
            'Owner': xml.attrib['OwnerName'],
            'Status': xml.attrib['Status'],
            'ID': xml.attrib['ID'],
            'IsOwner':
                True if xml.attrib['IsOwner'].lower() == 'true' else False,
            'Full modification rights':
                True if (xml.find(self._tag('Modificationrights')).
                         attrib['All'].lower() == 'true')
                else False
        }

        result['Creation'] = datetime.strptime(xml.find(
            self._tag('DomainDetails')).find(self._tag('CreatedDate')).text,
            '%m/%d/%Y')
        result['Expiration'] = datetime.strptime(xml.find(
            self._tag('DomainDetails')).find(self._tag('ExpiredDate')).text,
            '%m/%d/%Y')

        # WhoisGuard details
        wg = xml.find(self._tag('Whoisguard'))
        result['WhoisGuard'] = {
            'Enabled':
                True if wg.attrib['Enabled'].lower() == 'true'
                else False,
            'Expiration':
                datetime.strptime(wg.find(self._tag('ExpiredDate')).text,
                                  '%m/%d/%Y'),
            'ID': wg.find(self._tag('ID')).text,
            'Email':
                wg.find(self._tag('EmailDetails')).attrib['WhoisGuardEmail'],
            'Forwarded to':
                wg.find(self._tag('EmailDetails')).attrib['ForwardedTo'],
            'Last email auto-change date':
                wg.find(self._tag('EmailDetails')).attrib[
                'LastAutoEmailChangeDate']
                if wg.find(self._tag('EmailDetails')).attrib[
                    'LastAutoEmailChangeDate']
                else None,
            'Email auto-change frequency':
                wg.find(self._tag('EmailDetails')).attrib[
                    'AutoEmailChangeFrequencyDays']
        }

        # Premium DNS details
        pdns = xml.find(self._tag('PremiumDnsSubscription'))

        result['PremiumDNS'] = {
            'Creation': pdns.find(self._tag('CreatedDate')).text,
            'Expiration': pdns.find(self._tag('ExpirationDate')).text,
            'ID': pdns.find(self._tag('SubscriptionId')).text,
            'Auto-renew': True if pdns.find(
                self._tag('UseAutoRenew')).text.lower() == 'true' else False,
            'Active':
                True if pdns.find(self._tag('IsActive')).text.lower() == 'true'
                else False
        }

        # DNS details
        dns = xml.find(self._tag('DnsDetails'))
        result['DNS'] = {
            'Type': dns.attrib['ProviderType'],
            'Using NC DNS':
                True if dns.attrib['IsUsingOurDNS'].lower() == 'true'
                else False,
            'Host records count': dns.attrib['HostCount'],
            'Email type': dns.attrib['EmailType'],
            'Dynamic DNS':
                True if dns.attrib['DynamicDNSStatus'].lower() == 'true'
                else False,
            'Failover DNS': True if dns.attrib['IsFailover'].lower() == 'true'
                else False,
            'Nameservers': [ns.text for ns in
                            dns.findall(self._tag('Nameserver'))]
        }

        return result

    def get_list(self, _type: str = 'ALL',
                 search_term: str = '') -> typing.List[dict]:
        """Get the list of domains.

        Arguments:
            _type - possible values: 'ALL', 'EXPIRING', 'EXPIRED'
            search_term - keyword to look for in the domain list.

        Returns:
            A list containing dicts with domain information.
        """

        domains = []

        # A check on the total domain number (minimal PageSize is 10)
        xml = self._call(
            DOMAINS_GET_LIST, {'ListType': _type,
                               'SearchTerm': search_term,
                               'PageSize': 10}).find(
            self._tag('Paging'))
        total_domains = int(xml.find(self._tag('TotalItems')).text)

        # A call is sent for every 100-item page
        # TODO: check performance on big accounts
        for page in range(1, ceil(total_domains / 100) + 1):

            xml = self._call(
                DOMAINS_GET_LIST, {'ListType': _type,
                                   'SearchTerm': search_term,
                                   'Page': page,
                                   'PageSize': 100}).find(
                self._tag('DomainGetListResult'))

            for domain in xml.findall(self._tag('Domain')):
                domains.append({
                    'Domain': domain.attrib['Name'],
                    'ID': domain.attrib['ID'],
                    'Owner': domain.attrib['User'],
                    'Creation': datetime.strptime(domain.attrib['Created'],
                                                  '%m/%d/%Y'),
                    'Expiration': datetime.strptime(domain.attrib['Expires'],
                                                    '%m/%d/%Y'),
                    'WhoisGuard': domain.attrib['WhoisGuard'],
                    'Expired':
                        True if domain.attrib['IsExpired'].lower() == 'true'
                        else False,
                    'Locked':
                        True if domain.attrib['IsLocked'].lower() == 'true'
                        else False,
                    'Auto-renew':
                        True if domain.attrib['AutoRenew'].lower() == 'true'
                        else False,
                })

        return domains

    # TODO
    def get_tld_list(self) -> str:
        xml = self._call(DOMAINS_GET_TLD_LIST)
        res = {}

    def check(self, domains: typing.Iterable[str]) -> dict:
        """Check domain availability.

        Arguments:
        domains -- any iterable (str for single domain; list, set,
        dict for multiple domains)

        Returns:
        Dict with boolean values for domain availability.
        E.g.:
            {'google.com': False, 'yourawesomedomain.name': True}

        """
        if isinstance(domains, str):
            domains = [domains, ]
        xml = self._call(DOMAINS_CHECK, {'DomainList': ','.join(domains)})

        result = {}
        for item in xml.findall(self._tag('DomainCheckResult')):
            result[item.attrib['Domain']] = (
                True if item.attrib['Available'] == 'true' else False)

        return result

    def get_contacts(self):
        pass

    def set_contacts(self):
        pass

    def get_lock(self):
        pass

    def set_lock(self):
        pass

    def create_nameserver(self):
        pass

    def delete_nameserver(self):
        pass

    def update_nameserver(self):
        pass

    def get_nameserver_info(self):
        pass

    def set_nameservers(self, default: bool = False):
        """default=True will set Namecheap DNS
        """
        pass

    def get_nameservers(self):
        pass

    def get_host_records(self):
        pass

    def set_host_records(self):
        pass

    def get_email_forwarding(self):
        pass

    def set_email_forwarding(self):
        pass

    def create_transfer(self):
        pass

    def get_transfer_status(self):
        pass

    def update_transfer_status(self):
        pass

    def get_transfer_list(self):
        pass
<commit_msg>simplify logical checks<commit_after>import typing
from datetime import datetime
from math import ceil
from xml.etree.ElementTree import fromstring
from api.session import Session
from api.commands import *

class DomainAPI(Session):

    def register(self):
        pass

    def renew(self, domain: str, years: int,
              check_status_first: bool = False):
        """Domain renewal option.

        If check_status_first is set to True, get_info() will be
        called first to check if the domain is expired. If it is,
        reactivate() will be called instead.
        """
        pass

    def reactivate(self, domain: str):
        pass

    def get_info(self, domain: str) -> dict:
        """Get domain information.

        Domain must be listed in your Namecheap account.

        Arguments:
            domain -- domain name (e.g. 'google.com')

        Returns:
            Dict with domain information
        """
        xml = self._call(DOMAINS_GET_INFO, {'DomainName': domain}).find(
            self._tag('DomainGetInfoResult'))

        # Basic information
        result = {
            'Domain': xml.attrib['DomainName'],
            'Owner': xml.attrib['OwnerName'],
            'Status': xml.attrib['Status'],
            'ID': xml.attrib['ID'],
            'IsOwner': xml.attrib['IsOwner'].lower() == 'true',
            'Full modification rights':
                xml.find(self._tag('Modificationrights')).
                attrib['All'].lower() == 'true'
        }

        result['Creation'] = datetime.strptime(xml.find(
            self._tag('DomainDetails')).find(self._tag('CreatedDate')).text,
            '%m/%d/%Y')
        result['Expiration'] = datetime.strptime(xml.find(
            self._tag('DomainDetails')).find(self._tag('ExpiredDate')).text,
            '%m/%d/%Y')

        # WhoisGuard details
        wg = xml.find(self._tag('Whoisguard'))
        result['WhoisGuard'] = {
            'Enabled': wg.attrib['Enabled'].lower() == 'true',
            'Expiration':
                datetime.strptime(wg.find(self._tag('ExpiredDate')).text,
                                  '%m/%d/%Y'),
            'ID': wg.find(self._tag('ID')).text,
            'Email':
                wg.find(self._tag('EmailDetails')).attrib['WhoisGuardEmail'],
            'Forwarded to':
                wg.find(self._tag('EmailDetails')).attrib['ForwardedTo'],
            'Last email auto-change date':
                wg.find(self._tag('EmailDetails')).attrib[
                'LastAutoEmailChangeDate']
                if wg.find(self._tag('EmailDetails')).attrib[
                    'LastAutoEmailChangeDate']
                else None,
            'Email auto-change frequency':
                wg.find(self._tag('EmailDetails')).attrib[
                    'AutoEmailChangeFrequencyDays']
        }

        # Premium DNS details
        pdns = xml.find(self._tag('PremiumDnsSubscription'))

        result['PremiumDNS'] = {
            'Creation': pdns.find(self._tag('CreatedDate')).text,
            'Expiration': pdns.find(self._tag('ExpirationDate')).text,
            'ID': pdns.find(self._tag('SubscriptionId')).text,
            'Auto-renew': pdns.find(
                self._tag('UseAutoRenew')).text.lower() == 'true',
            'Active': pdns.find(self._tag('IsActive')).text.lower() == 'true'
        }

        # DNS details
        dns = xml.find(self._tag('DnsDetails'))
        result['DNS'] = {
            'Type': dns.attrib['ProviderType'],
            'Using NC DNS': dns.attrib['IsUsingOurDNS'].lower() == 'true',
            'Host records count': dns.attrib['HostCount'],
            'Email type': dns.attrib['EmailType'],
            'Dynamic DNS': dns.attrib['DynamicDNSStatus'].lower() == 'true',
            'Failover DNS': dns.attrib['IsFailover'].lower() == 'true',
            'Nameservers': [ns.text for ns in
                            dns.findall(self._tag('Nameserver'))]
        }

        return result

    def get_list(self, _type: str = 'ALL',
                 search_term: str = '') -> typing.List[dict]:
        """Get the list of domains.

        Arguments:
            _type - possible values: 'ALL', 'EXPIRING', 'EXPIRED'
            search_term - keyword to look for in the domain list.

        Returns:
            A list containing dicts with domain information.
        """

        domains = []

        # A check on the total domain number (minimal PageSize is 10)
        xml = self._call(
            DOMAINS_GET_LIST, {'ListType': _type,
                               'SearchTerm': search_term,
                               'PageSize': 10}).find(
            self._tag('Paging'))
        total_domains = int(xml.find(self._tag('TotalItems')).text)

        # A call is sent for every 100-item page
        # TODO: check performance on big accounts
        for page in range(1, ceil(total_domains / 100) + 1):

            xml = self._call(
                DOMAINS_GET_LIST, {'ListType': _type,
                                   'SearchTerm': search_term,
                                   'Page': page,
                                   'PageSize': 100}).find(
                self._tag('DomainGetListResult'))

            for domain in xml.findall(self._tag('Domain')):
                domains.append({
                    'Domain': domain.attrib['Name'],
                    'ID': domain.attrib['ID'],
                    'Owner': domain.attrib['User'],
                    'Creation': datetime.strptime(domain.attrib['Created'],
                                                  '%m/%d/%Y'),
                    'Expiration': datetime.strptime(domain.attrib['Expires'],
                                                    '%m/%d/%Y'),
                    'WhoisGuard': domain.attrib['WhoisGuard'],
                    'Expired': domain.attrib['IsExpired'].lower() == 'true',
                    'Locked': domain.attrib['IsLocked'].lower() == 'true',
                    'Auto-renew': domain.attrib['AutoRenew'].lower() == 'true',
                })

        return domains

    # TODO
    def get_tld_list(self) -> str:
        xml = self._call(DOMAINS_GET_TLD_LIST)
        res = {}

    def check(self, domains: typing.Iterable[str]) -> dict:
        """Check domain availability.

        Arguments:
        domains -- any iterable (str for single domain; list, set,
        dict for multiple domains)

        Returns:
        Dict with boolean values for domain availability.
        E.g.:
            {'google.com': False, 'yourawesomedomain.name': True}

        """
        if isinstance(domains, str):
            domains = [domains, ]
        xml = self._call(DOMAINS_CHECK, {'DomainList': ','.join(domains)})

        result = {}
        for item in xml.findall(self._tag('DomainCheckResult')):
            result[item.attrib['Domain']] = (
                item.attrib['Available'] == 'true')

        return result

    def get_contacts(self):
        pass

    def set_contacts(self):
        pass

    def get_lock(self):
        pass

    def set_lock(self):
        pass

    def create_nameserver(self):
        pass

    def delete_nameserver(self):
        pass

    def update_nameserver(self):
        pass

    def get_nameserver_info(self):
        pass

    def set_nameservers(self, default: bool = False):
        """default=True will set Namecheap DNS
        """
        pass

    def get_nameservers(self):
        pass

    def get_host_records(self):
        pass

    def set_host_records(self):
        pass

    def get_email_forwarding(self):
        pass

    def set_email_forwarding(self):
        pass

    def create_transfer(self):
        pass

    def get_transfer_status(self):
        pass

    def update_transfer_status(self):
        pass

    def get_transfer_list(self):
        pass
<|endoftext|>


<commit_before>#!/usr/local/bin/python
###################################################
# Jeffrey Herbstman
# nerv3.py
# Goal: Named entity recognition script to pull names/place from text
# called as python nerv3.py text_path_or_file
#
# Inputs:
# path - text file or directory containing text files
# output - output file name
# Outputs:
# Output file written
#
###################################################

#gonna need to install AlchemyAPI
import AlchemyAPI
import argparse
import xml.etree.ElementTree as ET
import collections
import codecs
import os
#from IPython import embed
#=================================================
def listwrite(output_file,thelist):
	for item in thelist:
		item.encode('utf-8')
		output_file.write("%s\n\n" % item)

#=================================================

def main():

	tmpdir = "/tmp/pagekicker"

	#personal api key saved as api_key.txt
	parser = argparse.ArgumentParser()
	parser.add_argument('path', help = "target file or directory for NER")
	parser.add_argument('output', help = "target file for output")
	parser.add_argument('uuid', help = "uuid")
	args = parser.parse_args()

	in_file = args.path
	out_file = args.output
	uuid = args.uuid
	folder = os.path.join(tmpdir, uuid)
	print folder
	cwd = os.getcwd()
	apikey_location = os.path.join(cwd, "api_key.txt")

	with open(in_file) as f:
		text = f.read()

	alchemyObj = AlchemyAPI.AlchemyAPI()
	alchemyObj.loadAPIKey(apikey_location)

	result = alchemyObj.TextGetRankedNamedEntities(text)

	root = ET.fromstring(result)

	place_list = ['City', 'Continent', 'Country', 'Facility', 'GeographicFeature',\
	'Region', 'StateOrCounty']
	People = {}
	Places = {}
	Other = {}

	for entity in root.getiterator('entity'):
		if entity[0].text == 'Person':
			People[entity[3].text]=[entity[1].text, entity[2].text]
		elif entity[0].text in place_list:
			Places[entity[3].text] = [entity[1].text, entity[2].text]
		else:
			Other[entity[3].text] = [entity[1].text, entity[2].text]

	#print lists ordered by relevance
	Places_s = sorted(Places, key = Places.get, reverse = True)
	People_s = sorted(People, key = People.get, reverse = True)
	Other_s = sorted(Other, key = Other.get, reverse = True)

	print Places_s

	with codecs.open(out_file, mode = 'w', encoding='utf-8') as o:
		listwrite(o, People_s)
		listwrite(o, Places_s)
		listwrite(o, Other_s)
  	out_file = os.path.join(folder, 'People')
	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
          listwrite(o, People_s)
  	out_file = os.path.join(folder, 'Places')
    	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
         listwrite(o, Places_s)
  	out_file = os.path.join(folder, 'Other')
	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
          listwrite(o, Other_s)
#=================================================
if __name__ == '__main__':
    main()
<commit_msg>Fixes ancient annoyance with extra newlines in output of named entities<commit_after>#!/usr/local/bin/python
###################################################
# Jeffrey Herbstman
# nerv3.py
# Goal: Named entity recognition script to pull names/place from text
# called as python nerv3.py text_path_or_file
#
# Inputs:
# path - text file or directory containing text files
# output - output file name
# Outputs:
# Output file written
#
###################################################

#gonna need to install AlchemyAPI
import AlchemyAPI
import argparse
import xml.etree.ElementTree as ET
import collections
import codecs
import os
#from IPython import embed
#=================================================
def listwrite(output_file,thelist):
	for item in thelist:
		item.encode('utf-8')
		output_file.write("%s\n" % item)

#=================================================

def main():

	tmpdir = "/tmp/pagekicker"

	#personal api key saved as api_key.txt
	parser = argparse.ArgumentParser()
	parser.add_argument('path', help = "target file or directory for NER")
	parser.add_argument('output', help = "target file for output")
	parser.add_argument('uuid', help = "uuid")
	args = parser.parse_args()

	in_file = args.path
	out_file = args.output
	uuid = args.uuid
	folder = os.path.join(tmpdir, uuid)
	print folder
	cwd = os.getcwd()
	apikey_location = os.path.join(cwd, "api_key.txt")

	with open(in_file) as f:
		text = f.read()

	alchemyObj = AlchemyAPI.AlchemyAPI()
	alchemyObj.loadAPIKey(apikey_location)

	result = alchemyObj.TextGetRankedNamedEntities(text)

	root = ET.fromstring(result)

	place_list = ['City', 'Continent', 'Country', 'Facility', 'GeographicFeature',\
	'Region', 'StateOrCounty']
	People = {}
	Places = {}
	Other = {}

	for entity in root.getiterator('entity'):
		if entity[0].text == 'Person':
			People[entity[3].text]=[entity[1].text, entity[2].text]
		elif entity[0].text in place_list:
			Places[entity[3].text] = [entity[1].text, entity[2].text]
		else:
			Other[entity[3].text] = [entity[1].text, entity[2].text]

	#print lists ordered by relevance
	Places_s = sorted(Places, key = Places.get, reverse = True)
	People_s = sorted(People, key = People.get, reverse = True)
	Other_s = sorted(Other, key = Other.get, reverse = True)

	print Places_s

	with codecs.open(out_file, mode = 'w', encoding='utf-8') as o:
		listwrite(o, People_s)
		listwrite(o, Places_s)
		listwrite(o, Other_s)
  	out_file = os.path.join(folder, 'People')
	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
          listwrite(o, People_s)
  	out_file = os.path.join(folder, 'Places')
    	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
         listwrite(o, Places_s)
  	out_file = os.path.join(folder, 'Other')
	with codecs.open(out_file, mode= 'w', encoding='utf-8') as o:
          listwrite(o, Other_s)
#=================================================
if __name__ == '__main__':
    main()
<|endoftext|>