-
Notifications
You must be signed in to change notification settings - Fork 51
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
Made it work with the 2023-10
version of the API
#116
Conversation
…ce_id Feature/create board by workspace
Feature/duplicate board query
Added: Create and Update columns
Added: Exception for catching Monday API query errors
Added: Custom queries resource
Added: Me queries resource
Added: Query for deleting updates
@willnaoosmith Thanks so much for doing this!! We'll take a look ASAP! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chdastolfo, I'm generally 👍 with these changes, though I'm mostly praying that the tests are reliable and trusting that the author has tested sufficiently on the new API version.
However, there's some formatting stuff that's upsetting the ProdPerfect linter. This is coming from the author's private fork, so I can't just mess fix it up on that branch, and I'm not going to ask them to fix up stuff specific to our linting rules. Instead, I assume we'll accept this PR, and then I'll throw up another PR just for the formatting stuff. Maybe that can include the deployment related changes (e.g. updated contributor list, incremented version).
Hopefully we can coordinate a deployment soon, maybe we bring in rhymiz if you want?
Anyway, I've left a few comments, nothing blocking, most of them are just me ranting. Glad to get this cooking.
return response.json() | ||
except requests.HTTPError as e: | ||
print(e) | ||
raise e | ||
except MondayQueryError as ex: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[non-blocking] This could be collapsed into the previous except block using a tuple, i.e. except (requests.HTTPError, MondayQueryError) as e
.
|
||
|
||
class MondayClient: | ||
def __init__(self, token): | ||
def __init__(self, token, headers={}): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[non-blocking] Given that this header is there primarily to handle the API change, are we planning on getting rid of this header parameter after Monday removes the old API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They probably will still update the API, and still let us set the API version on the headers, so at least I think it would be helpful to have the option to set the headers to every request nonetheless.
We depend on them to make the changes, so we'll never know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, looks like this is the only commented code that I actually made, so this is the only one I can say about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaulting a parameter to a mutable value is very dangerous:
client = MondayClient(token = "abc")
client2 = MondayClient(token = "MMM")
client.boards.fetch_columns_by_board_id("dsadsaa")
client2.boards.client.headers # {'Authorization': 'abc', 'Content-Type': 'application/json'} wrong API key
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative would be to pass the desired version api_version="2023-10"
to the constructor
Happy to hear your thoughts but here's why I think it'd be better:
- It makes it more obvious what we're trying to achieve with this param, since that's the only (current) logical use for the
headers
, outside of internal stuff like Content-Type etc. - By keeping the version on e.g. self.api_version, it would allow to keep the old stuff working if someone wants to use the old API for the time being. For example the added
items_page()
in queries could be added only if self.api_version == '2023-10' etc - See my comment above about mutating a default class param. A string doesn't have that issue
- Users of the lib don't need to know what the headers look like (yes, it's very simple to document, but still)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My $0.02: I'm still of the opinion that we should consider burning this parameter entirely come 16 Jan 2024, and have library version labels indicating what's happening.
Given that Monday is dropping support for the old version on 15 Jan, this would be a legacy parameter 3 months after release. There's certainly a chance that they'll do more versioning in the future, but I'd rather be handling it implicitly, through our client library versioning, than try to support multiple API versions in the same client library. I appreciate that their future versioning might be more incremental, but given that they're using date versioning, rather than semantic versioning, they're permitted hard breaks with each iteration.
Also, this is how I wish we were doing it now, but I'm not one to look a gift PR in the mouth, given that we've got a relatively short timeline for cutting this library over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My $0.02: I'm still of the opinion that we should consider burning this parameter entirely come 16 Jan 2024, and have library version labels indicating what's happening.
Given that Monday is dropping support for the old version on 15 Jan, this would be a legacy parameter 3 months after release. There's certainly a chance that they'll do more versioning in the future, but I'd rather be handling it implicitly, through our client library versioning, than try to support multiple API versions in the same client library. I appreciate that their future versioning might be more incremental, but given that they're using date versioning, rather than semantic versioning, they're permitted hard breaks with each iteration.
Also, this is how I wish we were doing it now, but I'm not one to look a gift PR in the mouth, given that we've got a relatively short timeline for cutting this library over.
I see your point, because they are literally deprecating the old API. Maybe there is simply no need for an extra parameter. In any case, it should definitely not be declared with a mutable dict as default params value :)
@@ -1,2 +1,6 @@ | |||
class MondayError(RuntimeError): | |||
pass | |||
|
|||
|
|||
class MondayQueryError(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[very non-blocking] I don't know why we inherit from RuntimeError
for MondayError
, but maybe we should follow that pattern? I think it's off, personally, but I don't have the context. Alternately, this could inherit from MondayError
, but that also seems wrong, as it doesn't seem to be used anywhere. Maybe it can go away? I don't know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I genuinely do not know or remember. That can almost certainly go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, MondayError
(base exception class for this library) should inherit from Exception
and MondayQueryError
should inherit from MondayError
. This makes it very easy to handle "all" potential exceptions that may be thrown inside the monday client by calling code:
try:
pass
except MondayError:
pass
folder_id = folder_id if folder_id else None | ||
keep_subscribers = keep_subscribers if keep_subscribers else False | ||
|
||
params = """board_id: %s, duplicate_type: %s, board_name: \"%s\"""" % ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[very non-blocking] Why does no one in this repo use f-strings?!
[edit] And then, randomly, later on, they do!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non f-strings were almost certainly my fault when I first wrote this, what feels like eons ago, and then f-strings were added later by either myself or contributors. I've been meaning to go through and clean this up.
): | ||
query = duplicate_board_query(board_id, duplicate_type, board_name, workspace_id, folder_id, keep_subscribers) | ||
return self.client.execute(query) | ||
return self.client.execute(query) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[non-blocking, but c'mon] Dupe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, can we delete this?
@pevner-p2 I wasn't that much experienced with the project, and I also forked a fork of the original project (As of right now, that fork is 21 commits ahead of the original). Worked for me at least, so I hope it helps more people haha |
Since you guys are working on migrating to the new API version, you should adjust the get_board_items_query() and get_items_by_group_query() to nest items within the items_page object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 @pevner-p2 I agree. Would like to get these in to solve the immediate issue and then go back and do some cleanup and make things agree with the linter. I have to do some work to re-acquaint myself to this and don't want to be a blocker.
I haven't used Monday in a little bit. Don't want to block this PR, but agree this should be done. Would you mind filing an issue for this? |
|
||
return query | ||
|
||
def duplicate_board_query(board_id: int, duplicate_type: DuplicateTypes): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this have the same name as the method above? It's going to override the above function, as it's defined later in the file.
super().__init__(token, headers) | ||
|
||
def create_column(self, board_id): | ||
query = get_columns_by_board_query(board_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the wrong query. Shouldn't this be calling create_column
from inside the query file?
if self.token is not None: | ||
headers[self.headername] = '{}'.format(self.token) | ||
|
||
if variables is None: | ||
headers['Content-Type'] = 'application/json' | ||
if 'Content-Type' not in headers: | ||
headers['Content-Type'] = 'application/json' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the great work @willnaoosmith and very happy to see it was picked up by the authors so quickly. Just a comment:
Mutating the headers seems like a bad idea; headers are not copied at any point in time in this PR, so they are shared across all Resources.
client = MondayClient(token = "abc",headers = {'API-Version' : '2023-10'})
client.items.client.headers # {'API-Version': '2023-10'}
client.boards.fetch_columns_by_board_id("lala") # Note this is on client.boards not client.items
client.items.client.headers
""" Output {'API-Version': '2023-10',
'Authorization': 'abc',
'Content-Type': 'application/json'}
"""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. This was caught by our internal linter, so I'd glossed over it as a syntactic issue, but this is definitely a genuine bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the great work @willnaoosmith and very happy to see it was picked up by the authors so quickly.
There are however some issues that would need to be addressed. I hope you consider the option of setting api_version
in constructor rather than passing headers, any maybe @pevner-p2 and @chdastolfo can weigh in on that option.
Thanks and sorry for the "long" review
|
||
|
||
class MondayClient: | ||
def __init__(self, token): | ||
def __init__(self, token, headers={}): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative would be to pass the desired version api_version="2023-10"
to the constructor
Happy to hear your thoughts but here's why I think it'd be better:
- It makes it more obvious what we're trying to achieve with this param, since that's the only (current) logical use for the
headers
, outside of internal stuff like Content-Type etc. - By keeping the version on e.g. self.api_version, it would allow to keep the old stuff working if someone wants to use the old API for the time being. For example the added
items_page()
in queries could be added only if self.api_version == '2023-10' etc - See my comment above about mutating a default class param. A string doesn't have that issue
- Users of the lib don't need to know what the headers look like (yes, it's very simple to document, but still)
board_name = board_name if board_name else "" | ||
workspace_id = workspace_id if workspace_id else None | ||
folder_id = folder_id if folder_id else None | ||
keep_subscribers = keep_subscribers if keep_subscribers else False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the 4 lines above,
- workspace_id and folder_id are already defaulting to None
- why not set the defaults in the function definition board_name: str = "", keep_subscribers: bool = False ?
- where possible, I'd avoid None since the typing is then incorrect (int = None will not be well liked); as we know that real workspace_id and folder_id will never be "0" why not set that as "falsy" value? Guess that's more of a preference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- agree
- agree
- Type annotations should be
int | None
orUnion[int, None]
Then bellow:
if workspace_id is not None:
# do something
if folder_id is not None:
# do something
) | ||
|
||
if workspace_id: | ||
params += """, workspace_id: %s""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The values are already merged in the lines above, so wouldn't this extra params remain unmerged (workspace_id is never used)
|
||
class ColumnsResource(BaseResource): | ||
def __init__(self, token, headers): | ||
super().__init__(token, headers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary since no change from super
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I notice this is likely because other Resource sub classes (from original code) have an unnecessary super().__init__
too; I think would be nice to remove but of course not so important
board_id: int, column_title: str, column_type: None, defaults: dict[str, any] = None, description: str = None | ||
): | ||
"""C```reate a new column by board ID | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra "```"
Return: | ||
[Dict[str,Any]]: Column's Monday details after creation | ||
""" | ||
defaults = defaults if defaults else "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaults
is typed as a dict? Should it be an empty string if not provided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matiboy you're calling out the correct thing -- I think the reason we're able to get away with dict
or str
is because monday_json_string
converts the dictionary into a string -- I do agree that for correctness, this should be:
defaults = defaults or {}
[Dict[str,Any]]: Column's Monday details after creation | ||
""" | ||
defaults = defaults if defaults else "" | ||
description = description if description else "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well declare "" as default value in function definition
@@ -167,6 +166,75 @@ def delete_item_query(item_id): | |||
return query | |||
|
|||
|
|||
# COLUMNS RESOURCE QUERIES | |||
def create_column( | |||
board_id: int, column_title: str, column_type: None, defaults: dict[str, any] = None, description: str = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
column_type
typed as None is likely erroneous. ColumnTypes
seems to be the correct type (from doc string below)
else: | ||
|
||
query = """mutation{ | ||
create_column(board_id: %s, title: "%s", description: "%s", column_type: %s, defaults: %s) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a warning as the column_type seems to have been meant to be typed to ColumnTypes
, string interpolation of enums has changed somewhere in 3.10 I believe:
class A(Enum):
ABC = "123"
"""enum value: %s""" % (A.ABC,) # wrong: 'From enum value: A.ABC'
"""enum value: %s""" % (A.ABC.value,) # correct: enum value: 123
files = [ | ||
('variables[file]', (variables['file'], open(variables['file'], 'rb'))) | ||
] | ||
|
||
try: | ||
response = requests.request("POST", self.endpoint, headers=headers, data=payload, files=files) | ||
response.raise_for_status() | ||
self._catch_error(response) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're not technically "catching" any errors here, you're checking for the present of any errors, so we should probably rename the method to _raise_if_error()
Hi there!
I made this wrapper work the new API, which will be live on 17 of January 2024.
There are some other changes that are from the repository that I forked, since it was more recent than the original repository.
This should fix (or at least help with) the #115 issue.
Tested on a test board and it works fine!
Changes I made:
fetch_updates_for_item
query doesn't use theboard ID
parameter anymore, so it should be like this:fetch_items_by_column_value
usesitems_page_by_column_values
now instead of theitems_by_column_values
, but this does not change anything about how it should be used.To test it, install it by running
pip install git+https://github.com/willnaoosmith/monday.git
and change the headers to use the2023-10
version.