Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v1] Implement collection operations #70

Merged
merged 5 commits into from
Jun 28, 2023
Merged

[v1] Implement collection operations #70

merged 5 commits into from
Jun 28, 2023

Conversation

jhamon
Copy link
Collaborator

@jhamon jhamon commented Jun 25, 2023

Problem

Similar to recent PRs #66 and #67, this PR is adding new functionality to the revamped node client. Specifically:

  • createCollection
  • describeCollection
  • listCollections
  • deleteCollection

Solution

More or less followed the approach used in #66 and #67 to provide simpler arguments, runtime argument validation, and improved error handling.

Type of Change

  • New feature (non-breaking change which adds functionality)

Test Plan

  • Added new unit tests covering these commands.
  • Manual testing with npm run repl

Creating a collection

Validating arguments

> await client.createCollection()
Uncaught:
PineconeArgumentError: Argument to createCollection has a problem. The argument must be object.
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:45:15
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:36:13
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:56:21
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:33:23)
    at Object.next (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:14:53)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:4:12)
    at Client.createCollection (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:51:40)
> await client.createCollection({})
Uncaught:
PineconeArgumentError: Argument to createCollection has a problem. The argument must have required property 'name'. The argument must have required property 'source'.
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:45:15
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:36:13
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:56:21
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:33:23)
    at Object.next (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:14:53)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:4:12)
    at Client.createCollection (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:51:40)
> await client.createCollection({ name: 'baz-collection', source: 'baz' })
Uncaught PineconeBadRequestError: source database baz does not exist
    at mapHttpStatusError (/Users/jhamon/workspace/pinecone-ts-client/dist/errors/http.js:127:20)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:88:63
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:33:23)

bad request

> await client.createCollection({ name: 'my-collection', source: 'asdf' })
Uncaught PineconeBadRequestError: source database asdf does not exist
    at mapHttpStatusError (/Users/jhamon/workspace/pinecone-ts-client/dist/errors/http.js:127:20)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:88:63
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/createCollection.js:33:23)

happy path

> await client.listIndexes()
[ 'bar', 'foofoo', 'jen-numpy-test' ]
> await client.createCollection({ name: 'bar-collection', source: 'bar' })
undefined

describeCollection

Validations

> await client.describeCollection()
Uncaught:
PineconeArgumentError: Argument to describeCollection has a problem. The argument must be string.
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:45:15
    at /Users/jhamon/workspace/pinecone-ts-client/dist/validator.js:36:13
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:78:21
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:44:23)
    at Object.next (/Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:25:53)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:19:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:15:12)
    at Client.describeCollection (/Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:73:37)

bad request

> await client.describeCollection('asdf')
Uncaught:
PineconeNotFoundError: Collection 'asdf' does not exist. Valid collection names: []
    at mapHttpStatusError (/Users/jhamon/workspace/pinecone-ts-client/dist/errors/http.js:131:20)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:98:63
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/describeCollection.js:44:23)

happy path

> await client.describeCollection('bar-collection')
{ name: 'bar-collection', size: undefined, status: 'Initializing' }
> await client.describeCollection('bar-collection')
{ name: 'bar-collection', size: 3088261, status: 'Ready' }

listCollection

> await client.listCollections()
[ 'bar-collection' ]

deleteCollection

> await client.deleteCollection('foo-collection')
Uncaught:
PineconeNotFoundError: Collection 'foo-collection' does not exist. Valid collection names: ['bar-collection']
    at mapHttpStatusError (/Users/jhamon/workspace/pinecone-ts-client/dist/errors/http.js:131:20)
    at /Users/jhamon/workspace/pinecone-ts-client/dist/control/deleteCollection.js:82:63
    at step (/Users/jhamon/workspace/pinecone-ts-client/dist/control/deleteCollection.js:44:23)
> await client.deleteCollection('bar-collection')
undefined
> await client.listCollections()
[]
>

@jhamon jhamon marked this pull request as ready for review June 26, 2023 01:01
Copy link

@micmmakarov micmmakarov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like to all these beautiful tests

Copy link
Member

@iamannamai iamannamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Big fan of the attention to DevX. Still using PRs as a way of getting up to speed, nothing majorly blocking 🚀

Comment on lines +13 to +18
const listCollectionsError = e as ResponseError;
throw mapHttpStatusError({
status: listCollectionsError.response.status,
url: listCollectionsError.response.url,
message: await listCollectionsError.response.text(),
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd conveniently forgotten that JS error catching is... so rough.

nit Would it help to be a bit defensive here and do an instanceof ResponseError check in case this isn't for whatever reason a ResponseError?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good thought. I will add a follow-up task to my todo list.

export const listCollections = (api: IndexOperationsApi) => {
return async (): Promise<CollectionList> => {
try {
return await api.listCollections();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit I think you can just return api.listCollections() if the intent is to return a promise, since you'd have to await on the other end anyway, right? Same for anywhere you return await.

export { listCollections } from './listCollections';
export { deleteCollection } from './deleteCollection';

export type { CollectionName, CollectionList } from './listCollections';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

Comment on lines +11 to +15
// If user passes the empty string for collection name, the generated
// OpenAPI client will call /databases/ which is the list
// collection endpoint. This returns 200 instead of 404, but obviously
// no descriptive information is returned for an collection named empty
// string. To avoid this confusing case, we require lenth > 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤩

Comment on lines +16 to +18
const DescribeCollectionSchema = Type.String({ minLength: 1 });

export type CollectionName = Static<typeof DescribeCollectionSchema>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool! I know @zaeemadamjee mentioned he was looking at zod for something on the console-side. Probably not too important to do now, but think it might be nice to align on a single tool at some point

const removeDeprecatedFields = (result: any) => {
if (result.database) {
for (const key of Object.keys(result.database)) {
if (result.database[key] === undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question Is it always going to be true that an undefined value is always deprecated? I'm wondering if it's safer to allow/deny-list fields and build a new object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's on my todo list to dig into this question more. Probably for the long term we want some way to distinguish deprecated from undefined.


try {
await api.deleteCollection({ collectionName: collectionName });
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought I think I'm elixir-braining this a bit so you can ignore if you want--Do we want to pass along some kind of indicator of success or is undefined standard practice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall return value here is Promise<void> which I think makes sense. I considered this question when I was implementing and couldn't think of anything very meaningful to return; on success the underlying generated openapi client just returns '' when deleting which I don't think adds any information and can actually be misleading because '' is a falsy value even though it succeeded.

If the request fails, this will throw and the promise will be rejected. Some APIs echo back the name/id of the thing that was deleted or a delete count when deleting multiple records, but our API doesn't return those today and to me it doesn't seem that useful anyway. As a caller, if I ask an api to return collection 'foo' and it says it succeeded, I already know everything I need to know about the state of the world.

source: nonemptyString,
});

export type CreateCollectionOptions = Static<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL, line length limits.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the line length formatting has honestly been a big pain because // @ts-ignore doesn't work if things break across two lines and a lot of my test cases are specifically passing params that violate the typescript expectations to test method validation. Thinking about disabling that formatting rule.

import { IndexOperationsApi } from '../../pinecone-generated-ts-fetch';
import type { CreateCollectionOperationRequest as CCOR } from '../../pinecone-generated-ts-fetch';

const setOpenAPIResponse = (fakeCreateCollectionResponse) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion For the future, I think potentially a better way to do this would mock of IOA could be to use jest manual mocks but I think typing this could be hellish too...

@jhamon jhamon merged commit ad9f894 into main Jun 28, 2023
@jhamon jhamon deleted the jhamon/collections branch June 28, 2023 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants