diff --git a/geonodectl b/geonodectl index 8760a4a..5b55375 100755 --- a/geonodectl +++ b/geonodectl @@ -18,7 +18,7 @@ from geonoderest.resources import ( ) from geonoderest.documents import GeonodeDocumentsHandler from geonoderest.maps import GeonodeMapsHandler -from geonoderest.people import GeonodePeopleHandler +from geonoderest.users import GeonodeUsersHandler from geonoderest.geoapps import GeonodeGeoappsHandler from geonoderest.uploads import GeonodeUploadsHandler from geonoderest.executionrequest import GeonodeExecutionRequestHandler @@ -224,14 +224,16 @@ To use this tool you have to set the following environment variables before star "patch", help="patch datasets metadata" ) datasets_patch.add_argument(type=int, dest="pk", help="pk of dataset to patch") - datasets_patch.add_argument( + datasets_patch_mutually_exclusive_group = datasets_patch.add_mutually_exclusive_group() + + datasets_patch_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='patch metadata by providing a json string like: \'{"category":"{"identifier": "farming"}}\'', ) - datasets_patch.add_argument( + datasets_patch_mutually_exclusive_group.add_argument( "--json_path", dest="json_path", type=str, @@ -289,16 +291,22 @@ To use this tool you have to set the following environment variables before star ) # PATCH - documents_patch = documents_subparsers.add_parser( - "patch", help="patch documents metadata" - ) - documents_patch.add_argument(type=int, dest="pk", help="pk of document to patch") - documents_patch.add_argument( + documents_patch = documents_subparsers.add_parser("patch", help="patch documents metadata") + documents_patch.add_argument(type=int, dest="pk", help="pk of documents to patch") + documents_patch_mutually_exclusive_group = documents_patch.add_mutually_exclusive_group() + + documents_patch_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='patch metadata by providing a json string like: \'{"category":"{"identifier": "farming"}}\'', ) + documents_patch_mutually_exclusive_group.add_argument( + "--json_path", + dest="json_path", + type=str, + help="add metadata by providing a path to a json file", + ) # DESCRIBE documents_describe = documents_subparsers.add_parser( @@ -329,12 +337,20 @@ To use this tool you have to set the following environment variables before star # PATCH maps_patch = maps_subparsers.add_parser("patch", help="patch maps metadata") maps_patch.add_argument(type=int, dest="pk", help="pk of map to patch") - maps_patch.add_argument( + maps_patch_mutually_exclusive_group = maps.add_mutually_exclusive_group() + + maps_patch_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='patch metadata by providing a json string like: \'{"category":"{"identifier": "farming"}}\'', ) + maps_patch_mutually_exclusive_group.add_argument( + "--json_path", + dest="json_path", + type=str, + help="add metadata by providing a path to a json file", + ) # DESCRIBE maps_describe = maps_subparsers.add_parser("describe", help="get map details") @@ -347,22 +363,21 @@ To use this tool you have to set the following environment variables before star # CREATE maps_create = maps_subparsers.add_parser("create", help="create an (empty) map") - maps_create.add_argument( + maps_create_mutually_exclusive_group = maps_create.add_mutually_exclusive_group() + maps_create_mutually_exclusive_group.add_argument( "--title", type=str, - required=True, dest="title", help="title of the new dataset ...", ) - - maps_create.add_argument( + maps_create_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='add metadata by providing a json string like: \'\'{ "category": {"identifier": "farming"}, "abstract": "test abstract" }\'\'', ) - maps_create.add_argument( + maps_create_mutually_exclusive_group.add_argument( "--json_path", dest="json_path", type=str, @@ -395,13 +410,22 @@ To use this tool you have to set the following environment variables before star "patch", help="patch geoapps metadata" ) geoapps_patch.add_argument(type=int, dest="pk", help="pk of geoapp to patch") - geoapps_patch.add_argument( + + geoapps_patch_mutually_exclusive_group = geoapps_patch.add_mutually_exclusive_group() + geoapps_patch_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='patch metadata by providing a json string like: \'{"category":"{"identifier": "farming"}}\'', ) + geoapps_patch_mutually_exclusive_group.add_argument( + "--json_path", + dest="json_path", + type=str, + help="patch metadata (user credentials) by providing a path to a json file, like --set written in file ...", + ) + # DESCRIBE geoapps_describe = geoapps_subparsers.add_parser( "describe", help="get geoapp details" @@ -419,41 +443,50 @@ To use this tool you have to set the following environment variables before star ########################## # USERS ARGUMENT PARSING # ########################## - people = subparsers.add_parser( - "people", help="people|users commands", aliases=("users", "user") + users = subparsers.add_parser( + "users", help="user | users commands", aliases=("user",) ) - people_subparsers = people.add_subparsers( - help="geonodectl people commands", dest="subcommand", required=True + users_subparsers = users.add_subparsers( + help="geonodectl users commands", dest="subcommand", required=True ) # PATCH - people_patch = people_subparsers.add_parser("patch", help="patch users metadata") - people_patch.add_argument(type=int, dest="pk", help="pk of user to patch") - people_patch.add_argument( + users_patch = users_subparsers.add_parser("patch", help="patch users metadata") + users_patch.add_argument(type=int, dest="pk", help="pk of user to patch") + + user_patch_mutually_exclusive_group = users_patch.add_mutually_exclusive_group() + user_patch_mutually_exclusive_group.add_argument( "--set", dest="fields", type=str, help='patch metadata by providing a json string like: \'{"category":"{"identifier": "farming"}}\'', ) + user_patch_mutually_exclusive_group.add_argument( + "--json_path", + dest="json_path", + type=str, + help="patch metadata (user credentials) by providing a path to a json file, like --set written in file ...", + ) + # DESCRIBE - people_describe = people_subparsers.add_parser( - "describe", help="get people details" + users_describe = users_subparsers.add_parser( + "describe", help="get users details" ) - people_describe.add_argument( - type=int, dest="pk", help="pk of people to describe ..." + users_describe.add_argument( + type=int, dest="pk", help="pk of users to describe ..." ) - people_describe_subgroup = people_describe.add_mutually_exclusive_group( + users_describe_subgroup = users_describe.add_mutually_exclusive_group( required=False ) - people_describe_subgroup.add_argument( + users_describe_subgroup.add_argument( "--groups", dest="user_groups", required=False, action="store_true", help="show groups of user with given -pk ...", ) - people_describe_subgroup.add_argument( + users_describe_subgroup.add_argument( "--resources", dest="user_resources", required=False, @@ -462,11 +495,77 @@ To use this tool you have to set the following environment variables before star ) # LIST - people_subparsers.add_parser("list", help="list documents") + users_subparsers.add_parser("list", help="list documents") # DELETE - people_delete = people_subparsers.add_parser("delete", help="delete existing user") - people_delete.add_argument(type=int, dest="pk", help="pk of geoapp to delete ...") + users_delete = users_subparsers.add_parser("delete", help="delete existing user") + users_delete.add_argument(type=int, dest="pk", help="pk of geoapp to delete ...") + + # CREATE + users_create = users_subparsers.add_parser("create", help="create a new user") + user_create_mutually_exclusive_group = users_create.add_mutually_exclusive_group() + user_create_mutually_exclusive_group.add_argument( + "--username", + type=str, + dest="username", + help="username of the new user ... (mutually exclusive [a])", + ) + + users_create.add_argument( + "--email", + type=str, + required=False, + dest="email", + help="email of the new user ... (only working combined with --username) ...", + ) + + users_create.add_argument( + "--first_name", + type=str, + required=False, + dest="first_name", + help="first_name of the new user (only working combined with --username) ...", + ) + + users_create.add_argument( + "--last_name", + type=str, + required=False, + dest="last_name", + help="last_name of the new user (only working combined with --username) ...", + ) + + users_create.add_argument( + "--is_superuser", + action='store_true', + required=False, + dest="is_superuser", + default=False, + help="set to make the new user a superuser (only working combined with --username) ...", + ) + + users_create.add_argument( + "--is_staff", + action='store_true', + required=False, + dest="is_staff", + default=False, + help="set to make the new user a staff user (only working combined with --username) ...", + ) + + user_create_mutually_exclusive_group.add_argument( + "--json_path", + dest="json_path", + type=str, + help="add metadata (user credentials) by providing a path to a json file, like --set written in file ...(mutually exclusive [b])", + ) + + user_create_mutually_exclusive_group.add_argument( + "--set", + dest="fields", + type=str, + help='create user by providing a json string like: \'{"username":"test_user", "email":"test_email@gmail.com", "first_name": "test_first_name", "last_name":"test_last_name", "is_staff": true, "is_superuser": true}\' ... (mutually exclusive [c])', + ) ########################### # UPLOAD ARGUMENT PARSING # @@ -530,8 +629,8 @@ To use this tool you have to set the following environment variables before star g_obj = GeonodeDocumentsHandler(env=geonode_env) case "maps": g_obj = GeonodeMapsHandler(env=geonode_env) - case "people" | "users" | "user": - g_obj = GeonodePeopleHandler(env=geonode_env) + case "users" | "user": + g_obj = GeonodeUsersHandler(env=geonode_env) case "geoapps" | "apps": g_obj = GeonodeGeoappsHandler(env=geonode_env) case "uploads": @@ -540,7 +639,6 @@ To use this tool you have to set the following environment variables before star g_obj = GeonodeExecutionRequestHandler(env=geonode_env) case _: raise NotImplemented - g_obj_func = getattr(g_obj, "cmd_" + args.subcommand) g_obj_func(**args.__dict__) diff --git a/geonoderest/apiconf.py b/geonoderest/apiconf.py index 3dfaf98..d1cca7e 100644 --- a/geonoderest/apiconf.py +++ b/geonoderest/apiconf.py @@ -39,7 +39,6 @@ def from_env_vars() -> "GeonodeApiConf": "GEONODE_API_URL" not in os.environ or "GEONODE_API_BASIC_AUTH" not in os.environ ): - raise SystemExit( "env vars not set: GEONODE_API_URL, GEONODE_API_BASIC_AUTH" ) diff --git a/geonoderest/geonodeobject.py b/geonoderest/geonodeobject.py index 6bc5a7b..da01694 100644 --- a/geonoderest/geonodeobject.py +++ b/geonoderest/geonodeobject.py @@ -69,26 +69,23 @@ def cmd_patch( ValueError: catches json.decoder.JSONDecodeError and raises ValueError as decoding is not working """ - json_content: Dict = {} if json_path: with open(json_path, "r") as file: try: - j_dict = json.load(file) + json_content = json.load(file) except json.decoder.JSONDecodeError as E: json_decode_error_handler(str(file), E) - if "attribute_set" in j_dict: - j_dict.pop("attribute_set", None) - json_content = {**json_content, **j_dict} + if json_content is not None and "attribute_set" in json_content: + json_content.pop("attribute_set", None) - if fields: + elif fields: try: - f_dict = json.loads(fields) - json_content = {**json_content, **f_dict} + json_content = json.loads(fields) except json.decoder.JSONDecodeError as E: json_decode_error_handler(fields, E) - if json_content == {}: + else: raise ValueError( "At least one of 'fields' or 'json_path' must be provided." ) diff --git a/geonoderest/maps.py b/geonoderest/maps.py index d40f496..d60c00e 100644 --- a/geonoderest/maps.py +++ b/geonoderest/maps.py @@ -27,6 +27,7 @@ def cmd_create( title: Path, fields: Optional[str] = None, json_path: Optional[str] = None, + maplayers: Optional[List[int]] = [], **kwargs, ): """ @@ -41,25 +42,22 @@ def cmd_create( Raises: Json.decoder.JSONDecodeError: when decoding is not working """ - json_content: Dict = {} + json_content = None if json_path: with open(json_path, "r") as file: try: - j_dict = json.load(file) + json_content = json.load(file) except json.decoder.JSONDecodeError as E: json_decode_error_handler(str(file), E) - - if "attribute_set" in j_dict: - j_dict.pop("attribute_set", None) - json_content = {**json_content, **j_dict} - if fields: + elif fields: try: - f_dict = json.loads(fields) - json_content = {**json_content, **f_dict} + json_content = json.loads(fields) except json.decoder.JSONDecodeError as E: json_decode_error_handler(fields, E) - obj = self.create(title=title, extra_json_content=json_content, **kwargs) + obj = self.create( + title=title, json_content=json_content, maplayers=maplayers, **kwargs + ) print_json(obj) def create( diff --git a/geonoderest/people.py b/geonoderest/people.py deleted file mode 100644 index 483e1eb..0000000 --- a/geonoderest/people.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Dict - -from geonoderest.geonodeobject import GeonodeObjectHandler -from geonoderest.geonodetypes import GeonodeCmdOutListKey -from geonoderest.cmdprint import print_list_on_cmd, print_json - - -class GeonodePeopleHandler(GeonodeObjectHandler): - ENDPOINT_NAME = JSON_OBJECT_NAME = "users" - SINGULAR_RESOURCE_NAME = "user" - - LIST_CMDOUT_HEADER = [ - GeonodeCmdOutListKey(key="pk"), - GeonodeCmdOutListKey(key="username"), - GeonodeCmdOutListKey(key="first_name"), - GeonodeCmdOutListKey(key="last_name"), - ] - - def cmd_describe( - self, pk: int, user_resources: bool = False, user_groups: bool = False, **kwargs - ): - """show requested user in detail on cmd. Further show groups or accessable items of user - - Args: - pk (int): id of user to show in detail - user_resources (bool, optional): if set to true ressources accessable - by given user are listed. Defaults to False. - user_groups (bool, optional): if set to true groups of given user are printed. - Defaults to False. - - Raises: - SystemExit: if user_resource and user_groups are true at the same time this function gets confused and exits - """ - if user_resources and user_groups: - raise SystemExit( - "user_resource and user_group cannot be active at the same time ..." - ) - - obj = self.get( - pk, user_resources=user_resources, user_groups=user_groups, **kwargs - ) - # in this case print as list of ressources - if user_resources is True: - print_list_on_cmd(obj, self.LIST_CMDOUT_HEADER) - else: - print_json(obj) - - def get( - self, pk: int, user_resources: bool = False, user_groups: bool = False, **kwargs - ) -> Dict: - """get user details - - Args: - pk (int): id of user to get details about - user_resources (bool, optional): if set to true ressources accessable by given user are returned. - Defaults to False. - user_groups (bool, optional): if set to true groups of given user are returned. - Defaults to False. - - Raises: - AttributeError: if user_resource and user_groups are true at the same time this function gets confused and exits - - Returns: - Dict: requested info, details of user or list of accessable resources or groups of user are returned - """ - if user_resources and user_groups: - raise AttributeError( - "cannot handle user_resources and user_groups True at the same time ..." - ) - r: Dict - if user_groups is True: - r = self.http_get( - endpoint=f"{self.ENDPOINT_NAME}/{pk}/groups?page_size={kwargs['page_size']}" - ) - return r - elif user_resources is True: - r = self.http_get( - endpoint=f"{self.ENDPOINT_NAME}/{pk}/resources?page_size={kwargs['page_size']}" - ) - return r - else: - r = self.http_get( - endpoint=f"{self.ENDPOINT_NAME}/{pk}?page_size={kwargs['page_size']}" - ) - return r[self.SINGULAR_RESOURCE_NAME] diff --git a/geonoderest/rest.py b/geonoderest/rest.py index 63b59fc..cd155c0 100644 --- a/geonoderest/rest.py +++ b/geonoderest/rest.py @@ -17,7 +17,6 @@ class GeonodeRest(object): - DEFAULTS = {"page_size": 100, "page": 1} def __init__(self, env: GeonodeApiConf): @@ -135,7 +134,6 @@ def http_get(self, endpoint: str, params: Dict = {}) -> Dict: r = requests.get(url, headers=self.header, json=params, verify=self.verify) r.raise_for_status() except requests.exceptions.HTTPError as err: - logging.error(r.json()) raise SystemExit(err) return r.json() @@ -160,7 +158,6 @@ def http_patch(self, endpoint: str, params: Dict = {}) -> Dict: ) r.raise_for_status() except requests.exceptions.HTTPError as err: - logging.error(r.json()) raise SystemExit(err) return r.json() @@ -184,6 +181,5 @@ def http_delete(self, endpoint: str, params: Dict = {}) -> Dict: r = requests.delete(url, headers=self.header, verify=self.verify) r.raise_for_status() except requests.exceptions.HTTPError as err: - logging.error(r.json()) raise SystemExit(err) return r.json() diff --git a/geonoderest/users.py b/geonoderest/users.py new file mode 100644 index 0000000..0416f6b --- /dev/null +++ b/geonoderest/users.py @@ -0,0 +1,247 @@ +import json +from typing import Dict, Optional + +from geonoderest.resources import GeonodeResourceHandler +from geonoderest.geonodeobject import GeonodeObjectHandler +from geonoderest.geonodetypes import GeonodeCmdOutListKey +from geonoderest.cmdprint import ( + print_list_on_cmd, + print_json, + json_decode_error_handler, +) + + +class GeonodeUsersHandler(GeonodeObjectHandler): + ENDPOINT_NAME = JSON_OBJECT_NAME = "users" + SINGULAR_RESOURCE_NAME = "user" + + LIST_CMDOUT_HEADER = [ + GeonodeCmdOutListKey(key="pk"), + GeonodeCmdOutListKey(key="username"), + GeonodeCmdOutListKey(key="first_name"), + GeonodeCmdOutListKey(key="last_name"), + GeonodeCmdOutListKey(key="email"), + GeonodeCmdOutListKey(key="is_staff"), + GeonodeCmdOutListKey(key="is_superuser"), + ] + + def cmd_describe( + self, pk: int, user_resources: bool = False, user_groups: bool = False, **kwargs + ): + """show requested user in detail on cmd. Further show groups or accessable items of user + + Args: + pk (int): id of user to show in detail + user_resources (bool, optional): if set to true ressources accessable + by given user are listed. Defaults to False. + user_groups (bool, optional): if set to true groups of given user are printed. + Defaults to False. + + Raises: + SystemExit: if user_resource and user_groups are true at the same time this function gets confused and exits + """ + + obj = self.get( + pk, user_resources=user_resources, user_groups=user_groups, **kwargs + ) + # in this case print as list of ressources + if user_resources is True: + print_list_on_cmd( + obj["resources"], GeonodeResourceHandler.LIST_CMDOUT_HEADER + ) + else: + print_json(obj) + + def get( + self, pk: int, user_resources: bool = False, user_groups: bool = False, **kwargs + ) -> Dict: + """get user details + + Args: + pk (int): id of user to get details about + user_resources (bool, optional): if set to true ressources accessable by given user are returned. + Defaults to False. + user_groups (bool, optional): if set to true groups of given user are returned. + Defaults to False. + + Raises: + AttributeError: if user_resource and user_groups are true at the same time this function gets confused and exits + + Returns: + Dict: requested info, details of user or list of accessable resources or groups of user are returned + """ + if user_resources and user_groups: + raise AttributeError( + "cannot handle user_resources and user_groups True at the same time ..." + ) + r: Dict + if user_groups is True: + r = self.http_get( + endpoint=f"{self.ENDPOINT_NAME}/{pk}/groups?page_size={kwargs['page_size']}&page={kwargs['page']}" + ) + return r + elif user_resources is True: + endpoint = f"{GeonodeResourceHandler.ENDPOINT_NAME}?page_size={kwargs['page_size']}&page={kwargs['page']}" + endpoint += "&filter{owner.pk}=" + str(pk) + r = self.http_get(endpoint=endpoint) + return r + else: + r = self.http_get( + endpoint=f"{self.ENDPOINT_NAME}/{pk}?page_size={kwargs['page_size']}&page={kwargs['page']}" + ) + return r[self.SINGULAR_RESOURCE_NAME] + + def cmd_patch( + self, + pk: int, + fields: Optional[str] = None, # JSON string or path to JSON file + json_path: Optional[str] = None, # Path to JSON file + **kwargs, + ): + """Patch user details and print the result. + + Args: + pk (int): User ID. + fields (Optional[str]): JSON string. Defaults to None. + json_path (Optional[str]): Path to JSON file. Defaults to None. + kwargs: Additional keyword arguments. + """ + # Load JSON content from file or string + json_content = None + if json_path: + with open(json_path, "r") as file: + try: + json_content = json.load(file) + except json.decoder.JSONDecodeError as E: + json_decode_error_handler(str(file), E) + elif fields: + try: + json_content = json.loads(fields) + except json.decoder.JSONDecodeError as E: + json_decode_error_handler(fields, E) + + if json_content is None: + raise ValueError( + "At least one of 'fields' or 'json_path' must be provided." + ) + + # Apply patch and print result + obj = self.patch(pk=pk, json_content=json_content, **kwargs) + print_json(obj) + + def patch( + self, + pk: int, + json_content: Optional[Dict] = None, + **kwargs, + ) -> Dict: + """Patch user details and return the result. + + Args: + pk (int): User ID. + json_content (Optional[Dict]): JSON content to patch. + kwargs: Additional keyword arguments. + + Returns: + Dict: Updated user details. + """ + + # Send a PATCH request to the GeoNode API to update the user details. + # The request includes the JSON content to patch. + # The endpoint is constructed using the resource name and the user ID. + # The JSON content is sent as part of the request parameters. + # The response from the API is returned. + + obj = self.http_patch( + endpoint=f"{self.ENDPOINT_NAME}/{pk}/", params=json_content + ) + return obj + + def cmd_create( + self, + username: str, + email: str = "", + first_name: str = "", + last_name: str = "", + is_superuser: bool = False, + is_staff: bool = False, + fields: Optional[str] = None, + json_path: Optional[str] = None, + **kwargs, + ): + """ + creates an user with the given characteristics + + Args: + username (str): username of the new user + password (Optional[str]): password of the new user + email (str): email of the new user + first_name (str): first name of the new user + last_name (str): last name of the new user + is_superuser (bool): if true user will be a superuser + is_staff (bool): if true user will be staff user + fields (str): string of potential json object + json_path (str): path to a json file + """ + json_content = None + if json_path: + with open(json_path, "r") as file: + try: + json_content = json.load(file) + except json.decoder.JSONDecodeError as E: + json_decode_error_handler(str(file), E) + elif fields: + try: + json_content = json.loads(fields) + except json.decoder.JSONDecodeError as E: + json_decode_error_handler(fields, E) + + obj = self.create( + username=username, + email=email, + first_name=first_name, + last_name=last_name, + is_superuser=is_superuser, + is_staff=is_staff, + json_content=json_content, + **kwargs, + ) + print_json(obj) + + def create( + self, + username: str, + email: str = "", + first_name: str = "", + last_name: str = "", + is_superuser: bool = False, + is_staff: bool = False, + json_content: Optional[Dict] = None, + **kwargs, + ) -> Dict: + """ + creates an user with the given characteristics + + Args: + username (str): username of the new user + password (Optional[str]): password of the new user + email (str): email of the new user + first_name (str): first name of the new user + last_name (str): last name of the new user + is_superuser (bool): if true user will be a superuser + is_staff (bool): if true user will be staff user + json_content (dict) dict object with addition metadata / fields + """ + if json_content is None: + json_content = { + "username": username, + "email": email, + "first_name": first_name, + "last_name": last_name, + "is_staff": is_staff, + "is_superuser": is_superuser, + } + return self.http_post( + endpoint=self.ENDPOINT_NAME, + params=json_content, + )