When using autocompletion in VSCode in my python project, it works very well except for one class and I cannot figure out why.
The class is coming from a module database which contains 3 classes total: DatabaseClient, Database, Collection
DatabaseClient has a get_database() method which returns a Database object.
Database has a get_collection() method which returns a Collection object.
Collection has several methods.
The autocompletion works as intended on the DatabaseClient & Database objects, but not at all on the Collection object.
The code below works and give the expected results (line 15 and 20 of the screenshot results match), but not the autocompletion while editing as stated above.
import lib.database as database
print( database.DatabaseClient().get_database('business').get_collection('reviews').find() )
client = database.DatabaseClient()
db = client.get_database(client.list_database_names()[0])
coll = db.get_collection(db.list_collection_names()[0])
print(coll.find())
Tried to reload VSCode, unload and reload the python extensions.
Tried to uninstall and re-install the python extensions.
Tried to change the name of my module.
Tried to change the name of the class.
Tried to change the name of the method of the Collection class.
Tried using single line command line 3 and noticed it worked this way.
Found out the solution.
The problem comes from not using typing in the method headers.
Once I added proper typing, the autocompletion worked everywhere as expected.
def get_database(self, database_name: str) -> Database:
def get_collection(self, collection_name: str) -> Collection:
Related
We have a bigger Python project and the run-time errors became more and more annoying, like mistypings, that would be avoidable using a compiled language.
I started to use the typing feaures of Python to catch many errors in code "inspection" time instead of runtime, like in C++ or ObjectPascal.
Using type annotation and PyCharm is much better, though I have discovered a serious problem:
class TNormalClass():
def __init__(self):
self.x : int = 1
c1 = TNormalClass()
c1.x = 2
c1.y = 5 # no error signalized here, but should have been!
After searching on the Internet i've found a solution with __slots__:
class TStrictClass():
__slots__ = ['x']
def __init__(self):
self.x : int = 1
c1 = TStrictClass()
c1.x = 2
c1.y = 5 # PyCharm code inpection warning + runtime error: "object has no attribute 'y'"
That's what I wanted, but e.g. we have an object with 40 long named members. Duplicating every name in the __slots__ is far from elegant in the year 2022.
I've tried the library "autoslot", the c1.y = 5 resulted in runtime error, but this error was not detected inspection time with the PyCharm.
Is there some feature already in Python or maybe planned something in 3.10+ which would help me to create my "strict" objects
without duplicating every member in the __slots__
and also works with the PyCharm code inspection?
br,
nvitya
This has been answered here: Prevent creating new attributes outside __init__
As the second answer there says, __slots__ is the pythonic way.
As suggested in the comments, you probably want to try using dataclasses that were introduced in Python 3.7. If you want more features or need to support older versions of Python you could try using the attrs package that dataclasses was based on.
I'd write your code as:
from dataclasses import dataclass
#dataclass(slots=True)
class TNormalClass:
x: int = 1
Note that slots was only introduced in Python 3.10, hence my references to the attrs package.
These declarations are exposed to Python's typing support, so will be picked by tools like mypy and by editors like PyCharm.
i have used win32com.client to to something with excel.
so far, ".cells(1, 1).value" method succesfully read the value in row1, column1.
but from now, that code doesn't work.
it occurs error >>
'<win32com.gen_py.Microsoft Excel 16.0 Object Library._Worksheet instance at 0x2284591006720>' object has no attribute 'cells'
what is a problem?
xlsApp = win32com.client.GetActiveObject("Excel.Application")
wb = xlsApp.Workbooks(wb_name)
ws = wb.Worksheets(ws_name)
test = ws.cells(1, 1).value
print(test)
The method name is Cells() with an uppercase ‘C’ is the simple answer.
It is fair to ask: “If cells() worked before, why doesn’t it work now?”. IMHO it is a failing of win32com.
win32com will, if asked, generate a wrapper for COM objects (using a package called gen_py which creates a Python file for each object). If such a wrapper exists win32com will use it by default. This wrapper is case-sensitive. This is “early binding”.
If there is not a wrapper then calls like GetActiveObject() and Dispatch() will use “late binding” where win32com will try and call whatever method you want on the object (whether the method exists or not). The package takes whatever method you ask for and tries to get the ID of that function from the COM object via IDispatch.GetIdsOfNames(). Crucially, this lookup is case-INsensitive: using cells(),Cells() or even cElLs() will all work.
And this is the problem. The generated wrappers are stored in the user’s directory. Your code might have been happily using late-binding and cells(), but another programme may have created the wrapper at a later date. Now your code will see this wrapper and switch to early-binding and now cells() no longer works: you need to use Cells().
So, when using win32com it is always worth finding out the exact function name from the reference, eg from here
I'm building a little program but I'm trying to make it more easily expandable for the future (without the need of much refactoring). I'm experimenting with things I have never used before like typings.
I have a small typings file that looks like this (called typings.py)
from typing import (
Union,
Any,
Optional
)
from typing import ForwardRef as ref
CustomLevel = ref("levels.level.CustomLevel")
OfficialLevel = ref("levels.level.OfficialLevel")
__all__ = (
"Union",
"Any",
"Optional",
"CustomLevel",
"OfficialLevel",
)
I also have a level.py file under ./levels/. In the file I have a base class but also a CustomLevel and OfficialLevel. At the top of level.py I am importing the CustomLevel typing. The issue is I am getting an error saying:
class already defined (line 5)
because the import of the CustomLevel type is clashing with definition of the CustomLevel class, like so:
from ..typings import (
CustomLevel
)
class CustomLevel(): #<--- erros here since 'customlevel' is already defined
def __init__(self) -> None:
pass
def somefunc(self) -> CustomLevel:
pass
I am actually using an already existing library as a sort of "guide". What I mean is I am looking at the code of the library, because I think it's pretty good code, and using some of the features I think will be helpful in my program (like typings). In their library they do exactly what I am doing where they import a type from their typing file, and then create a class of the same name. In theirs they don't get an error though.
The Library is this. It happens in a lot of the python scrips but a short one is the comment.py. At the top of the file, from gd.typing, Comment is imported. Not too much further down Comment is created as a class. Like I said, this library has no issues.
In their typing.py file the only extra bit is:
def __forward_ref_repr__(self: ref) -> str:
return str(self.__forward_arg__)
setattr(ref, "__repr__", __forward_ref_repr__)
I have tried adding this but it doesn't make any difference. I am using VSCode with pylint which could very well be the case but at the moment I have other errors which won't allow me to run the script to test.
I'm using openpyxl with python 2.7 to manipulate some workbooks. I've implemented my own methods for class Worksheet except they're not being found when I try to access them. I''m on windows and I've adding the following code to site-packages/openpyxl/worksheet/worksheet.py. Source code here.
class Worksheet(_WorkbookChild):
"""
Library defined methods here.
"""
#My Method.
def hello_world(self):
print "Hello from worksheet."
When I open up a workbook and try and call my function it raises an error for 'no attribute' even though it's been defined (properly?) under the scope of the class Worksheet.
import openpyxl
wb = openpyxl.load_workbook('helloworld.xlsx')
sheet = wb.get_active_sheet()
sheet.hello_world()
AttributeError: 'Worksheet' object has no attribute 'hello_world'
Do I need to do anything differently to update the existing library? I've deleted the .pyc, recompiled and am still getting the same error.
For anyone else who runs into this problem. The library was using 4 spaces for each indent while I was using a regular tab. The variation in indents caused the method to not be defined even though it appeared to be.
Using GData Python libraries, version 2.0.18
Attempting to retrieve contact list using the Service approach (not Client like the sample app). It appears that the return is mapped to a ContactEntry (good), but it gives error when I try to access the name attribute:
AttributeError: 'ContactEntry' object has no attribute 'name'
from gdata.contacts.service import ContactsService
(...)
self.client = ContactsService(source='appname', additional_headers=additional_headers )
feed = self.client.GetContactsFeed(uri=query.ToUri())
self.client is a gdata.contacts.service
GetContactsFeed uses
def GetContactsFeed(self, uri=None):
uri = uri or self.GetFeedUri()
return self.Get(uri, converter=gdata.contacts.ContactsFeedFromString)
The sample code uses desired_class=gdata.contacts.data.ContactsFeed
Seems like there should be a name attribute.
Is my syntax wrong?
Ok, here is the issue for the python contacts sample vs my implementation:
in gdata/sample/contacts/contacts_example.py it uses gdata.contacts.Client which (long chain to it) calls for the atom classes to use desired_class=gdata.contacts.data.ContactsFeed.
The service, as pointed out in the question, uses converter=gdata.contacts.ContactsFeedFromString.
This converter comes from the init file, src/gdata/contacts/init.py, as do the class definitions. Obviously at this point, you know what's coming -- the classes for the xml in the initializer do not match the ones in the data file.
I added these missing, incorrect ones in to initializer and things worked as expected. Alternatively, changing to use desired_class would do it too (at some point you'd have to map to converter...isn't supported directly in service.py), or adding converter to data.ContactsFeed, etc.
Hope this helps someone.