11import * as assert from "node:assert"
22import {
3- collectAPIRouterSubclasses ,
4- collectFastAPIAliases ,
3+ collectRecognizedNames ,
54 decoratorExtractor ,
65 extractPathFromNode ,
76 extractStringValue ,
@@ -477,44 +476,50 @@ def list_users():
477476 assert . strictEqual ( result , null )
478477 } )
479478
480- test ( "recognizes custom APIRouter subclass when subclasses set provided " , ( ) => {
479+ test ( "recognizes custom APIRouter subclass" , ( ) => {
481480 const code = `
482481class AdminAPIRouter(APIRouter):
483482 pass
484483
485484admin_router = AdminAPIRouter(prefix="/admin")
486485`
487486 const tree = parse ( code )
488- const subclasses = collectAPIRouterSubclasses ( tree . rootNode )
489- assert . ok ( subclasses . has ( "AdminAPIRouter" ) )
487+ const { apiRouterNames } = collectRecognizedNames ( tree . rootNode )
488+ assert . ok ( apiRouterNames . has ( "AdminAPIRouter" ) )
490489
491490 const assignments = findNodesByType ( tree . rootNode , "assignment" )
492- const result = routerExtractor ( assignments [ 0 ] , subclasses )
491+ const result = routerExtractor ( assignments [ 0 ] , apiRouterNames )
493492
494493 assert . ok ( result )
495494 assert . strictEqual ( result . variableName , "admin_router" )
496495 assert . strictEqual ( result . type , "APIRouter" )
497496 assert . strictEqual ( result . prefix , "/admin" )
498497 } )
499498
500- test ( "does not treat FastAPI subclass as APIRouter " , ( ) => {
499+ test ( "recognizes FastAPI subclass" , ( ) => {
501500 const code = `
502501class MyApp(FastAPI):
503502 pass
504503
505504app = MyApp()
506505`
507506 const tree = parse ( code )
508- const subclasses = collectAPIRouterSubclasses ( tree . rootNode )
509- assert . ok (
510- ! subclasses . has ( "MyApp" ) ,
511- "FastAPI subclass should not be in APIRouter subclasses" ,
507+ const { fastAPINames, apiRouterNames } = collectRecognizedNames (
508+ tree . rootNode ,
512509 )
510+ assert . ok ( fastAPINames . has ( "MyApp" ) )
511+ assert . ok ( ! apiRouterNames . has ( "MyApp" ) )
513512
514513 const assignments = findNodesByType ( tree . rootNode , "assignment" )
515- const result = routerExtractor ( assignments [ 0 ] , subclasses )
514+ const result = routerExtractor (
515+ assignments [ 0 ] ,
516+ apiRouterNames ,
517+ fastAPINames ,
518+ )
516519
517- assert . strictEqual ( result , null )
520+ assert . ok ( result )
521+ assert . strictEqual ( result . variableName , "app" )
522+ assert . strictEqual ( result . type , "FastAPI" )
518523 } )
519524
520525 test ( "recognizes aliased FastAPI import (FastAPI as FA)" , ( ) => {
@@ -524,11 +529,17 @@ from fastapi import FastAPI as FA
524529app = FA()
525530`
526531 const tree = parse ( code )
527- const { fastAPIAliases } = collectFastAPIAliases ( tree . rootNode )
528- assert . ok ( fastAPIAliases . has ( "FA" ) )
532+ const { fastAPINames, apiRouterNames } = collectRecognizedNames (
533+ tree . rootNode ,
534+ )
535+ assert . ok ( fastAPINames . has ( "FA" ) )
529536
530537 const assignments = findNodesByType ( tree . rootNode , "assignment" )
531- const result = routerExtractor ( assignments [ 0 ] , undefined , fastAPIAliases )
538+ const result = routerExtractor (
539+ assignments [ 0 ] ,
540+ apiRouterNames ,
541+ fastAPINames ,
542+ )
532543
533544 assert . ok ( result )
534545 assert . strictEqual ( result . variableName , "app" )
@@ -542,26 +553,83 @@ from fastapi import APIRouter as AR
542553router = AR(prefix="/items")
543554`
544555 const tree = parse ( code )
545- const { apiRouterAliases } = collectFastAPIAliases ( tree . rootNode )
546- assert . ok ( apiRouterAliases . has ( "AR" ) )
556+ const { apiRouterNames } = collectRecognizedNames ( tree . rootNode )
557+ assert . ok ( apiRouterNames . has ( "AR" ) )
547558
548559 const assignments = findNodesByType ( tree . rootNode , "assignment" )
549- const result = routerExtractor ( assignments [ 0 ] , apiRouterAliases )
560+ const result = routerExtractor ( assignments [ 0 ] , apiRouterNames )
550561
551562 assert . ok ( result )
552563 assert . strictEqual ( result . variableName , "router" )
553564 assert . strictEqual ( result . type , "APIRouter" )
554565 assert . strictEqual ( result . prefix , "/items" )
555566 } )
556567
557- test ( "collectFastAPIAliases ignores non-aliased imports" , ( ) => {
568+ test ( "recognizes subclass of aliased APIRouter (class MyRouter(AR))" , ( ) => {
569+ const code = `
570+ from fastapi import APIRouter as AR
571+
572+ class MyRouter(AR):
573+ pass
574+
575+ router = MyRouter(prefix="/items")
576+ `
577+ const tree = parse ( code )
578+ const { apiRouterNames } = collectRecognizedNames ( tree . rootNode )
579+ assert . ok ( apiRouterNames . has ( "AR" ) )
580+ assert . ok ( apiRouterNames . has ( "MyRouter" ) )
581+
582+ const assignments = findNodesByType ( tree . rootNode , "assignment" )
583+ const result = routerExtractor ( assignments [ 0 ] , apiRouterNames )
584+
585+ assert . ok ( result )
586+ assert . strictEqual ( result . type , "APIRouter" )
587+ } )
588+
589+ test ( "collectRecognizedNames ignores non-aliased imports" , ( ) => {
558590 const code = "from fastapi import FastAPI, APIRouter"
559591 const tree = parse ( code )
560- const { fastAPIAliases, apiRouterAliases } = collectFastAPIAliases (
592+ const { fastAPINames, apiRouterNames } = collectRecognizedNames (
593+ tree . rootNode ,
594+ )
595+ // Only the defaults — no extras from non-aliased imports
596+ assert . strictEqual ( fastAPINames . size , 2 ) // "FastAPI", "fastapi.FastAPI"
597+ assert . strictEqual ( apiRouterNames . size , 2 ) // "APIRouter", "fastapi.APIRouter"
598+ } )
599+
600+ test ( "recognizes module alias (import fastapi as f)" , ( ) => {
601+ const code = `
602+ import fastapi as f
603+
604+ app = f.FastAPI()
605+ router = f.APIRouter(prefix="/items")
606+ `
607+ const tree = parse ( code )
608+ const { fastAPINames, apiRouterNames } = collectRecognizedNames (
561609 tree . rootNode ,
562610 )
563- assert . strictEqual ( fastAPIAliases . size , 0 )
564- assert . strictEqual ( apiRouterAliases . size , 0 )
611+ assert . ok ( fastAPINames . has ( "f.FastAPI" ) )
612+ assert . ok ( apiRouterNames . has ( "f.APIRouter" ) )
613+
614+ const assignments = findNodesByType ( tree . rootNode , "assignment" )
615+ const appResult = routerExtractor (
616+ assignments [ 0 ] ,
617+ apiRouterNames ,
618+ fastAPINames ,
619+ )
620+ assert . ok ( appResult )
621+ assert . strictEqual ( appResult . variableName , "app" )
622+ assert . strictEqual ( appResult . type , "FastAPI" )
623+
624+ const routerResult = routerExtractor (
625+ assignments [ 1 ] ,
626+ apiRouterNames ,
627+ fastAPINames ,
628+ )
629+ assert . ok ( routerResult )
630+ assert . strictEqual ( routerResult . variableName , "router" )
631+ assert . strictEqual ( routerResult . type , "APIRouter" )
632+ assert . strictEqual ( routerResult . prefix , "/items" )
565633 } )
566634 } )
567635
@@ -575,6 +643,24 @@ router = AR(prefix="/items")
575643 assert . ok ( result )
576644 assert . strictEqual ( result . modulePath , "fastapi" )
577645 assert . deepStrictEqual ( result . names , [ "fastapi" ] )
646+ assert . deepStrictEqual ( result . namedImports , [
647+ { name : "fastapi" , alias : null } ,
648+ ] )
649+ assert . strictEqual ( result . isRelative , false )
650+ } )
651+
652+ test ( "extracts aliased module import (import fastapi as f)" , ( ) => {
653+ const code = "import fastapi as f"
654+ const tree = parse ( code )
655+ const imports = findNodesByType ( tree . rootNode , "import_statement" )
656+ const result = importExtractor ( imports [ 0 ] )
657+
658+ assert . ok ( result )
659+ assert . strictEqual ( result . modulePath , "fastapi" )
660+ assert . deepStrictEqual ( result . names , [ "f" ] )
661+ assert . deepStrictEqual ( result . namedImports , [
662+ { name : "fastapi" , alias : "f" } ,
663+ ] )
578664 assert . strictEqual ( result . isRelative , false )
579665 } )
580666
0 commit comments