Skip to content

Commit dd09a80

Browse files
andamianAdrian Damian
andauthored
Accept links to external targets (CADC-13802) (#229)
* Support for external links --------- Co-authored-by: Adrian Damian <[email protected]>
1 parent 1a40451 commit dd09a80

File tree

8 files changed

+48
-66
lines changed

8 files changed

+48
-66
lines changed

.github/workflows/cibuild.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
16-
- name: Set up Python 3.10
16+
- name: Set up Python 3.11
1717
uses: actions/setup-python@v5
1818
with:
19-
python-version: "3.10"
19+
python-version: "3.11"
2020
- name: Install tox
2121
run: python -m pip install --upgrade tox
2222
- name: egg-info vos
@@ -28,7 +28,7 @@ jobs:
2828
strategy:
2929
fail-fast: true
3030
matrix:
31-
python-version: ["3.7","3.8","3.9","3.10","3.11","3.12"]
31+
python-version: ["3.8","3.9","3.10","3.11","3.12", "3.13"]
3232
package: [vos]
3333
steps:
3434
- name: Checkout code
@@ -54,10 +54,10 @@ jobs:
5454
needs: tests
5555
steps:
5656
- uses: actions/checkout@v4
57-
- name: Set up Python 3.10
57+
- name: Set up Python 3.11
5858
uses: actions/setup-python@v5
5959
with:
60-
python-version: "3.10"
60+
python-version: "3.11"
6161
- name: Setup Graphviz
6262
uses: ts-graphviz/setup-graphviz@v2
6363
- name: Install tox

vos/setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ edit_on_github = False
5252
github_project = opencadc/vostools
5353
install_requires =
5454
html2text>=2016.5.29
55-
cadcutils>=1.5.3
55+
cadcutils>=1.5.4
5656
aenum
5757

5858
# version should be PEP440 compatible (http://www.python.org/dev/peps/pep-0440)
59-
version = 3.6.1.1
59+
version = 3.6.2
6060

6161
[options.extras_require]
6262
test =

vos/test/scripts_local/vospace-link-atest.tcsh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,8 @@ foreach resource ($resources)
136136
echo " [OK]"
137137

138138
echo -n "create link to unknown scheme in URI"
139-
#TODO not sure why this is not working anymore
140-
#$LNCMD $CERT unknown://cadc.nrc.ca~vault/CADCAuthtest1 $CONTAINER/e2link >& /dev/null && echo " [FAIL]" && exit -1
141-
echo " [SKIPPED - TODO]"
139+
$LNCMD $CERT unknown://cadc.nrc.ca~vault/CADCAuthtest1 $CONTAINER/e2link >& /dev/null && echo " [FAIL]" && exit -1
140+
echo " [OK]"
142141

143142
echo -n "follow the invalid link and fail"
144143
$CPCMD $CERT $CONTAINER/e2link/somefile /tmp >& /dev/null && echo " [FAIL]" && exit -1

vos/test/scripts_prod/vospace-link-atest.tcsh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,8 @@ foreach resource ($resources)
130130
echo " [OK]"
131131

132132
echo -n "create link to unknown scheme in URI"
133-
#TODO not sure why this is not working anymore
134-
#$LNCMD $CERT unknown://cadc.nrc.ca~vault/CADCRegtest1 $CONTAINER/e2link >& /dev/null && echo " [FAIL]" && exit -1
135-
echo " [SKIPPED - TODO]"
133+
$LNCMD $CERT file:///cadc.nrc.ca~vault/CADCRegtest1 $CONTAINER/e2link >& /dev/null || echo " [FAIL]" && exit -1
134+
echo " [OK]"
136135

137136
echo -n "follow the invalid link and fail"
138137
$CPCMD $CERT $CONTAINER/e2link/somefile /tmp >& /dev/null && echo " [FAIL]" && exit -1

vos/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ name = vos
55

66
[tox]
77
envlist =
8-
py{37,38,39,310,311,312}
8+
py{38,39,310,311,312,313}
99
requires =
1010
pip >= 19.3.1
1111

vos/vos/commands/vln.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
33
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
44
#
5-
# (c) 2022. (c) 2022.
5+
# (c) 2025. (c) 2025.
66
# Government of Canada Gouvernement du Canada
77
# National Research Council Conseil national de recherches
88
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -89,7 +89,8 @@
8989
9090
vln vos:vospace/junk.txt vos:vospace/linkToJunk.txt
9191
vln vos:vospace/directory vos:vospace/linkToDirectory
92-
vln http://external.data.source vos:vospace/linkToExternalDataSource
92+
vln https://external.data.source vos:vospace/linkToExternalDataSource
93+
vln file:///data/localfile vos:vospace/linkToLocalFile
9394
9495
""".format(URI_DESCRIPTION)
9596

@@ -106,11 +107,10 @@ def vln():
106107
vospace_certfile=opt.certfile,
107108
vospace_token=opt.token,
108109
insecure=opt.insecure)
109-
if not client.is_remote_file(opt.source) or \
110-
not client.is_remote_file(opt.target):
110+
if not client.is_remote_file(opt.target):
111111
raise ArgumentError(
112112
None,
113-
"source must be vos node or http url, target must be vos node")
113+
"target must be vos node")
114114

115115
client.link(opt.source, opt.target)
116116
except ArgumentError as ex:

vos/vos/tests/test_vos.py

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -330,51 +330,36 @@ def test_glob(self):
330330
# /anode/abc /anode/def - > anode/a* should return
331331
# /anode/adc
332332

333-
mock_node = MagicMock(type='vos:ContainerNode')
334-
mock_node.configure_mock(name='anode')
335-
mock_child_node1 = Mock(type='vos:DataNode')
336-
mock_child_node1.name = 'abc'
337-
mock_child_node2 = Mock(type='vos:DataNode')
338-
mock_child_node2.name = 'def'
339-
# because we use wild characters in the root node,
340-
# we need to create a corresponding node for the base node
341-
mock_base_node = Mock(type='vos:ContainerNode')
342-
mock_base_node.name = 'vos:'
343-
mock_base_node.node_list = [mock_node]
344-
mock_node.node_list = [mock_base_node, mock_child_node1,
345-
mock_child_node2]
346333
client = Client()
347-
client.get_node = Mock(
348-
side_effect=[mock_node, mock_base_node, mock_node])
334+
client.listdir = Mock(
335+
return_value=['abc', 'def']) # list parent dir and anode dir
349336
self.assertEqual(['vos:/anode/abc'], client.glob('vos:/anode/a*'))
337+
client.listdir = Mock(
338+
return_value=['abc', 'def']) # list parent dir and anode dir
339+
self.assertEqual([], client.glob('vos:/anode/m*'))
340+
client.access = Mock()
341+
client.listdir = Mock(
342+
side_effect=[['anode'], ['abc', 'def']])
350343
self.assertEqual(['vos:/anode/abc'], client.glob('vos:/*node/abc'))
344+
client.listdir = Mock(
345+
side_effect=[['anode'], ['abc', 'def']])
346+
self.assertEqual([], client.glob('vos:/*foo/abc'))
351347

352348
# test nodes:
353349
# /anode/.test1 /bnode/sometests /bnode/blah
354-
# /[a,c]node/*test* should return /bnode/somtests (.test1 is filtered
350+
# /[a,c]node/*test* should return /bnode/sometests (.test1 is filtered
355351
# out as a special file)
356352

357-
mock_node1 = MagicMock(type='vos:ContainerNode')
358-
mock_node1.configure_mock(name='anode')
359-
mock_node1.node_list = [mock_child_node1]
360-
361-
mock_child_node2 = Mock(type='vos:DataNode')
362-
mock_child_node2.name = 'sometests'
363-
mock_child_node3 = Mock(type='vos:DataNode')
364-
mock_child_node3.name = 'blah'
365-
mock_node2 = MagicMock(type='vos:ContainerNode')
366-
mock_node2.configure_mock(name='bnode')
367-
mock_node2.node_list = [mock_child_node2, mock_child_node3]
368-
369353
# because we use wild characters in the root node,
370354
# we need to create a corresponding node for the base node
371-
mock_base_node = Mock(type='vos:DataNode')
372-
mock_base_node.name = 'vos:'
373-
mock_base_node.node_list = [mock_node1, mock_node2]
374355
client = Client()
375356
client.is_remote_file = Mock()
376-
client.get_node = Mock(
377-
side_effect=[mock_base_node, mock_node1, mock_node2])
357+
dirs = ['anode', 'bnode']
358+
anode = ['.test']
359+
bnode = ['sometests', 'blah']
360+
client.listdir = Mock(
361+
side_effect=[dirs, anode, bnode])
362+
client.access = Mock()
378363
self.assertEqual(['vos:/bnode/sometests'],
379364
client.glob('vos:/[a,b]node/*test*'))
380365

vos/vos/vos.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# ****************** CANADIAN ASTRONOMY DATA CENTRE *******************
33
# ************* CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
44
#
5-
# (c) 2024. (c) 2024.
5+
# (c) 2025. (c) 2025.
66
# Government of Canada Gouvernement du Canada
77
# National Research Council Conseil national de recherches
88
# Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
@@ -811,7 +811,7 @@ def get_children(self, client, sort, order, limit=None):
811811
""" Gets an iterator over the nodes held to by a ContainerNode"""
812812
# IF THE CALLER KNOWS THEY DON'T NEED THE CHILDREN THEY
813813
# CAN SET LIMIT=0 IN THE CALL Also, if the number of nodes
814-
# on the firt call was less than 500, we likely got them
814+
# on the first call was less than 500, we likely got them
815815
# all during the init
816816
if not self.isdir():
817817
return
@@ -1890,7 +1890,7 @@ def copy(self, source, destination, send_md5=False, disposition=False,
18901890
# purposes:
18911891
# 1. Check if source is identical to destination and
18921892
# avoid sending the bytes again.
1893-
# 2. send info to the service so it can recover in case
1893+
# 2. send info to the service so that it can recover in case
18941894
# the bytes got corrupted on the way
18951895
src_md5 = md5_cache.MD5Cache.compute_md5(source)
18961896
if src_md5 == dest_node_md5:
@@ -2186,8 +2186,7 @@ def get_node_url(self, uri, method='GET', view=None, limit=None,
21862186
def link(self, src_uri, link_uri):
21872187
"""Make link_uri point to src_uri.
21882188
2189-
:param src_uri: the existing resource, either a vospace uri or a http
2190-
url
2189+
:param src_uri: the existing resource to link to
21912190
:type src_uri: unicode
21922191
:param link_uri: the vospace node to create that will be a link to
21932192
src_uri
@@ -2197,7 +2196,8 @@ def link(self, src_uri, link_uri):
21972196
HttpException exceptions declared in the cadcutils.exceptions module
21982197
"""
21992198
link_uri = self.fix_uri(link_uri)
2200-
src_uri = self.fix_uri(src_uri)
2199+
if "://" not in src_uri:
2200+
src_uri = self.fix_uri(src_uri)
22012201

22022202
# if the link_uri points at an existing directory then we try and
22032203
# make a link into that directory
@@ -2723,24 +2723,23 @@ def get_info_list(self, uri):
27232723

27242724
def listdir(self, uri, force=False):
27252725
"""
2726-
Walk through the directory structure a la os.walk.
2727-
Setting force=True will make sure no cached results are used.
2726+
Return a list with the content of the directory
27282727
Follows LinksNodes to their destination location.
2728+
Note: this method returns a list of children names. For larger
2729+
directories, use get_children_info() to iterate through it and
2730+
avoid loading the entire content into memory.
27292731
27302732
:param force: don't use cached values, retrieve from service.
27312733
:param uri: The ContainerNode to get a listing of.
27322734
:rtype [unicode]
27332735
"""
2734-
names = []
27352736
logger.debug(str(uri))
2736-
node = self.get_node(uri, limit=None, force=force)
2737+
node = self.get_node(uri, limit=0, force=force)
27372738
while node.type == "vos:LinkNode":
27382739
uri = node.target
27392740
# logger.debug(uri)
2740-
node = self.get_node(uri, limit=None, force=force)
2741-
for thisNode in node.node_list:
2742-
names.append(thisNode.name)
2743-
return names
2741+
node = self.get_node(uri, limit=0, force=force)
2742+
return [i.name for i in self.get_children_info(node.uri, force=force)]
27442743

27452744
def _node_type(self, uri):
27462745
"""

0 commit comments

Comments
 (0)