Categories
AI

Cursor (1): AI leverage while programming.

Today’s post will be in line with the current trend: AI. Although AI is being introduced into many industries, the programming industry is one of the most transformed by this new revolution. The possibility of reviewing code as you write it; of creating, launching, and correcting unit and functional tests; or of analysing complex code and how it behaves, provides invaluable help for those of us who dedicate ourselves to this.

The tools developed in a very short time are impressive. Not only the conversational web interfaces, such as ChatGPT or Gemini, but also the development environments with integrated AI. An example is Cursor, a fork of VSCode with direct interaction with various AI backends.

What is Cursor? Of course, let’s ask Gemini: Cursor is an AI-powered code editor and integrated development environment (IDE). It integrates AI features like an “Agent” that turns ideas into code and “Tab” for magically accurate autocomplete. It aims to be “the best way to code with AI” and offers support for various cutting-edge models like GPT-5, Claude Sonnet, and Gemini.

See how easy is?

Installation and first steps.

Regardless of the operating system, the installation is very easy. The website provides several binaries for different platforms. Once installed using the GUI, the first step is to login with you account, that in my case is provided by my company.

The first step is to add a project. The editor will ask you for a workplace (directory); all projects stored inside this directory will be trusted. I initially added the ones I use the most: Neutron, neutron-lib, all oslo libraries, ovsdbapp, etc.

First blocker: the Neutron code was not correctly importing the modules from the other projects loaded in the same workspace. Use the forceAgent“. This agent is a AI powered assistant that will autocomplete code, analyse it and curse you for your poor coding skills (I’m not sure about the last statement).

So my very first interaction was this, using the chat (on the left in the previous image): “I have multiple projects in the workspace. I would like the “Neutron” project to be able to import the code from the directories present in this workspace, not from the installed libraries in the system. How can I do it?. The agent created a set of files and directories (.vscode/settings.json, VS Code/Cursor workspace settings; .cursor/settings.json, Cursor-specific settings; pyrightconfig.json, Pyright/Pylance configuration), provided the description of each file and a gave a set of steps to load them (reloading the current window). Amazing.

Let’s create some unit tests.

I was initially reluctant to give Cursor the control to the running code, so after the creation of a small patch, I asked it (him/her?) to only create the unit tests. This is the original patch that is updating one single method.

This is the chat input: “I’ve updated the method ovsdbapp.utils.get_uuid. Create the corresponding unit tests.”

And this is the output:

   def test_get_uuid_with_uuid_string(self):
       """Test get_uuid with a UUID string."""
       test_uuid = str(uuid.uuid4())
       result = utils.get_uuid(test_uuid)
       self.assertEqual(test_uuid, result)


   def test_get_uuid_with_uuid_object(self):
       """Test get_uuid with a UUID object."""
       test_uuid = uuid.uuid4()
       result = utils.get_uuid(test_uuid)
       self.assertEqual(test_uuid, result)


   def test_get_uuid_with_object_with_uuid_attribute(self):
       """Test get_uuid with an object that has a uuid attribute."""
       test_uuid = str(uuid.uuid4())
       mock_obj = mock.Mock()
       mock_obj.uuid = test_uuid
       result = utils.get_uuid(mock_obj)
       self.assertEqual(test_uuid, result)


   def test_get_uuid_with_object_without_uuid_attribute(self):
       """Test get_uuid with an object that doesn't have a uuid attribute."""
       mock_obj = mock.Mock(spec=[])  # No attributes
       result = utils.get_uuid(mock_obj)
       self.assertEqual(mock_obj, result)


   def test_get_uuid_with_basecommand_with_rowview_result(self):
       """Test get_uuid with a BaseCommand that has a RowView result."""
       test_uuid = str(uuid.uuid4())
       mock_rowview = mock.Mock()
       mock_rowview.uuid = test_uuid
      
       mock_command = mock.Mock(spec=['result'])
       mock_command.result = mock_rowview
      
       result = utils.get_uuid(mock_command)
       self.assertEqual(test_uuid, result)


   def test_get_uuid_with_basecommand_with_uuid_string_result(self):
       """Test get_uuid with a BaseCommand that has a UUID string as result."""
       test_uuid = str(uuid.uuid4())
       mock_command = mock.Mock(spec=['result'])
       mock_command.result = test_uuid
      
       result = utils.get_uuid(mock_command)
       self.assertEqual(test_uuid, result)


   def test_get_uuid_with_basecommand_with_non_uuid_result(self):
       """Test get_uuid with a BaseCommand that has a non-UUID result."""
       mock_result = mock.Mock(spec=[])  # No uuid attribute
       mock_command = mock.Mock(spec=['result'])
       mock_command.result = mock_result
      
       result = utils.get_uuid(mock_command)
       self.assertEqual(mock_result, result)


   def test_get_uuid_with_rowview_object(self):
       """Test get_uuid with a RowView object directly."""
       test_uuid = str(uuid.uuid4())
       mock_rowview = mock.Mock()
       mock_rowview.uuid = test_uuid
      
       result = utils.get_uuid(mock_rowview)
       self.assertEqual(test_uuid, result)

Not bad but maybe excessive. Of course this code is testing all possible inputs but mocking the values. The tests I implemented instead are creating real objects (Row, RowView, BaseCommand and UUID), passing them to the method.

Adding the Python annotations.

I think Cursor was not aware of the possible inputs for this method and this is why the unit tests lacked a definition in the input and output types. Thus my next step was to ask for the method annotations, to provide better context. This was the chat input: “In the same method, add the method signature annotations. For a Python method, what is the recommended format for a signature?”

Following the PEP484, as annotated in the answer, the output code was this:

def get_uuid(_obj: Any) -> Any:

Ok, my bad: the method code is very generic and it is not possible to infer the input and output types from it. But the method description was explicit on this, then I updated the input chat: “Read the method description”.

Now the output was richer and more explicit:

if TYPE_CHECKING:
   from ovsdbapp.backend.ovs_idl import command
   from ovsdbapp.backend.ovs_idl import rowview

def get_uuid(_obj: Union[str, uuid.UUID, "command.BaseCommand",
                         "rowview.RowView", Any]) -> Union[str, uuid.UUID, Any]:

My next step was the code analysis. That, without a doubt, pleasantly surprised me, even when the result had certain errors that I had to point out and that Cursor corrected. Follow me in the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *