Skip to content

Commit b0adf36

Browse files
feat: added support for new api in beta - analyze api (#656)
1 parent cba5a12 commit b0adf36

File tree

10 files changed

+197
-15
lines changed

10 files changed

+197
-15
lines changed

lib/analysis/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const utils = require("../utils");
2+
const {call_analysis_api} = require('../api_client/call_analysis_api');
3+
4+
function analyze_uri(uri, analysis_type, options = {}, callback) {
5+
const params = {
6+
uri,
7+
analysis_type
8+
}
9+
10+
if (analysis_type === 'custom') {
11+
if (!('model_name' in options) || !('model_version' in options)) {
12+
throw new Error('Setting analysis_type to "custom" requires additional params: "model_name" and "model_version"');
13+
}
14+
params.parameters = {
15+
custom: {
16+
model_name: options.model_name,
17+
model_version: options.model_version
18+
}
19+
}
20+
}
21+
22+
let api_uri = ['analysis', 'analyze', 'uri'];
23+
return call_analysis_api('POST', api_uri, params, callback, options);
24+
}
25+
26+
module.exports = {
27+
analyze_uri
28+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const utils = require("../utils");
2+
const config = require("../config");
3+
const ensureOption = require('../utils/ensureOption').defaults(config());
4+
const execute_request = require("./execute_request");
5+
6+
const {ensurePresenceOf} = utils;
7+
8+
function call_analysis_api(method, uri, params, callback, options) {
9+
ensurePresenceOf({
10+
method,
11+
uri
12+
});
13+
const api_url = utils.base_api_url_v2()(uri, options);
14+
let auth = {};
15+
if (options.oauth_token || config().oauth_token) {
16+
auth = {
17+
oauth_token: ensureOption(options, "oauth_token")
18+
};
19+
} else {
20+
auth = {
21+
key: ensureOption(options, "api_key"),
22+
secret: ensureOption(options, "api_secret")
23+
};
24+
}
25+
options.content_type = 'json';
26+
27+
return execute_request(method, params, auth, api_url, callback, options);
28+
}
29+
30+
module.exports = {
31+
call_analysis_api
32+
};

lib/api_client/call_api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { ensurePresenceOf } = utils;
88

99
function call_api(method, uri, params, callback, options) {
1010
ensurePresenceOf({ method, uri });
11-
const api_url = utils.base_api_url(uri, options);
11+
const api_url = utils.base_api_url_v1()(uri, options);
1212
let auth = {};
1313
if (options.oauth_token || config().oauth_token){
1414
auth = {

lib/api_client/execute_request.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,15 @@ function execute_request(method, params, auth, api_url, callback, options = {})
9999
if (result.error) {
100100
result.error.http_code = res.statusCode;
101101
} else {
102-
result.rate_limit_allowed = parseInt(res.headers["x-featureratelimit-limit"]);
103-
result.rate_limit_reset_at = new Date(res.headers["x-featureratelimit-reset"]);
104-
result.rate_limit_remaining = parseInt(res.headers["x-featureratelimit-remaining"]);
102+
if (res.headers["x-featureratelimit-limit"]) {
103+
result.rate_limit_allowed = parseInt(res.headers["x-featureratelimit-limit"]);
104+
}
105+
if (res.headers["x-featureratelimit-reset"]) {
106+
result.rate_limit_reset_at = new Date(res.headers["x-featureratelimit-reset"]);
107+
}
108+
if (res.headers["x-featureratelimit-remaining"]) {
109+
result.rate_limit_remaining = parseInt(res.headers["x-featureratelimit-remaining"]);
110+
}
105111
}
106112

107113
if (result.error) {

lib/cloudinary.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ exports.config = require("./config");
33
exports.utils = require("./utils");
44
exports.uploader = require("./uploader");
55
exports.api = require("./api");
6-
let account = require("./provisioning/account");
6+
exports.analysis = require('./analysis');
7+
8+
const account = require("./provisioning/account");
79

810
exports.provisioning = {
911
account: account

lib/utils/index.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,17 +1058,31 @@ function unsigned_url_prefix(
10581058
return prefix;
10591059
}
10601060

1061-
function base_api_url(path = [], options = {}) {
1062-
let cloudinary = ensureOption(options, "upload_prefix", UPLOAD_PREFIX);
1063-
let cloud_name = ensureOption(options, "cloud_name");
1064-
let encode_path = unencoded_path => encodeURIComponent(unencoded_path).replace("'", '%27');
1065-
let encoded_path = Array.isArray(path) ? path.map(encode_path) : encode_path(path);
1066-
return [cloudinary, "v1_1", cloud_name].concat(encoded_path).join("/");
1061+
function base_api_url_v1_1() {
1062+
return base_api_url('v1_1');
1063+
}
1064+
1065+
function base_api_url_v2() {
1066+
return base_api_url('v2');
1067+
}
1068+
1069+
function base_api_url(api_version) {
1070+
if (!api_version || api_version.length === 0) {
1071+
throw new Error('api_version needs to be a non-empty string');
1072+
}
1073+
1074+
return (path = [], options = []) => {
1075+
let cloudinary = ensureOption(options, "upload_prefix", UPLOAD_PREFIX);
1076+
let cloud_name = ensureOption(options, "cloud_name");
1077+
let encode_path = unencoded_path => encodeURIComponent(unencoded_path).replace("'", '%27');
1078+
let encoded_path = Array.isArray(path) ? path.map(encode_path) : encode_path(path);
1079+
return [cloudinary, api_version, cloud_name].concat(encoded_path).join("/");
1080+
};
10671081
}
10681082

10691083
function api_url(action = 'upload', options = {}) {
10701084
let resource_type = options.resource_type || "image";
1071-
return base_api_url([resource_type, action], options);
1085+
return base_api_url_v1_1()([resource_type, action], options);
10721086
}
10731087

10741088
function random_public_id() {
@@ -1225,7 +1239,7 @@ function download_backedup_asset(asset_id, version_id, options = {}) {
12251239
asset_id: asset_id,
12261240
version_id: version_id
12271241
}, options);
1228-
return exports.base_api_url(['download_backup'], options) + "?" + hashToQuery(params);
1242+
return exports.base_api_url_v1()(['download_backup'], options) + "?" + hashToQuery(params);
12291243
}
12301244

12311245
/**
@@ -1681,7 +1695,8 @@ exports.only = pickOnlyExistingValues; // for backwards compatibility
16811695
exports.pickOnlyExistingValues = pickOnlyExistingValues;
16821696
exports.jsonArrayParam = jsonArrayParam;
16831697
exports.download_folder = download_folder;
1684-
exports.base_api_url = base_api_url;
1698+
exports.base_api_url_v1 = base_api_url_v1_1;
1699+
exports.base_api_url_v2 = base_api_url_v2;
16851700
exports.download_backedup_asset = download_backedup_asset;
16861701
exports.compute_hash = compute_hash;
16871702
exports.build_distribution_domain = build_distribution_domain;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const assert = require('assert');
2+
const sinon = require('sinon');
3+
const ClientRequest = require('_http_client').ClientRequest;
4+
const api_http = require('https');
5+
6+
const cloudinary = require('../../../../cloudinary');
7+
const helper = require('../../../spechelper');
8+
9+
describe('Analyze API', () => {
10+
describe('uri analysis', () => {
11+
const mocked = {};
12+
const config = {};
13+
14+
beforeEach(function () {
15+
mocked.xhr = sinon.useFakeXMLHttpRequest();
16+
mocked.write = sinon.spy(ClientRequest.prototype, 'write');
17+
mocked.request = sinon.spy(api_http, 'request');
18+
19+
config.cloud_name = cloudinary.config().cloud_name;
20+
});
21+
22+
afterEach(function () {
23+
mocked.request.restore();
24+
mocked.write.restore();
25+
mocked.xhr.restore();
26+
});
27+
28+
it('should call analyze endpoint with non-custom analysis_type', () => {
29+
cloudinary.analysis.analyze_uri('https://example.com', 'captioning');
30+
31+
sinon.assert.calledWith(mocked.request, sinon.match({
32+
pathname: sinon.match(new RegExp(`/v2/${config.cloud_name}/analysis/analyze/uri`)),
33+
method: sinon.match('POST')
34+
}));
35+
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('uri', 'https://example.com')));
36+
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('analysis_type', 'captioning')));
37+
});
38+
39+
it('should call analyze endpoint with custom analysis_type', () => {
40+
cloudinary.analysis.analyze_uri('https://example.com', 'custom', {
41+
model_name: 'my_model',
42+
model_version: 1
43+
});
44+
45+
sinon.assert.calledWith(mocked.request, sinon.match({
46+
pathname: sinon.match(new RegExp(`/v2/${config.cloud_name}/analysis/analyze/uri`)),
47+
method: sinon.match('POST')
48+
}));
49+
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('uri', 'https://example.com')));
50+
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('analysis_type', 'custom')));
51+
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('parameters', {
52+
custom: {
53+
model_name: 'my_model',
54+
model_version: 1
55+
}
56+
})));
57+
});
58+
59+
it('should not allow calling analyze endpoint with incorrect custom analysis parameters', () => {
60+
assert.throws(() => {
61+
cloudinary.analysis.analyze_uri('https://example.com', 'custom');
62+
}, {
63+
message: 'Setting analysis_type to "custom" requires additional params: "model_name" and "model_version"'
64+
});
65+
});
66+
});
67+
});

test/utils/utils_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ describe("utils", function () {
458458
});
459459
it('should escape api urls', function () {
460460
const folderName = "sub^folder's test";
461-
const url = utils.base_api_url(['folders', folderName]);
461+
const url = utils.base_api_url_v1()(['folders', folderName]);
462462
expect(url).to.match(/folders\/sub%5Efolder%27s%20test$/);
463463
});
464464
});

types/cloudinary_ts_spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,14 @@ cloudinary.v2.provisioning.account.user_group_users(
11511151

11521152
});
11531153

1154+
// $ExpectType Promise<AnalyzeResponse>
1155+
cloudinary.v2.analysis.analyze_uri('https://example.com', 'captioning');
1156+
1157+
// $ExpectType Promise<AnalyzeResponse>
1158+
cloudinary.v2.analysis.analyze_uri('https://example.com', 'custom', {
1159+
model_name: 'my_name',
1160+
model_version: 1
1161+
});
11541162

11551163
// $ExpectType string
11561164
cloudinary.v2.utils.private_download_url('foo', 'foo', {

types/index.d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,26 @@ declare module 'cloudinary' {
839839

840840
export type SignApiOptions = Record<string, any>;
841841

842+
export interface DatasourceEntry {
843+
external_id?: string;
844+
value: string;
845+
}
846+
847+
export type AnalysisType = 'custom' | 'captioning' | 'cld_fashion' | 'cld_text' | 'coco' | 'google_tagging' | 'human_anatomy' | 'lvis' | 'shop_classifier' | 'unidet';
848+
849+
export type CustomAnalysisOptions = {
850+
model_name: string,
851+
model_version: number
852+
}
853+
854+
export interface AnalyzeResponse {
855+
data: {
856+
entity: string,
857+
analysis: Record<string, Record<string, string> | Array<string> | string>
858+
},
859+
request_id: string,
860+
}
861+
842862
export namespace v2 {
843863

844864
/****************************** Global Utils *************************************/
@@ -1437,5 +1457,9 @@ declare module 'cloudinary' {
14371457
function user_group_users(groupId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise<any>;
14381458
}
14391459
}
1460+
1461+
namespace analysis {
1462+
function analyze_uri(uri: string, analysis_type: AnalysisType, options?: ConfigOptions & CustomAnalysisOptions): Promise<AnalyzeResponse>
1463+
}
14401464
}
14411465
}

0 commit comments

Comments
 (0)