-
Notifications
You must be signed in to change notification settings - Fork 668
Create a issue board list by milestone_id still depends on label_id #1897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is due to: python-gitlab/gitlab/v4/objects/boards.py Line 26 in bd1ecdd
So that would need to be changed. Probably should make it |
That's exactly what I was going to post. I can do the changes if it's possible |
@kurisukun feel free to go ahead :) IIUC this will technically also be incorrect since at least one of them is required. So (as a follow-up maybe) we could also introduce mutually exclusive/"requires one of" type of flow, ala Ansible: |
@kurisukun I've assigned this to you for now for the initial simple change as you expressed interest, feel free to unassign if you don't feel like working on this though :) |
the same issue exists for creating assignee boards this_board.lists.create({ 'assignee_id': user.id })
There is explicit reference to being able to create assignee and milestone lists in the API docs. https://gitlab.com/gitlab-org/gitlab/-/issues/6397 https://docs.gitlab.com/ee/api/boards.html#create-a-board-list
|
@nejch .. where would we implement mutually exclusive/requires? argparse has https://docs.python.org/3/library/argparse.html#mutual-exclusion |
@walterrowe thanks for looking into this! For an initial fix, I think we'd be happy with first just making the arguments optional and let the API response guide the user (the user will get a 4xx response with the details I believe). However if you're willing to work on a fix in the code itself, that'd be awesome :) We do already make some limited use of this in our CLI, see: Lines 229 to 246 in 387a140
However, we would need to handle this in the API client itself, not just the CLI wrapper, and I'm not sure we should bring in Lines 333 to 336 in 387a140
Currently, we validate args in |
@nejch I agree.
Then in places like boards.pyL26 we would see:
I don't want to fork and work on it until we agree on direction. |
@walterrowe that sounds good!
I think we might even need a list/tuple of tuples here, because there could be several sets of mutually exclusive attributes for busier endpoints, but I'm not 100%.
Sounds great. Another option, since these two are mostly just copies of each other with the |
@nejch took a simple stab at this for _check_missing_create_attrs() def _check_missing_create_attrs(self, data: Dict[str, Any]) -> None:
missing = [ attr for attr in self._create_attrs.required if attr not in data ]
if len(missing) > 0:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")
exclusives = [ attr for attr in data if attr in self._create_attrs.exclusive ]
if len(exclusives) > 1:
raise AttributeError(f"Provide only one of these attributes: {', '.join(exclusives)}")
if len(exclusives) == 0:
raise AttributeError(f"Must provide one of these attributes: {', '.join(self._create_attrs.exclusive)}")
Does this achieve the desired effect? I'm thinking about your proposal for a common validate_attrs(). That seems to make a lot of sense on the surface. |
@nejch - is this what you are thinking for a common _validate_attrs() .. ? How do we discern whether to reference _update_attrs vs _create_attrs? Any direction on this would be helpful in pushing this forward. def _validate_attrs(self, data: Dict[str, Any], excludes=[]) -> None:
if TYPE_CHECKING:
assert self._obj_cls is not None
# Remove the id field from the required list as it was previously moved
# to the http path.
required = tuple(
[k for k in self._update_attrs.required if k != excludes]
)
missing = [ attr for attr in required if attr not in data ]
if len(missing) > 0:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")
exclusives = [ attr for attr in data if attr in self._create_attrs.exclusive ]
if len(exclusives) > 1:
raise AttributeError(f"Provide only one of these attributes: {', '.join(exclusives)}")
if len(exclusives) == 0:
raise AttributeError(f"Must provide one of these attributes: {', '.join(self._create_attrs.exclusive)}") |
@walterrowe I was thinking we could supply attributes explicitly as an argument, e.g.: def _validate_attrs(
self,
data: Dict[str, Any],
attributes: RequiredOptional,
excludes: Optional[list] = None
) -> None:
if excludes is None:
excludes = []
required = [k for k in attributes.required if k not in excludes]
missing = [attr for attr in required if attr not in data]
if missing:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")
exclusives = [attr for attr in data if attr in attributes.exclusive]
if len(exclusives) > 1:
raise AttributeError(f"Provide only one of these attributes: {', '.join(exclusives)}")
if not exclusives:
raise AttributeError(f"Must provide one of these attributes: {', '.join(attributes.exclusive)}") So generally that looks good but I'm also thinking there might be some completely optional but mutually exclusive parameters, in which case we'd need to change this logic to simply check both required and optional data provided against the lists of mutually exclusive groups. We need to check the upstream API docs first though to see where this appears, e.g. https://docs.gitlab.com/search/?query=Mutually%20exclusive, I'll take a look today as well. |
Ah .. would _check_update_attrs and _check_create_attrs call this and pass in the requisite attributes and excludes lists? |
trying to get a sense of right place to do this
this introduces the least change and implements it once. class inheritance is new to me so trying to get a handle on how this should be done. in base.py RequiredOptional dataclass class add the exclusive attribute: @dataclass(frozen=True)
class RequiredOptional:
required: Tuple[str, ...] = ()
optional: Tuple[str, ...] = ()
exclusive: Tuple[str, ...] = () in base.py in class RESTManager we add: def _validate_attrs(
self,
data: Dict[str, Any],
attributes: RequiredOptional,
excludes: Optional[list] = None
) -> None:
if excludes is None:
excludes = []
required = [k for k in attributes.required if k not in excludes]
missing = [attr for attr in required if attr not in data]
if missing:
raise AttributeError(f"Missing attributes: {', '.join(missing)}")
exclusives = [attr for attr in data if attr in attributes.exclusive]
if len(exclusives) > 1:
raise AttributeError(f"Provide only one of these attributes: {', '.join(exclusives)}")
if not exclusives:
raise AttributeError(f"Must provide one of these attributes: {', '.join(attributes.exclusive)}") in mixin.py we change _check_missing_create_attrs() to: def _check_missing_create_attrs(self, data: Dict[str, Any]) -> None:
self._validate_attrs(data, attributes=self._create_required) in mixin.py we change _check_update_create_attrs() to: def _check_missing_update_attrs(self, data: Dict[str, Any]) -> None:
if TYPE_CHECKING:
assert self._obj_cls is not None
excludes = [ self._obj_cls._id_attr ]
self._validate_attrs(data, attributes=self._update_required, excludes=excludes) in gitlab/v4/objects/boards.py we change GroupBoardListManager class _create_attrs as follows: _create_attrs = RequiredOptional(exclusive=("label_id","assignee_id","milestone_id")) as I said class inheritance is new to me so looking for some more seasoned guidance on proper syntax, etc. |
@nejch - I spent quite a bit of time reading Python docs and perusing base.py and mixin.py to get up to speed on how all of this is glued together. I feel pretty confident about my proposal above. I have a fork of python-gitlab with local changes I have not yet committed. Let me know if you agree with the above, let me know what else I should include (doc updates?), etc, and I will commit, push, and submit a merge request. This doesn't handle the more complex case of multiple mutually exclusive sets of params. I think that can come later. We have people waiting on this specific enhancement. |
Here is a proposed commit message:
|
Probably will need to be something like this:
|
Thanks @JohnVillalovos! @walterrowe I'd say it's even simpler, you can remove the I think you can go ahead and open a PR, it's actually easier to do code review once we have something pushed ;) |
…n to fix board lists (#2037) add exclusive tuple to RequiredOptional data class to support for mutually exclusive attributes consolidate _check_missing_create_attrs and _check_missing_update_attrs from mixins.py into _validate_attrs in utils.py change _create_attrs in board list manager classes from required=('label_ld',) to exclusive=('label_id','asignee_id','milestone_id') closes #1897
Description of the problem, including code/CLI snippet
I followed the API documentation which says that the usage of label_id, milestone_id and assignee_id is mutually exclusive. So I wanted to create a board which would relate on milestones to have a timeline. The function goes like:
Expected Behavior
Normally the board should be created according to the documentation.
Actual Behavior
Specifications
The text was updated successfully, but these errors were encountered: