diff --git a/CHANGELOG.md b/CHANGELOG.md index 11253bf2d..c0898bda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,27 @@ Categories for each release: Added, Changed, Deprecated, Removed, Fixed, Securit ## Unreleased -## [385.0] - beta +## [387.0] - beta + +### Added + +* `--name-mode` parameter for `dx find data` + +### Fixed + +* Nonce generation for python 3 + +## [386.0] - 2024.12.2 + +### Added + +* `--database-results-restricted` `--unset-database-results-restricted` for `dx new project` + +### Fixed + +* Remove pipes import for Python 3.13 compatibility + +## [385.0] - 2024.11.8 ### Added diff --git a/src/R/dxR/DESCRIPTION b/src/R/dxR/DESCRIPTION index b21f9d2ca..16a7b2e19 100644 --- a/src/R/dxR/DESCRIPTION +++ b/src/R/dxR/DESCRIPTION @@ -1,7 +1,7 @@ Package: dxR Type: Package Title: DNAnexus R Client Library -Version: 0.385.0 +Version: 0.387.0 Author: Katherine Lai Maintainer: Katherine Lai Description: dxR is an R extension containing API wrapper functions for diff --git a/src/R/dxR/R/dxR-package.R b/src/R/dxR/R/dxR-package.R index 3925b6afa..eac916be2 100644 --- a/src/R/dxR/R/dxR-package.R +++ b/src/R/dxR/R/dxR-package.R @@ -4,7 +4,7 @@ ##' the new DNAnexus platform. ##' ##' \tabular{ll}{ Package: \tab dxR\cr Type: \tab Package\cr Version: \tab -##' 0.385.0\cr License: \tab Apache License (== 2.0)\cr +##' 0.387.0\cr License: \tab Apache License (== 2.0)\cr ##' } ##' ##' @name dxR-package diff --git a/src/R/dxR/man/dxR-package.Rd b/src/R/dxR/man/dxR-package.Rd index fb3d65c03..95d9b2b9c 100644 --- a/src/R/dxR/man/dxR-package.Rd +++ b/src/R/dxR/man/dxR-package.Rd @@ -9,7 +9,7 @@ } \details{ \tabular{ll}{ Package: \tab dxR\cr Type: \tab Package\cr - Version: \tab 0.385.0\cr License: \tab Apache License (== + Version: \tab 0.387.0\cr License: \tab Apache License (== 2.0)\cr } } \author{ diff --git a/src/python/dxpy/bindings/dxproject.py b/src/python/dxpy/bindings/dxproject.py index 99490fe8a..0bf210cb1 100644 --- a/src/python/dxpy/bindings/dxproject.py +++ b/src/python/dxpy/bindings/dxproject.py @@ -285,6 +285,7 @@ def new(self, name, summary=None, description=None, region=None, protected=None, restricted=None, download_restricted=None, contains_phi=None, tags=None, properties=None, bill_to=None, database_ui_view_only=None, external_upload_restricted=None, default_symlink=None, + database_results_restricted=None, **kwargs): """ :param name: The name of the project @@ -313,6 +314,8 @@ def new(self, name, summary=None, description=None, region=None, protected=None, :type database_ui_view_only: boolean :param external_upload_restricted: If provided, whether project members can upload data to project from external sources, e.g. outside of job :type external_upload_restricted: boolean + :param database_results_restricted: If provided, minimum amount of data that project members with VIEW access can see from databases in the project + :type database_results_restricted: int :param default_symlink: If provided, the details needed to have writable symlinks in the project. Dict must include drive, container, and optional prefix. :type default_symlink: dict @@ -346,6 +349,8 @@ def new(self, name, summary=None, description=None, region=None, protected=None, input_hash["databaseUIViewOnly"] = database_ui_view_only if external_upload_restricted is not None: input_hash["externalUploadRestricted"] = external_upload_restricted + if database_results_restricted is not None: + input_hash["databaseResultsRestricted"] = database_results_restricted if tags is not None: input_hash["tags"] = tags if properties is not None: @@ -360,7 +365,9 @@ def new(self, name, summary=None, description=None, region=None, protected=None, def update(self, name=None, summary=None, description=None, protected=None, restricted=None, download_restricted=None, version=None, allowed_executables=None, unset_allowed_executables=None, - database_ui_view_only=None, external_upload_restricted=None, **kwargs): + database_ui_view_only=None, external_upload_restricted=None, + database_results_restricted=None, unset_database_results_restricted=None, + **kwargs): """ :param name: If provided, the new project name :type name: string @@ -376,10 +383,16 @@ def update(self, name=None, summary=None, description=None, protected=None, :type download_restricted: boolean :param allowed_executables: If provided, these are the only executable ID(s) allowed to run as root executions in this project :type allowed_executables: list + :param unset_allowed_executables: If provided, removes any restrictions set by allowed_executables + :type unset_allowed_executables: boolean :param database_ui_view_only: If provided, whether the viewers on the project can access the database data directly :type database_ui_view_only: boolean :param external_upload_restricted: If provided, whether project members can upload data to project from external sources, e.g. outside of job :type external_upload_restricted: boolean + :param database_results_restricted: If provided, minimum amount of data that project members with VIEW access can see from databases in the project + :type database_results_restricted: int + :param unset_database_results_restricted: If provided, removes any restrictions set by database_results_restricted + :type unset_database_results_restricted: boolean :param version: If provided, the update will only occur if the value matches the current project's version number :type version: int @@ -413,6 +426,10 @@ def update(self, name=None, summary=None, description=None, protected=None, update_hash["databaseUIViewOnly"] = database_ui_view_only if external_upload_restricted is not None: update_hash["externalUploadRestricted"] = external_upload_restricted + if database_results_restricted is not None: + update_hash["databaseResultsRestricted"] = database_results_restricted + if unset_database_results_restricted is not None: + update_hash["databaseResultsRestricted"] = None dxpy.api.project_update(self._dxid, update_hash, **kwargs) def invite(self, invitee, level, send_email=True, **kwargs): diff --git a/src/python/dxpy/cli/parsers.py b/src/python/dxpy/cli/parsers.py index 3396bcbb3..59c218647 100644 --- a/src/python/dxpy/cli/parsers.py +++ b/src/python/dxpy/cli/parsers.py @@ -446,6 +446,10 @@ def get_update_project_args(args): input_params['allowedExecutables'] = args.allowed_executables if args.unset_allowed_executables: input_params['allowedExecutables'] = None + if args.database_results_restricted is not None: + input_params['databaseResultsRestricted'] = args.database_results_restricted + if args.unset_database_results_restricted: + input_params['databaseResultsRestricted'] = None if args.external_upload_restricted is not None: input_params['externalUploadRestricted'] = args.external_upload_restricted == 'true' return input_params diff --git a/src/python/dxpy/scripts/dx.py b/src/python/dxpy/scripts/dx.py index b2a9fd530..54ece2e4e 100644 --- a/src/python/dxpy/scripts/dx.py +++ b/src/python/dxpy/scripts/dx.py @@ -1432,6 +1432,8 @@ def new_project(args): inputs["containsPHI"] = True if args.database_ui_view_only: inputs["databaseUIViewOnly"] = True + if args.database_results_restricted is not None: + inputs["databaseResultsRestricted"] = args.database_results_restricted if args.monthly_compute_limit is not None: inputs["monthlyComputeLimit"] = args.monthly_compute_limit if args.monthly_egress_bytes_limit is not None: @@ -2479,7 +2481,7 @@ def find_data(args): visibility=args.visibility, properties=args.properties, name=args.name, - name_mode='glob', + name_mode=args.name_mode, typename=args.type, tags=args.tag, link=args.link, project=args.project, @@ -5427,6 +5429,9 @@ def positive_number(value): allowed_executables_group = parser_update_project.add_mutually_exclusive_group() allowed_executables_group.add_argument('--allowed-executables', help='Executable ID(s) this project is allowed to run. This operation overrides any existing list of executables.', type=str, nargs="+") allowed_executables_group.add_argument('--unset-allowed-executables', help='Removes any restriction to run executables as set by --allowed-executables', action='store_true') +database_results_restricted_group = parser_update_project.add_mutually_exclusive_group() +database_results_restricted_group.add_argument('--database-results-restricted', help='Viewers on the project can access only more than specified size of visual data from databases', type=positive_integer) +database_results_restricted_group.add_argument('--unset-database-results-restricted', help='Removes any restriction to return data from databases as set by --database-results-restricted', action='store_true') parser_update_project.set_defaults(func=update_project) register_parser(parser_update_project, subparsers_action=subparsers_update, categories="metadata") @@ -5813,6 +5818,7 @@ def __call__(self, parser, namespace, values, option_string=None): action='store_true') parser_new_project.add_argument('--database-ui-view-only', help='Viewers on the project cannot access database data directly', default=False, action='store_true') +parser_new_project.add_argument('--database-results-restricted', help='Viewers on the project can access only more than specified size of visual data from databases', type=positive_integer) parser_new_project.add_argument('--monthly-compute-limit', type=positive_integer, help='Monthly project spending limit for compute') parser_new_project.add_argument('--monthly-egress-bytes-limit', type=positive_integer, help='Monthly project spending limit for egress (in Bytes)') parser_new_project.add_argument('--monthly-storage-limit', type=positive_number, help='Monthly project spending limit for storage') @@ -6137,7 +6143,8 @@ def __call__(self, parser, namespace, values, option_string=None): ) parser_find_data.add_argument('--state', choices=['open', 'closing', 'closed', 'any'], help='State of the object') parser_find_data.add_argument('--visibility', choices=['hidden', 'visible', 'either'], default='visible', help='Whether the object is hidden or not') -parser_find_data.add_argument('--name', help='Name of the object') +parser_find_data.add_argument('--name', help='Search criteria for the object name, interpreted according to the --name-mode') +parser_find_data.add_argument('--name-mode', default='glob', help='Name mode to use for searching', choices=['glob', 'exact', 'regexp']) parser_find_data.add_argument('--type', help='Type of the data object') parser_find_data.add_argument('--link', help='Object ID that the data object links to') parser_find_data.add_argument('--all-projects', '--allprojects', help='Extend search to all projects (excluding public projects)', action='store_true') diff --git a/src/python/dxpy/toolkit_version.py b/src/python/dxpy/toolkit_version.py index 55d93a6e5..43119e567 100644 --- a/src/python/dxpy/toolkit_version.py +++ b/src/python/dxpy/toolkit_version.py @@ -1 +1 @@ -version = '0.385.0' +version = '0.387.0' diff --git a/src/python/dxpy/utils/__init__.py b/src/python/dxpy/utils/__init__.py index fdc360cd2..734417af5 100644 --- a/src/python/dxpy/utils/__init__.py +++ b/src/python/dxpy/utils/__init__.py @@ -292,10 +292,10 @@ class Nonce: ''' def __init__(self): try: - self.nonce = "%s%f" % (str(binascii.hexlify(os.urandom(32))), time.time()) + self.nonce = "%s%f" % (binascii.hexlify(os.urandom(32)).decode('utf-8'), time.time()) except: random.seed(time.time()) - self.nonce = "%s%f" % (str(random.getrandbits(8*26)), time.time()) + self.nonce = "%s%f" % (random.getrandbits(8*26), time.time()) def __str__(self): return self.nonce diff --git a/src/python/dxpy/utils/describe.py b/src/python/dxpy/utils/describe.py index 05fa836e7..6be9179bb 100644 --- a/src/python/dxpy/utils/describe.py +++ b/src/python/dxpy/utils/describe.py @@ -415,7 +415,7 @@ def print_project_desc(desc, verbose=False): 'containsPHI', 'databaseUIViewOnly', 'externalUploadRestricted', 'region', 'storageCost', 'pendingTransfer', 'atSpendingLimit', 'currentMonthComputeAvailableBudget', 'currentMonthEgressBytesAvailableBudget', 'currentMonthStorageAvailableBudget', 'currentMonthComputeUsage', 'currentMonthEgressBytesUsage', - 'currentMonthExpectedStorageUsage', 'defaultSymlink' + 'currentMonthExpectedStorageUsage', 'defaultSymlink', 'databaseResultsRestricted', # Following are app container-specific 'destroyAt', 'project', 'type', 'app', 'appName' ] @@ -455,6 +455,8 @@ def print_project_desc(desc, verbose=False): print_json_field('External Upload Restricted', desc['externalUploadRestricted']) if 'defaultSymlink' in desc and verbose: print_json_field('Default Symlink', desc['defaultSymlink']) + if 'databaseResultsRestricted' in desc and desc['databaseResultsRestricted']: + print_json_field('Database Results Restricted', desc['databaseResultsRestricted']) # Usage print_field("Created", render_timestamp(desc['created'])) diff --git a/src/python/test/test_dxpy.py b/src/python/test/test_dxpy.py index 8f31ccd30..d5b713afe 100755 --- a/src/python/test/test_dxpy.py +++ b/src/python/test/test_dxpy.py @@ -139,6 +139,7 @@ def test_new(self): self.assertEqual(desc["databaseUIViewOnly"], False) self.assertEqual(desc["externalUploadRestricted"], False) self.assertEqual(desc["tags"], []) + self.assertEqual(desc["databaseResultsRestricted"], None) prop = dxpy.api.project_describe(dxproject.get_id(), {'fields': {'properties': True}}) self.assertEqual(prop['properties'], {}) @@ -192,6 +193,7 @@ def test_update_describe(self): download_restricted=True, external_upload_restricted=False, allowed_executables=["applet-abcdefghijklmnopqrstuzwx"], + database_results_restricted=10, description="new description") desc = dxproject.describe() self.assertEqual(desc["id"], self.proj_id) @@ -203,13 +205,15 @@ def test_update_describe(self): self.assertEqual(desc["externalUploadRestricted"], False) self.assertEqual(desc["description"], "new description") self.assertEqual(desc["allowedExecutables"][0], "applet-abcdefghijklmnopqrstuzwx") + self.assertEqual(desc["databaseResultsRestricted"], 10) self.assertTrue("created" in desc) - dxproject.update(restricted=False, download_restricted=False, unset_allowed_executables=True) + dxproject.update(restricted=False, download_restricted=False, unset_allowed_executables=True, unset_database_results_restricted=True) desc = dxproject.describe() self.assertEqual(desc["restricted"], False) self.assertEqual(desc["downloadRestricted"], False) - self.assertTrue("allowedExecutables" not in desc) + self.assertEqual(desc["allowedExecutables"], None) + self.assertEqual(desc["databaseResultsRestricted"], None) def test_new_list_remove_folders(self): dxproject = dxpy.DXProject()