Source code for adamspy.adripy.tool
"""Contains the DrillTool class
"""
import os
import sys
import traceback
import shutil
from thornpy import numtype
from .utilities import TO_BLOCK_HEADER_PATTERN, get_cdb_location, get_cdb_path, get_full_path
from .constants import DATABASE_INFO, TO_PARAMETER_PATTERN
[docs]class DrillTool():
"""
Object representing an Adams Drill tool. Instances must be generated from a tool property file.
Attributes
----------
property_file : str
Tiem Orbit file representing the drill tool.
name : str
Name of the tool.
tool_type : str
Type of the tool.
extension : str
Extension used for tiem orbit file
table : str
Name of the cdb table used for the particular tool type
"""
_ACCEPTED_TYPES = [
'drillpipe',
'drill_collar',
'accelerator',
'stabilizer',
'short_collar',
'dart',
'jar',
'agitator',
'blade_reamer',
'drill_n_ream',
'dog_leg_reamer',
'crossover',
'darts',
'flex_pipe',
'hw_pipe',
'pdc_bit',
'motor',
'shock_sub',
'lwd_tool',
'mfr_tool',
'mwd_tool',
'instrumentation_sub',
'generic_long',
'generic_short',
'top_drive',
'equivalent_upper_string'
]
def __init__(self, property_file):
"""Initializes the `DrillTool` object.
Parameters
----------
property_file : str
Tiem Orbit file representing the drill tool.
"""
self._check_extension(property_file)
self.property_file = get_cdb_path(property_file)
self.name = self._get_name()
self.tool_type = self._get_type()
self.extension = self._get_extension()
self.table = self._get_table()
self._name_receivers = []
[docs] def bind_name_to(self, name_receiver):
"""Appends `name_receiver` to :attr:`_name_receivers`.
All the methods in the `name_receiver` :obj:list will be called anytime the :meth:`rename()` method is called
Parameters
----------
name_receiver : func
Any method that takes a :class:`DrillTool`.
"""
self._name_receivers.append(name_receiver)
[docs] def rename(self, new_name, remove_original=False):
"""Renames the tool
Note
----
If this tool is part of a :class:`DrillString` object, the :class:`DrillString` object will be updated with the new name and property file.
Parameters
----------
new_name : str
New name for the tool.
remove_original : bool
If `True` will rename the property file. If `False` will create a new one.
"""
# Determine the new filename
current_filename = get_full_path(self.property_file)
current_filepath = os.path.split(current_filename)[0]
new_filename = os.path.join(current_filepath, f'{new_name}.{self.extension}')
# Copy the file to the new location
shutil.copyfile(current_filename, new_filename)
self.property_file = get_cdb_path(new_filename)
# Modify the property file
self.modify_parameter_value('Name', new_name)
# Change the name variable
self.name = new_name
if remove_original is True:
# Delete the old property file
os.remove(current_filename)
# NOTE: The for loop below updates the name and property file in the DrillString object
# Run all the name receivers
for name_receiver in self._name_receivers:
name_receiver(self)
[docs] def copy_file(self, directory=None, cdb=None):
"""Copies the file to `directory` or to the appropriate table in `cdb`.
Parameters
----------
write_directory : str
Directory in which to write the file. (default is None.)
cdb : str
Name of the cdb in which to write the file. This argument overrides the directory. (default is None.)
Raises
------
ValueError
Raised if neither directory nor cdb are given.
ValueError
Raised if not all parameters have been defined.
Note
----
Either `directory` or `cdb` must be given.
"""
# Check that directory or cdb was given.
if directory is None and cdb is None:
raise ValueError('Either directory or cdb is required!')
# Determine the filename for writing
if cdb is not None:
# If cdb was given
filename = os.path.join(get_cdb_location(cdb), self.table, f'{self.name}.{self.extension}')
else:
# If cdb not given and directory was given
filename = os.path.join(directory, f'{self.name}.{self.extension}')
# Copy the file to the new location
shutil.copyfile(get_full_path(self.property_file), filename)
# Change the property_file attribute
self.property_file = get_cdb_path(filename)
[docs] def get_parameter_value(self, parameter_to_get):
"""
Returns the value of the specified parameter from the property file.
Note
----
This method cannot get parameters from the UNITS block.
Parameters
----------
parameter_to_get : str
Name of the parameter to get from the property file.
Returns
-------
:obj:`string` or :obj:`float`
Value of the specified parameter from the property file.
"""
# Initilize return variable as None
found = False
# Read the property file
filename = get_full_path(self.property_file)
with open(filename, 'rb') as fid:
lines_b = fid.readlines()
# Decode each line ignore any exceptions that occur on commented lines
lines = []
for line_b in lines_b:
try:
lines.append(line_b.decode().replace('\r', ''))
except UnicodeDecodeError as err:
if line_b[0:1].decode() == '$':
pass
else:
raise err
# Initialize a boolean specifying if the for loop is at a line in the [UNITS] block
at_units_block = False
for line in lines:
# For each line in the property file
if at_units_block and TO_BLOCK_HEADER_PATTERN.match(line):
# If end of units block reached
at_units_block = False
elif line.startswith('[UNITS]'):
# If beginning of units block reached
at_units_block = True
if TO_PARAMETER_PATTERN.match(line) and at_units_block is False:
# If the line matches the pattern of a parameter definition
# Split the line into parameter and value
[current_parameter, current_value] = line.replace('\n','').replace(' ','').split('=')
if current_parameter.lower() == parameter_to_get.lower():
# If the parameter on the current line is the parameter to get
if "'" in current_value:
# If the value is a string
value = current_value.replace("'",'').strip()
else:
# If the value is a number
value = float(current_value) if numtype.str_is_float(current_value) else int(current_value)
found = True
break
if not found:
# If the parameter wasn't found raise an exception
filename = self.property_file
raise DrillToolError(f'The parameter {parameter_to_get} was not found in {filename}!')
return value
[docs] def modify_parameter_value(self, parameter_to_change, new_value):
"""Modifies a parameter in a :class:`DrillTool` property file.
Parameters
----------
parameter_to_change : str
Name of the parameter to modify. Must match the name in the property file. (Not case sensitive).
new_value : :obj:`float` or :obj:`str`
New value of the parameter to change
new_filename : str
Filename of new property file. If None the original property file will be overwritten (the default is None.)
"""
filename = get_full_path(self.property_file)
# Read the lines in the original file into a list
with open(filename, 'r') as fid:
original_lines = fid.readlines()
# Write a new list with the correct line modified
new_lines = []
for line in original_lines:
# For each line in the original file, append to new_lines
new_lines.append(line)
if TO_PARAMETER_PATTERN.match(line):
# If the line matches the pattern of a parameter definition
[current_parameter, value] = line.replace('\n','').replace(' ','').replace('\t','').split('=')
if current_parameter.lower() == parameter_to_change.lower():
# If the parameter is the parameter to be changed
if "'" in value:
# If the value is a string, add quotes
new_value = "'{}'".format(new_value)
new_lines[-1] = ' {} = {}\n'.format(current_parameter, new_value)
with open(filename, 'w') as fid:
fid.writelines(new_lines)
def _get_name(self):
return self.get_parameter_value('Name')
def _get_type(self):
return self.get_parameter_value('File_Type').lower()
def _get_extension(self):
extension = DATABASE_INFO[self.tool_type]['extension']
return extension
def _get_table(self):
table = DATABASE_INFO[self.tool_type]['table']
return table
@classmethod
def _check_extension(cls, property_file):
"""Checks if the extension on `property_file` is in the list of supported tool file types.
Parameters
----------
property_file : str
Filename of an Adams Drill property file.
Raises
------
DrillToolError
Raised if the extension on `property_file` is not in the list of supported tool file types.
"""
ext = os.path.splitext(property_file)[1]
if ext not in ['.' + info['extension'] for name, info in DATABASE_INFO.items() if name in cls._ACCEPTED_TYPES]:
raise DrillToolError(f'The extension {ext} is not supported by the DrillTool class.')