import os
import pickle
import json
import threading
import Pyro4.naming
[docs]def nic_name_to_host(nic_name):
	""" helper function to translate the name of a network card into a valid host name"""
	from netifaces import ifaddresses, AF_INET
	host = ifaddresses(nic_name).setdefault(AF_INET, [{'addr': 'No IP addr'}] )[0]['addr']
	return(host) 
[docs]class NameServer(object):
	"""
	The nameserver serves as a phonebook-like lookup table for your workers. Unique names are created so the workers
	can work in parallel and register their results without creating racing conditions. The implementation uses
	`PYRO4 <https://pythonhosted.org/Pyro4/nameserver.html>`_ as a backend and this class is basically a wrapper.
	"""
	def __init__(self, run_id, working_directory=None, host=None, port=0, nic_name=None):
		"""
		Parameters
		----------
			run_id: str
				unique run_id associated with the HPB run
			working_directory: str
				path to the working directory of the HPB run to store the nameservers credentials.
				If None, no config file will be written.
			host: str
				the hostname to use for the nameserver
			port: int
				the port to be used. Default (=0) means a random port
			nic_name: str
				name of the network interface to use (only used if host is not given)
		"""
		self.run_id = run_id
		self.host = host
		self.nic_name = nic_name
		self.port = port
		self.dir = working_directory
		self.conf_fn = None
		self.pyro_ns = None
[docs]	def start(self):
		"""	
		starts a Pyro4 nameserver in a separate thread
		
		Returns
		-------
			tuple (str, int):
				the host name and the used port
		"""
	
		if self.host is None:
			if self.nic_name is None:
				self.host = 'localhost'
			else:
				self.host = nic_name_to_host(self.nic_name)
		uri, self.pyro_ns, _ = Pyro4.naming.startNS(host=self.host, port=self.port)
		self.host, self.port = self.pyro_ns.locationStr.split(':')
		self.port = int(self.port)
		
		thread = threading.Thread(target=self.pyro_ns.requestLoop, name='Pyro4 nameserver started by HpBandSter')
		thread.start()
		if not self.dir is None:
			os.makedirs(self.dir, exist_ok=True)
			self.conf_fn = os.path.join(self.dir, 'HPB_run_%s_pyro.pkl'%self.run_id)
			with open(self.conf_fn, 'wb') as fh:
				pickle.dump((self.host, self.port), fh)
		
		return(self.host, self.port) 
[docs]	def shutdown(self):
		"""
			clean shutdown of the nameserver and the config file (if written)
		"""
		if not self.pyro_ns is None:
			self.pyro_ns.shutdown()
			self.pyro_ns = None
		
		if not self.conf_fn is None:
			os.remove(self.conf_fn)
			self.conf_fn = None 
	def __del__(self):
		self.shutdown()