Skip to content

Commit

Permalink
Merge pull request NSLS-II-LIX#4 from hhslepicka/master
Browse files Browse the repository at this point in the history
Bug fixes. Added configuration file for services connection and others
  • Loading branch information
hhslepicka authored Oct 27, 2016
2 parents 2067e39 + ad019ee commit 7b215ee
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 38 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
# lixdc
User interface for data collection at LIX beam line.


## lixdc configuration

Lixdc requires the following configuration information:

```
amostra_host: localhost
amostra_port: 7772
base_path: /Users/hugoslepicka/data
```


This configuration information can live in up to four different places, as
defined in the docstring of the `load_configuration` function in
`lixdc/conf.py`. In order of increasing precedence:

1. The conda environment
- CONDA_ENV/etc/{name}.yaml (if CONDA_ETC_env is defined)
1. At the system level
- /etc/{name}.yml
1. In the user's home directory
- ~/.config/{name}/connection.yml
1. Environmental variables
- {PREFIX}_{FIELD}

where

- {name} is lixdc
- {PREFIX} is LIXDC and {FIELD} is one of {amostra_host, amostra_port, base_path}
54 changes: 54 additions & 0 deletions lixdc/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# no yaml, just python or command line for now
import os
import yaml
import logging

logger = logging.getLogger(__name__)


def load_configuration(name, prefix, fields, allow_missing=False):
"""
Load configuration data form a cascading series of locations.
The precedence order is (highest priority last):
1. CONDA_ENV/etc/{name}.yaml (if CONDA_ETC_ env is defined)
2. /etc/{name}.yml
3. ~/.config/{name}/connection.yml
4. reading {PREFIX}_{FIELD} environmental variables
Parameters
----------
name : str
The expected base-name of the configuration files
prefix : str
The prefix when looking for environmental variables
fields : iterable of strings
The required configuration fields
Returns
------
conf : dict
Dictionary keyed on ``fields`` with the values extracted
"""
filenames = [os.path.join('/etc', name + '.yml'),
os.path.join(os.path.expanduser('~'), '.config',
name, 'connection.yml'),
]
if 'CONDA_ETC_' in os.environ:
filenames.insert(0, os.path.join(os.environ['CONDA_ETC_'],
name + '.yml'))

config = {}
for filename in filenames:
if os.path.isfile(filename):
with open(filename) as f:
config.update(yaml.load(f))
logger.debug("Using db connection specified in config file. \n%r",
config)

for field in fields:
var_name = prefix + '_' + field.upper().replace(' ', '_')
config[field] = os.environ.get(var_name, config.get(field, None))

missing = [k for k, v in config.items() if v is None]
if not allow_missing and missing:
raise KeyError("The configuration field(s) {0} were not found in any "
"file or environmental variable.".format(missing))
return config
16 changes: 13 additions & 3 deletions lixdc/db/request.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import collections
import pandas
import amostra.client.commands as acc
from lixdc.conf import load_configuration

config_params = {k: v for k, v in load_configuration('lixdc', 'LIXDC',
[
'amostra_host',
'amostra_port',
]
).items() if v is not None}

host, port = config_params['amostra_host'], config_params['amostra_port']
request_ref = acc.RequestReference(host=host, port=port)

request_ref = acc.RequestReference()

def upsert_request(req_info):
if 'uid' in req_info:
Expand All @@ -16,9 +26,9 @@ def upsert_request(req_info):
def find_requests(**kwargs):
return list(request_ref.find(**kwargs))

def find_request_by_barcode(owner, project, beamline_id, barcode):
def find_request_by_barcode(owner, proposal_id, beamline_id, barcode):
try:
req_info = list(container_ref.find(owner=owner, project=project,
req_info = list(container_ref.find(owner=owner, proposal_id=proposal_id,
beamline_id=beamline_id,
container_barcode=barcode))[0]
except IndexError:
Expand Down
20 changes: 14 additions & 6 deletions lixdc/db/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pandas
import amostra.client.commands as acc
import lixdc
from lixdc.conf import load_configuration

path = os.path.dirname(lixdc.__file__)+"/"

Expand All @@ -20,8 +21,16 @@
"image": "{}12cells.png".format(path)}
}

container_ref = acc.ContainerReference()
sample_ref = acc.SampleReference()
config_params = {k: v for k, v in load_configuration('lixdc', 'LIXDC',
[
'amostra_host',
'amostra_port',
]
).items() if v is not None}

host, port = config_params['amostra_host'], config_params['amostra_port']
container_ref = acc.ContainerReference(host=host, port=port)
sample_ref = acc.SampleReference(host=host, port=port)


def get_container_type_by_id(cont_id):
Expand All @@ -34,7 +43,6 @@ def upsert_container(cont_info):
if 'uid' in cont_info:
cont = cont_info['uid']
q = {'uid': cont_info.pop('uid', '')}
print('Update Container with: ', cont_info)
container_ref.update(q, cont_info)
else:
cont = container_ref.create(**cont_info)
Expand All @@ -51,7 +59,6 @@ def upsert_sample_list(samples):
result.append(q['uid'])
else:
s.pop('uid', '')
print('Will Create sample: ', s)
result.append(sample_ref.create(**s))

return result
Expand All @@ -61,9 +68,10 @@ def find_containers(**kwargs):
return list(container_ref.find(**kwargs))


def find_container_by_barcode(owner, project, beamline_id, barcode, fill=True):
def find_container_by_barcode(owner, proposal_id, beamline_id, barcode, fill=True):

try:
cont_info = list(container_ref.find(owner=owner, project=project,
cont_info = list(container_ref.find(owner=owner, proposal_id=proposal_id,
beamline_id=beamline_id,
barcode=barcode))[0]
except IndexError:
Expand Down
21 changes: 15 additions & 6 deletions lixdc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,36 @@
from .state.general import State
from .widgets.main import MainWindow
from .widgets.login import Login
from .conf import load_configuration

config_params = {k: v for k, v in load_configuration('lixdc', 'LIXDC',
[
'amostra_host',
'amostra_port',
'base_path'
]
).items() if v is not None}

def run():
app_state = State()
app_state.configs = config_params
app = QtGui.QApplication(sys.argv)
#print("Styles: ", QtGui.QStyleFactory.keys())
app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
### TURNS ON LOGIN SCREEN
login = Login(app_state=app_state)
if login.exec_() == QtGui.QDialog.Accepted:
#if True:
print("After Login App State: ", app_state)
main = MainWindow(app_state=app_state)
sys.exit(app.exec_())

def run_ipython(username, project):
if username == '' or project == '':
raise Exception('Illegal username or project. Please check.')
def run_ipython(username, proposal, run):
if username == '' or proposal == '' or run == '':
raise Exception('Illegal username, proposal or run. Please check.')
app_state = State()
app_state.configs = config_params
app_state.user = username
app_state.project = project
app_state.proposal_id = proposal
app_state.run_id = run
app_state.login_timestamp = time.time()
params = {'app_state': app_state}
main = create_window(MainWindow, **params)
Expand Down
14 changes: 10 additions & 4 deletions lixdc/state/general.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
class State():
def __init__(self):
self.user = None
self.project = None
self.proposal_id = None
self.run_id = None
self.beamline_id = "LIX"
self.login_timestamp = None
self.configs = None

def __str__(self):
return "User: {}, Project: {}, Beamline: {}, Login Timestamp: {}".format(self.user, self.project, self.beamline_id, self.login_timestamp)
return "User: {}, Proposal: {}, Beamline: {}, Login Timestamp: {}".format(self.user, self.proposal_id, self.beamline_id, self.login_timestamp)

def get_default_fields(self):
return {'owner': self.user, 'project': self.project, 'beamline_id':
self.beamline_id}
return {'owner': self.user, 'proposal_id': self.proposal_id, 'beamline_id':
self.beamline_id, 'run_id': self.run_id}

def get_user_path(self):
return "{}/{}/{}/".format(self.configs['base_path'], self.proposal_id,
self.run_id)
19 changes: 19 additions & 0 deletions lixdc/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from PyQt4 import QtGui, QtCore

ANSWER_OK = QtGui.QMessageBox.Ok
Expand Down Expand Up @@ -32,3 +33,21 @@ def show_information(text, informative, title):
msg_box.setStandardButtons(QtGui.QMessageBox.Ok)
ret = msg_box.exec_()

# xf16id - 5008, lix - 3009
def makedirs(path, mode=0o777, owner_uid=5008, group=3009):
'''Recursively make directories and set permissions'''
# Permissions not working with os.makedirs -
# See: http://stackoverflow.com/questions/5231901
if not path or os.path.exists(path):
return []

head, tail = os.path.split(path)
ret = makedirs(head, mode)
try:
os.mkdir(path)
except OSError as ex:
if 'File exists' not in str(ex):
raise
os.chmod(path, mode)
ret.append(path)
return ret
2 changes: 1 addition & 1 deletion lixdc/widgets/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def filter_containers(self):
params = dict()

params['owner'] = self.app_state.user
params['project'] = self.app_state.project
params['proposal_id'] = self.app_state.proposal_id
params['beamline_id'] = self.app_state.beamline_id

if self.cmb_type.currentText() != '':
Expand Down
24 changes: 16 additions & 8 deletions lixdc/widgets/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ def init_ui(self):
self.textPass.setEchoMode(QtGui.QLineEdit.Password)
self.textPass.setText("lix")

self.labelProject = QtGui.QLabel(self)
self.labelProject.setText("Project: ")
self.textProject = QtGui.QLineEdit(self)
self.textProject.setText("")
self.labelProposal = QtGui.QLabel(self)
self.labelProposal.setText("Proposal ID: ")
self.textProposal = QtGui.QLineEdit(self)
self.textProposal.setText("")

self.labelRun = QtGui.QLabel(self)
self.labelRun.setText("Run #: ")
self.textRun = QtGui.QLineEdit(self)
self.textRun.setText("{}".format(
time.strftime("%Y%m%d")))

self.buttonLogin = QtGui.QPushButton('Login', self)
self.buttonLogin.clicked.connect(self.handle_login)
Expand All @@ -33,20 +39,22 @@ def init_ui(self):
form_layout = QtGui.QFormLayout()
form_layout.addRow(self.labelName, self.textName)
form_layout.addRow(self.labelPass, self.textPass)
form_layout.addRow(self.labelProject, self.textProject)
form_layout.addRow(self.labelProposal, self.textProposal)
form_layout.addRow(self.labelRun, self.textRun)
vbox_layout.addLayout(form_layout)
vbox_layout.addWidget(self.buttonLogin)

def handle_login(self):
# TODO: Implement real authentication
if self.textProject.text() == '':
QtGui.QMessageBox.warning(self, 'Error', 'Project is required.')
if self.textProposal.text() == '' or self.textRun.text() == '':
QtGui.QMessageBox.warning(self, 'Error', 'Proposal and Run Number are required.')
return

if self.textName.text() != '' and self.textPass.text() == 'lix':
self.app_state.user = self.textName.text()
self.app_state.project = self.textProject.text()
self.app_state.login_timestamp = time.time()
self.app_state.proposal_id = self.textProposal.text()
self.app_state.run_id = self.textRun.text()
self.accept()
else:
QtGui.QMessageBox.warning(self, 'Error', 'Bad user or password')
4 changes: 2 additions & 2 deletions lixdc/widgets/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ def init_ui(self):
self.setStatusBar(self.status_bar)

self.lbl_user_data = QtGui.QLabel()
user_data_str = "Logged in at: {} as {} | Project: {}"
user_data_str = "Logged in at: {} as {} | Proposal: {}"
fmt_date = datetime.fromtimestamp(self.app_state.login_timestamp)
fmt_date = fmt_date.strftime("%D %H:%M:%S")
self.lbl_user_data.text = user_data_str.format(fmt_date,
self.app_state.user,
self.app_state.project)
self.app_state.proposal_id)
self.status_bar.addWidget(self.lbl_user_data,1)


Expand Down
6 changes: 3 additions & 3 deletions lixdc/widgets/sample_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def filter_containers(self):
params = dict()

params['owner'] = self.app_state.user
params['project'] = self.app_state.project
params['proposal_id'] = self.app_state.proposal_id
params['beamline_id'] = self.app_state.beamline_id

if self.cmb_type.currentText() != '':
Expand Down Expand Up @@ -445,7 +445,7 @@ def read_import_file(self, fname):
plate_info = {
"uid": None,
"owner": self.app_state.user,
"project": self.app_state.project,
"proposal_id": self.app_state.proposal_id,
"beamline_id": self.app_state.beamline_id,
"kind": kind,
"name": name,
Expand All @@ -460,7 +460,7 @@ def read_import_file(self, fname):
s_temperature = line[1][9]
sample_info = {
"uid": None,
"project": self.app_state.project,
"proposal_id": self.app_state.proposal_id,
"beamline_id": self.app_state.beamline_id,
"owner": self.app_state.user,
"name": s_name,
Expand Down
8 changes: 5 additions & 3 deletions lixdc/widgets/tab_hplc_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def gen_batch(self):
self.create_request()
fname = self.compose_file()
utils.show_information('Batch file created.',
'Now copy the file located at: {} to the HPLC computer and execute the batch.'.format(fname),
'Now copy the file located at:\n\t{}\n\nTo the HPLC computer and execute the batch.'.format(fname),
'Success')

def create_request(self):
Expand Down Expand Up @@ -220,7 +220,9 @@ def compose_file(self):
plate['name'])
prefix = 'TBD'
sep = '\r\n'
with open(fname, 'w') as f:
utils.makedirs(self.app_state.get_user_path())
full_file_path = "{}{}".format(self.app_state.get_user_path(),fname)
with open(full_file_path, 'w') as f:
f.write(''+sep)
f.write('[Header]'+sep)
f.write('Batch File Name\t{}\{}'.format(batch_path, fname)+sep)
Expand Down Expand Up @@ -381,4 +383,4 @@ def compose_file(self):
multi_injection, barcode, sampler_file, conc_overrides
)
)
return fname
return full_file_path
Loading

0 comments on commit 7b215ee

Please sign in to comment.