Modifying Microsoft Outlook contacts from Python - python

I have written a few Python tools in the past to extract data from my Outlook contacts. Now, I am trying to modify my Outlook Contacts. I am finding that my changes are being noted by Outlook, but they aren't sticking. I seem to be updating some cache, but not the real record.
The code is straightforward.
import win32com.client
import pywintypes
o = win32com.client.Dispatch("Outlook.Application")
ns = o.GetNamespace("MAPI")
profile = ns.Folders.Item("My Profile Name")
contacts = profile.Folders.Item("Contacts")
contact = contacts.Items[43] # Grab a random contact, for this example.
print "About to overwrite ",contact.FirstName, contact.LastName
contact.categories = 'Supplier' # Override the categories
# Edit: I don't always do these last steps.
ns = None
o = None
At this point, I change over to Outlook, which is opened to the Detailed Address Cards view.
I look at the contact summary (without opening it) and the category is unchanged (not refreshed?).
I open the contact and its category HAS changed, sometimes. (Not sure of when, but it feels like it is cache related.) If it has changed, it prompts me to Save Changes when I close it which is odd, because I haven't changed anything in the Outlook UI.
If I quit and restart Outlook, the changes are gone.
I suspect I am failing to call SaveChanges, but I can't see which object supports it.
So my question is:
Should I be calling SaveChanges? If so, where is it?
Am I making some other silly mistake, which is causing my data to be discarded?

I believe there is a .Save() method on the contact, so you need to add:
contact.Save()

Related

Marking an email as read python

I'd like to mark an email as read from my python code. I'm using
from exchangelib import Credentials, Account
my_account = Account(...)
credentials = Credentials(...)
to get access to the account. This part works great. I then get into my desired folder using this
var1 = my_account.root / 'branch1' / 'desiredFolder'
Again, this works great. This is where marking it as read seems to not work.
item = var1.filter(is_read=False).values('body')
for i, body in enumerate(item):
#Code doing stuff
var1.filter(is_read=False)[i].is_read = True
var1.filter(is_read=False)[i].save(updated_fields=['is_read'])
I've tried the tips and answers from this post Mark email as read with exchangelib, but the emails still show as unread. What am I doing wrong?
I think you the last line of code that you save() do not work as you think that after you set is_read of unread[i] element to True, this unread[i] of course not appear in var1.filter(is_read=False)[i] again, so you acttually did not save this.
I think this will work.
for msg in my_account.inbox.filter(is_read=False):
msg.is_read = True
msg.save(updated_fields=['is_read'])
To expand on #thangnd's answer, the reason your code doesn't work is that you are calling .save() on a different Python object than the object you are setting the is_read flag on. Every time you call var1.filter(is_read=False)[i], a new query is sent to the server, and a new Python object is created.
Additionally, since you didn't specify a sort order (and new emails may be incoming while the code is running), you may not even be hitting the same email each time you call var1.filter(is_read=False)[i].

Tweepy Check Blocked User?

Is there a way to check if the user is blocked like an is_blocked method? The way I have it set up right now is to get a list of my blocked users and comparing the author of a Tweet to the list, but it's highly inefficient, as it constantly runs into the rate limit.
Relevent Code:
blocked_screen_names = [b.screen_name for b in tweepy.Cursor(api.blocks).items()]
for count, tweet in enumerate(tweepy.Cursor(api.search, q = query, lang = 'en', result_type = 'recent', tweet_mode = 'extended').items(500)):
if tweet.user.screen_name in blocked_screen_names:
continue
No, you'll have to do that the way you're currently doing it.
(Also, just a side note, you're better off checking your blocked users via their account ID rather than their screen name, because this value will not change, whereas a user's screen name can)
For future reference, just check the Twitter API documentation, where you can get the answer for something like this straight away :) save yourself the waiting for someone to answer it for you here!
You'll notice that both the documentation for V1 and V2 do not contain an attribute as you have described:
V1 User Object:
https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet
V2 User Object:
https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/user

How to send current user_id to Sentry scope.user()?

Project is in PYTHON
I want to log all current users affected by some random "IndexError" (For instance) error.
Currently when I try to do this:
from sentry_sdk import push_scope, capture_exception
with push_scope() as scope:
scope.user = {"email": user.email}
capture_exception(ex)
it sets first user's email and that doesn't change with other users facing the same error.
I want to see all users who were affected by that error.

Python - Get list of all attributes/properties of a win32com class

I am a newbie in Python. I have to extract emails from Outlook and get all attributes/properties of the emails.
Retrieving properties one by one, for the attributes/properties that I know they exist works fine (.Subject, .Body, etc.).
But, I need to get all possible attributes. That's where my problem is.
I have been looking for hours, the only answers I found were using:
vars()
dir()
inspect.getmembers(obj)
__dict__
etc.
Which does not give me the list of properties like:
.Subject
.Body
.SentOn
etc.
Could someone help ?
Here is the extract of my test Notebook :
####### Retrieve email from Outlook #######
import win32com.client
objOutlookMAPI=win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
​
​### Define folder
objOlFolder = objOutlookMAPI.GetDefaultFolder(6)
​### Retrieve ant print email
objOlMessages = objOlFolder.Items
​
# objMessage : class 'win32com.client.CDispatch'
objMessage = objOlMessages.GetLast()
print(objMessage.Subject)
> Are you going to Las Vegas for Black Hat, DefCon, Bsides, or Hacking Diversity? Either or join us on our adventures!
vars(objMessage)
> {'_builtMethods_': {},
'_enum_': None,
'_lazydata_': (<PyITypeInfo at 0x0000021EC7B7D170 with obj at 0x0000021EC7B4B2F8>,
<PyITypeComp at 0x0000021EC7B7D620 with obj at 0x0000021EC7B4B058>),
'_mapCachedItems_': {},
'_oleobj_': <PyIDispatch at 0x0000021EC7B7D290 with obj at 0x0000021EC7B4AAA8>,
'_olerepr_': <win32com.client.build.LazyDispatchItem at 0x21ec8a7ba90>,
'_unicode_to_string_': None,
'_username_': 'GetLast'}
dir(objMessage)
#import inspect
#inspect.getmembers(objMessage)
> [`'_ApplyTypes_'`,
`'_FlagAsMethod'`,
`'_LazyAddAttr_'`,
`'_NewEnum'`,
`'_Release_'`,
`'__AttrToID__'`,
`'__LazyMap__'`,
`'__bool__'`,
`'__call__'`,
`'__class__'`,
`'__delattr__'`,
`'__dict__'`,
`'__dir__'`,
`'__doc__'`,
`'__eq__'`,
`'__format__'`,
`'__ge__'`,
`'__getattr__'`,
`'__getattribute__'`,
`'__getitem__'`,
`'__gt__'`,
`'__hash__'`,
`'__init__'`,
`'__init_subclass__'`,
`'__int__'`,
`'__le__'`,
`'__len__'`,
`'__lt__'`,
`'__module__'`,
`'__ne__'`,
`'__new__'`,
`'__reduce__'`,
`'__reduce_ex__'`,
`'__repr__'`,
`'__setattr__'`,
`'__setitem__'`,
`'__sizeof__'`,
`'__str__'`,
`'__subclasshook__'`,
`'__weakref__'`,
`'_builtMethods_'`,
`'_enum_'`,
`'_find_dispatch_type_'`,
`'_get_good_object_'`,
`'_get_good_single_object_'`,
`'_lazydata_'`,
`'_make_method_'`,
`'_mapCachedItems_'`,
`'_oleobj_'`,
`'_olerepr_'`,
`'_print_details_'`,
`'_proc_'`,
`'_unicode_to_string_'`,
`'_username_'`,
`'_wrap_dispatch_'`]
For posterity here, I found what I needed in the AppointmentItem object on this page:
Outlook Visual Basic for Applications (VBA) reference
Just expand that object on the left side of the page, and then expand properties. Found everything I needed there. Also a MeetingItem object, still in the process of figuring out the difference (which sounds like it's obvious, but a lot of the appointments I'm getting back are actually meetings (i.e. have attendees).
I found a way to do it, actually:
The plan is to create a macro in a second presentation that you call from within python and that is, with the help of tlbinf32.dll, able to get all members of an object.
The Second Presentation
To start, you want to download the tlbinf32.dll.
FOR 64-bit SYSTEMS: Follow this guide to properly set up the 32-bit-DLL! I also had problems with the HP Print Scan Doctor Service: I needed to disable that service and uninstall HP Smart. (and reboot)
Create a new .pptm file (pptx with macros) and open it (I named it GetMembersUtil.pptm)
In that presentation, create a new macro (the name is irrelevant) and edit it (View->Macros)
Add a reference to your tlbinf32.dll that you just downloaded (Tools->References)
Paste the following function after the macro
Function GetMembers(obj)
' Declare vars
Dim TLI
Dim TypeInfo
Dim Members
' Create new ArrayList since we don't know how many Members obj has
Set Members = CreateObject("System.Collections.ArrayList")
' Initialize tlbinf
Set TLI = CreateObject("TLI.TLIApplication")
Set TypeInfo = TLI.InterfaceInfoFromObject(obj)
' iterate over each Member
For Each MemberInfo In TypeInfo.Members
Members.Add MemberInfo.Name
Next
' Convert ArrayList back to native vba Array because you can't return an ArrayList
GetMembers = Members.ToArray()
End Function
this function takes an object as input and returns all the members of that object
Save your presentation in the local directory of your python file
The Python Side
To be able to use the macro we need to make sure that the presentation we just created is openend
def vba_get_members(app: win32com.client.CDispatch, obj: win32com.client.CDispatch) -> tuple[str, ...]:
# os.path.abspath is very important
ensure_opened(app, os.path.abspath("GetMembersUtil.pptm"))
return app.Run("GetMembersUtil.pptm!GetMembers", obj)
def ensure_opened(app: win32com.client.CDispatch, file_path):
# check if presentation is already opened
for presentation in app.Presentations:
pp_filepath = os.path.join(presentation.Path, presentation.Name)
if pp_filepath == file_path:
# presentation already opened
break
else:
# presentation wasn't already opened
app.Presentations.Open(file_path)

Gender being changed to "Directory server" on Google Contacts insert

I'm using the gdata-python-client library (http://code.google.com/p/gdata-python-client/) to insert contacts into a Google account. When I set the gender like this:
google_contact.gender = gdata.contacts.data.Gender(text="Male")
it's actually being set on the contact in the field "Directory server".
Any ideas why this might be happening?
In case it affects anything, my code is running on a Google App Engine development server.
Verily, I say unto you, this is indeed a bug in the gData client. Here is the line containing the bug, and here is the bug report I filed concerning the issue, and here's a patch I've submitted that solves the issue
The specifics of patching one's local copy of the library while the upstream fix is pending is left as an exercise for the reader.
This might be a bug in gdata library. At least that's how I see it. Not a google-contact expert here
consider this:
>>> print gdata.contacts.data.City(text="Foo")
<ns0:city xmlns:ns0="http://schemas.google.com/contact/2008">Foo</ns0:city>
while Gender returns this:
>>> print gdata.contacts.data.Gender(text="Male")
<ns0:directoryServer xmlns:ns0="http://schemas.google.com/contact/2008">Male</ns0:directoryServer>
Of course you can alter the tag property of Gender object manually:
>>> g = gdata.contacts.data.Gender(text="Male")
>>> g.tag='gender'
>>> print g
<ns0:gender xmlns:ns0="http://schemas.google.com/contact/2008">Male</ns0:gender>
Hope this helps

Categories

Resources