Skip to content

Commit

Permalink
[_586] implement xml_mode() in a new irods.helpers module
Browse files Browse the repository at this point in the history
  • Loading branch information
d-w-moore committed Jul 25, 2024
1 parent d70f151 commit 47f23f9
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 2 deletions.
37 changes: 37 additions & 0 deletions irods/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import contextlib
import re
from ..test.helpers import (home_collection,
make_session as make_test_session)
from irods.message import (ET, XML_Parser_Type)

__all__ = ['make_session', 'home_collection', 'xml_mode']


def make_session(test_server_version = False, **kwargs):
return make_test_session(test_server_version = test_server_version, **kwargs)

make_session.__doc__ = re.sub(r'(test_server_version\s*)=\s*\w+',r'\1 = False',make_test_session.__doc__)


@contextlib.contextmanager
def xml_mode(s):
"""In a with-block, this context manager can temporarily change the client's choice of XML parser.
Example usages:
with("QUASI_XML"):
# ...
with(XML_Parser_Type.QUASI_XML):
# ..."""

try:
if isinstance(s,str):
ET(getattr(XML_Parser_Type,s)) # e.g. xml_mode("QUASI_XML")
elif isinstance(s,XML_Parser_Type):
ET(s) # e.g. xml_mode(XML_Parser_Type.QUASI_XML)
else:
msg = "xml_mode argument must be a string (e.g. 'QUASI_XML') or an XML_Parser_Type enum."
raise ValueError(msg)
yield
finally:
ET(None)

7 changes: 6 additions & 1 deletion irods/message/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def __repr__(self):
XML_Parser_Type.__members__ = {k:v for k,v in XML_Parser_Type.__dict__.items()
if isinstance(v,XML_Parser_Type)}

PARSER_TYPE_STRINGS = {v:k for k,v in XML_Parser_Type.__members__.items() if v.value != 0}
# This creates a mapping from the "valid" (nonzero) XML_Parser_Type enums -- those which represent the actual parser
# choices -- to their corresponding names as strings (e.g. XML_Parser_Type.STANDARD_XML is mapped to 'STANDARD_XML'):
PARSER_TYPE_STRINGS = {v:k for k,v in XML_Parser_Type.__members__.items() if v.value != 0}

# We maintain values on a per-thread basis of:
# - the server version with which we're communicating
Expand Down Expand Up @@ -111,6 +113,9 @@ def default_XML_parser(get_module = False):
d = _default_XML
return d if not get_module else _XML_parsers[d]

def string_for_XML_parser(parser_enum):
return PARSER_TYPE_STRINGS[parser_enum]

_XML_parsers = {
XML_Parser_Type.STANDARD_XML : ET_xml,
XML_Parser_Type.QUASI_XML : ET_quasi_xml,
Expand Down
51 changes: 51 additions & 0 deletions irods/test/data_obj_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,57 @@ def test_access_through_resc_hierarchy__243(self):
finally:
s.resources.remove('parent')

@unittest.skipIf(set(os.environ.keys()) & {'PYTHON_IRODSCLIENT_CONFIG__CONNECTIONS__XML_PARSER_DEFAULT',
'PYTHON_IRODSCLIENT_CONFIGURATION_PATH', 'PYTHON_IRODSCLIENT_DEFAULT_XML'},
"skipping due to possible overwriting of test-apropos settings by a configuration file or environment setting")
def test_temporary_xml_mode_change_with_operation_as_proof__issue_586(self):
from irods.helpers import (xml_mode, home_collection)
sess = irods.test.helpers.make_session()
hc = home_collection(sess)
odd_name = '{hc}/\1'.format(**locals())

# Currently 'STANDARD_XML' is the default, and 'QUASI_XML' is a convenient alternative to use when
# object names are used which contain special characters (e.g. '\1') hostile to standard XML parsers.
default_xml_parser = 'STANDARD_XML'

from irods.message import (current_XML_parser, string_for_XML_parser)
active_xml_parser_for_thread = lambda : string_for_XML_parser(current_XML_parser())

self.assertEqual(active_xml_parser_for_thread(), default_xml_parser)

with xml_mode('QUASI_XML'):
sess.data_objects.create(odd_name)

# Test that the xml parser setting isn't permanently changed
self.assertEqual(active_xml_parser_for_thread(), default_xml_parser)

try:
if default_xml_parser == 'STANDARD_XML':
with self.assertRaises(xml.etree.ElementTree.ParseError):
sess.collections.get(hc).data_objects
finally:
with xml_mode('QUASI_XML'):
sess.data_objects.unlink(odd_name, force = True)

def test_temporary_xml_mode_changes_have_desired_thread_limited_effect__issue_586(self):
from irods.message import (current_XML_parser, string_for_XML_parser)
active_xml_parser_for_thread = lambda : string_for_XML_parser(current_XML_parser())
from concurrent.futures import ThreadPoolExecutor
from irods.helpers import xml_mode
original_xml_parser = active_xml_parser_for_thread()
other_xml_parser = list({'STANDARD_XML', 'QUASI_XML', 'SECURE_XML'} - {original_xml_parser})[0]

self.assertNotEqual(other_xml_parser, original_xml_parser)

with xml_mode(other_xml_parser):
# Test that this thread is the only one affected, and that in it we get 'QUASI_XML' when we call
# current_XML_parser(), i.e. the function used internally by ET() to retrieve the current parser module.
self.assertEqual(other_xml_parser, active_xml_parser_for_thread())
self.assertEqual(original_xml_parser, ThreadPoolExecutor(max_workers = 1).submit(active_xml_parser_for_thread).result())

self.assertEqual(active_xml_parser_for_thread(), original_xml_parser)


def test_register_with_xml_special_chars(self):
test_dir = helpers.irods_shared_tmp_dir()
loc_server = self.sess.host in ('localhost', socket.gethostname())
Expand Down
16 changes: 15 additions & 1 deletion irods/test/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ def recast(k):
# Create a connection for test, based on ~/.irods environment by default.

def make_session(test_server_version = True, **kwargs):
"""Connect to an iRODS server as determined by any client environment
file present at a standard location, and by any keyword arguments given.
Arguments:
test_server_version: Of type bool; in the `irods.test.helpers` version of this
function, defaults to True. A True value causes
*iRODS_Server_Too_Recent* to be raised if the server
connected to is more recent than the current Python iRODS
client's advertised level of compatibility.
**kwargs: Keyword arguments. Fed directly to the iRODSSession
constructor. """

try:
env_file = kwargs.pop('irods_env_file')
except KeyError:
Expand All @@ -159,7 +173,6 @@ def make_session(test_server_version = True, **kwargs):
except KeyError:
env_file = os.path.expanduser('~/.irods/irods_environment.json')
session = iRODSSession( irods_env_file = env_file, **kwargs )

if test_server_version:
connected_version = session.server_version[:3]
advertised_version = IRODS_VERSION[:3]
Expand All @@ -172,6 +185,7 @@ def make_session(test_server_version = True, **kwargs):


def home_collection(session):
"""Return a string value for the given session's home collection."""
return "/{0.zone}/home/{0.username}".format(session)


Expand Down

0 comments on commit 47f23f9

Please sign in to comment.