10
10
import functools
11
11
from typing import (
12
12
TYPE_CHECKING ,
13
- Any ,
14
13
final ,
15
14
)
16
15
import warnings
@@ -405,17 +404,31 @@ def accessor_entry_point_loader() -> None:
405
404
"""
406
405
Load and register pandas accessors declared via entry points.
407
406
408
- This function scans the 'pandas.accessor' entry point group for accessors
409
- registered by third-party packages. Each entry point is expected to follow
410
- the format:
407
+ This function scans the 'pandas.<pd_obj>. accessor' entry point group for
408
+ accessors registered by third-party packages. Each entry point is expected
409
+ to follow the format:
411
410
412
- TODO
411
+ # setup.py
412
+ entry_points={
413
+ 'pandas.DataFrame.accessor': [ <name> = <module>:<AccessorClass>, ... ],
414
+ 'pandas.Series.accessor': [ <name> = <module>:<AccessorClass>, ... ],
415
+ 'pandas.Index.accessor': [ <name> = <module>:<AccessorClass>, ... ],
416
+ }
413
417
414
- For example :
418
+ OR for pyproject.toml file :
415
419
416
- TODO
417
- TODO
418
- TODO
420
+ # pyproject.toml
421
+ [project.entry-points."pandas.DataFrame.accessor"]
422
+ <name> = "<module>:<AccessorClass>"
423
+
424
+ [project.entry-points."pandas.Series.accessor"]
425
+ <name> = "<module>:<AccessorClass>"
426
+
427
+ [project.entry-points."pandas.Index.accessor"]
428
+ <name> = "<module>:<AccessorClass>"
429
+
430
+ For more information about entrypoints:
431
+ https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#plugin-entry-points
419
432
420
433
421
434
For each valid entry point:
@@ -428,6 +441,7 @@ def accessor_entry_point_loader() -> None:
428
441
Notes
429
442
-----
430
443
- This function is only intended to be called at pandas startup.
444
+ - For more information about accessors read their documentation.
431
445
432
446
Raises
433
447
------
@@ -436,48 +450,67 @@ def accessor_entry_point_loader() -> None:
436
450
437
451
Examples
438
452
--------
439
- df.myplugin.do_something() # Assuming such accessor was registered
440
- """
441
-
442
- ENTRY_POINT_GROUP : str = "pandas.accessor"
443
-
444
- accessors : EntryPoints = entry_points (group = ENTRY_POINT_GROUP )
445
- accessor_package_dict : dict [str , str ] = {}
446
-
447
- for new_accessor in accessors :
448
- if new_accessor .dist is not None :
449
- # Try to get new_accessor.dist.name,
450
- # if that's not possible: new_pkg_name = 'Unknown'
451
- new_pkg_name : str = getattr (new_accessor .dist , "name" , "Unknown" )
452
- else :
453
- new_pkg_name : str = "Unknown"
453
+ # setup.py
454
+ entry_points={
455
+ 'pandas.DataFrame.accessor': [
456
+ 'myplugin = myplugin.accessor:MyPluginAccessor',
457
+ ],
458
+ }
459
+ # END setup.py
454
460
455
- # Verifies duplicated accessor names
456
- if new_accessor .name in accessor_package_dict :
457
- loaded_pkg_name : str = accessor_package_dict .get (new_accessor .name )
461
+ - That entrypoint would allow the following code:
458
462
459
- if loaded_pkg_name is None :
460
- loaded_pkg_name = "Unknown"
463
+ import pandas as pd
461
464
462
- warnings .warn (
463
- "Warning: you have two accessors with the same name:"
464
- f" '{ new_accessor .name } ' has already been registered"
465
- f" by the package '{ new_pkg_name } '. So the "
466
- f"'{ new_accessor .name } ' provided by the package "
467
- f"'{ loaded_pkg_name } ' is not being used. "
468
- "Uninstall the package you don't want"
469
- "to use if you want to get rid of this warning.\n " ,
470
- UserWarning ,
471
- stacklevel = 2 ,
472
- )
473
-
474
- accessor_package_dict .update ({new_accessor .name : new_pkg_name })
475
-
476
- def make_accessor (ep ):
477
- def accessor (self ) -> Any :
478
- cls_ = ep .load ()
479
- return cls_ (self )
480
-
481
- return accessor
465
+ df = pd.DataFrame({"A": [1, 2, 3]})
466
+ df.myplugin.do_something() # Calls MyPluginAccessor.do_something()
467
+ """
482
468
483
- register_dataframe_accessor (new_accessor .name )(make_accessor (new_accessor ))
469
+ PD_OBJECTS_ENTRYPOINTS : list [str ] = [
470
+ "pandas.DataFrame.accessor" ,
471
+ "pandas.Series.accessor" ,
472
+ "pandas.Index.accessor" ,
473
+ ]
474
+
475
+ ACCESSOR_REGISTRY_FUNCTIONS : dict [str , Callable ] = {
476
+ "pandas.DataFrame.accessor" : register_dataframe_accessor ,
477
+ "pandas.Series.accessor" : register_series_accessor ,
478
+ "pandas.Index.accessor" : register_index_accessor ,
479
+ }
480
+
481
+ for pd_obj_entrypoint in PD_OBJECTS_ENTRYPOINTS :
482
+ accessors : EntryPoints = entry_points (group = pd_obj_entrypoint )
483
+ accessor_package_dict : dict [str , str ] = {}
484
+
485
+ for new_accessor in accessors :
486
+ dist = getattr (new_accessor , "dist" , None )
487
+ new_pkg_name = getattr (dist , "name" , "Unknown" ) if dist else "Unknown"
488
+
489
+ # Verifies duplicated accessor names
490
+ if new_accessor .name in accessor_package_dict :
491
+ loaded_pkg_name : str = accessor_package_dict .get (new_accessor .name )
492
+
493
+ if loaded_pkg_name is None :
494
+ loaded_pkg_name : str = "Unknown"
495
+
496
+ warnings .warn (
497
+ "Warning: you have two accessors with the same name:"
498
+ f" '{ new_accessor .name } ' has already been registered"
499
+ f" by the package '{ new_pkg_name } '. The "
500
+ f"'{ new_accessor .name } ' provided by the package "
501
+ f"'{ loaded_pkg_name } ' is not being used. "
502
+ "Uninstall the package you don't want"
503
+ "to use if you want to get rid of this warning.\n " ,
504
+ UserWarning ,
505
+ stacklevel = 2 ,
506
+ )
507
+
508
+ accessor_package_dict .update ({new_accessor .name : new_pkg_name })
509
+
510
+ def make_accessor (ep ):
511
+ return lambda self , ep = ep : ep .load ()(self )
512
+
513
+ register_fn = ACCESSOR_REGISTRY_FUNCTIONS .get (pd_obj_entrypoint )
514
+
515
+ if register_fn is not None :
516
+ register_fn (new_accessor .name )(make_accessor (new_accessor ))
0 commit comments