Skip to content

Commit 20f596f

Browse files
committed
Misc minor refactoring
1 parent 9386729 commit 20f596f

File tree

5 files changed

+125
-108
lines changed

5 files changed

+125
-108
lines changed

conftest.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import d1_test.d1_test_case
4242
import d1_test.instance_generator.random_data
4343
import d1_test.sample
44+
import d1_test.test_files
4445

4546
from d1_client.cnclient_1_2 import CoordinatingNodeClient_1_2 as cn_v1
4647
from d1_client.cnclient_2_0 import CoordinatingNodeClient_2_0 as cn_v2
@@ -53,8 +54,6 @@
5354
import django.db.transaction
5455
import django.db.utils
5556

56-
57-
5857
D1_SKIP_LIST = 'skip_passed/list'
5958
D1_SKIP_COUNT = 'skip_passed/count'
6059

@@ -146,20 +145,18 @@ def pytest_sessionstart(session):
146145
"""Called by pytest before calling session.main()
147146
- When running in parallel with xdist, this is called once for each worker. By
148147
default, the number of workers is the same as the number of CPU cores.
149-
- NOTE: These cannot be used when running in parallel with xdist.
150148
"""
151149
exit_if_switch_used_with_xdist([
152150
'--sample-ask',
153151
'--sample-update',
154152
'--sample-review',
155-
'--sample-tidy',
153+
# --sample-tidy is supported with xdist
156154
'--pycharm',
157155
'--fixture-refresh',
158156
'--skip',
159157
'--skip-clear',
160158
'--skip-print',
161159
])
162-
163160
if pytest.config.getoption('--sample-tidy'):
164161
logging.info('Starting sample tidy')
165162
d1_test.sample.start_tidy()
@@ -169,11 +166,9 @@ def pytest_sessionstart(session):
169166
'Dropping and creating GMN template database from JSON fixture file'
170167
)
171168
db_drop(TEMPLATE_DB_KEY)
172-
173169
if pytest.config.getoption('--skip-clear'):
174170
logging.info('Clearing list of passed tests')
175171
_clear_skip_list()
176-
177172
if pytest.config.getoption('--skip-print'):
178173
logging.info('Printing list of passed tests')
179174
_print_skip_list()
@@ -260,9 +255,15 @@ def _print_skip_list():
260255

261256
def _open_error_in_pycharm(call):
262257
"""Attempt to open error locations in PyCharm. Use with --exitfirst (-x)"""
263-
src_path = call.excinfo.traceback[-1].path
264-
src_line = call.excinfo.traceback[-1].lineno + 1
265-
d1_test.pycharm.open_and_set_cursor(src_path, src_line)
258+
logging.error('Test raised exception: {}'.format(call.excinfo.exconly()))
259+
test_path, test_lineno = d1_test.d1_test_case.D1TestCase.get_d1_test_case_location(call.excinfo.tb)
260+
logging.error('D1TestCase location: {}:{}'.format(test_path, test_lineno))
261+
exc_frame = call.excinfo.traceback.getcrashentry()
262+
logging.error('Exception location: {}({})'.format(exc_frame.path, exc_frame.lineno + 1))
263+
# src_path = call.excinfo.traceback[-1].path
264+
# src_line = call.excinfo.traceback[-1].lineno + 1
265+
d1_test.pycharm.open_and_set_cursor(test_path, test_lineno)
266+
266267

267268
# Fixtures
268269

@@ -351,6 +352,17 @@ def mn_client_v2(request):
351352
def mn_client_v1_v2(request):
352353
yield request.param(d1_test.d1_test_case.MOCK_MN_BASE_URL)
353354

355+
@pytest.fixture(
356+
scope='function',
357+
params=d1_test.test_files.TRICKY_IDENTIFIER_LIST
358+
)
359+
def tricky_identifier_tup(request):
360+
"""Unicode identifiers that use various reserved characters and embedded URL
361+
segments. Each fixture is a 2-tuple where the first value is a
362+
Unicode identifier and the second is a URL escaped version of the identifier.
363+
"""
364+
yield request.param
365+
354366

355367
# Settings
356368

@@ -408,9 +420,8 @@ def django_db_setup(request, django_db_blocker):
408420
# acquire() and release(). It's probably related to how the worker processes
409421
# relate to each other when launched by pytest-xdist as compared to what the
410422
# multiprocessing module expects.
411-
with posix_ipc.Semaphore(
412-
'/{}'.format(__name__), flags=posix_ipc.O_CREAT, initial_value=1
413-
):
423+
with posix_ipc.Semaphore('/{}'.format(__name__), flags=posix_ipc.O_CREAT,
424+
initial_value=1):
414425
logging.warning(
415426
'LOCK BEGIN {} {}'.format(
416427
db_get_name_by_key(TEMPLATE_DB_KEY), d1_common.date_time.utc_now()
@@ -464,8 +475,9 @@ def db_create_from_template():
464475
new_db_name = db_get_name_by_key(TEST_DB_KEY)
465476
template_db_name = db_get_name_by_key(TEMPLATE_DB_KEY)
466477
logging.info(
467-
'Creating new db from template. new_db="{}" template_db="{}"'.
468-
format(new_db_name, template_db_name)
478+
'Creating new db from template. new_db="{}" template_db="{}"'.format(
479+
new_db_name, template_db_name
480+
)
469481
)
470482
run_sql(
471483
'postgres',
@@ -480,7 +492,7 @@ def db_populate_by_json(db_key):
480492
'loaddata',
481493
'db_fixture',
482494
database=db_key,
483-
# d1_test.sample.get_path('db_fixture.json.bz2'),
495+
# d1_test.sample.get_path_list('db_fixture.json.bz2'),
484496
# verbosity=0,
485497
# commit=False,
486498
)

gmn/src/d1_gmn/app/auth.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ def assert_allowed(request, level, pid):
216216
"""Assert that one or more subjects are allowed to perform action on object.
217217
Raise NotAuthorized if object exists and subject is not allowed.
218218
Raise NotFound if object does not exist.
219-
Return NoneType if subject is allowed.
220219
"""
221220
if not d1_gmn.app.models.ScienceObject.objects.filter(pid__did=pid).exists():
222221
raise d1_common.types.exceptions.NotFound(

gmn/src/d1_gmn/app/middleware/session_cert.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,6 @@
2929
equivalent identities and group memberships (no SubjectInfo). This limits the
3030
user's access to data that is publicly available and that is available directly
3131
to that user (as designated in the Subject DN).
32-
33-
The list of subjects to use for access control is created with the following
34-
algorithm:
35-
36-
- Start with empty set of subjects
37-
- Add the symbolic subject, "public"
38-
- If the connection was made without a certificate:
39-
- Stop.
40-
- Add the symbolic subject, "authenticatedUser"
41-
- Get the DN from the Subject and serialize it to a standardized string. This
42-
string is called Subject below.
43-
- Add Subject
44-
- If the certificate does not have a SubjectInfo extension:
45-
- Stop.
46-
- Add subjects from SubjectInfo.
4732
"""
4833

4934
import d1_common.cert.subjects
@@ -71,14 +56,14 @@ def get_subjects(request):
7156

7257

7358
def get_authenticated_subjects(cert_pem):
74-
primary_str, equivalent_set = d1_common.cert.subjects.extract_subjects(
75-
cert_pem.encode('utf-8')
59+
"""Return primary subject and set of equivalents authenticated by certificate
60+
- {cert_pem} can be str or bytes
61+
"""
62+
if isinstance(cert_pem, str):
63+
cert_pem = cert_pem.encode('utf-8')
64+
return d1_common.cert.subjects.extract_subjects(
65+
cert_pem
7666
)
77-
equivalent_set |= {
78-
d1_common.const.SUBJECT_PUBLIC,
79-
d1_common.const.SUBJECT_AUTHENTICATED,
80-
}
81-
return primary_str, equivalent_set
8267

8368

8469
def _is_certificate_provided(request):

lib_common/src/d1_common/type_conversions.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,29 @@
1919
# See the License for the specific language governing permissions and
2020
# limitations under the License.
2121
"""
22-
In the DataONE Python stack, XML docs are represented in a few different ways.
22+
- Handle conversions between XML representations used in the D1 Python stack
23+
- Handle conversions between v1 and v2 DataONE XML types
2324
24-
- Received and transmitted as utf-8 text documents.
25-
- On the borders of the Python domain, handled as utf-8 or Unicode strings.
26-
- Schema validation and manipulation in Python code as PyXB binding objects.
27-
- General processing as ElementTrees.
25+
In the stack, XML docs are represented as follows:
26+
27+
- As native Unicode str, typically "pretty printed" with indentations, when
28+
formatted for display
29+
- As UTF-8 encoded byte strings when send sending or receiving over the network,
30+
or loading or saving as files
31+
- Schema validation and manipulation in Python code as PyXB binding objects
32+
- General processing as ElementTrees
33+
34+
In order to allow conversions between all representations without having to
35+
implement separate conversions for each combination of input and output
36+
representation, a "hub and spokes" model is used. Native Unicode str was
37+
selected as the "hub" representation due to:
2838
2939
- PyXB provides translation to/from string and DOM.
3040
- ElementTree provides translation to/from string.
31-
32-
We select string as the "hub" representation for XML.
3341
"""
3442

3543
import re
44+
import xml.etree
3645
import xml.etree.ElementTree
3746

3847
import pyxb
@@ -48,13 +57,23 @@
4857
# PyXB shares information about all known types between all imported bindings.
4958
PYXB_BINDING = d1_common.types.dataoneTypes_v1
5059

60+
# Map common namespace prefixes to namespaces
5161
NS_DICT = {
5262
'v1': str(v1_0.Namespace),
5363
'v1_1': str(v1_1.Namespace),
5464
'v1_2': str(v1_2.Namespace),
5565
'v2': str(v2_0.Namespace),
66+
'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
67+
'ore': 'http://www.openarchives.org/ore/terms/',
68+
'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
69+
'dcterms': 'http://purl.org/dc/terms/',
70+
'cito': 'http://purl.org/spar/cito/',
5671
}
5772

73+
74+
# Map common namespaces to prefixes
75+
NS_REVERSE_DICT = { v: k for k, v in NS_DICT.items() }
76+
5877
BINDING_TO_VERSION_TAG_DICT = {
5978
v1_0: 'v1',
6079
v1_1: 'v1',
@@ -271,9 +290,19 @@ def str_to_pyxb(xml_str):
271290
return PYXB_BINDING.CreateFromDocument(xml_str)
272291

273292

274-
def str_to_etree(xml_str):
275-
return xml.etree.ElementTree.fromstring(xml_str)
293+
def str_to_etree(xml_str, encoding='utf-8'):
294+
"""Parse an XML doc to an ElementTree"""
295+
# parser = xml.etree.ElementTree.XMLParser(encoding=encoding)
296+
# return xml.etree.ElementTree.ElementTree(
297+
# return xml.etree.ElementTree.fromstring(xml_str)
298+
# )
299+
# parser = xml.etree.ElementTree.XMLParser(encoding=encoding)
300+
# return xml.etree.ElementTree.ElementTree(
301+
# xml.etree.ElementTree.fromstring(xml_str, parser=parser)
302+
# )
276303

304+
parser = xml.etree.ElementTree.XMLParser(encoding=encoding)
305+
return xml.etree.ElementTree.fromstring(xml_str, parser=parser)
277306

278307
def pyxb_to_str(pyxb_obj):
279308
return pyxb_obj.toxml('utf-8')
@@ -292,21 +321,30 @@ def etree_to_pyxb(etree_obj):
292321

293322

294323
# ElementTree
295-
# https://docs.python.org/2/library/xml.etree.elementtree.html
324+
325+
def replace_namespace_with_prefix(tag_str, ns_reverse_dict=None):
326+
"""Given a tag on the form "{namespace}name", return "prefix:name"
327+
E.g.: {http://www.openarchives.org/ore/terms/}ResourceMap -> ore:ResourceMap
328+
"""
329+
ns_reverse_dict = ns_reverse_dict or NS_REVERSE_DICT
330+
for namespace_str, prefix_str in ns_reverse_dict.items():
331+
tag_str = tag_str.replace('{{{}}}'.format(namespace_str), '{}:'.format(prefix_str))
332+
return tag_str
296333

297334

298335
def etree_replace_namespace(etree_obj, ns_str):
299336
_replace_namespace_recursive(etree_obj, ns_str)
300337

301338

302339
def _replace_namespace_recursive(el, ns_str):
303-
el.tag = re.sub(r'\{.*\}', '{{{}}}'.format(ns_str), el.tag)
340+
el.tag = re.sub(r'{.*\}', '{{{}}}'.format(ns_str), el.tag)
304341
el.text = el.text.strip() if el.text else None
305342
el.tail = el.tail.strip() if el.tail else None
306343
for child_el in el:
307344
_replace_namespace_recursive(child_el, ns_str)
308345

309346

347+
310348
def strip_v2_elements(etree_obj):
311349
"""Remove elements and attributes that are only valid in v2 types"""
312350
if etree_obj.tag == v2_0_tag('logEntry'):
@@ -358,3 +396,5 @@ def strip_node_list(etree_obj):
358396

359397
def v2_0_tag(element_name):
360398
return '{{{}}}{}'.format(NS_DICT['v2'], element_name)
399+
400+

0 commit comments

Comments
 (0)