IT Consultant – Java, J2EE, IBM WebSphere Portal, Lotus Notes/Domino
RSS icon Home icon
  • [Solved] Windows 7 “Safely Remove Hardware” pop-up menu horrendously slow

    (4 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 14 May 2012 No comments

    Since I upgraded to Windows 7 an annoying problem regarding USB device ejection bugged me for quite some time. At least for my system configuration I now seem to have found a working solution.

    The symptom: When you try to eject an USB device using the “Safely Remove Hardware and Eject Media” tray icon it takes ages until the popup menu appears where you can eventually select the device to eject from.

    The problem #1: When Windows tries to build the list of ejectable devices it also seems to scan all configured printers – unfortunately this does not only include local printers connected via USB but also network printers. Since I am working from different locations not all network printers are reachable all the time. But Windows still tries to determine the printers’ connection state and for offline printers this takes until a network time out occurs.
    The solution #1: The real solution for this would be if Microsoft would provide a patch that network printers are not taken in account when building the list of ejectable devices (which makes no sense anyway). As a workaround I disabled the “SNMP Status Enabled” option of the TCP/IP printer ports. You can do this as follows:

    1. Open the “Devices and Printers” control panel
    2. Click the “Printer server properties” button or via the menu File->Printer server properties
    3. Click the “Ports” tab
    4. For each port where the description says “Standard TCP/IP Port”, click “Configure Port…” and disable the option “SNMP Status Enabled”
    5. Voilá¡, the “Safely Remove Hardware” pop-up menu appears in a snap again!

    The only disadvantage of disabling SNMP status checking is that the printer control panel does not indicate the connection state of the network printers anymore. But for me this is the smaller issue.

    The problem #2: Unfortunately the described solution did not work on all of my machines (especially Lenovo ThinkPads).

    The solution #2: After a long search I eventually I came acros this post http://forums.lenovo.com/t5/T400-T500-and-newer-T-series/quot-Safely-remove-hardware-and-eject-media-quot-is-very-slow/m-p/442531#M44303 where it says:

    Symptom:
    Windows 7 Lenovo preload
    It will take longer time to show a list for removal device candidates than previous OS, when clicking “Show hidden icons” from task tray then clicking “Safely Remove Hardware and Eject Media”.

    Cause of the issue:
    Windows 7 will start to scan some files in folders when click “Safely Remove Hardware and Eject Media”. Due to this scanning process, it takes times to prepare a list of devices.

    How to solve this problem:
    Eliminating number of unused files will shorten the scanning process.
    There are lots of files for national language support of the Windows in %programdata%\Microsoft\Windows\DeviceMetadataStore
    Usually in C:\ProgramData\Microsoft\Windows\MetadataStore. It carries various icon data and resource data for displaying in some managing process such as defining printers.

    There are “READ ONLY” folders like following:

    • cs-CZ for Czech
    • da-DK for Danish
    • de-DE for German
    • el-GR for Greek
    • en-US for English
    • es-ES for Spanish
    • fi-FI for Finnish
    • fr-FR for French
    • he-IL for Hebrew
    • hu-HU for Hungarian
    • it-IT for Italian
    • ja-JP for Japanese
    • ko-KR for Korean
    • nb-NO for Norwegian
    • nl-NL for Dutch
    • pl-PL for Polish
    • pt-BR for Portuguese (Brazil)
    • pt-PT for Portuguese (Portugal)
    • ru-RU for Russian
    • sl-SI for Slovenian
    • sv-SE for Swedish
    • tr-TR for Turkish
    • zh-CN for Chinese (Simplified, PRC)
    • zh-TW for Chinese (Traditional, Taiwan)

    Each folder carries 24 device metadata files except en-US carrying 26 files. Those file name will be “hexadecimal numbers”.devicemetadata-ms.

    Depends on your computing environment, there may be some language folders never used. Removing those folders and files will speed up scanning process remarkably.

    Note: en-US folder should not be removed. There are commonly used files in other language.

    Since those folders are read only, a following dialog will come up with attempting delete a folder. Click “Continue” button will delete a folder. If you need a administrator access to delete, please consult your system administrator.

  • Bash: Capturing stderr in a variable while still printing to the console.

    (2 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 31 January 2011 No comments

    Storing the stdout output of a command in a variable and displaying it is simple:

    OUTPUT=$(command)
    echo $OUTPUT
    

    If you have longer running commands where you want to display stdout in realtime and also store it in a variable you can tee the output to stderr:

    OUTPUT=$(command | tee /dev/stderr)
    
    OUTPUT=$(command | tee /proc/self/fd/2)
    
    OUTPUT=$(command | tee >(cat - >&2))
    

    If you have longer running commands where you want to display stdout/stderr in realtime and also store stderr in a variable it gets a bit complicated.
    However, this can be achieved by switching stdout and stderr and then teeing the new stdout (which is stderr now) back to stderr for console output.

    ERROR=$(command 3>&1 1>&2 2>&3 | tee /dev/stderr)
    
    ERROR=$(command 3>&1 1>&2 2>&3 | tee /proc/self/fd/2)
    
    ERROR=$(command 3>&1 1>&2 2>&3 | tee >(cat - >&2))
    

    Good reading:
    Bash FAQ: How can I store the return value/output of a command in a variable?

  • Configuring EMF Teneo with Hibernate, Commons DBCP, Spring Hibernate Transaction Manager, and the OpenSessionInViewFilter

    (5 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 2 October 2010 No comments

    While trying to get an application working with , Hibernate, Commons DBCP, Spring Hibernate Transaction Manager and the OpenSessionInViewFilter I encountered several unexpected issues. Here is an example of a working configuration for this software stack. Read the comments below the XML file for some explanations.

    HbDataStoreWithDataSource.java:

    package de.sebthom.util;
    
    import java.sql.*;
    import java.util.Properties;
    import javax.sql.DataSource;
    import org.eclipse.emf.teneo.PersistenceOptions;
    import org.eclipse.emf.teneo.hibernate.HbSessionDataStore;
    import org.hibernate.HibernateException;
    import org.hibernate.cfg.*;
    import org.hibernate.connection.ConnectionProvider;
    
    /**
     * @author Sebastian Thomschke
     */
    public class HbDataStoreWithDataSource extends HbSessionDataStore {
      public final static class CustomConnectionProvider implements ConnectionProvider {
        private DataSource ds;
    
        public void close() throws HibernateException { }
    
        public void closeConnection(final Connection conn) throws SQLException {
          conn.close();
        }
    
        public void configure(final Properties props) throws HibernateException {
          ds = _CONFIG_TIME_DS_HOLDER.get();
        }
    
        public Connection getConnection() throws SQLException {
          return ds.getConnection();
        }
    
       public boolean supportsAggressiveRelease() {
         return false;
       }
      }
    
      private static final long serialVersionUID = 1L;
      private static HbDataStoreWithDataSource INSTANCE;
    
      /**
       * the data source holder acts as a thread safe bridge between the DSConnectionProvider which is instantiated by Hibernate internally
       * and the data source injected into the session factory during spring configuration time. 
       */
      private static final ThreadLocal _CONFIG_TIME_DS_HOLDER = new ThreadLocal();
    
      public static HbDataStoreWithDataSource getInstance() {
    		return INSTANCE;
      }
    
      private final Properties props = new Properties();
    
      public HbDataStoreWithDataSource() {
        INSTANCE = this;
    
        props.setProperty(PersistenceOptions.ADD_INDEX_FOR_FOREIGN_KEY, "true");
        props.setProperty(PersistenceOptions.FETCH_CONTAINMENT_EAGERLY, "false");
        props.setProperty(PersistenceOptions.FETCH_ASSOCIATION_EXTRA_LAZY, "false");
    
        // tell Teneo to not change the table names, we do this using Hibernate's ImprovedNamingStrategy
        props.setProperty(PersistenceOptions.SQL_CASE_STRATEGY, "none");
        props.setProperty(PersistenceOptions.MAXIMUM_SQL_NAME_LENGTH, "-1");
      }
    
      protected Configuration createConfiguration() {
        Configuration cfg = super.createConfiguration();
        cfg.setNamingStrategy(ImprovedNamingStrategy.INSTANCE);
    
        // only if a DS is injected we register our connection provider
        if (_CONFIG_TIME_DS_HOLDER.get() != null)
          cfg.setProperty(Environment.CONNECTION_PROVIDER, CustomConnectionProvider.class.getName());
        return cfg;
      }
    
      public void setDataSource(final DataSource ds) {
        _CONFIG_TIME_DS_HOLDER.set(ds);
      }
    
      public final void setHibernateProperties(final Properties hibernateProperties) {
        throw new UnsupportedOperationException();
      }
    
      public final void setPersistenceProperties(final Properties persistenceOptions) {
        throw new UnsupportedOperationException();
      }
    
      public void setProperties(final Properties props) {
        this.props.putAll(props);
        super.setProperties(this.props);
      }
    }
    

    web.xml:

    
      openSessionInViewFilter
      org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
        
          singleSession
          true
        
        
          sessionFactoryBeanName
          realSessionFactory
        
    
    
      openSessionInViewFilter
      /*
      REQUEST
      ERROR
    
    

    applicationContext.xml:

    
    
      
    
      
        
          
            ${jdbc.driverClassName}
            ${jdbc.url}
            ${jdbc.username}
            ${jdbc.password}
            false
            ${jdbc.maxConnections}
            ${jdbc.minConnections}
            10000
            ${jdbc.minConnections}
            ${jdbc.validationQuery}
            false
            true
            1200000
            1800000
            5
          
        
      
    
      
        
          hibernate.dialect=${hibernate.dialect}
          hibernate.hbm2ddl.auto=${hibernate.hbm2ddl.auto}
          hibernate.jdbc.batch_size=2000
          hibernate.show_sql=${hibernate.show_sql}
          hibernate.transaction.auto_close_session=false
          hibernate.current_session_context_class=org.springframework.orm.hibernate3.SpringSessionContext
          hibernate.transaction.factory_class=org.springframework.orm.hibernate3.SpringTransactionFactory
        
        com.acme.ShopPackageImpl 
      
    
      
    
      
    
      
    
    
    

    Comments:

    1. VERY IMPORTANT:The HibernateTransactionManager does NOT get the teneoSessionFactory(HbSessionDataStore) object passed in as sessionFactory, but the underlying Hibernate SessionFactory that can be retrieved from the HbSessionDataStore via the getSessionFactory() method. Not doing so will result in duplicate Hibernate Sessions completely breaking the transaction managment. This happens because in that case the TransactionSynchronizationManager will sometimes internally bind the hibernate session to the current thread based on different keys and thus does not find it again. Here are some stacktraces showing the problem:
      TransactionSynchronizationManager.bindResource(Object, Object) line: 170	
      at OpenSessionInViewFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 183
      => results in TransactionSynchronizationManager.bindResource(HbSessionDataStore, SessionHolder) => as key the Teneo SessionFactory is used
      
      TransactionSynchronizationManager.bindResource(Object, Object) line: 170	
      at SessionFactoryUtils.doGetSession(SessionFactory, Interceptor, SQLExceptionTranslator, boolean) line: 339	
      at SessionFactoryUtils.doGetSession(SessionFactory, boolean) line: 256	
      at SpringSessionContext.currentSession() line: 60	
      at SessionFactoryImpl.getCurrentSession() line: 700	
      at HbSessionDataStore(HbBaseSessionDataStore).getCurrentSession() line: 161	
      => results in TransactionSynchronizationManager.bindResource(SessionFactoryImpl, SessionHolder) => as key the Hibernate SessionFactory is used
      
      TransactionSynchronizationManager.bindResource(Object, Object) line: 170	
      at HibernateTransactionManager.doBegin(Object, TransactionDefinition) line: 577	
      at HibernateTransactionManager(AbstractPlatformTransactionManager).getTransaction(TransactionDefinition) line: 371
      at TransactionInterceptor(TransactionAspectSupport).createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String) line: 316	
      at TransactionInterceptor.invoke(MethodInvocation) line: 105	
      at ReflectiveMethodInvocation.proceed() line: 172
      => results in TransactionSynchronizationManager.bindResource(HbSessionDataStore, SessionHolder) => as key the Teneo SessionFactory is used
      
    2. defaultAutoCommit is set to false to ensure that Spring has full control over the transaction and that the connection pool is not committing statements automatically based on it’s own strategies.
    3. The example uses an extended version of the org.eclipse.emf.teneo.hibernate.HbSessionDataStore class that allows the usage of a DataSource object and that configures Teneo to not alter the table, column names but let’s Hibernate handle this via its ImprovedNamingStrategy.
    4. hibernate.transaction.auto_close_session is set to false to avoid “SessionException: Session is closed!” in conjunction with the OpenSessionInViewListener.
    5. hibernate.current_session_context_class and hibernate.transaction.factory_class are set to special Spring implementations to avoid “org.hibernate.HibernateException: No CurrentSessionContext configured!” when not using Spring Hibernate Template via HibernateDAOSupport. It is not recommended to use the Hibernate Template anymore, see http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate-andor-jpatemplate/
  • Using EMF ECore model objects with Wicket components

    (2 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 8 September 2010 1 comment

    Apache Wicket uses so called model objects to bind data objects to Wicket components. The framework provides a number of model implementations to access data objects and their properties in various ways. The PropertyModel implementation for example is used to access and set the value of a Java object using reflection.

    EPropertyModel

    If you are working with EMF (Eclipse Modeling Framework) generated model classes you can also use PropertyModels to bind structural features (properties in EMF terminology) of an EObject to a Wicket component. E.g. if your EObject has an attribute called “comment” you could instantiate a model like this

    IModel model = new PropertyModel(myEObject, "comment");
    

    This approach has several disadvantages. First, the name of the attribute is declared as a String and not as a compile-time reference to the actual attribute. This means the application could break during runtime if you have renamed the attribute at one point and forgot to also change the String value. Second, this property model uses runtime reflection which is a slow alternative to compile time based access.
    Java classes generated from EMF Models by default extend the EObject class which provides a compile-time reflection API. Using the generic eGet and eSet methods any public property of the EObject can be accessed. The EMF Generator also generates a Package class for each EMF package that holds a Literals interface listing all available attributes and references of all generated EClasses of the given package. If you lets say have an EClass “Ticket” with the attribute “comment” in the package “com.acme.service” you could utilize the compile time reflection API as follows:

    EAttribute commentAttribute = com.acme.service.ServicePackage.Literals.TICKET__COMMENT;
    Ticket newTicket = com.acme.service.ServiceFactory.eINSTANCE.create(Ticket.class);
    newTicket.eSet(commentAttribute, "my comment");
    

    Using the EMF compile-time reflection API I created a generic EMF ECore Feature Model implementation that leverages the compile-time references to structural features. Most of the code is actually related to working around the fact that EAttributes and EReferences are not serializable but which they should to be part safely referenced in a model that potentially is serialized by Wicket between requests.

    import org.apache.wicket.model.IDetachable;
    import org.apache.wicket.model.IObjectClassAwareModel;
    import org.eclipse.emf.ecore.EClass;
    import org.eclipse.emf.ecore.EClassifier;
    import org.eclipse.emf.ecore.EObject;
    import org.eclipse.emf.ecore.EPackage;
    import org.eclipse.emf.ecore.EStructuralFeature;
    
    /**
     * A property model to bind an attribute of an EMF ECore object
     * 
     * @author Sebastian Thomschke
     */
    @SuppressWarnings("unchecked")
    public class EFeatureModel<T> implements IObjectClassAwareModel<T>
    {
    	private static final long serialVersionUID = 1L;
    
    	private transient EStructuralFeature feat;
    	private String featContainerClassName;
    	private String featName;
    	private String featPackageNsURI;
    
    	private EObject target;
    
    	public EFeatureModel(EObject target, EStructuralFeature efeature)
    	{
    		this.target = target;
    		this.feat = efeature;
    		this.featName = efeature.getName();
    		this.featContainerClassName = efeature.getContainerClass().getName();
    		this.featPackageNsURI = ((EPackage) efeature.eContainer().eContainer()).getNsURI();
    	}
    
    	public void detach()
    	{
    		if (target instanceof IDetachable) ((IDetachable) target).detach();
    	}
    
    	public EStructuralFeature getFeature()
    	{
    		if (feat == null)
    		{
    			EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(featPackageNsURI);
    			for (Object eClassifierItem : ePackage.getEClassifiers())
    			{
    				EClassifier eClassifier = (EClassifier) eClassifierItem;
    				if (eClassifier.getInstanceClass().getName().equals(featContainerClassName))
    					feat = ((EClass) eClassifier).getEStructuralFeature(featName);
    			}
    		}
    		return feat;
    	}
    
    	public T getObject()
    	{
    		return (T) target.eGet(getFeature());
    	}
    
    	public Class<T> getObjectClass()
    	{
    		return getFeature().getEType().getInstanceClass();
    	}
    
    	public void setObject(T object)
    	{
    		// do nothing if the object is the same instance that is returned by the getter
    		// this is esp. important for collection properties
    		if (target.eGet(getFeature()) == object) return;
    
    		target.eSet(getFeature(), object);
    	}
    
    	@Override
    	public String toString()
    	{
    		return new StringBuilder("Model:classname=[").append(getClass().getName()).append("]").append(":target=[")
    				.append(target).append("]").toString();
    	}
    }

    Using this new model implementation is as simple as using the PropertyModel provided by Wicket:

    IModel model = new EFeatureModel(myTicket, com.acme.service.ServicePackage.Literals.TICKET__COMMENT);
    

    You can now fully enjoy the compile-time safeness of your EMF model.

    Making EObjects serializable

    Wicket usually serializes components and their associated models between HTTP requests. If you are using the provided EPropertyModel your EObject classes must implement the Serializable interface. To achieve this, simply extend the EObject interface with the Serializables interface and set “Root Extends Interface” property in your genmodel configuration to this interface.

    /**
     * @author Sebastian Thomschke
     */
    public interface SerializableEObject extends EObject, Serializable {
    }
    

    When you now regenerate the model classes they will extend the SerializableEObject interface and Wicket will stop complaining about objects in the model being not serializable.

  • Installing Tomcat 6 on Debian Squeeze

    (10 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 13 March 2010 10 comments

    This post describes how to setup Tomcat 6 on Debian Squeeze. The configured Tomcat serves requests on port 80 without the need of an additional web server. This is especially good for virtual servers (VPS) providing limit memory. It also has multiple virtual hosts configured, each with it’s own webapp with context root / and optional support for PHP via the Quercus PHP implementation.

    Installing Sun Java 6

    Ensure the non-free section is enabled for the APT repository configuration in /etc/apt/sources.list, e.g. “deb http://ftp.de.debian.org/debian testing main contrib non-free”

    apt-get update
    apt-get install sun-java6-jdk
    echo 'JAVA_HOME="/usr/lib/jvm/java-6-sun"' >> /etc/environment
    echo 'JRE_HOME="/usr/lib/jvm/java-6-sun/jre"' >> /etc/environment
    

    Installing Tomcat 6

    apt-get install tomcat6 tomcat6-admin
    /etc/init.d/tomcat6 stop
    

    Creating standard Tomcat directory layout (optional)

    mkdir /opt/tomcat
    cd /opt/tomcat
    ln -s /etc/tomcat6/ conf
    ln -s /usr/share/tomcat6/bin/ bin
    ln -s /usr/share/tomcat6/lib/ lib
    ln -s /var/lib/tomcat6/webapps webapps
    ln -s /var/log/tomcat6/ logs
    

    Creating a Tomcat admin user

    In /opt/tomcat/conf/tomcat-users.xml add an entry like:

    <user name="ADMIN_USERNAME" password="ADMIN_PASSWORD" roles="admin,manager" />
    

    Setting up virtual hosts

    For each virtual host execute the following command. Replace “mydomain.com” with the desired virtual host name, but omit the “www.” part.

    mkdir -p /opt/tomcat/webapps.mydomain.com/ROOT
    

    In the <Engine> tag of “/opt/tomcat/conf/server.xml” add one host entry for each virtual host.

    <Host name="mydomain.com" appBase="/opt/tomcat/webapps.mydomain.com">
        <Alias>www.mydomain.com</Alias>
        <Valve className="org.apache.catalina.valves.AccessLogValve" prefix="mydomain_access_log." suffix=".txt" pattern="common"/>
    </Host>
    

    The <Alias> tag tells Tomcat to redirect from www.mydomain.com to mydomain.com.
    The <Valve> tag enables access logging in the standard logging format.

    Using xinetd to configure port 80 for Tomcat

    Binding a service on port 80 requires root permissions. Thus we use port forwarding to “bind” Tomcat to port 80. My VPS does not support the use of “iptables -j REDIRECT” therefore I am using xinetd as a web proxy.
    Ensure that no other service is listening on port 80/443:

    netstat -pan | grep ":80\|:443"
    

    Register the required xinetd services:

    echo echo "
    service www
    {
            socket_type     = stream
            protocol        = tcp
            user            = root
            wait            = no
            bind            = 88.80.198.181
            port            = 80
            redirect        = localhost 8080
            disable         = no
            flags           = REUSE
            log_type        = FILE /var/log/wwwaccess.log
            log_on_success  -= PID HOST DURATION EXIT
    
            per_source      = UNLIMITED
            instances       = UNLIMITED
    }
    
    service https
    {
            socket_type     = stream
            protocol        = tcp
            user            = root
            wait            = no
            bind            = 88.80.198.181
            port            = 443
            redirect        = localhost 8443
            disable         = no
            flags           = REUSE
            log_type        = FILE /var/log/httpsaccess.log
            log_on_success  -= PID HOST DURATION EXIT
    
            per_source      = UNLIMITED
            instances       = UNLIMITED
    }
    " > /etc/xinetd.d/tomcat
    /etc/init.d/xinetd restart
    

    If you want to use a different service name, e.g. “tomcat” instead of “www” you must add this service to /var/services, e.g. “tomcat 80/tcp”
    In /opt/tomcat/conf/server.xml modify the <Connector> as follows:

    <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" proxyPort="80" address="127.0.0.1" />
    

    This binds Tomcat to localhost. It also tells Tomcat that port 80 is the proxy port which is necessary for correct URL generation.
    From now on the Tomcat admin applications are only accessible via localhost. You can use SSH port forwarding to still access the applications from your workstation’s web browser. E.g. if you are using PuTTY you can use this command line option “-L 8080:localhost:8080″ to forward the server’s local 8080 port to your workstation’s local 8080 port. On your workstation’s browser you then simply enter http://localhost:8080/manager/html and are connected to the server’s Tomcat admin application.

    Enabling PHP support (optional)

    Download Quercus.

    mkdir -p /opt/downloads
    wget -o /opt/downloads/quercus-4.0.3.war http://caucho.com/download/quercus-4.0.3.war
    

    Install Quercus as a shared library.

    unzip -j /opt/downloads/quercus-4.0.3.war \*.jar -d /opt/tomcat/lib
    

    Enable PHP support for the virtual hosts by installing the quercus web.xml. For each virtual host execute:

    unzip /opt/downloads/quercus-4.0.3.war *web.xml -d /opt/tomcat/webapps.mydomain.com/ROOT
    

    Starting Tomcat

    /etc/init.d/tomcat start
    

    References

  • Leveraging PyDev’s auto-completion for indirectly created objects

    (1 votes) 1 Star2 Stars3 Stars4 Stars5 Stars
    Loading...Loading...
    Posted on 20 February 2010 4 comments

    This is just a quick tip how to enable auto completion in PyDev for indirectly created objects.

    By default, PyDev has no problems in presenting you the possible object attributes and methods on a variable if that variable gets a new object instance assigned directly in the code.

    For example, when you first type …

    mybook = Book()

    … and in the next line you enter …

    mybook.

    … PyDev will dutifully present you the statically declared features for the Book class in a popup.

    If you however get that Book instance as the result of another method, e.g. …

    mybook = bookstore.findBook("foobar")

    … PyDev currently seems to be helpless.

    Because of Python’s dynamic nature it is of course a bit harder for an IDE to figure out what kind of objects may a method. But PyDev could for example honor the @return / @rtype PyDoc annotations (which you can add to the findBook’s method declaration) but currently it just does not.

    To still have auto-completion you have two options:

    1. Temporarily instantiate a Book object in the code.
      mybook = Book()
      mybook = bookstore.findBook("foobar")
      mybook. #--> now you have auto completion
      

      This has the drawback, that if you forget to remove the first line before you finish coding an unneccessary Book instance will be created on every execution during runtime.

    2. Use an assertion to ensure that the mybook variable contains an object of type Book
      mybook = bookstore.findBook("foobar")
      assert isinstance(mybook, Book)
      mybook. #--> now you have auto completion