Skip to content

Commit b984f3e

Browse files
SONARPY-2415 Add stubs for pymsql, mysql and pgdb
1 parent c23e301 commit b984f3e

File tree

16 files changed

+155
-61
lines changed

16 files changed

+155
-61
lines changed

python-checks/src/main/java/org/sonar/python/checks/DbNoPasswordCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ public class DbNoPasswordCheck extends PythonSubscriptionCheck {
5252
private static final List<String> CONNECT_METHODS = Arrays.asList(
5353
"mysql.connector.connect",
5454
"mysql.connector.connection.MySQLConnection",
55-
"pymysql.connect",
55+
"pymysql.connections.connect",
5656
"psycopg2.connect",
57-
"pgdb.connect",
57+
"pgdb.connect.connect",
5858
"pg.DB",
5959
"pg.connect"
6060
);

python-checks/src/main/java/org/sonar/python/checks/hotspots/HardCodedCredentialsCheck.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ private Map<String, Integer> sensitiveArgumentByFQN() {
8383
sensitiveArgumentByFQN.put("mysql.connector.connect", 2);
8484
sensitiveArgumentByFQN.put("mysql.connector.connection.MySQLConnection", 2);
8585
sensitiveArgumentByFQN.put("pymysql.connect", 2);
86+
sensitiveArgumentByFQN.put("pymysql.connections.connect", 2);
8687
sensitiveArgumentByFQN.put("pymysql.connections.Connection", 2);
8788
sensitiveArgumentByFQN.put("psycopg2.connect", 2);
8889
sensitiveArgumentByFQN.put("pgdb.connect", 2);
90+
sensitiveArgumentByFQN.put("pgdb.connect.connect", 2);
8991
sensitiveArgumentByFQN.put("pg.DB", 5);
9092
sensitiveArgumentByFQN.put("pg.connect", 5);
9193
sensitiveArgumentByFQN = Collections.unmodifiableMap(sensitiveArgumentByFQN);

python-frontend/src/test/java/org/sonar/python/semantic/v2/typeshed/TypeShedDescriptorsProviderTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,19 @@ void shouldResolvePackages() {
150150
assertThat(provider.descriptorsForModule("kazoo")).isEmpty();
151151
}
152152

153+
@Test
154+
void customDbStubs() {
155+
var provider = typeshedDescriptorsProvider();
156+
var pgdb = provider.descriptorsForModule("pgdb");
157+
assertThat(pgdb.get("connect")).isInstanceOf(FunctionDescriptor.class);
158+
159+
var mysql = provider.descriptorsForModule("mysql.connector");
160+
assertThat(mysql.get("connect")).isInstanceOf(FunctionDescriptor.class);
161+
162+
var pymysql = provider.descriptorsForModule("pymysql");
163+
assertThat(pymysql.get("connect")).isInstanceOf(FunctionDescriptor.class);
164+
}
165+
153166
@Test
154167
void unknownModule() {
155168
var provider = typeshedDescriptorsProvider();

python-frontend/src/test/java/org/sonar/python/types/TypeShedTest.java

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ void package_lxml_reexported_symbol_fqn() {
313313
@Test
314314
void package_sqlite3_connect_type_in_ambiguous_symbol() {
315315
Map<String, Symbol> sqlite3Symbols = symbolsForModule("sqlite3");
316-
ClassSymbol connectionSymbol = (ClassSymbol) sqlite3Symbols.get("Connection");
316+
ClassSymbol connectionSymbol = (ClassSymbol) sqlite3Symbols.get("Connection");
317317
AmbiguousSymbol cursorFunction = connectionSymbol.declaredMembers().stream().filter(m -> "cursor".equals(m.name())).findFirst().map(AmbiguousSymbol.class::cast).get();
318318
Set<Symbol> alternatives = cursorFunction.alternatives();
319319
assertThat(alternatives)
@@ -378,21 +378,21 @@ void deserialize_nonexistent_or_incorrect_protobuf() {
378378
void class_symbols_from_protobuf() throws TextFormat.ParseException {
379379
SymbolsProtos.ModuleSymbol moduleSymbol = moduleSymbol(
380380
"fully_qualified_name: \"mod\"\n" +
381-
"classes {\n" +
382-
" name: \"Base\"\n" +
383-
" fully_qualified_name: \"mod.Base\"\n" +
384-
" super_classes: \"builtins.object\"\n" +
385-
"}\n" +
386-
"classes {\n" +
387-
" name: \"C\"\n" +
388-
" fully_qualified_name: \"mod.C\"\n" +
389-
" super_classes: \"builtins.str\"\n" +
390-
"}\n" +
391-
"classes {\n" +
392-
" name: \"D\"\n" +
393-
" fully_qualified_name: \"mod.D\"\n" +
394-
" super_classes: \"NOT_EXISTENT\"\n" +
395-
"}");
381+
"classes {\n" +
382+
" name: \"Base\"\n" +
383+
" fully_qualified_name: \"mod.Base\"\n" +
384+
" super_classes: \"builtins.object\"\n" +
385+
"}\n" +
386+
"classes {\n" +
387+
" name: \"C\"\n" +
388+
" fully_qualified_name: \"mod.C\"\n" +
389+
" super_classes: \"builtins.str\"\n" +
390+
"}\n" +
391+
"classes {\n" +
392+
" name: \"D\"\n" +
393+
" fully_qualified_name: \"mod.D\"\n" +
394+
" super_classes: \"NOT_EXISTENT\"\n" +
395+
"}");
396396
Map<String, Symbol> symbols = TypeShed.getSymbolsFromProtobufModule(moduleSymbol);
397397
assertThat(symbols.values()).extracting(Symbol::kind, Symbol::fullyQualifiedName)
398398
.containsExactlyInAnyOrder(tuple(Kind.CLASS, "mod.Base"), tuple(Kind.CLASS, "mod.C"), tuple(Kind.CLASS, "mod.D"));
@@ -408,44 +408,44 @@ void class_symbols_from_protobuf() throws TextFormat.ParseException {
408408
void function_symbols_from_protobuf() throws TextFormat.ParseException {
409409
SymbolsProtos.ModuleSymbol moduleSymbol = moduleSymbol(
410410
"fully_qualified_name: \"mod\"\n" +
411-
"functions {\n" +
412-
" name: \"foo\"\n" +
413-
" fully_qualified_name: \"mod.foo\"\n" +
414-
" parameters {\n" +
415-
" name: \"p\"\n" +
416-
" kind: POSITIONAL_OR_KEYWORD\n" +
417-
" }\n" +
418-
"}\n" +
419-
"overloaded_functions {\n" +
420-
" name: \"bar\"\n" +
421-
" fullname: \"mod.bar\"\n" +
422-
" definitions {\n" +
423-
" name: \"bar\"\n" +
424-
" fully_qualified_name: \"mod.bar\"\n" +
425-
" parameters {\n" +
426-
" name: \"x\"\n" +
427-
" kind: POSITIONAL_OR_KEYWORD\n" +
428-
" }\n" +
429-
" has_decorators: true\n" +
430-
" resolved_decorator_names: \"typing.overload\"\n" +
431-
" is_overload: true\n" +
432-
" }\n" +
433-
" definitions {\n" +
434-
" name: \"bar\"\n" +
435-
" fully_qualified_name: \"mod.bar\"\n" +
436-
" parameters {\n" +
437-
" name: \"x\"\n" +
438-
" kind: POSITIONAL_OR_KEYWORD\n" +
439-
" }\n" +
440-
" parameters {\n" +
441-
" name: \"y\"\n" +
442-
" kind: POSITIONAL_OR_KEYWORD\n" +
443-
" }\n" +
444-
" has_decorators: true\n" +
445-
" resolved_decorator_names: \"typing.overload\"\n" +
446-
" is_overload: true\n" +
447-
" }\n" +
448-
"}\n");
411+
"functions {\n" +
412+
" name: \"foo\"\n" +
413+
" fully_qualified_name: \"mod.foo\"\n" +
414+
" parameters {\n" +
415+
" name: \"p\"\n" +
416+
" kind: POSITIONAL_OR_KEYWORD\n" +
417+
" }\n" +
418+
"}\n" +
419+
"overloaded_functions {\n" +
420+
" name: \"bar\"\n" +
421+
" fullname: \"mod.bar\"\n" +
422+
" definitions {\n" +
423+
" name: \"bar\"\n" +
424+
" fully_qualified_name: \"mod.bar\"\n" +
425+
" parameters {\n" +
426+
" name: \"x\"\n" +
427+
" kind: POSITIONAL_OR_KEYWORD\n" +
428+
" }\n" +
429+
" has_decorators: true\n" +
430+
" resolved_decorator_names: \"typing.overload\"\n" +
431+
" is_overload: true\n" +
432+
" }\n" +
433+
" definitions {\n" +
434+
" name: \"bar\"\n" +
435+
" fully_qualified_name: \"mod.bar\"\n" +
436+
" parameters {\n" +
437+
" name: \"x\"\n" +
438+
" kind: POSITIONAL_OR_KEYWORD\n" +
439+
" }\n" +
440+
" parameters {\n" +
441+
" name: \"y\"\n" +
442+
" kind: POSITIONAL_OR_KEYWORD\n" +
443+
" }\n" +
444+
" has_decorators: true\n" +
445+
" resolved_decorator_names: \"typing.overload\"\n" +
446+
" is_overload: true\n" +
447+
" }\n" +
448+
"}\n");
449449
Map<String, Symbol> symbols = TypeShed.getSymbolsFromProtobufModule(moduleSymbol);
450450
assertThat(symbols.values()).extracting(Symbol::kind, Symbol::fullyQualifiedName)
451451
.containsExactlyInAnyOrder(tuple(Kind.FUNCTION, "mod.foo"), tuple(Kind.AMBIGUOUS, "mod.bar"));
@@ -457,7 +457,7 @@ void function_symbols_from_protobuf() throws TextFormat.ParseException {
457457
@Test
458458
void pythonVersions() {
459459
Symbol range = TypeShed.builtinSymbols().get("range");
460-
assertThat(((SymbolImpl) range).validForPythonVersions()).containsExactlyInAnyOrder( "38", "39", "310", "311", "312", "313");
460+
assertThat(((SymbolImpl) range).validForPythonVersions()).containsExactlyInAnyOrder("38", "39", "310", "311", "312", "313");
461461
assertThat(range.kind()).isEqualTo(Kind.CLASS);
462462

463463
// python 2
@@ -524,7 +524,7 @@ private static SymbolsProtos.ModuleSymbol moduleSymbol(String protobuf) throws T
524524
@Test
525525
void variables_from_protobuf() throws TextFormat.ParseException {
526526
SymbolsProtos.ModuleSymbol moduleSymbol = moduleSymbol(
527-
"fully_qualified_name: \"mod\"\n" +
527+
"fully_qualified_name: \"mod\"\n" +
528528
"vars {\n" +
529529
" name: \"foo\"\n" +
530530
" fully_qualified_name: \"mod.foo\"\n" +
@@ -564,15 +564,15 @@ void symbol_from_submodule_access() {
564564
}
565565

566566
@Test
567-
void typeshed_private_modules_should_not_affect_fqn() {
567+
void typeshed_private_modules_should_not_affect_fqn() {
568568
Map<String, Symbol> socketModule = symbolsForModule("socket");
569569
ClassSymbol socket = (ClassSymbol) socketModule.get("socket");
570570
assertThat(socket.declaredMembers()).extracting(Symbol::name, Symbol::fullyQualifiedName).contains(tuple("connect", "socket.socket.connect"));
571571
assertThat(socket.superClasses()).extracting(Symbol::fullyQualifiedName).containsExactly("object");
572572
}
573573

574574
@Test
575-
void overloaded_function_alias_has_function_annotated_type() {
575+
void overloaded_function_alias_has_function_annotated_type() {
576576
Map<String, Symbol> gettextModule = symbolsForModule("gettext");
577577
Symbol translation = gettextModule.get("translation");
578578
Symbol catalog = gettextModule.get("Catalog");
@@ -587,4 +587,16 @@ void stubFilesSymbols_third_party_symbols_should_not_be_null() {
587587
symbolsForModule("six");
588588
assertThat(TypeShed.stubFilesSymbols()).doesNotContainNull();
589589
}
590+
591+
@Test
592+
void customDbStubs() {
593+
var pgdb = symbolsForModule("pgdb");
594+
assertThat(pgdb.get("connect")).isInstanceOf(FunctionSymbol.class);
595+
596+
var mysql = symbolsForModule("mysql.connector");
597+
assertThat(mysql.get("connect")).isInstanceOf(FunctionSymbol.class);
598+
599+
var pymysql = symbolsForModule("pymysql");
600+
assertThat(pymysql.get("connect")).isInstanceOf(FunctionSymbol.class);
601+
}
590602
}

python-frontend/typeshed_serializer/resources/custom/mysql/__init__.pyi

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from .connection import MySQLConnection
2+
from .cursor import MySQLCursor
3+
4+
def connect(dsn: str | None = None,
5+
user: str | None = None, password: str | None = None,
6+
host: str | None = None, database: str | None = None,
7+
**kwargs: Any) -> MySQLConnection:
8+
...
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .cursor import MySQLCursor
2+
class MySQLConnection:
3+
def cursor(self) -> MySQLCursor:
4+
...
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import Sequence, Union
2+
class MySQLCursor:
3+
def execute(self, operation: str, parameters: Union[Sequence, None] = None
4+
): # should return Cursor
5+
...
6+
7+
def executemany(self, operation: str,
8+
seq_of_parameters: Sequence[Union[Sequence, None]]): # should return Cursor
9+
...
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .connect import connect
2+
from .connection import Connection
3+
from .cursor import Cursor
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from .connection import Connection
2+
3+
4+
def connect(dsn: str | None = None,
5+
user: str | None = None, password: str | None = None,
6+
host: str | None = None, database: str | None = None,
7+
**kwargs: Any) -> Connection:
8+
...

0 commit comments

Comments
 (0)