How to get the parent folder name of Message with Exchangelib python - python

I have an Item object obtained by filtering on an account using exchangelib in python 3.7. It is an email object. I need to find the parent folder name of this item. I specifically need the name field of the folder(This is for tracking where specific emails are moved in a mailbox).
I can see a field parent_folder_id in the item object which returns what I think is a valid folder id. This is also for a production mailbox where account.root.get_folder(folder_id=idObj) times out due to Exchange settings which I cannot change. Pretty much any request which caches fails with a timeout.
account=Account(...)
mailItems=account.inbox.all().filter(subject="foo")
print([i.parent_folder_id.id for i in mailItems])
This prints a list of folder ids. I need the names of these folders. Unclear how to proceed. Any help would be appreciated

Since you're only searching account.inbox and not its subfolders, parent_folder_id will always point to account.inbox.
There's not a very good API yet for looking up folders by e.g. ID. The best solution currently is to use a folder QuerySet:
from exchangelib.folders import SingleFolderQuerySet
f = SingleFolderQuerySet(
account=account,
folder=account.root
).get(id=i.parent_folder_id.id)

Related

Unable to update SharePoint List items using shareplum

I have a Python script that I've been running for months that uses shareplum to update various columns of a SharePoint List using UpdateListItems(kind = 'Update'). It has suddenly stopped working and I've no idea why. I don't get any error messages but the changes I am trying to make are not showing up. Creating a new item is working fine, it's only when I try to update an existing list item. I use the Office365 version of SharePoint.
The List is relatively large with over 500 list items and over 100 columns. The maximum List ID is 1050.
I tested it on another smaller list and it works OK, so it must be an issue with this particular List.
I'd be really grateful if someone could help. Since I'm not getting any error messages I don't know how to check what could be wrong with it.
from shareplum import Site
from shareplum import Office365
authcookie = Office365('https://XXX.sharepoint.com', username= XXX, password= XXX).GetCookies()
site = Site('https://XXX.sharepoint.com/sites/XXX', authcookie=authcookie)
list_ = site.List('TestList')
my_data = [{'ID': '3', 'Title': 'TestTitle'}]
list_.UpdateListItems(data=my_data, kind='Update')
UPDATE: I printed off the result of the response which says '0x80070057', 'Bad parameter passed to Web Server Extensions. Check the information you entered and try again.' I'm not sure if this helps identify what the issue could be?
I am now able to update my List items using Office365-REST-Python-Client instead of shareplum, but still love to know what the issue with shareplum is.
Idk Python, but PowerShell/CSOM allows you to update the item after you have made the update. I'm assuming Shareplum is a wrapper for CSOM, so you should still have the ability to manually trigger the update.
I don't remember the code of the top of my head, but to manually update it it's something like this (in CSOM):
$listItem = get-pnplistitem -Identity 1 -list "Documents"
$listItem.Fields["Title"] = "New Title"
$listItem.Update()
$listItem.context.executeQuery()

office365-rest-client: How to get name of file modifier

I am mapping a SharePoint document library using office365-rest-client. My intention is to make a dictionary of the form:
file_dict = {File.serverRelativeUrl: [file_attribute_1, file_attribute_2, ...]}
Where File.serverRealtiveUrl is a string, and one of the above-mentioned attributes is to be the name of the latest person to modify the file.
The File class (seen here) also has a method modified_by() that I have been trying to use to determine the name of the person who last modified the file. However, using this returns an instance of the User class (seen here).
When looking at the code behind User, it doesn't appear to contain any method that would allow for the name of the modifier to be retrieved.
When looking at the files saved within my SharePoint document library, it is clear that the names of these users are present:
Therefore, I would like to know the following:
Does anybody know of the correct method to determine the names of the file modifiers (if one exists)?
Alternatively, is it possible to determine the email addresses of these users instead?
I have already attempted to use the File.properties attribute, but have found that the modifier name / mailing address is not included within these properties:
import json
# Get file
print(json.dumps(file.properties))

rtcclient ownedBy returns User ID and not email address

I am using the Python package rtclcient (v0.6.0) to get info from work item.
Some of the properties I am querying from my RTC work item include:
returned_properties_list = [
"rtc_cm:modifiedBy",
"dc:modified",
"rtc_cm:contextId",
"dc:subject",
"oslc_cm:priority",
"dc:creator",
"rtc_cm:due",
"rtc_cm:estimate",
"rtc_cm:correctedEstimate",
"rtc_cm:timeSpent",
"rtc_cm:startDate",
"dc:created",
"rtc_cm:resolvedBy",
"rtc_cm:plannedFor",
"rtc_cm:ownedBy",
]
When the query for these items finish and I examine the returned variables, I notice that the 'ownedBy' value is the user's id, not their email address.
If I look at the value of raw_data['rtc_cm:ownedBy']['#rdf:resource'] it is a link which goes to that user's profile.
On the profile I can see the same User ID which is being recieved as the 'ownedBy' value.
Also on the profile is the User's E-mail Address. This email address value is what I need, but I can't seem to find a way to query for this value using the rtcclient python package.
I've tried adding "rtc_cm:emailAddress" to my query properties but no luck,
Can anyone help me get the ownedBy user email address for a RTC ticket using the rtcclient package? Thanks
fixed by cloning the rtcclient repo, and adding my own custom function getUserEmail

Getting SalesforceMalformedRequest: Malformed request error

I am trying to execute this following code to push data to Salesforce using the simple_salesforce python library :
from simple_salesforce import Salesforce
staging_df = hive.execute("select * from hdmni")
staging_df = staging_df.toPandas()
# # staging_df['birth_date']= staging_df['birth_date'].dt.date
staging_df['birth_date'] = staging_df['birth_date'].astype(str)
staging_df['encounter_start_date'] = staging_df['encounter_start_date'].astype(str)
staging_df['encounter_end_date'] = staging_df['encounter_end_date'].astype(str)
bulk_data = []
for row in staging_df.itertuples():
d= row._asdict()
del d['Index']
bulk_data.append(d)
sf = Salesforce(password='', username='', security_token='')
sf.bulk.Delivery_Detail__c.insert(bulk_data)
I am getting this error while trying to send dictionary to salesforce :
SalesforceMalformedRequest: Malformed request
https://subhotutorial-dev-ed.my.salesforce.com/services/async/38.0/job/7500o00000HtWP6AAN/batch/7510o00000Q15TnAAJ/result.
Response content: {'exceptionCode': 'InvalidBatch',
'exceptionMessage': 'Records not processed'}
There's something about your query that is not correct. While I don't know your use case, by reading this line, you can tell that you are attempting to insert into a custom object/entity in Salesforce:
sf.bulk.Delivery_Detail__c.insert(bulk_data)
The reason you can tell is because of the __c suffix, which gets appended onto custom objects and fields (that's two underscores, by the way).
Since you're inserting into a custom object, your fields would have to be custom, too. And note, you've not appended that suffix onto them.
Note: Every custom object/entity in Salesforce does come with a few standard fields to support system features like record key (Id), record name (Name), audit fields (CreatedById, CreatedDate, etc.). These wouldn't have a suffix. But none of the fields you reference are any of these standard system fields...so the __c suffix would be expected.
I suspect that what Salesforce is expecting in your insert operation are field names like this:
Birth_Date__c
Encounter_Start_Date__c
Encounter_End_Date__c
These are referred to as the API name for both objects and fields, and anytime code interacts with them (whether via integration, or on code that executes directly on the Salesforce platform) you need to make certain you're using this API name.
Incidentally, you can retrieve this API name through a number of ways. Probably easiest is to log into your Salesforce org, and in Setup > Object Manager > [some object] > Fields and Relationships you can view details of each field, including the API name. Here's a screen shot.
You can also use SObject describe APIs, either in native Apex code, or via integration and either the REST or SOAP APIs. Here's part of the response from the describe API request to the describe REST endpoint for the same object as my UI example above, found here at https://[domain]/services/data/v47.0/sobjects/Expense__c/describe:
Looking at the docs for the simple-salesforce python library you're using, they've surfaced the describe API. You can find some info under Other Options. You would invoke it as sf.SObject.describe where "SObject" is the actual object you want to find the information about. For instance, in your case you would use:
sf.Delivery_Detail__c.describe()
As a good first troubleshooting step when interacting with a Salesforce object, I'd always recommend double-checking correctly referencing an API name. I can't tell you how many times I've bumped into little things like adding or missing an underscore. Especially with the __c suffix.

python win32com: Delete multiple emails in Outlook

I need to delete multiple email messages in Outlook from python via win32com module.
I understand there is a VBA method MailItem.Delete() available to win32com via COM and it works; but it is VERY VERY slow when deleting more than one email since one would have to delete emails sequentially ie loop over the MailItem collection of emails.
Is there any way to delete a selected collection of mailItems at once, something like MailItemCollection.DeleteAll()?
Also, if above is not possible; is it at all possible to delete many emails via multi-threaded approach ie divide the collection of mailItems into, let's say, 4 subsets; have 4 threads operate on those?
I figure since I can delete multiple emails in outlook via its GUI very fast, there has to be a way where I can do the same thing via COM API.
Not in OOM - MailItem.Delete or Items.Remove(Index) is all you get.
On the Extended MAPI level (C++ or Delphi, but not Python), you can delete multiple messages using IMAPIFolder.DeleteMessages (which takes a list of entry ids). Or you can use IMAPIFolder.EmptyFolder (deletes all messages in a folder).
If using Redemption (any language; I am its author) is an option, you can use RDOFolder2.EmptyFolder or RDOFolder.Items.RemoveMultiple. RDOFolder can be retrieved from RDOSession.GetRDOObjectFromOutlookObject if you pass Outlook's MAPIFolder object as a parameter.
On top of a great answer by #Dimitry I'll add a remark which may be important for you: if you start deleting from Items as you iterate over it, strange things may happen.
For example on my system the following Python code:
for mail in folder.Items:
mail.Delete()
as well as
for index, mail in enumerate(folder.Items, 1):
folder.Remove(index)
both remove only half of the items in the folder! The reason seems to be that Items uses a range of indices internally to provide an iterator so each time an element is deleted, the tail of the list is shifted by one...
To remove all items in the folder try:
for i in range(len(folder.Items)):
folder.Remove(1)
If you need to filter by a certain criterion consider first gathering EntryIDs and then deleting searching for ID:
ids = []
for i in range(len(folder.Items), 1):
if to_be_deleted(folder.Items[index]):
ids.append(index)
for id in ids:
outlook.GetEntryByID(id).Delete()
I imagine performance of that is even worse, though :c
Great answer from Dedalus above. Wanted to make a more concise version of the code:
import win32com.client
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
# Select main Inbox
inbox = outlook.GetDefaultFolder(6)
messages = inbox.Items
# Delete all messages from a specific sender
sender = 'myname#abc.com'
try:
for message in messages:
try:
s = message.sender
s = str(s)
if s == sender:
message.Delete()
except:
pass
except:
pass
You may not need two "trys" but I found it was more stable when applying the script to a long and heavily used inbox. Usually I combine this with a script that limits the message = inbox.Items to within a week so it doesn't do the entire inbox.
For me it worked by iterating the items in reverse.
Old:
for mail in folder.Items:
if 'whatever' in mail.Subject: # just a condition (optional)
mail.Delete()
New code:
for mail in reversed(folder.Items): # just tried deleting Items in reverse order
if 'whatever' in mail.Subject: # just a condition (optional)
mail.Delete()
Hope this helps someone.
Am I missing something? Neither Application nor NameSpace objects appear to have a GetEntryByID method, though the rest of what Dedalus pointed out was correct.
Namespace objects have a GetItemFromID method, and MailItem objects have a EntryID property which will uniquely identify them so long as they don't get reorganized into different folders.
Documentation: https://learn.microsoft.com/en-us/office/vba/outlook/how-to/items-folders-and-stores/working-with-entryids-and-storeids
My full solve:
import win32com.client
outlook = win32com.client.gencache.EnsureDispatch("Outlook.Application")
folders = outlook.GetNamespace("MAPI")
inbox= folders.GetDefaultFolder(6)
messages=inbox.Items
email_ids = []
folder_id = inbox.StoreID
# Here create a function to isolate/exclude. Below is just an example of filtering by a subject line.
email_subjects = ['Subj1','Subj2','Subj3']
for i in range(len(messages)):
if any(header in inbox.Items[i].Subject for header in email_subjects):
email_ids.append(inbox.Items[i].EntryID)
for id in email_ids:
folders.GetItemFromID(id, folder_id).Delete()
I've implemented an alternative solution in local Outlook, by moving email ítems from.inbox folder to deleted items folder or to an archive folder, by using VBA code or Outlook filter rules directly.
This way, I just mannualy empty the deleted items folder once a week (of course this periodic step can also be programmed).
I observed that this strategy can be more efficient instead of delete item per item using code (you mentioned the internal.indexes problem).

Categories